src/components/sq-input-form-control/sq-input-form-control.component.ts
Componente de input moderno que implementa ControlValueAccessor e Validator. Versão melhorada do sq-input com suporte completo a Reactive Forms.
Usa um FormControl interno para gerenciar o valor e estado. Aplica automaticamente validators baseados no tipo (email, tel, url). Suporta debounce, customização de estilos, tooltips, e templates customizados.
Example :```html
<sq-input-form-control
[formControl]="emailControl"
[label]="'Email'"
[type]="'email'"
[placeholder]="'seu@email.com'"
[required]="true"
[tooltipMessage]="'Digite um email válido'"
></sq-input-form-control>
ControlValueAccessor
Validator
OnChanges
OnDestroy
| changeDetection | ChangeDetectionStrategy.OnPush |
| providers |
{
provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SqInputFormControlComponent), multi: true,
}
{
provide: NG_VALIDATORS, useExisting: forwardRef(() => SqInputFormControlComponent), multi: true,
}
|
| selector | sq-input-form-control |
| standalone | true |
| imports |
NgClass
NgStyle
NgTemplateOutlet
ReactiveFormsModule
SqTooltipComponent
UniversalSafePipe
|
| styleUrls | ./sq-input-form-control.component.scss |
| templateUrl | ./sq-input-form-control.component.html |
Properties |
|
Methods |
Inputs |
Outputs |
Accessors |
constructor()
|
|
Constructor that initializes the component. Sets up value change subscriptions and initial validators. |
| backgroundColor | |
Type : string
|
|
Default value : ''
|
|
|
Background color of the input element. |
|
| borderColor | |
Type : string
|
|
Default value : ''
|
|
|
Border color of the input element. |
|
| customClass | |
Type : string
|
|
Default value : ''
|
|
|
Custom CSS class for the input element. |
|
| id | |
Type : string
|
|
|
The id attribute for the input element. |
|
| inputMode | |
Type : string
|
|
Default value : ''
|
|
|
Input mode for mobile devices. |
|
| label | |
Type : string
|
|
|
An optional label for the input. |
|
| labelColor | |
Type : string
|
|
Default value : ''
|
|
|
Color of the input label. |
|
| maxLength | |
Type : number | null
|
|
Default value : null
|
|
|
Maximum length for the input element. |
|
| name | |
Type : string
|
|
Default value : `random-name-${(1 + Date.now() + Math.random()).toString().replace('.', '')}`
|
|
|
The name attribute for the input element. |
|
| pattern | |
Type : string
|
|
Default value : ''
|
|
|
Regular expression pattern for input validation. |
|
| placeholder | |
Type : string
|
|
Default value : ''
|
|
|
Placeholder text for the input element. |
|
| readonly | |
Type : boolean
|
|
Default value : false
|
|
|
Flag to make the input element readonly. |
|
| timeToChange | |
Type : number
|
|
Default value : 0
|
|
|
Time in milliseconds before triggering input timeout (debounce). |
|
| tooltipColor | |
Type : string
|
|
Default value : 'inherit'
|
|
|
Color of the tooltip. |
|
| tooltipIcon | |
Type : string
|
|
Default value : ''
|
|
|
Icon for the tooltip. |
|
| tooltipMessage | |
Type : string
|
|
Default value : ''
|
|
|
Tooltip message to display. |
|
| tooltipPlacement | |
Type : "center top" | "center bottom" | "left center" | "right center"
|
|
Default value : 'right center'
|
|
|
Placement of the tooltip. |
|
| type | |
Type : "text" | "email" | "email-multiple" | "hidden" | "password" | "tel" | "url" | "file"
|
|
Default value : 'text'
|
|
|
Type of the input element (e.g., text, email, password). |
|
| blurred | |
Type : EventEmitter<FocusEvent>
|
|
|
Event emitter for blur events. |
|
| focused | |
Type : EventEmitter<FocusEvent>
|
|
|
Event emitter for focus events. |
|
| keyPressDown | |
Type : EventEmitter<KeyboardEvent>
|
|
|
Event emitter for keydown events. |
|
| keyPressUp | |
Type : EventEmitter<KeyboardEvent>
|
|
|
Event emitter for keyup events. |
|
| valueChange | |
Type : EventEmitter<any>
|
|
|
Event emitter for input value changes. |
|
| ngOnChanges | ||||||
ngOnChanges(changes: SimpleChanges)
|
||||||
|
Lifecycle hook called when any input property changes. Updates validators when the type changes.
Parameters :
Returns :
void
|
| ngOnDestroy |
ngOnDestroy()
|
|
Cleanup on component destruction.
Returns :
void
|
| onBlur | ||||||
onBlur(event: FocusEvent)
|
||||||
|
Handle blur events.
Parameters :
Returns :
void
|
| onChangeEvent | ||||||
onChangeEvent(event: Event)
|
||||||
|
Handle input value changes with optional debounce.
Parameters :
Returns :
void
|
| onFocus | ||||||
onFocus(event: FocusEvent)
|
||||||
|
Handle focus events.
Parameters :
Returns :
void
|
| onKeyDown | ||||||||
onKeyDown(event: KeyboardEvent)
|
||||||||
|
Handle keydown events.
Parameters :
Returns :
void
|
| onKeyUp | ||||||||
onKeyUp(event: KeyboardEvent)
|
||||||||
|
Handle keyup events.
Parameters :
Returns :
void
|
| registerOnChange | ||||||||
registerOnChange(fn: any)
|
||||||||
|
ControlValueAccessor: Registers a callback function that is called when the control's value changes.
Parameters :
Returns :
void
|
| registerOnTouched | ||||||||
registerOnTouched(fn: any)
|
||||||||
|
ControlValueAccessor: Registers a callback function that is called when the control is touched.
Parameters :
Returns :
void
|
| registerOnValidatorChange | ||||||||
registerOnValidatorChange(fn: () => void)
|
||||||||
|
Validator: Registers a callback for validation changes.
Parameters :
Returns :
void
|
| setDisabledState | ||||||||
setDisabledState(isDisabled: boolean)
|
||||||||
|
ControlValueAccessor: Sets the disabled state of the control.
Parameters :
Returns :
void
|
| Private updateValidators |
updateValidators()
|
|
Updates validators based on the input type. Automatically applies appropriate validators for email, phone, URL, etc. Uses ValidatorHelper through InputValidators for consistency.
Returns :
void
|
| validate |
validate()
|
|
Validator: Validates the control.
Returns :
ValidationErrors | null
Validation errors or null. |
| writeValue | ||||||||
writeValue(value: any)
|
||||||||
|
ControlValueAccessor: Writes a new value to the element.
Parameters :
Returns :
void
|
| control |
Default value : new FormControl('')
|
|
Internal FormControl for managing the input value and state. |
| Private destroy$ |
Default value : new Subject<void>()
|
|
Subject for managing subscriptions. |
| labelTemplate |
Type : TemplateRef<HTMLElement> | null
|
Default value : null
|
Decorators :
@ContentChild('labelTemplate')
|
|
Reference to a label template. |
| leftLabel |
Type : TemplateRef<HTMLElement> | null
|
Default value : null
|
Decorators :
@ContentChild('leftLabel')
|
|
Reference to a left-aligned label template. |
| nativeElement |
Type : ElementRef
|
Default value : inject(ElementRef)
|
|
Reference to the native element. |
| Private onChange |
Type : function
|
Default value : () => {...}
|
|
ControlValueAccessor callback function called when the value changes. Registered via registerOnChange(). |
| Private onTouched |
Type : function
|
Default value : () => {...}
|
|
ControlValueAccessor callback function called when the control is touched. Registered via registerOnTouched(). |
| Private onValidationChange |
Type : function
|
Default value : () => {...}
|
|
External validator function (propagated from parent control). |
| rightLabel |
Type : TemplateRef<HTMLElement> | null
|
Default value : null
|
Decorators :
@ContentChild('rightLabel')
|
|
Reference to a right-aligned label template. |
| Private Optional timeoutInput |
Type : ReturnType<>
|
|
Timeout for input changes (debounce). |
| disabled |
getdisabled()
|
|
Getter for the disabled state.
Returns :
boolean
|
| value |
getvalue()
|
|
Getter for the value.
Returns :
any
|
import {
Component,
ContentChild,
ElementRef,
EventEmitter,
Input,
Output,
TemplateRef,
forwardRef,
OnDestroy,
OnChanges,
SimpleChanges,
inject,
ChangeDetectionStrategy,
} from '@angular/core';
import { NgClass, NgStyle, NgTemplateOutlet } from '@angular/common';
import {
ControlValueAccessor,
FormControl,
ReactiveFormsModule,
NG_VALUE_ACCESSOR,
NG_VALIDATORS,
Validator,
ValidationErrors,
} from '@angular/forms';
import { SqTooltipComponent } from '../sq-tooltip/sq-tooltip.component';
import { UniversalSafePipe } from '../../pipes/universal-safe/universal-safe.pipe';
import { Subject, takeUntil } from 'rxjs';
import { InputValidators } from '../../validators/input.validators';
/**
* Componente de input moderno que implementa ControlValueAccessor e Validator.
* Versão melhorada do sq-input com suporte completo a Reactive Forms.
*
* Usa um FormControl interno para gerenciar o valor e estado.
* Aplica automaticamente validators baseados no tipo (email, tel, url).
* Suporta debounce, customização de estilos, tooltips, e templates customizados.
*
* @example
* ```html
* <sq-input-form-control
* [formControl]="emailControl"
* [label]="'Email'"
* [type]="'email'"
* [placeholder]="'seu@email.com'"
* [required]="true"
* [tooltipMessage]="'Digite um email válido'"
* ></sq-input-form-control>
* ```
*/
@Component({
selector: 'sq-input-form-control',
templateUrl: './sq-input-form-control.component.html',
styleUrls: ['./sq-input-form-control.component.scss'],
standalone: true,
imports: [NgClass, NgStyle, NgTemplateOutlet, ReactiveFormsModule, SqTooltipComponent, UniversalSafePipe],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => SqInputFormControlComponent),
multi: true,
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => SqInputFormControlComponent),
multi: true,
},
],
})
export class SqInputFormControlComponent implements ControlValueAccessor, Validator, OnChanges, OnDestroy {
/**
* The name attribute for the input element.
*
* @default 'random-name-[hash-random-code]'
*/
@Input() name = `random-name-${(1 + Date.now() + Math.random()).toString().replace('.', '')}`;
/**
* The id attribute for the input element.
*/
@Input() id?: string;
/**
* An optional label for the input.
*/
@Input() label?: string;
/**
* Custom CSS class for the input element.
*/
@Input() customClass = '';
/**
* Placeholder text for the input element.
*/
@Input() placeholder = '';
/**
* Time in milliseconds before triggering input timeout (debounce).
*/
@Input() timeToChange = 0;
/**
* Flag to make the input element readonly.
*/
@Input() readonly = false;
/**
* Tooltip message to display.
*/
@Input() tooltipMessage = '';
/**
* Placement of the tooltip.
*/
@Input() tooltipPlacement: 'center top' | 'center bottom' | 'left center' | 'right center' = 'right center';
/**
* Color of the tooltip.
*/
@Input() tooltipColor = 'inherit';
/**
* Icon for the tooltip.
*/
@Input() tooltipIcon = '';
/**
* Background color of the input element.
*/
@Input() backgroundColor = '';
/**
* Border color of the input element.
*/
@Input() borderColor = '';
/**
* Color of the input label.
*/
@Input() labelColor = '';
/**
* Type of the input element (e.g., text, email, password).
*/
@Input() type: 'text' | 'email' | 'email-multiple' | 'hidden' | 'password' | 'tel' | 'url' | 'file' = 'text';
/**
* Maximum length for the input element.
*/
@Input() maxLength: number | null = null;
/**
* Regular expression pattern for input validation.
*/
@Input() pattern = '';
/**
* Input mode for mobile devices.
*/
@Input() inputMode = '';
/**
* Event emitter for keydown events.
*/
@Output() keyPressDown: EventEmitter<KeyboardEvent> = new EventEmitter();
/**
* Event emitter for keyup events.
*/
@Output() keyPressUp: EventEmitter<KeyboardEvent> = new EventEmitter();
/**
* Event emitter for input value changes.
*/
@Output() valueChange: EventEmitter<any> = new EventEmitter();
/**
* Event emitter for focus events.
*/
@Output() focused: EventEmitter<FocusEvent> = new EventEmitter<FocusEvent>();
/**
* Event emitter for blur events.
*/
@Output() blurred: EventEmitter<FocusEvent> = new EventEmitter<FocusEvent>();
/**
* Reference to a left-aligned label template.
*/
@ContentChild('leftLabel')
leftLabel: TemplateRef<HTMLElement> | null = null;
/**
* Reference to a right-aligned label template.
*/
@ContentChild('rightLabel')
rightLabel: TemplateRef<HTMLElement> | null = null;
/**
* Reference to a label template.
*/
@ContentChild('labelTemplate')
labelTemplate: TemplateRef<HTMLElement> | null = null;
/**
* Internal FormControl for managing the input value and state.
*/
control = new FormControl('');
/**
* Reference to the native element.
*/
nativeElement: ElementRef = inject(ElementRef);
/**
* Timeout for input changes (debounce).
*/
private timeoutInput?: ReturnType<typeof setTimeout>;
/**
* Subject for managing subscriptions.
*/
private destroy$ = new Subject<void>();
/**
* ControlValueAccessor callback function called when the value changes.
* Registered via registerOnChange().
*/
// eslint-disable-next-line @typescript-eslint/no-empty-function
private onChange: (value: any) => void = () => {};
/**
* ControlValueAccessor callback function called when the control is touched.
* Registered via registerOnTouched().
*/
// eslint-disable-next-line @typescript-eslint/no-empty-function
private onTouched: () => void = () => {};
/**
* External validator function (propagated from parent control).
*/
// eslint-disable-next-line @typescript-eslint/no-empty-function
private onValidationChange: () => void = () => {};
/**
* Constructor that initializes the component.
* Sets up value change subscriptions and initial validators.
*/
constructor() {
// Subscribe to internal control value changes
this.control.valueChanges.pipe(takeUntil(this.destroy$)).subscribe(value => {
this.onChange(value);
this.valueChange.emit(value);
});
// Apply initial validators based on type
this.updateValidators();
}
/**
* Lifecycle hook called when any input property changes.
* Updates validators when the type changes.
*/
ngOnChanges(changes: SimpleChanges): void {
if (changes['type']) {
this.updateValidators();
}
}
/**
* Updates validators based on the input type.
* Automatically applies appropriate validators for email, phone, URL, etc.
* Uses ValidatorHelper through InputValidators for consistency.
*/
private updateValidators(): void {
const validators = [];
switch (this.type) {
case 'email':
// Use our custom email validator (wraps ValidatorHelper.email)
validators.push(InputValidators.email());
break;
case 'email-multiple':
validators.push(InputValidators.emailMultiple());
break;
case 'tel':
validators.push(InputValidators.phone());
break;
case 'url':
validators.push(InputValidators.url());
break;
}
// Update the control's validators
this.control.setValidators(validators.length > 0 ? validators : null);
this.control.updateValueAndValidity({ emitEvent: false });
// Notify parent control that validators changed
this.onValidationChange();
}
/**
* Cleanup on component destruction.
*/
ngOnDestroy(): void {
if (this.timeoutInput) {
clearTimeout(this.timeoutInput);
}
this.destroy$.next();
this.destroy$.complete();
}
/**
* ControlValueAccessor: Writes a new value to the element.
* @param value - The new value.
*/
writeValue(value: any): void {
this.control.setValue(value ?? '', { emitEvent: false });
}
/**
* ControlValueAccessor: Registers a callback function that is called when the control's value changes.
* @param fn - The callback function.
*/
registerOnChange(fn: any): void {
this.onChange = fn;
}
/**
* ControlValueAccessor: Registers a callback function that is called when the control is touched.
* @param fn - The callback function.
*/
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
/**
* ControlValueAccessor: Sets the disabled state of the control.
* @param isDisabled - Whether the control should be disabled.
*/
setDisabledState(isDisabled: boolean): void {
if (isDisabled) {
this.control.disable({ emitEvent: false });
} else {
this.control.enable({ emitEvent: false });
}
}
/**
* Validator: Validates the control.
* @returns Validation errors or null.
*/
validate(): ValidationErrors | null {
// The component doesn't have custom validation logic
// It propagates the internal control's validation state
return this.control.errors;
}
/**
* Validator: Registers a callback for validation changes.
* @param fn - The callback function.
*/
registerOnValidatorChange(fn: () => void): void {
this.onValidationChange = fn;
}
/**
* Getter for the disabled state.
*/
get disabled(): boolean {
return this.control.disabled;
}
/**
* Getter for the value.
*/
get value(): any {
return this.control.value;
}
/**
* Handle input value changes with optional debounce.
* @param value - The input value.
*/
onChangeEvent(event: Event): void {
const value = (event.target as HTMLInputElement).value;
if (this.timeoutInput) {
clearTimeout(this.timeoutInput);
}
if (this.timeToChange > 0) {
this.timeoutInput = setTimeout(() => {
this.control.setValue(value);
}, this.timeToChange);
} else {
this.control.setValue(value);
}
}
/**
* Handle keydown events.
* @param event - The keydown event.
*/
onKeyDown(event: KeyboardEvent): void {
this.keyPressDown.emit(event);
}
/**
* Handle keyup events.
* @param event - The keyup event.
*/
onKeyUp(event: KeyboardEvent): void {
this.keyPressUp.emit(event);
}
/**
* Handle blur events.
*/
onBlur(event: FocusEvent): void {
this.onTouched();
this.blurred.emit(event);
}
/**
* Handle focus events.
*/
onFocus(event: FocusEvent): void {
this.focused.emit(event);
}
}
<div class="wrapper-all-inside-input {{ customClass }}">
@if (label?.length || tooltipMessage || labelTemplate) {
<label
class="display-flex"
[ngClass]="{
readonly: readonly,
}"
[for]="id"
>
@if (label && !labelTemplate) {
<div [ngStyle]="{ color: labelColor }" [innerHtml]="label | universalSafe"></div>
}
@if (labelTemplate) {
<div>
<ng-container *ngTemplateOutlet="labelTemplate"></ng-container>
</div>
}
@if (tooltipMessage) {
<sq-tooltip
class="ml-1"
[message]="tooltipMessage"
[placement]="tooltipPlacement"
[color]="tooltipColor"
[icon]="tooltipIcon"
></sq-tooltip>
}
</label>
}
<div
class="p-0 wrapper-input wrapper-input-squid text-ellipsisarea"
[ngClass]="{
readonly: readonly,
}"
>
@if (leftLabel) {
<span class="input-group-text m-0">
<ng-container *ngTemplateOutlet="leftLabel"></ng-container>
</span>
}
<input
class="col input"
[ngClass]="{
disabled: disabled,
readonly: readonly,
}"
[ngStyle]="{
'background-color': backgroundColor,
'border-color': borderColor,
}"
[id]="id"
[type]="type || 'text'"
[name]="name"
[placeholder]="placeholder || ''"
[readonly]="readonly"
[formControl]="control"
[maxlength]="maxLength"
(input)="onChangeEvent($event)"
(keydown)="onKeyDown($event)"
(keyup)="onKeyUp($event)"
(blur)="onBlur($event)"
(focus)="onFocus($event)"
[pattern]="pattern"
[attr.inputmode]="inputMode"
/>
@if (rightLabel) {
<span class="input-group-text m-0">
<ng-container *ngTemplateOutlet="rightLabel"></ng-container>
</span>
}
</div>
@if (maxLength) {
<div class="d-flex justify-content-end mt-1">
<small class="text-muted"> {{ maxLength - (control.value?.length || 0) }} caracteres restantes </small>
</div>
}
</div>
./sq-input-form-control.component.scss
.wrapper-all-inside-input {
position: relative;
.icon {
margin: 0;
font-size: 1rem;
font-weight: 700;
position: absolute;
right: 24px;
top: 40px;
&.no-label {
top: 12px;
}
}
.icon-external {
color: inherit !important;
}
.max-length-name {
font-size: inherit;
float: right;
}
}