Controles personalizados em Angular

Prólogo


Vamos falar sobre formas reativas em angular, aprender para controles personalizados como criar, usar e validar. O artigo pressupõe que você já esteja familiarizado com a estrutura angular, mas deseja aprofundar suas especificidades. Bom desejo, vamos começar.

Formulários reativos e orientados a modelos


Algumas palavras para ter certeza de que estamos na mesma página. Um angular tem dois tipos de formulários: Formulários acionados por modelo e Formulários reativos .

Os Formulários Orientados a Modelo são formulários baseados em ligação bidirecional (hi, angularjs). Especificamos o campo na classe (por exemplo, nome de usuário), no html na tag de entrada vinculamos [(value)] = "nome de usuário" e, quando o valor de entrada é alterado, o valor de nome de usuário é alterado. Em 2011, isso foi mágica! OK, mas há uma nuance. Dessa forma, construir formas complexas será ... difícil.

Formulários reativos é uma ferramenta conveniente para criar formulários simples e complexos. Eles nos dizem: "crie uma instância da classe de formulário ( FormGroup ), passe instâncias dos controles ( FormControl ) para ele e apenas entregue-o ao html, e eu farei o resto." Ok, vamos tentar:

class UsefullComponent { public control = new FormControl(''); public formG = new FormGroup({username: control}); } 

 <form [formGroup]="formG"> <input type="text" formControlName="username"> </form> 

Como resultado, temos uma forma reativa, com blackjack e ... bem, com todas as comodidades. Por exemplo, formG.valueChanges nos fornecerá um Observável (fluxo) de alterações de formulário. Você também pode adicionar novos controles, excluir os existentes, alterar as regras de validação, obter o valor do formulário ( formG.value ) e muito mais. E tudo isso trabalhando com uma instância do formG .

