Angular fluxos de dados de componentes usando @Output e EventEmitter
Ao contrário do AngularJS, Angular não possui vinculação de dados bidirecional. Quando digo que Angular não tem vinculação de dados bidirecional, isso não significa que você não possa conseguir isso.
Nenhuma estrutura da Web moderna pode existir sem suporte de vinculação de dados bidirecional. Angular fornece essa função usando a diretiva ngModel. Para usar isso, você precisa importar o FormsModule e, em seguida, pode usá-lo de várias maneiras, como:
- ngModel
- [ngModel]
- [(ngModel)]
Você pode se perguntar por que Angular não tem vinculação de dados bidirecional como o AngularJS. O AngularJS já implementou a vinculação de dados bidirecional usando ciclo de resumo, algoritmos de verificação suja e outros, e essas abordagens tinham seus próprios problemas. Neste artigo, vamos nos concentrar em problemas com a vinculação de dados bidirecional do AngularJS para que você entenda por que ela é tratada de forma diferente em Angular.
Voltando ao Angular, a vinculação de dados bidirecional pode ser representada conforme mostrado abaixo:

Os colchetes representam a associação de propriedade, os colchetes pequenos representam a associação de eventos e a combinação de ambos representa a associação de dados bidirecional. A sintaxe da ligação de dados bidirecional também é chamada de sintaxe banana na caixa. Saiba mais sobre associações de dados aqui.
Agora, você tem alguma compreensão dos diferentes tipos de ligação em Angular; Vamos ver como os dados podem fluir entre Angular componentes. Os dados podem ser de tipos primitivos, matrizes, objetos ou eventos. Para fluir dados entre componentes, o Angular fornece:
- @Input decorator
- @Output decorator
Ambos os decoradores fazem parte de @angular/núcleo.
@Input() decorator marks a class field as an input property and supplies configuration metadata. It declares a data-bound input property, which Angular automatically updates during change detection.
@Output decorador marca um campo de classe como uma propriedade de saída e fornece metadados de configuração. Ele declara uma propriedade de saída associada a dados, que Angular atualiza automaticamente durante a detecção de alterações.

Esses dois decoradores são usados para fluir dados entre componentes. Lembre-se de que, além desses dois decoradores, você também pode usar Angular serviços para fluir dados entre os componentes. Se você tiver que passar dados para um componente, use @Input decorador e, se precisar emitir dados de um componente, use @Output decorador.
Ambos os decoradores trabalham entre os componentes quando estão relacionados entre si. Por exemplo, se você estiver usando um componente chamado ChildComponent dentro de outro componente chamado AppComponent, eles estarão relacionados entre si. Você pode ver isso na imagem abaixo, e qualquer propriedade ChildComponent decorada com @Input decorador pode receber dados do AppComponent.

Para entendê-lo melhor, considere ChildComponent conforme listado abaixo:
import {
Component,
OnInit,
Input
}
from '@angular/core';
@Component({
selector: 'app-child',
template: ` <p>count= {
{
count
}
}
</p> `
}) export class ChildComponent implements OnInit {
constructor() {}
@Input() count: number;
ngOnInit() {}
}
Se você observar atentamente o snippet de código acima, estamos usando o decorador @Input() com a propriedade count, o que implica que o valor da propriedade count será definido de fora do ChildComponent. Podemos usar ChildComponent dentro de AppComponent e podemos usar a vinculação de propriedade para passar o valor da propriedade de contagem, conforme mostrado na listagem abaixo:
import { Component } from "@angular/core";
@Component({
selector: "app-root",
template: `<h2>{{ title }}</h2>
<app-child [count]="count"></app-child> `,
})
export class AppComponent {
count = 9;
}
Estamos usando ChildComponent dentro de AppComponent e usando a associação de propriedade passando dados no ChildComponent. No momento, os dados estão sendo passados em um fluxo direcional unidirecional para o ChildComponent. Lembre-se de que sempre que o valor de uma propriedade associada à entrada for atualizado, Angular executará o detector de alterações. No entanto, você pode configurar como o detector de alterações deve ser executado em @Input decorador. Além disso, cada vez que @input propriedade do decorador é atualizada, Angular executa o gancho do ciclo de vida ngOnChanges(), para que você possa ler o valor atualizado da propriedade vinculada à entrada no gancho do ciclo de vida ngOnChanges().
Agora que sabemos como passar dados do componente pai para um componente filho, vamos mudar nosso foco para como os dados e eventos podem ser emitidos de um componente filho para um componente pai. Para emitir dados e eventos de um componente, precisamos
- Para decorar a propriedade com @output decorador. Ao fazer isso, essa propriedade se torna uma propriedade de dados de saída.
- Crie uma instância de EventEmitter e decore-a com @Output decorador.
Observe na imagem abaixo que os dados ou eventos saem de um componente usando @Output decorador.

