Ligação bidirecional angular, um pouco mais de compreensão

Do tradutor
De um tradutor : há dois anos, iniciei meu primeiro projeto em Angular (2+), com uma ampla e bem-sucedida experiência em AngularJS. A transição exigiu uma formatação notável de pensamento, uma vez que muito em A1 e A2 + é feito "de maneira um pouco diferente". A dor da transição reduziu acentuadamente o blog do buttram para mim . Há um ano, recebi permissão para traduzir este artigo "sobre elementar e facilmente compreensível para todos". Mas são essas mãos (seus artigos são muitos inacabados). Surpreendentemente, o artigo traduz bem no Google translate. Mas algumas das nuances desta tradução foram perdidas, sem mencionar o estilo do autor. O estilo do autor não foi totalmente preservado na minha versão. Mas, espero, consegui transmitir o humor e os pensamentos do artigo.

Entendo que Angular não é o tópico mais popular de Habré, mas espero que a tradução ajude alguém, assim como o artigo original já me ajudou.

Foi isso que causou o efeito uau no bom e velho AngularJS, por isso é uma "ligação de mão dupla". Essa mágica instantaneamente se apaixonou pelo AngularJS e quebrou todas as idéias sobre programação de páginas chata e formulários da Web (oh, horror!). Alterações nos dados são exibidas instantaneamente na tela e vice-versa. Aqueles que desenvolveram aplicativos jQuery anteriormente consideravam obrigatório o fato de estarem caindo em um conto de fadas. E monstros barbudos, vendo clientes gordos antes do jQuery, começaram a contar freneticamente os meses estupidamente perdidos.

Além disso, a magia da encadernação bidirecional estava disponível não apenas para notações especiais e componentes selecionados. Poderíamos usá-lo facilmente em nossas próprias diretivas e componentes (apenas definindo o parâmetro de configuração).

No Angular2 +, os criadores abandonaram a ligação de dados bidirecional integrada (exceto por meio do ngModel). Mas isso não significa que não podemos usar a vinculação bidirecional em nossas próprias diretivas ... É apenas que o brinde acabou e agora precisamos fazer algo por conta própria. E, de preferência, com um entendimento de como funciona no Angular.

Sumário



Encadernação bidirecional em poucas palavras


No A2 +, apenas uma única diretiva implementa a ligação de dados bidirecional : ngModel . E, à primeira vista, essa é a mesma mágica que no AngularJS (apenas em uma notação diferente). Mas o que há sob o capô?

Surpreendentemente, sob o capô, tudo é relativamente simples e lógico: a ligação bidirecional é reduzida à associação de propriedades e eventos. Duas ligações unilaterais, em vez de uma bilateral? Ok, vamos dois.

E imediatamente um exemplo:

<input [(ngModel)]="username"> <p>Hello {{username}}!</p> 

Sim, sim, esta é uma demo Angular2 linda e incrível de 2009. Sem brincadeira, linda. Ao alterar o campo, o valor do nome de usuário cai no modelo e é imediatamente refletido na mensagem de boas-vindas no formulário.

Mas como isso funciona? Lembre-se de que a ligação bidirecional no Angular2 é a propriedade e a evento. E sim, eles podem estar disponíveis simultaneamente em uma diretiva. Além disso, mesmo sem o ngModel , poderíamos implementar facilmente a ligação de dados bidirecional. Por exemplo, assim:

 <input [value]="username" (input)="username = $event.target.value"> <p>Hello {{username}}!</p> 

A saída {{nomedeusuário}} é clara, mas o que está escrito na entrada ? Vamos entender:

  • [value] = "nome de usuário" - notação entre colchetes, associa a expressão de nome de usuário à propriedade value
  • (input) = "expression" - uma notação entre parênteses, a expressão é anexada ao evento de entrada (sim, existe esse evento). No nosso caso:
    • nome de usuário = $ event.target.value - essa expressão será executada em resposta ao evento de entrada
    • $ event é uma variável sintética em eventos angulares que carrega uma carga: nesse caso, contém informações sobre o que aconteceu e seus arredores

