Interação entre componentes
Objetivos do capítulo:
- demonstrar a modularização do código utilizando componentes
- demonstrar a integração entre componentes usando input e output
Branches: livro-componentes-lista e livro-componentes-editor.
Uma vez que a quantidade de funcionalidades em um componente começa a aumentar, prejudicando, por exemplo, a legibilidade e a organização do código-fonte, o componente torna-se um candidato a passar por um processo de modularização.
O AppComponent
, no momento, fornece as funcionalidades de CRUD de disciplinas, ou seja:
- Listar disciplinas
- Cadastrar disciplinas
- Editar disciplinas
- Excluir disciplinas
Como alternativa ao fato de haver um só componente realizando todas as essas funcionalidades vamos utilizar o conceito de componentes do Angular e criar níveis de interações entre eles.
Criando componentes
O Angular CLI é a ferramenta ideal para criar conteúdo para o software, já vimos isso quando iniciamos o próprio projeto a partir de uma linha de código. Outro comando importante é o generate (cujo atalho é g). Ele é usado para criar conteúdo como componentes, módulos e serviços. Para cada tipo de comando há uma opção, como c, atalho para component.
Vamos começar pelo componente ListaDeDisciplinas
, que implementa a funcionalidade de listar disciplinas. Para criá-lo use a linha de comando:
ng g c ListaDeDisciplinas --spec false
O comando cria o diretório ./src/app/lista-de-disciplinas
e, dentro dele, os arquivos necessários para o componente, ou seja CSS, Template e Controller.
Componente lista de disciplinas
O componente ListaDeDisciplinas é um tipo de componente diferente do que vimos até agora: ele não funciona sozinho. Isso quer dizer que ele precisa ser inserido em outro componente para funcionar. No nosso contexto, o componente App inclui e utiliza o componente ListaDeDisciplinas. Vamos chamar o componente App de host (hospedeiro) e o componente ListaDeDisciplinas de guest (hóspede). Nessa comunicação o host deve fornecer recursos necessários para o guest funcionar, o que chamamos input (entrada). Por outro lado, quando o guest notifica o host sempre que fizer qualquer coisa importante, o que chamamos output (saída ou evento).
Pode ser que não tenha te ocorrido, mas é uma analogia bem interessante. Imagine que você está hospedando um convidado na sua casa. Você é o host, enquanto seu convidado é o guest. Sua casa é onde você irá receber seu convidado. O convidado precisa de um lugar para dormir, então você fornece para ele uma cama (input). Seu convidado te notifica quer ir embora (output), então você se despede dele e o encaminha até a saída. Pensou naquele convidado exigente (chato)? Com um componente guest é a mesma coisa: muita necessidade e muita notificação!
A figura a seguir ilustra o componente ListaDeDisciplinas.
O componente apresenta uma lista de disciplinas. Cada item da lista contém:
- o nome da disciplina
- um botão para excluir a disciplina
- um botão para editar a disciplina
Os dois botões não executam as ações correspondentes (excluir ou editar) porque isso não é responsabilidade desse componente. O que o componente faz é notificar o host que o usuário deseja excluir ou editar determinada disciplina. Além disso o componente, ao apresentar a lista das disciplinas, precisa saber qual disciplina está sendo editada, para que destaque essa disciplina na lista (perceba, na figura, o ícone do lápis e o fundo da linha com a cor cinza na disciplina "Língua Portuguesa"). Assim:
- input: a lista das disciplinas e a disciplina que está sendo editada; e
- output: a disciplina que o usuário deseja excluir ou editar.
O host utiliza (ou recebe) guest, então ele deve fornecer os inputs requeridos. Além disso, o host também pode realizar alguma ação quando for notificado de que o usuário deseja realizar uma exclusão ou uma edição de uma disciplina.
A figura a seguir ilustra essa relação entre os dois componentes.
O diagrama apresentado pela figura demonstra as conexões entre os componentes:
- App fornece as entradas para ListaDeDisciplinas
- os atributos
disciplinas
eeditando
deAppComponent
são vinculados aos inputdisciplinas
eeditando
doListaDeDisciplinasComponent
- os atributos
- ListaDeDisciplinas notifica App quando o usuário desejar excluir ou editar uma disciplina
- o método
editar()
doAppComponent
é executado quando ocorrer o outputonEditar
- o método
excluir()
doAppComponent
é executado quando ocorrer o outputonExcluir
- o método
[info] O diagrama dos componentes
A UML possui um diagrama para representar componentes de um software e as relações entre eles. Entretanto, por ser uma representação gráfica mais formal, requer que entradas e saídas sejam interfaces. Aqui adotamos uma simplificação desse diagrama para suportar o conceito de Input e Output do Angular.
Uma observação importante: os nomes dos atributos das classes (host e guest) não precisam ser os mesmos.
Antes de utilizar o componente ListaDeDisciplinas no App, vamos ver detalhes da sua composição. Primeiro, o Controller:
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
@Component({
selector: 'app-lista-de-disciplinas',
...
})
export class ListaDeDisciplinasComponent implements OnInit {
@Input()
disciplinas = null;
@Input()
editando = null;
@Output()
onEditar = new EventEmitter<any>();
@Output()
onExcluir = new EventEmitter<any>();
constructor() {
}
ngOnInit() {
}
excluir(disciplina) {
this.onExcluir.emit(disciplina);
}
editar(disciplina) {
this.onEditar.emit(disciplina);
}
}
Atenção para os atributos disciplinas
e editando
. Ambos são anotados com @Input()
, o que indica para o Angular tratá-los como input. Os atributos onEditar
e onExcluir
são anotados com @Output()
, o que indica para o Angular tratá-los como output. Além disso, perceba também mudanças na linha do import
.
Na prática, os atributos onEditar
e onExcluir
definem eventos, por isso são inicializados com uma instância de EventEmitter
, a classe do Angular para representar eventos. Note, na instanciação de EventEmitter
, que o código define o tipo de dados do evento utilizando a notação de diamante: <any>
. Isso é melhor interpretado assim: um evento pode carregar consigo alguma informação (dados) e, para isso, é importante indicar o tipo desses dados.
Os métodos excluir(disciplina)
e editar(disciplina)
são utilizados, respectivamente, como tratadores de eventos para quando o usuário clicar nos botões correspondentes, escolhendo excluir ou editar uma disciplina. O importante aqui é que, como estamos utilizando eventos (output), não queremos que seja responsabilidade desse componente ListaDeDisciplinas realizar essas tarefas. Pelo contrário, o esperado é notificar o host de que isso aconteceu. Para indicar para o host que uma disciplina deve ser excluída é usada a chamada para o método this.onExcluir.emit(disciplina)
. Isso fará um "disparo" do evento, notificando o host de que ele ocorreu, e incluirá, como dado do evento, a disciplina em questão (o objeto disciplina
). Da mesma forma, para notificar o host que deve editar uma disciplina é usada uma chamada para o método this.onEditar.emit(disciplina)
.
O Template do ListaDeDisciplinas contém:
<table class="table table-hover table-bordered">
<thead class="thead-light">
<tr>
<th>Disciplina</th>
<th width="200">Ações</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let disciplina of disciplinas"
[class.table-active]="editando == disciplina">
<td>
<i *ngIf="editando == disciplina"
class="fa fa-pencil-square"></i>
{{disciplina.nome}}
</td>
<td>
<button class="btn btn-sm btn-danger"
(click)="excluir(disciplina)">
<i class="fa fa-trash"></i> Excluir
</button>
<button class="btn btn-sm btn-primary"
(click)="editar(disciplina)">
<i class="fa fa-pencil-square"></i> Editar
</button>
</td>
</tr>
</tbody>
</table>
Percebeu semelhança com o Template do capítulo anterior? Isso não é coincidência. Foi realmente isso que aconteceu. Tanto é que a melhor dica para se trabalhar com múltiplos componentes em Angular, pelo menos enquanto você for um dev iniciante, é usar apenas um componente e implementar nele todas as funcionalidades desejadas. Depois, a tarefa é "recortar" partes desse componente e usar em outros componentes.
A forma pela qual o host incorpora o guest e informa o vínculo com input e output é por meio do Template. Veja o trecho a seguir, do Template do componente App:
<app-lista-de-disciplinas
[disciplinas]="disciplinas"
[editando]="editando"
(onEditar)="editar($event)"
(onExcluir)="excluir($event)">
</app-lista-de-disciplinas>
Primeiro, o elemento app-lista-de-disciplinas
é utilizado para indicar para o Angular que nesse local deve ser apresentado o componente ListaDeDisciplinas. Isso acontece por causa do selector
do componente ListaDeDisciplinas (veja o Controller desse componente).
Segundo, a sintaxe que tem um atributo HTML com nome entre colchetes você já conhece. É a mesma que já utilizamos para definir, por exemplo, a classe CSS de um elemento do HTML com base em uma condição (ex: o fundo da linha da tabela na cor cinza quando a disciplina está sendo editada). Então o trecho [disciplinas]="disciplinas"
indica o uso de Data Binding: o input chamado disciplinas
está vinculado ao atributo disciplinas
. Da mesma forma o trecho [editando]="editando"
usa Data binding para vincular o input editando
ao atributo editando
.
Por fim, a sintaxe que tem um atributo HTML com nome entre parênteses você também já conhece. É a mesma que já utilizamos para tratar eventos (ex: excluir uma disciplina quando o usuário clicar em um botão associado a ela). Então o trecho (onExcluir)="excluir($event)"
indica o uso de um tratador de eventos: o método excluir()
deve ser chamado passando $event
como parâmetro quando ocorrer o evento onExcluir
do componente ListaDeDisciplinas. É importante observar que $event
é uma forma de acessar os dados do evento quando disparado no componente ListaDeDisciplinas. Nesse caso, do evento onEditar
, o dado é a disciplina que deve ser excluída. O mesmo comportamento acontece para o evento onEditar
.
A figura a seguir procura resumir todas as informações apresentadas até aqui e procura demonstrar a interação entre os componentes App e ListaDeDisciplinas.
O repositório livro-componentes-editor ainda fornece mais um nível de criação de componentes, demonstrando o código para o componente EditorDeDisciplina, que fornece um formulário para cadastro e para edição de uma disciplina. Fica como exercício para fixação da aprendizagem o estudo do código desse repositório.
[info] Resumo do capítulo:
- Utilizamos Angular CLI para criar um componente
- Um host é um componente que utiliza ou inclui um outro, chamado de guest
- Um guest não funciona sem ser utilizado em um host
- O conceito de Input é utilizado para o host fornecer entrada para o guest
- O conceito de Output é utilizado para o guest notificar o host de que algo (um evento) aconteceu
- A interação entre componentes é mais um recurso que usa Data binding para vincular entradas do guest a atributos do host