Antes de explicar como o EventEmitter opera em detalhes, examine o exemplo de código abaixo. Modifique ChildComponent com um botão, conforme mostrado na lista abaixo:
import {
Component,
OnInit,
Input
}
from '@angular/core';
@Component({
selector: 'app-child',
template: ` <p>count= {
{
count
}
}
</p> <button (click)='updateCount()' >update count </button> `
})
export class ChildComponent implements OnInit {
constructor() {}
@Input() count: number;
ngOnInit() {}
updateCount() {
this.count=this.count+1;
}
}
Quando você clica no botão no ChildComponent, ele atualiza o valor da contagem. Super simples. Até agora, o evento click é manipulado dentro do próprio ChildComponent.
Agora, vamos ajustar um pouco o requisito. E se você quiser executar uma função de AppComponent no evento de clique de um botão dentro de ChildComponent?
Para fazer isso, você terá que emitir o evento de clique do botão de ChildComponent. Com a emissão de eventos, você também pode enviar dados do ChildComponent. Vamos modificar ChildComponent para emitir dados e eventos a serem capturados no AppComponent.
import {
Component,
OnInit,
Input,
Output,
EventEmitter
}
from '@angular/core';
@Component({
selector: 'app-child',
template: ` <p>count= {
{
count
}
}
</p><button (click)='updateCount()' >update count </button> `
})
export class ChildComponent implements OnInit {
constructor() {}
@Input() count: number;
@Output() countChange=new EventEmitter();
ngOnInit() {}
updateCount() {
this.count=this.count+1;
this.countChange.emit(this.count);
}
}
No momento, estamos executando as seguintes tarefas no ChildComponent
- Criada instância de EventEmitter chamada countChange, que será emitida para o componente pai no evento de clique do botão.
- Criou uma função chamada updateCount(). Essa função é chamada no evento de clique do botão e, dentro do evento de função, countChange é emitido.
- Ao emitir o evento countChange, o valor da propriedade count também é enviado para fora do componente.
Antes de prosseguirmos, vamos entender EventEmitter. Ele é usado nas diretivas e componentes para emitir eventos personalizados de forma síncrona ou assíncrona. Qualquer manipulador pode manipular esses eventos assinando uma instância da classe EventEmitter. É diferente de um evento HTML normal, pois usa RxJS observável de modo que o manipulador possa assinar o evento.
Se você examinar a implementação da classe EventEmitter, ela estenderá a classe Subject.

Como a classe EventEmitter estende a classe RxJs Subject, isso significa que ela é observável e pode ser multicast para muitos observadores. Não é o mesmo que um evento DOM, qual? não pode ser muticast e observado.
No AppComponent, você pode manipular o evento emitido de ChildComponent, conforme mostrado na listagem de código abaixo:
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `<h2>{{title}}</h2>
<app-child [count]='count' (countChange)=changeCount($event)></app-child>`
})
export class AppComponent {
count = 9;
changeCount(data) {
console.log(data);
}
}
No momento, estamos executando as seguintes tarefas na classe AppComponent:
- Usando <app-child> no modelo.
- No elemento <app-child>, usando a associação de eventos para usar o evento countChange.
- Chamando a função changeCount no evento countChange.
- Na função changeCount, imprimindo o valor da contagem passada do ChildComponent.
Como você pode ver, a função de AppComponent é chamada no evento de clique do botão colocado no ChildComponent. Isso pode ser feito com @Output e EventEmitter. No momento, os dados estão fluindo entre os componentes usando os decoradores @Input() e @Output().

