En esta serie de posts, con un ejercicio bonus al final de la parte #3, vamos a tratar los servicios y la inyección de dependencias en profundidad. Si acabas de aterrizar aquí y estos temas de Angular te suenan a chino, puedes echar un vistazo a este artículo antes, donde te doy las claves para entenderlo y empezar a usarlo.
servicios = services
inyección de dependencias = dependecy injection
Qué son los services y para qué sirven
Los services sirven para evitarnos repetir código y para centralizar tareas comunes. Una tarea común es, por ejemplo, el gestionar adecuadamente el almacenamiento de datos (en inglés, data storage).
Supongamos que tenemos una app con varios componentes, en dos de esos componentes tenemos un método que sirve para imprimir ciertos datos en la consola, mientras que en otros componente tenemos un método que sirve para guardar datos que el usuario inserte.
Un service es sencillamente otra parte de tu app, que construimos a través de una clase normal de TypeScript (en adelante, TS),
y que actúa como un repositorio central, un almacén donde podemos centralizar nuestro código.
Estado inicial de nuestra app
Para entender bien los services, vamos a construir una pequeña demo-app con un par de componentes que se comunican entre sí sin usar services, para reflejar lo complejo que puede volverse todo en una app relativamente pequeña. Posteriormente la refactorizaremos usando services.
Este es su aspecto final, para que nos hagamos una idea:
Nuestra app está inspirada en el esquema de arriba, desarrollado. Será una app que mostrará tres "cuentas de usuario" por defecto, y un campo para añadir más cuentas. Podremos cambiar el estado (en inglés, status) de las cuentas, alternando entre "activo", "inactivo" y "desconocido". Aquí es donde entrará en juego un código para imprimir por consola el status al que haya cambiado el usuario.
También imprimiremos por consola el status de cada nuevo usuario que registremos, a la par que quedará registrado en nuestro código. Es decir, como se muestra en el esquema, usaremos un método para imprimir por consola un estado y otro para almacenar datos de registro de usuarios. Este es un caso típico donde podríamos centralizar dichos métodos y convertirlos en services. ¡Vamos a ello!
1. Creamos una app con Angular CLI y le añadimos Bootstrap para tener una UI decente a la velocidad del rayo.
2. Creamos dos componentes dentro de la carpeta app: account y new-account. Nuestro appComponent será el componente que abarcará los dos anteriores (el parent component).
3. Creamos el esqueleto inicial del app.component.html.
4. Creamos tres cuentas hardcoded en el app.component.ts y un método para añadir una cuenta nueva a un array de cuentas que posteriormente utilizaremos.
import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { accounts = [ { name: 'Master Account', status: 'active' }, { name: 'Testaccount', status: 'inactive' }, { name: 'Hidden Account', status: 'unknown' } ]; onAccountAdded(newAccount: { name: string, status: string }) { this.accounts.push(newAccount); } }
5. Iteramos (a través de un loop) todas las cuentas en el app.component.html y añadimos un custom event que luego configuraremos.
Ese custom event se disparará cuando hagamos clic sobre alguno de estos botones para cambiar el estado:
Así que ya que estamos, vamos a crearlos (en el account.component.html), donde también añadimos un click event vinculado a un método que capturará ese cambio de estado.
6. Configuramos nuestro custom event (statusChanged) en el account.component.ts, que emitimos a través del método onSetTo.
Recuerda que en el appComponent extraemos la posición de cada account del array "accounts" usando el index, actuando así como un ID al que se le asignará un número empezando desde 0. Ese ID quedará asignado desde el appComponent, por tanto, podemos crear una propiedad de TS en el account.component.ts, llamada id, que reciba ese dato desde ahí. Para avisar a Angular de esto, decoramos la propiedad con el @Input.
Creamos también otra propiedad llamada account que recibirá su valor desde su parent component, por tanto, la decoramos con el @Input.
import { Component, EventEmitter, Input, Output } from '@angular/core'; @Component({ selector: 'app-account', templateUrl: './account.component.html', styleUrls: ['./account.component.css'] }) export class AccountComponent { @Input() id: number; @Input() account: {name: string, status: string}; @Output() statusChanged = new EventEmitter<{ id: number, newStatus: string }>(); onSetTo(status: string) { this.statusChanged.emit({ id: this.id, newStatus: status }); console.log('An account status changed, new status: ' + status); } }
La propiedad account es de tipo objeto y contiene sus propias propiedades (name y status), así que las vinculamos mediante string interpolation en el account.component.html.
7. Añadimos las propiedades account y id al app.component.html mediante property binding, y las vinculamos al index y a la variable local del loop.
</app-account>
8. Configuramos el método onStastusChanged en el app.component.ts.
onStatusChanged(updateInfo: { id: number, newStatus: string }) { this.accounts[updateInfo.id].status = updateInfo.newStatus; }
9. Creamos el esqueleto inicial del new-account.component.html.
10. En el botón, añadimos un click event que escuche lo que ocurre cuando el usuario crea una cuenta nueva. Lo vinculamos a un método que se encargue de eso, llamado onCreateAccount, que se ayudará de local references para capturar el nombre y el estado de la nueva cuenta.
11. Configuramos otro custom event (esta vez en el new-account.component.ts) para emitir los datos que el usuario introduzca al crear una nueva cuenta.
import { Component, EventEmitter, Output } from '@angular/core'; @Component({ selector: 'app-new-account', templateUrl: './new-account.component.html', styleUrls: ['./new-account.component.css'] }) export class NewAccountComponent { @Output() accountAdded = new EventEmitter<{name: string, status: string}>(); onCreateAccount(accountName: string, accountStatus: string) { this.accountAdded.emit({ name: accountName, status: accountStatus }); console.log('An account status changed, new status: ' + accountStatus); } }
Esos datos los capturará el app.component.html, por medio del custom event "accountAdded" y el método onAccountAdded.
¡Nuestra app está terminada! Como verás, ocurren dos cosas:
- cada vez que cambiamos el estado de una cuenta, el cambio se imprime en la consola.
- cada vez que creamos una cuenta nueva, la cuenta queda registrada y su estado se imprime por consola.
¡Uuufff...! Como habrás comprobado, todo esto funciona, pero es innecesariamente complejo. Así que aquí entran en juego los services. Veamos a continuación cómo podemos utilizarlos a nuestro favor para simplificar y mejorar nuestro código.
Cómo crear un service que imprima datos en la consola
Vamos a crear un servicio que imprima datos (en inglés, log data) en la consola, como ya lo estábamos haciendo en el new-account.component.ts y en el account.component.ts. Ya que este servicio se hará cargo de una tarea bastante genérica, lo lógico es colocarlo dentro de la carpeta raíz (la carpeta app).
1. Creamos un archivo manualmente dentro de la carpeta app, llamado log.service.ts. Lo de "service" no es obligatorio, sino una convención para poder identificar rápidamente qué contiene un archivo. Además, al añadir esa coletilla, Visual Studio Code detectará que es un service y le dará al archivo un icono amarillo distintivo.
Una vez creado, vamos al archivo y exportamos una clase de TS a la que llamamos LogService.
Al contrario que un componente o una directiva, un servicio es una clase de TS normal y corriente que no necesita un decorador para funcionar ni ser reconocida por Angular como tal.
2. Creamos un método para imprimir por consola un cambio en el status de una cuenta. Por parámetro le pasamos un status, que será de tipo string. Hacemos un console.log del parámetro para conseguir nuestro objetivo.
export class LogService { logStatusChange(status: string) { console.log('An account status changed, new status: ' + status); } }
Con este sencillo paso, ya podemos decir que nuestro código para imprimir datos por consola está centralizado en un solo sitio. ¡Estupendo!
Cómo NO usar un service
Una vez construido nuestro servicio, vamos a usarlo en nuestros componentes. Con nociones sobre databinding, podríamos llegar a la conclusión de que la forma de usar un servicio podría ser la siguiente:
1. Importando el archivo en el componente donde queramos usarlo (cojamos el new-account.component.ts como ejemplo).
2. Creando una instancia de la clase LogService, llamando al método logStatusChange y pasándole por parámetro la propiedad del método onCreateAccount (accountStatus).
Lo chocante de esta manera, es que si comentamos el console.log, nuestro código funcionaría perfectamente.
import { LogService } from '../log.service'; ... const service = new LogService(); service.logStatusChange(accountStatus); // console.log('An account status changed, new status: ' + accountStatus); }
Pruébalo y verás. Sin embargo, esta manera de instanciar servicios manualmente no es la concebida por Angular para usar los servicios. Por tanto, está considerada una mala práctica. ¡Aléjate de ella!
Más adelante a lo largo de esta serie de posts explicaremos las razones. De momento, si quieres descubrir cómo usar los servicios y acceder a ellos correctamente, ¡no te pierdas la parte #2 de esta serie! 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