Está ficando mais claro? Nós consertamos isso.

Ligamos a propriedade de nome de usuário do modelo Angular à propriedade de valor do elemento de entrada do navegador (ligação unidirecional do modelo para a visualização).

Também vinculamos uma expressão ao evento de entrada do nosso elemento. Que atribui o valor de $ event.target.value à propriedade de nome de usuário do modelo.

O que é $ event.target.value ? Como já mencionado, $ event está cheio de várias informações úteis sobre o evento. Nesse caso, é um InputEventObject no qual a propriedade target se refere ao elemento DOM que acionou o evento (ou seja, nosso elemento de entrada).

Então, tudo o que basicamente fazemos é ler o conteúdo ( valor ) do elemento de entrada ( $ event.target ) quando o usuário digitar um valor. E quando atribuímos esse valor de nome de usuário, os dados da visualização são enviados para o modelo.

Isso é tudo. Isso é "ligação bidirecional em poucas palavras" . Beleza?

Mas quando o ngModel entra em ação ? O cenário de trabalhar com elementos de entrada é muito comum e demanda. E, por algum motivo, quero ter uma diretiva que oculte a implementação e salve as teclas extra.

Entendendo o ngModel


Se você olhar a fonte, poderá garantir que ngModel também tenha uma ligação com a propriedade e o evento. Aqui está a aparência do nosso exemplo ngModel, mas sem usar a sintaxe abreviada:

 <input [ngModel]="username" (ngModelChange)="username = $event"> <p>Hello {{username}}!</p> 

Quase tudo é igual. A ligação da propriedade [ngModel] cuida da atualização do valor do elemento de entrada. Uma ligação de evento (ngModelChange) notifica o mundo que mudanças estão ocorrendo no DOM.

E você notou que a expressão manipuladora usa apenas $ event , não $ event.target.value . Há algo errado aqui? Nem um pouco. Como mencionado acima, $ event é uma variável sintética que carrega uma carga útil . A decisão do que é considerado útil é tomada pela Angular. Em outras palavras, o ngModelChange cuida da extração de target.value do evento $ interno e simplesmente nos dá o que queremos, sem empacotar e um pandeiro. Para ser tecnicamente preciso, esses são os de DefaultValueAccessor : é ele quem extrai os dados e os transfere para o objeto DOM base, embora ... você não consiga pensar nisso).

Por último, mas não menos importante, como escrever o nome de usuário e o ngModel duas vezes ainda é redundante, o Angular permite o uso da sintaxe abreviada [()] , também chamada de "banana em uma caixa". Que é semelhante ao exemplo anterior e nos retorna ao exemplo desde o início da seção, mas com um entendimento da implementação do ngModel . Fornecendo a mesma ligação bidirecional.

 <input [(ngModel)]="username"> <p>Hello {{username}}!</p> 


Crie suas próprias ligações de dados bidirecionais


Agora sabemos o suficiente para criar nossas próprias ligações de dados bidirecionais. Tudo o que você precisa fazer é simplesmente seguir as mesmas regras do ngModel , a saber:

  • Insira uma ligação de propriedade (por exemplo: [foo] )
  • Associe-se a um evento com o mesmo nome e sufixo Change (por exemplo: (fooChange) )
  • Certifique-se de que a ligação do evento cuide da recuperação da propriedade (se necessário)

Observe que a criação de ligação de dados bidirecional requer muito mais trabalho que o AngularJS? Isso pode ser muito frustrante para nós ... Se tentarmos usar nossa própria ligação bidirecional sempre que possível. Na vida real, você deve sempre considerar se precisamos de uma ligação bidirecional e, se necessário, é mais fácil tirar vantagem do ngModel. O último, por exemplo, ocorre ao criar controles de formulário personalizados .

