En la primera parte de esta guía aprendimos qué es un service y cómo crear uno. En esta parte vamos a empezar a configurarlo para poder usarlo correctamente.
Cómo inyectar un servicio en un componente
Ya vimos que instanciar un servicio manualmente no era la manera proporcionada por Angular para usarlo 😬. En lugar de eso, debemos usar una manera más sencilla: la inyección de dependencias (en inglés, dependency injection).
La inyección de dependencias es la manera correcta de acceder a nuestros servicios.
Una dependencia es algo que una clase de TypeScript (en adelante, TS) va a necesitar para funcionar. Por ejemplo, el newAccountComponent depende del servicio LogService, porque necesitamos el método logStatusChange, contenido en el servicio. 😌
El inyector de dependencias se encargará de inyectar esa dependencia (una instancia del LogService) en nuestro componente de manera automática. Lo único que tendremos que hacer por nuestra parte es informar a Angular de que necesitamos dicha instancia. Veamos cómo hacerlo. 👀
1. En el archivo TS del componente donde vayamos a usar el servicio (el newAccountComponent), añadimos un constructor.
En el constructor, vinculamos el servicio usando la palabra clave private, que lo transformará en una propiedad de TS lista para usar. Llamamos a esa propiedad logService, aunque puedes darle el nombre que quieras. Lo fundamental aquí es especificar el tipo de propiedad que es, es decir, le indicamos que es un servicio, concretamente, el nuestro (LogService).
2. Lo importamos desde ../log.service
Así es como Angular sabrá que necesitamos una instancia de la clase LogService, pero aún no sabe cómo proporcionárnosla. Porque necesitamos dar un paso final: proveer el servicio.
"Proveer" (en inglés, "provide") significa contarle a Angular cómo debe crear el servicio.
Es un paso muy sencillo. Lo único que debemos hacer es crear una propiedad en el decorador @Component llamada providers, que acepta un array, y especificarle nuestro servicio.
Con estos pasos, ya podemos usar nuestro servicio en cualquier lugar del new-account.component.ts, accediendo a la propiedad logService, que a su vez nos da acceso al método logStatusChange. Al método le pasamos por parámetro accountStatus.
import { LogService } from '../log.service'; @Component({ ... providers: [LogService] }) export class NewAccountComponent { ... onCreateAccount(accountName: string, accountStatus: string) { this.accountAdded.emit({ name: accountName, status: accountStatus }); this.logService.logStatusChange(accountStatus); ... } }
¡Y listo! Tu app debería funcionar igual que antes, con la diferencia de que ahora estamos inyectando el servicio. 💉
Cómo informar a Angular: esquema
Utilizar los servicios de esta manera nos permite mantenernos dentro del ecosistema de Angular, lo que, a parte de ser una buena práctica, nos aporta diversas ventajas que veremos más adelante.
3. Sigamos los mismos pasos en el account.component.ts.
import { LogService } from '../log.service'; @Component({ ... providers: [LogService] }) export class AccountComponent { ... constructor(private logService: LogService) {} onSetTo(status: string) { this.statusChanged.emit({ id: this.id, newStatus: status }); // console.log('An account status changed, new status: ' + status); this.logService.logStatusChange(status); } }
¡Y ya está! Con esto hemos conseguido centralizar la funcionalidad de imprimir por consola el cambio de estado de una cuenta, consiguiendo así un código más corto y limpio. ¡Imagínate cómo afectaría esto si fuera una app mucho más grande! 😯
Cómo crear un servicio para gestionar datos
Otro uso típico de los servicios es aquel que nos permite guardar y mostrar información. Es lo que ya está haciendo el método onAccountAdded del app.component.ts, aunque con una lógica de comunicación entre componentes muy enrevesada, como ya vimos. Así que vamos a crear un servicio que se encargue de eso, y que también abarque la funcionalidad del método onStatusChanged. ¡Vamos allá!
1. Creamos un archivo a la altura del LogService y lo llamamos AccountsDataService.
2. Vamos al archivo accounts-data.service.ts y exportamos la clase.
export class AccountsDataService { }
3. Vamos al archivo app.component.ts, cogemos el array de accounts (lo cortamos) y lo pegamos en el servicio.
Lo siguiente que vamos a hacer es añadir un par de métodos al service que sigan la misma lógica que los métodos del app.component.ts, los cuales ya podemos borrar porque no nos harán falta.
Así que volvemos al accounts-data.service.ts y creamos un método al que llamamos addAccount, que espera un nombre y un estado de la cuenta. Utilizamos el método push para añadir una cuenta nueva, que definimos como un objeto con un nombre y un estado.
4. Creamos otro método al que llamamos updateStatus, que espera un id y un nuevo estado de la cuenta. Para acceder al id, le pasamos ese id como la posición del array "accounts".
export class AccountsDataService { accounts = [ { name: 'Master Account', status: 'active' }, { name: 'Testaccount', status: 'inactive' }, { name: 'Hidden Account', status: 'unknown' } ]; addAcount(name: string, status: string) { this.accounts.push({name: name, status: status}); } updateStatus(id: number, newStatus: string) { this.accounts[id].status = newStatus; } }
¡Chachi! Nuestro servicio está hecho, pero tenemos un problemilla 😬. Y es que si vamos al app.component.html, verás que tu IDE te marca errores en los selectores app-new-account y app-account. Solucionamos esto creando un array de accounts en el app.component.ts, pero esta vez le asignamos como data type un array de cuentas, y lo definimos como un array vacío.
5. Inyectamos el servicio y lo proveemos (en la propiedad providers, recuerda).
6. Utilizamos el ngOnInit para inicializar el servicio, ya que no es una buena práctica inicializarlo en el constructor.
7. Al array "accounts" le damos el valor de la propiedad accounts de nuestro servicio, ¡y así es como accedemos a nuestras cuentas! 🤗
Ten en cuenta que estamos accediendo exactamente al mismo array, y no creando una copia del mismo, porque un array es un reference type.
import { Component, OnInit } from '@angular/core'; import { AccountsDataService } from './accounts-data.service'; @Component({ ... providers: [AccountsDataService] }) export class AppComponent implements OnInit { accounts: {name: string, status: string}[] = []; constructor(private accountsDataService: AccountsDataService) {} ngOnInit() { this.accounts = this.accountsDataService.accounts; } }
Si guardamos y vamos a nuestro navegador, verás que la apariencia de nuestra app es correcta, ya que podemos ver las cuentas almacenadas en nuestro servicio AccountsDataService, pero la funcionalidad está rota 🥴. Para solucionar esto, debemos actualizar los componentes new-account y account.
8. En el new-account.component.ts nos deshacemos del event emitter, inyectamos el servicio AccountsDataService y lo proveemos.
9. En el método onCreateAccount, accedemos al método addAccount del AccountsDataService y le pasamos los mismos parámetros que espera el método onCreateAccount.
import { Component } from '@angular/core'; ... import { AccountsDataService } from '../accounts-data.service'; @Component({ ... providers: [LogService, AccountsDataService] }) export class NewAccountComponent { constructor(private logService: LogService, private accountsDataService: AccountsDataService) {} onCreateAccount(accountName: string, accountStatus: string) { ... this.accountsDataService.addAcount(accountName, accountStatus); } }
10. Vamos al account.component.ts, nos deshacemos del event emitter y seguimos el mismo proceso para inyectar el servicio AccountsDataService.
11. En el método onSetTo, accedemos al método updateStatus desde el AccountsDataService y le pasamos el id y el status.
import { Component, Input } from '@angular/core'; ... import { AccountsDataService } from '../accounts-data.service'; @Component({ ... providers: [LogService, AccountsDataService] }) export class AccountComponent { ... constructor(private logService: LogService, private accountsDataService: AccountsDataService) {} onSetTo(status: string) { ... this.accountsDataService.updateStatus(this.id, status); } }
Si vas al navegador, verás que ya no hay ningún error en la consola. Sin embargo, la app no funciona correctamente, ya que la funcionalidad está ahí, pero nuestra UI no la refleja. ¡¿Por qué?! 🤯
Funcionamiento de la herencia en la inyección de dependencias
Nuestro código funciona correctamente. El problema es que estamos usando mal los servicios.
Un inyector funciona como un sistema jerárquico de una sola dirección (desde arriba hacia abajo), de manera que Angular, cuando detecta una servicio en un componente, sabe cómo crear instancias de ese servicio en todos los child components de ese componente.
A mí me resulta más sencillo verlo en un esquema. Léelo de arriba a abajo:
El problema viene desencadenado por el cuadro naranja, porque precisamente eso es lo que estamos haciendo en nuestra app: sobrescribir el servicio AccountsData.
Si quieres saber cómo solucionarlo, ¡no te pierdas la parte #3! (disponible próximamente). Por lo pronto, espero que hayas aprendido algo nuevo 😊. Si te queda alguna duda, ¡nos vemos en los comentarios!
Sobre la autora de este post
Soy Rocío, una abogada reconvertida en programadora. Soy una apasionada de aprender cosas nuevas y ferviente defensora de que la única manera de ser feliz es alcanzando un equilibrio entre lo que te encanta hacer y lo que te saque de pobre. Mi historia completa, aquí.
Más recursos de aprendizaje
En mi experiencia, la manera más eficaz para aprender Angular es combinando varias vías de aprendizaje. Uno de mis métodos favoritos son los vídeo-cursos y mi plataforma predilecta para eso es Udemy. He hecho varios cursos pero sólo recomiendo aquellos que verdaderamente me han sido útiles. Aquí van:
Si necesitas apoyo en forma de libro, puede que éstos te sirvan de ayuda:
La programación es un mundo que evoluciona a una velocidad de vértigo. Los autores de estos libros lo saben, por eso suelen encargarse de actualizar su contenido regularmente. Asegúrate de que así sea antes de adquirirlos .
Participo en el programa de afiliados de Udemy y Amazon, lo que significa que, si compras alguno de estos cursos y/o libros, yo me llevaré una pequeña comisión y a ti no costará nada extra. Vamos, lo que se dice un win-win .
Otros artículos que pueden interesarte