Services e injeção de dependência no Angular: o que são e como funcionam?
Injeção de dependência é um termo que pode parecer assustador para quem está iniciando na área de programação, mas não é um bicho de sete cabeças. Hoje vamos acabar com o medo dessa injeção através deste artigo e aprender com exemplos simples:
- O que é injeção de dependência;
- Como utilizar a injeção de dependência no Angular;
- Quais as vantagens desse padrão de projeto;
- O que é e como criar um serviço injetável;
- Como injetar serviços em componentes e em outros serviços.
Se você está começando agora, aprenda como criar sua primeira aplicação Angular e quais os primeiros passos com este framework.
O que é injeção de dependência?
Considere o seguinte exemplo: se a classe A precisa de informações da classe B para poder funcionar, então, A depende de B, ou seja, B é uma dependência para A. Olhando para um outro contexto: sabe quando você acorda super cedo para treinar e depende do seu cafezinho para conseguir fazer os exercícios?
Nayanne, então, você está dizendo que o café é a minha dependência?
Isso mesmo, é essa a ideia!
De forma mais técnica, dependências são serviços, objetos, funções ou até mesmo um valor que uma classe necessita para desempenhar sua função. No nosso exemplo, a classe A pode ela mesma ser responsável por criar uma instância da classe B, ou então, essa dependência pode ser passada para ela, ou melhor dizendo, ser injetada nela. Esse processo recebe o nome de injeção de dependência.
De acordo com a documentação do Angular, injeção de dependência é um padrão de projeto no qual uma classe solicita dependências de fontes externas ao invés de criá-las.
Como utilizar injeção de dependência no Angular?
Para exemplificar como funciona a injeção de dependência no Angular, vamos aprender a criar serviços (services) injetáveis e utilizá-los em um componente da nossa aplicação. Mas antes vamos entender o que são serviços.
O que são services?
Os arquivos em Angular possuem responsabilidades bem definidas. É uma boa prática que o componente contenha apenas a lógica para definir comportamentos e conseguir renderizar os arquivos na tela. Assim, é necessário que haja um arquivo para guardar toda a lógica de negócios e que seja responsável pela comunicação com o servidor. Esse arquivo é o service.
Os serviços no Angular nos auxiliam a separar do componente algumas informações importantes e também o modo como vamos obtê-las, lógica de negócios, além de dados de requisições ao servidor. Eles são úteis porque o código contido neles pode ser utilizado por toda a aplicação e não será repetido em vários locais diferentes, visto que essas funcionalidades podem ser compartilhadas entre os componentes.
Como criar um serviço injetável?
Temos uma aplicação de delivery e vamos criar um serviço chamado FoodService, utilizando o seguinte comando do Angular CLI:
ng generate service food
ou a forma abreviada:
ng g s food
Será gerado um arquivo com a seguinte estrutura:
food.service.ts
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class FoodService {
constructor() { }
}
Analisando este arquivo, percebemos que se trata de uma classe que contém um decorator chamado @Injectable(), importado do pacote @angular/core. Esse decorator indica ao Angular que essa classe é injetável e pode ser utilizada em outras classes.
Tipos de Injetores
O decorator @injectable(), por padrão, possui um metadado chamado providedIn. Esse nome vem de provider (provedor), que significa fornecedor. Ele é o responsável por fornecer uma instância dessa classe através da injeção de dependência. Nesse caso, o valor dessa propriedade: providedIn: 'root'
, indica que o Angular deve fornecer o serviço no injetor raiz, em outras palavras, significa que esse serviço é visível para toda a aplicação e você pode injetá-lo em qualquer lugar do seu projeto. Essa definição de provider root
acontece quando queremos ter uma única instância de um serviço em toda a aplicação e pode ser chamada de Singleton.
Vale destacar, que o Singleton é um padrão de projeto que busca limitar a quantidade de instâncias de uma classe específica, para que todos os elementos dependentes acessem uma única instância compartilhada. Essa configuração como Singleton pode ser realizada tanto no nível raiz da hierarquia de injeção de dependência quanto através do uso do modificador providedIn: 'root'
.
Recomenda-se sempre fornecer seu serviço no injetor 'root'
, a menos que haja um caso em que você deseja que o serviço esteja disponível apenas se o consumidor importar um @NgModule
específico.
Nesse caso, é possível especificar que um serviço deve ser fornecido em um determinado @NgModule. Por exemplo, se você quisesse que o FoodService só pudesse ser usado nos locais em que um módulo UserModule
que você criou estivesse importado, você poderia especificar que o service deve ser fornecido no módulo da seguinte forma:
import { Injectable } from '@angular/core';
import { UserModule } from './user.module';
@Injectable({
providedIn: UserModule
})
export class FoodService {
constructor() { }
}
Além disso, o provider também pode ser definido como any
, indicando que um serviço deve ser instanciado de forma diferente com base em se está sendo utilizado em módulos carregados imediatamente (eager loading) ou tardiamente (lazy loading). Se definimos any
em módulos carregados imediatamente, todos compartilham a mesma instância do serviço, assim como ocorreria com root
. No entanto, em módulos carregados tardiamente, cada módulo recebe sua própria instância do serviço, garantindo isolamento entre os diferentes módulos e evitando conflitos.
Ainda, podemos definir o provider como platform
. Na hierarquia de injeção em módulos, o platform
é “pai” de root
, assim, ele é compartilhado por todas as aplicações na página. Essa abordagem é útil quando você tem várias aplicações Angular em execução em uma única página. Ao fornecer o serviço ao nível da plataforma, você garante que uma única instância desse serviço seja compartilhada entre todas as aplicações Angular na página.
Por fim, há a possibilidade de deixar o injetor como null
. Nesse caso, você desabilita a funcionalidade padrão de injeção de dependência para esse serviço e deve fornecer manualmente a instância do serviço sempre que for necessária.
Então, agora que já sabemos como criar serviços injetáveis, vamos aprender como usá-los em nossos componentes?
Como injetar serviços em componentes?
Para mostrar a injeção de dependência em ação, vamos utilizar o seguinte exemplo.
Imagine que durante um dia mais corrido, você não tenha tempo de preparar uma refeição e decida ir a um restaurante. Você precisa enfrentar o trânsito para ir de bike ou de carro até lá, pegar o cardápio, esperar ser atendido, fazer o pedido e depois pagar por ele. Mas, e se houvesse um jeito mais prático e fácil de fazer isso? E se, ao invés de você ir até o restaurante, o pedido viesse até você? Isso pode ser resolvido por meio de um pedido de refeições via aplicativo, certo?
Note que, ao fazer o pedido pelo aplicativo, você não fica mais responsável por todo o processo e deixa isso a cargo do serviço de delivery, não é mesmo? Bem, esse é outro exemplo de injeção de dependência e é com esse contexto que vamos exemplificar o uso de serviços em componentes.
No nosso projeto, temos um componente chamado delivery, que é utilizado para renderizar na tela as opções de pedidos num restaurante. Esse componente precisa do serviço que criamos anteriormente, o FoodService, para a escolha do tipo de refeição.
Em alguns dias da semana, o restaurante faz uma promoção para as pessoas que quiserem pedir, além da refeição, uma sobremesa ou uma bebida. Nesse caso, o FoodService
precisa se comunicar com outros dois serviços, o DessertService
e o DrinkService
.
Agora, vamos ao código ver como resolver isso!
Sem a injeção de dependência, teríamos que instanciar manualmente todos os serviços de que precisamos no componente, além de ter que passar todos os possíveis parâmetros que esses serviços utilizam. Assim:
delivery.component.ts
export class DeliveryComponent {
//declaração dos atributos dos services
drinkService: DrinkService;
dessertService: DessertService;
foodService: FoodService;
constructor() {
this.drinkService = new DrinkService();
this.dessertService = new DessertService();
this.foodService = new FoodService(this.dessertService, this.drinkService);
}
//métodos da classe
}
Já imaginou o trabalhão? Além do acoplamento de classes, repetição de código e dificuldade nos testes que isso iria causar na sua aplicação? E olha que esse componente utiliza apenas três serviços. Definitivamente, não queremos criar essas classes manualmente, queremos que o serviço nos forneça isso.
Vamos ao exemplo com injeção de dependência:
delivery.component.ts
export class DeliveryComponent {
constructor(private foodService: FoodService) { }
//métodos da classe
}
Sim, é só isso mesmo! No Angular, a injeção de dependência pode ser feita via construtor, onde especificamos um parâmetro com o tipo da dependência (foodService: FoodService) e, ao colocar o modificador de acesso private, fazemos com que esse atributo seja automaticamente declarado como atributo dessa classe.
Além disso, a partir da versão 14 do Angular, recebemos a nova função inject
, que também nos permite fazer a injeção de dependências de um modo diferente. Com ela, podemos fazer a mesma injeção que fizemos no bloco anterior, mas da seguinte forma:
import { Component, inject } from '@angular/core';
export class DeliveryComponent {
foodService = inject(FoodService);
//métodos da classe
}
Nesse caso, usamos inject
para injetar o FoodService
na classe DeliveryComponent
, atribuindo o serviço à propriedade foodService
.
A injeção de dependências pode ser feita tanto através do construtor, como através da função inject()
. A escolha entre elas fica a seu critério, mas é crucial lembrar que ao utilizar inject
é necessário lembrar de realizar sua importação de @angular/core
.🙂
Assim, o DeliveryComponent
está pedindo para o FoodService
ser injetado, em vez de criar sua própria instância dessa classe de serviço.
Certo, mas e como o FoodService
está consumindo os outros dois serviços? Vamos ao próximo tópico!
Usando serviços em outros serviços
Para usar serviços em outros serviços, a abordagem é semelhante à que realizamos para injetar essas classes em componentes. Abaixo, temos o arquivo FoodService
com os outros dois serviços sendo injetados via construtor e alguns métodos de exemplo consumindo dados referentes às classes injetadas, apenas para visualizarmos no navegador que está tudo certo.
food.service.ts
import { DrinkService } from '../drink/drink.service';
import { DessertService } from '../dessert/dessert.service';
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class FoodService {
constructor(
private dessertService: DessertService,
private drinkService: DrinkService
) { }
food!: string;
selectFood(food: string) {
this.food = food;
console.log(this.food);
}
selectFoodAndDessert(food: string, dessert: string) {
this.selectFood(food);
this.dessertService.selectDessert(dessert);
}
selectFoodAndDrink(food: string, drink: string) {
this.selectFood(food);
this.drinkService.selectDrink(drink);
}
}
Resumindo, fizemos a injeção dos serviços DrinkService
e DessertService
no FoodService
e injetamos apenas este último no nosso componente. Na imagem abaixo, pelas mensagens no console do navegador, vemos que no componente delivery podemos consumir informações dos três serviços. Tudo isso, por meio da injeção de dependência! Que demais, né?
Cuidados ao usar injeção de dependências
- Overhead de desempenho: lidar com muitas dependências na injeção de dependência pode sobrecarregar o desempenho, o que vai exigir mais monitoramento e trabalho de otimização. Por isso, é necessário avaliar se os benefícios da flexibilidade superam a possível perda de eficiência nas aplicações em que a velocidade de resposta é relevante para a experiência da pessoa usuária.
- Configuração complexa: em projetos menores, é importante equilibrar simplicidade e modularidade para evitar que haja muita complexidade desnecessária na configuração inicial da injeção de dependências.
- Dificuldade na rastreabilidade: projetos grandes podem dificultar o rastreamento das dependências do projeto, o que impacta a manutenção. Por isso, é importante gerenciar e organizar o projeto para facilitar a resolução de problemas e melhorar a rastreabilidade das informações.
- Experiência da equipe: a injeção de dependências apresenta uma curva de aprendizado para pessoas desenvolvedoras que não estão acostumadas a trabalhar com essa técnica. Desse modo, é importante levar em conta a experiência da equipe para evitar erros na configuração e uso da injeção de dependências e fazer uma implementação mais eficaz.
Bônus: 10 motivos para usar injeção de Dependências
Vimos os recursos para que o Angular forneça automaticamente uma instância do serviço de modo a ser utilizada sem nos preocuparmos em instanciá-la manualmente. Além disso, vou te dar mais 10 motivos para começar a usar injeção de dependência:
- aumentar a flexibilidade e a modularidade em suas aplicações;
- diminuir código boilerplate (código repetitivo, em várias partes do projeto);
- deixar seu código mais limpo e eficiente;
- ajudar no gerenciamento de dependências;
- melhorar a reutilização do código;
- permitir a clara separação de responsabilidades entre os arquivos;
- diminuir o acoplamento entre as classes;
- facilitar a manutenção de código;
- possibilitar a alteração do serviço sem afetar os componentes dependentes;
- favorecer o desenvolvimento de testes unitários usando dependências simuladas.
Conclusão
Neste artigo, nós entendemos o que significa dependência, injeção de dependência, serviços, e aprendemos como utilizar esses conceitos de forma prática no Angular. Agora, você sabe criar serviços injetáveis para serem consumidos por componentes e outros serviços, organizando melhor sua aplicação.
Querida Dev e querido Dev, espero que você tenha aprendido algo novo com este artigo e se interessado em estudar mais sobre o tema! Por isso, indico o estudo sobre services no Angular e injeção de dependências.
E caso tenha interesse, mergulhe ainda mais fundo, estudando nossas formações:
- A partir do zero: HTML e CSS para projetos web;
- Desenvolva aplicações Web com JavaScript;
- Explore o Framework Angular.
Espero te encontrar em breve em novos mergulhos no Angular. Até mais!