Mas digamos que criamos um componente de contador personalizado (e não queremos usar um controle de formulário personalizado).

 @Component({ selector: 'custom-counter', template: ` <button (click)="decrement()">-</button> <span>{{counter}}</span> <button (click)="increment()">+</button> ` }) export class CustomCounterComponent { counterValue = 0; get counter() { return this.counterValue; } set counter(value) { this.counterValue = value; } decrement() { this.counter--; } increment() { this.counter++; } } 

Temos a propriedade do componente do contador para exibir o valor atual do contador. Para fornecer uma ligação bidirecional, a primeira coisa a fazer é transformá-la em um parâmetro de entrada . Para isso, o decorador @Input () é muito útil:

 @Component() export class CustomCounterComponent { counterValue = 0; @Input() get counter() { return this.counterValue; } ... } 

Isso já permite vincular a propriedade do componente ao consumidor da seguinte maneira:

 <custom-counter [counter]="someValue"></custom-counter> 

Agora precisamos definir o evento @Output () com o mesmo nome ( contador ) e o sufixo Change (o resultado é counterChange). Queremos aumentar esse evento sempre que o contador mudar. Por que adicionar a propriedade @Output () . E finalizamos, em alguns getters, o contador, no qual interceptaremos a alteração no valor e jogaremos o evento com o valor atual do contador:

 @Component() export class CustomCounterComponent { ... @Output() counterChange = new EventEmitter(); set counter(val) { this.counterValue = val; this.counterChange.emit(this.counterValue); } ... } 

É isso aí! Agora podemos ligar a expressão a essa propriedade usando a sintaxe de ligação de dados bidirecional:

 <custom-counter [(counter)]="someValue"></custom-counter> <p>counterValue = {{someValue}}</p> 

Confira a demonstração e experimente!

Novamente, lembre-se de que um componente como um contador personalizado é melhor implementado com um controle de formulário personalizado e aproveite o ngModel para implementar a ligação de dados bidirecional, conforme descrito neste artigo .

Conclusão


O Angular não vem mais com a ligação de dados bidirecional integrada. Em vez disso, existem APIs na caixa que permitem implementar a ligação completa como propriedades e eventos de ligação.

O ngModel é fornecido como uma diretiva de ligação bidirecional interna no FormsModule (lembre-se de adicioná-lo à seção de importações da declaração @NgModule : aprox. por). A vinculação via ngModel deve ser preferida ao criar componentes que servem como controles de formulário personalizados. Caso contrário, tudo depende da sua imaginação.

PS do tradutor: a implementação vinculativa no A2 + tornou-se mais moderna. Agora, setters quase "gratuitos" são usados ​​para monitorar as alterações pelo "feng shui" (embora seja claro que os mecanismos de verificação suja permanecem, pelo menos para os componentes de alto nível do usuário). Isso possibilitou o abandono de 100.500 observadores (procedimentos que monitoram alterações nos dados "deles"). Que em A1 adorava criar uma carga maliciosa no navegador e exigia mãos incomuns e diretas ao planejar páginas interativas avançadas.

Com componentes projetados corretamente, o A2 pronto para uso tornou-se significativamente mais responsivo. Deixe às custas do trabalho dos programadores. Agora você pode colocar uma legião de componentes na página e não se preocupe com os recursos do processador.

O outro lado da moeda foi o custo inicial do "processo de entrada" em A2 +, o que afetou a popularidade da estrutura. Mas o A1 também teve um alto custo de entrada, mas foi rebaixado para a liga principal. Devido à falta de entendimento de como organizar aplicativos grandes, muitos protótipos “decolaram” na A1, depois “desmoronaram” e corresponderam a React e Vue.

Espero que, com este artigo, ajude a diminuir um pouco o limiar da entrada inicial no A2 +, que continua a ser procurado (o que eu sei em primeira mão).

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


All Articles