Como você pode ver, criamos a vinculação de dados bidirecional entre dois componentes. Se você examinar o código, ele é uma combinação de associação de propriedade e associação de evento.

Um exemplo em tempo real
Vamos dar um exemplo em tempo real para descobrir como @Output e EventEmitter são mais úteis. Considere que o AppComponent está renderizando uma lista de produtos em forma de tabela, conforme mostrado na imagem abaixo:

Para criar a tabela de produtos acima, temos uma classe AppComponent muito simples com apenas uma função: retornar uma lista de produtos.
export class AppComponent implements OnInit {
products = [];
title = 'Products';
ngOnInit() {
this.products = this.getProducts();
}
getProducts() {
return [
{ 'id': '1', 'title': 'Screw Driver', 'price': 400, 'stock': 11 },
{ 'id': '2', 'title': 'Nut Volt', 'price': 200, 'stock': 5 },
{ 'id': '3', 'title': 'Resistor', 'price': 78, 'stock': 45 },
{ 'id': '4', 'title': 'Tractor', 'price': 20000, 'stock': 1 },
{ 'id': '5', 'title': 'Roller', 'price': 62, 'stock': 15 },
];
}
}
No gancho do ciclo de vida ngOnInit, estamos chamando a função getPrdoducts() e atribuindo os dados retornados à variável products para que possam ser usados no modelo. Lá, estamos usando a diretiva *ngFor para iterar pela matriz e exibir os produtos. Veja o código abaixo:
<div class="container">
<br/>
<h1 class="text-center">{{title}}</h1>
<table class="table">
<thead>
<th>Id</th>
<th>Title</th>
<th>Price</th>
<th>Stock</th>
</thead>
<tbody>
<tr *ngFor="let p of products">
<td>{{p.id}}</td>
<td>{{p.title}}</td>
<td>{{p.price}}</td>
<td>{{p.stock}}</td>
</tr>
</tbody>
</table>
</div>
Com esse código, os produtos são renderizados em uma tabela, conforme a imagem abaixo:

Agora, digamos que queremos adicionar uma nova coluna com um botão e uma caixa de entrada, conforme mostrado na imagem abaixo:

