[ Angular ] Explicando Comunicação entre componentes
Olá devs! Sou desenvolvedor front end com Angular 2+ e estou aqui para tirar duas duvidas ao mesmo tempo, comunicação entre componentes e services no Angular.
Dados:
Angular CLI: 17.0.8
Node: 20.10.0
Package Manager: npm 10.2.5
OS: linux x64
O que são componentes?
Primeiro, vamos a uma breve explicação sobre o que são componentes.
Observe o seguinte html.
<table class="table">
<thead>
<tr>
<th scope="col"><input type="checkbox" disabled></th>
<th scope="col">Nome</th>
<th scope="col">Status</th>
<th scope="col">Criado em</th>
<th scope="col">Criado por</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row"><input type="checkbox"></th>
<td>Aliexpress</td>
<td>Habilitado</td>
<td>hoje, 10:31</td>
<td>Admin</td>
</tr>
<tr>
<th scope="row"><input type="checkbox"></th>
<td>Shopee</td>
<td>Desabilitado</td>
<td>23/11/2023, 15:22</td>
<td>Usuario</td>
</tr>
</tbody>
</table>
Imagine uma query no banco de dados que retorne 10 linhas na tabela. Você precisaria escrever 10 vezes o mesmo <tr>
certo?
Pois é, e se a gente transformar isso em um componente? Vamos ver?
<tbody>
<tr>
<th scope="row"><input type="checkbox"></th>
<td>{{nome}}</td>
<td>{{enable}}</td>
<td>{{date}}</td>
<td>{{author}}</td>
</tr>
</tbody>
Nossa, muito mais simples. Agora só passar um script que faz um loop e está pronto.
Criando um componente no Angular
Certo, agora você já viu a estrutura de uma linha de tabela que aceita valores dinamicos.
Agora, vamos colocar isso dentro de um componente e chamar com uma tag html.
Primeiro, vamos utilizar o ng
para gerar um componente utilizando o comando ng generate componente linha-tabela
, esse é o comando CLI do angular para criar um componente, mas vamos chamar ele com os alias.
ng g c components/linha-tabela
Observe que se não passar o caminho, ele vai jogar o seu componente dentro de app, então cuidado!
Dependendo da versão da sua CLI, vamos ter uma estrutura assim:
components
linha-tabela
inha-tabela.components.css
linha-tabela.components.html
linha-tabela.components.spec.ts
linha-tabela.components.ts
Agora cole a linha de sua tabela dentro do html e sinta-se livre para estilizar com css.
Dentro de linha-tabela.components.ts
vamos ter a seguinte estrutura (pode mudar de acordo com a versão da sua CLI)
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-linha-tabela',
standalone: true,
imports: [CommonModule],
templateUrl: './linha-tabela.components.html',
styleUrl: './linha-tabela.components.css',
})
export class LinhaTabela implements OnInit {
constructor() {}
ngOnInit(): void {}
}
OBS: Esté é um componente standalone, então não precisa de modulo.
Agora, dentro de um elemento pai de sua preferencia, chame o seu seletor com
<app-linha-tabela></app-linha-tabela>
.
Passando valores dinamicamente
Certo, agora vamos passar os valores utilizando um @Input
.
Na sua classe, defina os valores que vão ser passados como atributos
export class LinhaTabela implements OnInit {
constructor() {}
ngOnInit(): void {}
@Input() nome: string = ''
@Input() enable: string = ''
@Input() date: string = ''
@Input() author: string = ''
}
Agora no seu elemento pai, adicione os atributos no componente html vindo de uma api ou um array.
<app-linha-tabela
*ngFor="let item of items; let i = index;"
[nome]="item.name"
[enable]="item.enabled"
[date]="item.date"
[author]="item.author">
</app-linha-tabela>
E temos um loop for of
dentro do html.
Aqui tivemos uma interação parent --> child
, interações child --> parent
é feita com @Output()
, onde @Output emite um evento:
export class Child {
@Output() meuEvento = new EventEmitter<string>();
}
<app-parent (newItemEvent)="faça algo"></app-parent>
Comunicação com serviços
Passar valores com pai --> filho
ou filho --> pai
precisa de muita informação e as vezes é muito confuso ou trabalhoso. É perfeito quando o filho recebe algo do pai quando o pai é carregado.
Mas e pra passar valores de irmão para irmão?
Seguindo a lógica do apresentado até agora, podemos fazer a seguinte instrução:
Quando botão x do componente irmão 1 for clicado, passa o evento para o elemento pai.
Quando elemento pai receber um evento de irmão 1, troque a cor do irmão 2.
Irmão 1 --event--> Pai --Value--> Irmão 2
Mas e se eu quiser manipular mais de um elemento? E se eu precisar observar algo no irmão 1?
Dependendo da complexidade do sistema, essa instrução pode ser inviavél. E algum comportamento na rota /
decidir o que acontece na rota /map
?
Serviço para comunicação de componentes
Em Angular, tudo é um componente. Não existe página, Angular é um framework SPA (Sigle Page Application). Com isso em mente, vamos criar algo que possa ser compartilhado na página inteira, um service!
Services são classes injetaveis que possuem a mesma instancia para todos os componentes, sabendo disso, vamos criar uma service para compartilhar dados entre componentes.
Utilizando o comando ng g s core/services/data
, criamos uma classe injetável dentro da pasta core/services.
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class DataService {
private data = new BehaviorSubject<boolean>(false);
public changeData(value: boolean) {
this.botVisibled.next(value);
}
public getData(): BehaviorSubject<boolean> {
return this.data;
}
}
Mas o que fizemos aqui?
Então, importamos o BehaviorSubject
do rxjs
e vamos utilizar ele para monitorar os dados de algum componente. Intanciamos o BehaviorSubject
em um atributo privado e passamos um generics para dizer que é um boolean true/false
.
Em seguida, criamos dois metodos, um para alterar e outro para retornar o nosso Subject. Agora, vamos utilizar esses metodos em algum componente.
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DataService } from '../../core/services/data.service';
@Component({
selector: 'app-linha-tabela',
standalone: true,
imports: [CommonModule],
templateUrl: './linha-tabela.components.html',
styleUrl: './linha-tabela.components.css',
})
export class LinhaTabela implements OnInit {
constructor(private data: DataService) {}
ngOnInit(): void {
this.data.getData().subscribe((visible) => {
this.visible = visible;
});
}
public visible: boolean = false;
}
Certo, o que fizemos aqui?
Fizemos a importação do service e injetamos direto no construtor da classe com private data: DataService
.
Em seguida, utilizamos o metodo de ciclo de vida ngOnInit()
para se inscrever no atributo data
dentro da service, agora sempre que o valor mudar, automaticamente o valor de visible
também vai mudar.
Para alterar o valor na service, vou utilizar o metodo changeData(value: boolean)
.
Agora, dentro de qualquer lugar, crie algo de sua preferencia que chame o changeData()
.
import { inject } from '@angular/core';
public botsChangeCardString(): void {
inject(DataService).changeData(true);
}
E como funciona? O que você fez?
Lembra que também colocamo esse metodo:
public changeData(value: boolean): void {
this.botVisibled.next(value);
}
Dentro da service?
Dentro do next(value: any)
, está o novo valor que será passado para o BehaviorSubject
. Apenas passamos o valor como argumento na função.
E o inject(Service)
, é apenas mais uma maneira de injetar um serviço em algum lugar. Como não utilizei construtor para o exemplo de troca, importei o inject que faz a mesma coisa.
Declaração Final
OBS: Não vendo curso! Na verdade, não vendo nada!
Eu tive muita dificuldade em encontrar uma comunidade para conversar sobre Angular, a maioria daas pessoas utilizam React/Next para front end, enquanto os usuários de Angular/Vue já possuem mais um pouco de dificuldade de se alocar em algum nicho.
Portanto, criamos um grupo no whatsapp dedicado somente para usuarios de Angular na AllStack Community. Possuimos mais de 200 participantes em diversos grupos, e agora estamos criando mais um focado apenas em Angular.
AllStack Angular Devs: https://chat.whatsapp.com/IXTB4UZmRFM9bTZGu2ZL1g
AllStack Community: https://chat.whatsapp.com/J5KyhoUYapJ7izSQYJGv67