¿POR QUÉ no estás usando estos providers de Angular?
Mariano Álvarez 🇨🇷
Posted on June 4, 2024
En este blog post, vamos a explorar 4 características que no se utilizan comúnmente en Angular, pero que ofrecen un gran poder y flexibilidad para escribir código escalable.
Ponte en al situación que necesitas implementar una lógica para manejar los logs en tu aplicación. Inicialmente, quieres configurar el tipo de log a mostrar: informativo, de error o de depuración. Para lograr esto, crearemos:
Tokens de Inyección (Injection Tokens)
Son una forma simple de inyectar valores en servicios, componentes y directivas utilizando un token como identificador y almacenando un valor.
export type LoggerConfig = 'debug' | 'info' | 'report';
export const LOGGER_CONFIG = new InjectionToken<LoggerConfig>('LOGGER_CONFIG');
export interface Logger {
log: (message: string) => void;
}
En el bootstrapApplication
, vamos a definir su valor utilizando useValue
.
useValue
Como su nombre indica, useValue
le asigna un valor a un token.
bootstrapApplication(AppComponent, {
providers: [
{ provide: LOGGER_CONFIG, useValue: 'debug' }
]
// ...
});
Con el token y su valor definido, lo usaremos inyectándolo en un nuevo servicio que se encargará de "loguear" los mensajes.
@Injectable({ providedIn: 'root' })
export class LoggerService {
constructor(@Inject(LOGGER_CONFIG) private config: LoggerConfig, private http: HttpClient) {}
log(message: string): void {
if (this.config === 'debug') { } // ....
if (this.config === 'info') { } // ....
if (this.config === 'report') {
// petición HTTP para almacenar los errores
}
// ...
}
}
Uno de los problemas es que la opción report
hace una llamada al servidor para almacenar los logs, pero esto no tiene sentido hacerlo mientras nos encontramos en desarrollo local.
Para solucionar este problema, crearemos otro servicio que solo se utilizará en producción, con la lógica necesaria para la función log
, este caso el supuesto llamado HTTP.
@Injectable({ providedIn: 'root' })
export class ProdLoggerService implements Logger {
private readonly URL = 'https://production...';
constructor(
@Inject(LOGGER_CONFIG) private config: LoggerConfig,
private http: HttpClient
) {}
log(message: string): void {
// ...
}
Ahora, vamos a configurar el servicio para que sea utilizado según el enterno.
useClass
Nos permite reemplazar la implementación de un proveedor con una clase que tenga las mismas propiedades y funciones. En este caso, agregaremos una condición para que solo se use en producción.
bootstrapApplication(AppComponent, {
providers: [
...
{
provide: LoggerService,
useClass: environment.production ? ProdLoggerService : LoggerService,
},
...
]
});
Aunque parece magia, no es necesario que hagamos ningún cambio en los components que consume LoggerService
, todo sigue funcionando igual
@Component({
selector: 'app-root',
template: '<h1>Tokens de Inyección de Dependencias</h1>'
})
export class AppComponent implements OnInit {
constructor(@Inject(LOGGER_SERVICE) private loggerService: LoggerService) {}
ngOnInit(): void {
this.loggerService.log('La aplicación ha iniciado');
}
}
Aunque hay algo que me molesta, ambos servicios se injecta HttpClient
, pero solo uno lo usa. Un servicio solo debería depender de lo que necesita.
useFactory
Vamos a utilizar useFactory
para proporcionar solo las dependencias que cada servicio necesita.
function createLogger(
config: LoggerConfig,
httpClient: HttpClient
): LoggerService | ProdLoggerService {
if (environment.production) {
return new ProdLoggerService(config, httpClient);
}
return new LoggerService(config);
}
...
providers: [
{
provide: LoggerService,
useFactory: createLogger,
deps: [LOGGER_CONFIG, HttpClient],
},
]
💡 useFactory
puedes utilizarlo para otras cosas, como por ejemplo realizar llamadas HTTP y obtener configuraciones que necesita el servicio.
Por último, si un caso eventual, LoggerService
tiene muchas métodos y no quieres exponer todos pero si mantener la misma lógica puedes utilizar useExisting
.
useExisting
Supongamos que hemos añadido una función a LoggerService
llamada delete
, pero no queremos que sea accesible desde todos los componentes. Podemos hacer lo siguiente:
export abstract class LoggerUseExistingService {
abstract log: void;
}
...
providers: [
{
provide: LoggerUseExistingService,
useExisting: LoggerService,
},
]
Si inyectas LoggerUseExistingService
en un componente, la función delete
no estará disponible.
Gracias a este tutorial, ya puedes entender algunas practicas comunes en Angular como definir Interceptors o el Locale ID
Interceptor
{
provide: HTTP_INTERCEPTORS,
useFactory: MyInterceptorFactory,
deps: [
{ provide: 'API_TOKEN', useValue: 'YOUR_API_TOKEN' }
],
multi: true
}
LOCALE_ID
{ provide: LOCALE_ID, useValue: 'en-US' }
¡Y ahora también sabes cómo crear soluciones más robustas que te permiten reemplazar cualquier proveedor nativo de Angular!
Puedes encontrar un demo completo acá, solo tienes que eliminar quitar los comentarios según el la característica que quieras probar
No olvides dejar tu 'like' y tus preguntas en los comentarios.
Puedes seguirme en:
Posted on June 4, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.