Nossos requisitos são os seguintes:
- Se o valor do estoque for superior a 10, a cor do botão deve ser verde.
- Se o valor do estoque for menor que 10, a cor do botão deve ser vermelha.
- O usuário pode inserir um número na caixa de entrada, que será adicionado a esse valor de estoque específico.
- A cor do botão deve ser atualizada com base no valor alterado do estoque do produto.
Para realizar essa tarefa, vamos criar um novo componente filho chamado StockStausComponent. Essencialmente, no modelo de StockStatusComponent, há um botão e uma caixa de entrada numérica. Em StockStatusComponent:
- Precisamos ler o valor das ações passadas da AppComponnet. Para isso, precisamos usar @Input
- Precisamos emitir um evento para que uma função em AppComponent possa ser chamada com o clique do botão StockStatusComponent. Para isso, precisamos usar @Output e EventEmitter.
Consider the code below:
stockstatus.component.ts
import { Component, Input, EventEmitter, Output, OnChanges } from '@angular/core';
@Component({
selector: 'app-stock-status',
template: `<input type='number' [(ngModel)]='updatedstockvalue'/> <button class='btn btn-primary'
[style.background]='color'
(click)="stockValueChanged()">Change Stock Value</button> `
})
export class StockStatusComponent implements OnChanges {
@Input() stock: number;
@Input() productId: number;
@Output() stockValueChange = new EventEmitter();
color = '';
updatedstockvalue: number;
stockValueChanged() {
this.stockValueChange.emit({ id: this.productId, updatdstockvalue: this.updatedstockvalue });
this.updatedstockvalue = null;
}
ngOnChanges() {
if (this.stock > 10) {
this.color = 'green';
} else {
this.color = 'red';
}
}
}
Vamos explorar a classe acima linha por linha.
- Na primeira linha, estamos importando tudo o que é necessário: @Input, @Output etc.
- No modelo, há uma caixa de entrada numérica que está vinculada à propriedade updatedStockValue usando [(ngModel)]. Precisamos passar esse valor com um evento para o AppComponent.
- No modelo, há um botão. No evento de clique do botão, um evento é emitido para o AppComponent.
- Precisamos definir a cor do botão com base no valor do estoque do produto. Portanto, devemos usar a vinculação de propriedade para definir o plano de fundo do botão. O valor da propriedade color é atualizado na classe.
- Estamos criando duas propriedades decoradas com @Input() –stock e productId– porque o valor dessas duas propriedades será passado do AppComponent.
- Estamos criando um evento chamado stockValueChange. Esse evento será emitido para AppComponent com o clique do botão.
- Na função stockValueChanged, estamos emitindo o evento stockValueChange e também passando o id do produto a ser atualizado e o valor a ser adicionado no valor do estoque do produto.
- Estamos atualizando o valor da propriedade color no gancho do ciclo de vida ngOnChanges() porque cada vez que o valor do estoque é atualizado no AppComponent, o valor da propriedade color deve ser atualizado.
Aqui estamos usando o decorador @Input para ler dados da classe AppComponent, que por acaso é a classe pai neste caso. Para passar dados da classe de componente pai para a classe de componente filho, use @Input decorador.
Além disso, estamos usando @Output com EventEmitter para emitir um evento para AppComponent. Portanto, para emitir um evento da classe de componente filho para a classe de componente pai, use EventEmitter com o decorador @Output().
Portanto, StockStatusComponent está usando @Input e @Output para ler dados do AppComponent e emitir um evento para o AppComponent.
Modify AppComponent to use StockStatusComponent
Vamos primeiro modificar o modelo. No modelo, adicione uma nova coluna de tabela. Dentro da coluna, o componente <app-stock-status> é usado.
<div class="container">
<br/>
<h1 class="text-center">{{title}}</h1>
<table class="table">
<thead>
<th>Id</th>
<th>Title</th>
<th>Price</th>
<th>Stock</th>
</thead>
<tbody>
<tr *ngFor="let p of products">
<td>{{p.id}}</td>
<td>{{p.title}}</td>
<td>{{p.price}}</td>
<td>{{p.stock}}</td>
<td><app-stock-status [productId]='p.id' [stock]='p.stock' (stockValueChange)='changeStockValue($event)'></app-stock-status></td>
</tr>
</tbody>
</table>
</div>
Estamos passando o valor para productId e stock usando a vinculação de propriedade (lembre-se, essas duas propriedades são decoradas com @Input() em StockStatusComponent) e usando a vinculação de evento para manipular o evento stockValueChange (lembre-se, esse evento é decorado com @Output() em StockStatusComponent).
Em seguida, precisamos adicionar a função changeStockValue no AppComponent. Adicione o seguinte código na classe AppComponent:
productToUpdate: any;
changeStockValue(p) {
this.productToUpdate = this.products.find(this.findProducts, [p.id]);
this.productToUpdate.stock = this.productToUpdate.stock + p.updatdstockvalue;
}
findProducts(p) {
return p.id === this[0];
}
Na função, estamos usando o método JavaScript Array.prototype.find para encontrar um produto com um productId correspondente e, em seguida, atualizando a contagem de estoque do produto correspondente. Ao executar o aplicativo, você obterá a seguinte saída:
Ao inserir um número na caixa numérica e clicar no botão, você executa uma tarefa no componente filho que atualiza o valor da operação no componente pai. Além disso, com base no valor do componente pai, o estilo está sendo alterado no componente filho. Tudo isso é possível usando Angular @Input, @Output e EventEmitter.
Fique atento para artigos futuros, onde nos aprofundamos em outros recursos do Angular!