E para não criar instâncias manualmente das classes acima de cada vez, os desenvolvedores do angular nos deram um conveniente FormBuilder com a ajuda da qual o exemplo acima pode ser reescrito da seguinte maneira:

 class UsefullComponent { public formG: FormGroup; constructor(private fb: FormBuilder) { this.formG = fb.group({ name: '' //  new FormControl() !! }) } } 

Controles personalizados


Angular nos diz: "mano, se você não tiver controles nativos suficientes (entrada, data, número e outros), ou se você não gostar deles de alguma forma, aqui está uma ferramenta simples, mas muito poderosa para criar seus próprios". Controles personalizados podem ser usados ​​mesmo com reativos, mesmo com formulários controlados por modelos e são implementados usando diretivas (surpresa!). Sabendo que os componentes são diretivas com um modelo (o que você precisa), escrevemos:

 import { Component, Input } from '@angular/core'; @Component({ selector: 'counter-control', template: ` <button (click)="down()">Down</button> {{ value }} <button (click)="up()">Up</button> ` }) class CounterControlComponent { @Input() value = 0; up() { this.value++; } down() { this.value - ; } } 

Como qualquer diretiva, isso deve ser declarado no módulo (para que o artigo não cresça, omitiremos essas nuances).

Agora podemos usar o componente criado:

 import { Component } from '@angular/core'; @Component({ selector: 'parent-component', template: ` <counter-control></counter-control> ` }) class ParentComponent {} 

Tudo funciona, é claro, mas onde estão os formulários ?! Pelo menos orientado a modelos ....
Sem pânico, tudo será depois de conhecer o ControlValueAccessor .

Controlador de valor de acesso


Para que mecanismos angulares interajam com seu controle personalizado, você precisa desse controle para implementar uma interface específica. Essa interface é chamada ControlValueAccessor . Este é um exemplo bastante bom de polimorfismo do OOP, quando nosso objeto (neste caso, controle) implementa uma interface e outros objetos (formas angulares) interagem com nosso objeto por meio dessa interface.

Graças ao ControlValueAccessor , temos uma maneira única de trabalhar com controles, que, aliás, não são usados ​​apenas para criar controles personalizados. Angular sob o capô também usa essa interface. Para que? E apenas para trazer uma aparência uniforme ao comportamento dos controles nativos. Por exemplo, na entrada, o valor está contido no atributo value, na caixa de seleção, o valor é determinado através do atributo verificado. Assim, cada tipo de controle possui seu próprio ControlValueAccessor: DefaultValueAccessor para entradas e área de texto, CheckboxControlValueAccessor para caixas de seleção, RadioControlValueAccessor para botões de opção e assim por diante.

Mas o que o angular faz usando ControlValueAccessor ? Tudo é bem simples, ele grava o valor do modelo no DOM (visualização) e também eleva o evento de controle de alterações ao FormGroup e outras diretivas.

Agora que aprendemos sobre o ControlValueAccessor , podemos aplicá-lo ao nosso controle.

Vejamos sua interface:

 interface ControlValueAccessor { writeValue(value: any): void registerOnChange(fn: any): void registerOnTouched(fn: any): void } 

writeValue (value: any) - chamado quando a fonte ( novo FormControl ('eu sou o valor padrão') ) ou um novo valor acima de control.setValue ('eu sou o valor definido') é configurada .

registerOnChange (fn: any) - um método que define o manipulador que deve ser chamado quando o valor for alterado (fn é um retorno de chamada que notificará o formulário de que o valor foi alterado neste controle).

registerOnTouched (fn: any) - define o retorno de chamada que é chamado no evento de desfoque (o controle é sinalizado com toque )

 import { Component, Input } from '@angular/core'; import { ControlValueAccessor } from '@angular/forms'; @Component({ selector: 'counter-control', template: ` <button (click)="down()">Down</button> {{ value }} <button (click)="up()">Up</button> ` }) class CounterControlComponent implements ControlValueAccessor { @Input() value = 0; onChange(_: any) {} up() { this.value++; } down() { this.value - ; } writeValue(value: any) { this.value = value; } registerOnChange(fn) { this.onChange = fn; } registerOnTouched() {} } 


Para que o formulário pai esteja ciente das alterações no controle, precisamos chamar o método onChange para cada alteração no valor do valor. Para não gravar uma chamada onChange em cada método (para cima e para baixo), implementamos o campo de valor por meio de getters e setters:

 // … class CounterControlComponent implements ControlValueAccessor { private _value; get value() { return this._value; } @Input() set value(val) { this._value = val; this.onChange(this._value); } onChange(_: any) {} up() { this.value++; } down() { this.value - ; } // … } 

No momento, o angular não sabe que o componente que implementa ControlValueAccessor deve ser considerado como um controle personalizado, vamos contar a ele:

 import { Component, Input, forwardRef } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; @Component({ … providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CounterControlComponent), multi: true }] }) class CounterControlComponent implements ControlValueAccessor { … } 

Nesta seção do código, dizemos ao angular "Quero registrar um novo controle personalizado ( forneça: NG_VALUE_ACCESSOR ), use a instância existente (neste momento, nosso componente será inicializado) do componente ( useExisting: forwardRef (() => CounterControlComponent )" ) .

multi: true indica que pode haver várias dependências com esse token ( NG_VALUE_ACCESSOR ).

Não apenas criado


É hora de zayuzat nosso controle personalizado. Não esqueça de adicionar FormsModule / ReactiveFormsModule às importações do módulo em que esse controle é usado.

Usar em formulários orientados a modelos


Tudo é simples aqui, usando a ligação bidirecional via ngModel , obtemos uma alteração de exibição quando o modelo muda e vice-versa:

 import { Component } from '@angular/core'; @Component({ selector: 'parent-component', template: ` <counter-control [(ngModel)]="controlValue"></counter-control> ` }) class ParentComponent { controlValue = 10; } 

Use em formas reativas


Conforme declarado no início do artigo, crie uma instância da forma reativa via FormBuilder , renderize em html e aproveite:

 import { Component, OnInit } from '@angular/core'; import { FormBuilder } from '@angular/forms'; @Component({ selector: 'parent-component', template: ` <form [formGroup]="form"> <counter-control formControlName="counter"></counter-control> </form> ` }) class ParentComponent implements OnInit { form: FormGroup; constructor(private fb: FormBuilder) {} ngOnInit() { this.form = this.fb.group({ counter: 5 }); } } 

Agora, essa é uma forma reativa completa com nossos controles personalizados, enquanto todos os mecanismos funcionam da mesma forma que os controles nativos (eu mencionei polimorfismo?). Para não carregar o artigo atual, falaremos sobre validar e exibir erros de controle personalizados no próximo artigo.

Materiais:

Artigo sobre controles personalizados de Pascal Precht.
Das docas se forma no angular.
Uma série de artigos sobre rxjs.

Source: https://habr.com/ru/post/pt481424/


All Articles