Services e injeção de dependência no Angular: o que são e como funcionam?

Services e injeção de dependência no Angular: o que são e como funcionam?
Nayanne Batista
Nayanne Batista

Compartilhe

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.

A imagem compara dois exemplos de código onde no primeiro há um componente que consome um serviço sem utilizar a injeção de dependências enquanto o segundo consome o serviço usando a injeção de dependências. O código está escrito em TypeScript e usa um fundo escuro com texto branco, verde e azul. O primeiro exemplo cria a dependência dentro do construtor da classe, enquanto o segundo exemplo a recebe como um parâmetro. Banner da Alura em fundo dinâmico azul, sugerindo tecnologia e modernidade para o Quizz Jornada Tech. À esquerda, em letras grandes, 'Quizz Jornada Tech' seguido por 'Teste seus conhecimentos em tecnologia e prepare-se para o próximo nível da sua carreira'. À direita, conectados por uma linha azul suave representando uma trilha, os tópicos: programação, front-end, inteligência artificial, ciência de dados, inovação e gestão. No final da trilha, um botão azul convidando a 'começar agora'. No canto superior direito, o logo da Alura. Clique e inscreva-se já!

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.

A imagem apresenta um diagrama sobre o conceito de injeção de dependência, o título da imagem é "Injeção de Dependência" e está em azul claro. O diagrama usa um fundo escuro com linhas azuis e texto branco. O diagrama mostra três serviços, cada um com uma função, e um componente. O diagrama ilustra como o serviço 2 é injetado no componente, usando uma seta verde em forma de seringa.

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.

A imagem apresenta um diagrama com fundo escuro com ícones brancos e azuis que mostra como um serviço funciona em uma aplicação Angular. O serviço é representado por um monitor de computador que se conecta aos componentes. Os componentes são retângulos numerados de 1 a 3 que se comunicam com o serviço. Também há o símbolo de uma pasta que representa a aplicação que contém os componentes e se conecta a eles por meio de setas.

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'.

A imagem apresenta um diagrama que ilustra o conceito de Singleton em uma aplicação, exemplificando através de uma impressora compartilhada por vários usuários. O diagrama tem o título ‘Exemplo: Singleton’ em texto branco no topo e tem um fundo escuro e elementos gráficos brancos e azuis. No lado esquerdo, há cinco ícones de pessoas, cada um enviando uma seta azul para o centro, representando uma solicitação para a impressora.  A impressora é um objeto único que processa todas as solicitações e imprime os documentos, assim como o serviço Singleton faria ao lidar com solicitações de vários componentes. No lado direito, há cinco ícones de documentos representando as impressões concluídas.

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.

Imagem exemplificando o modo tradicional de ir até o restaurante para fazer a refeição e o modo simplificado fazendo o pedido pelo aplicativo. Nesse caso o serviço de delivery seria a dependência injetada.

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.

Imagem mostrando um título: Escolha seu pedido e três botões cor de laranja com os nomes: Refeições; Refeição + sobremesa e Refeição + drink.

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é?

Imagem mostrando um título: Escolha seu pedido e três botões cor de laranja com os nomes: Refeições; Refeição + sobremesa e Refeição + drink e ao lado, imagem do console do navegador mostrando mensagens referentes aos serviços injetados

Cuidados ao usar injeção de dependências

  1. 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.
  2. 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.
  3. 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.
  4. 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:

Espero te encontrar em breve em novos mergulhos no Angular. Até mais!

Nayanne Batista
Nayanne Batista

Nayanne (Nay) é uma paraibana arretada que fez transição de carreira para a TI depois de concluir um doutorado na área da saúde e ser professora no ensino superior. Graduada em Análise e Desenvolvimento de Sistemas, já atuou como Dev Frontend em projetos e hoje é Instrutora na Alura. Acredita completamente no poder transformador da educação e da tecnologia na vida das pessoas. Ama livros e café! :)

Veja outros artigos sobre Front-end