¿POR QUÉ no estás usando estos providers de Angular?
Tags: angular, providers, patterns, javascript
Published: 2024-06-04 14:05:11

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.

```typescript
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.

```typescript
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.

```typescript
@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.

```typescript

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

```typescript

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

```typescript
@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.

```typescript
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],
  },
]
```

<aside>
💡 `useFactory` puedes utilizarlo para otras cosas, como por ejemplo realizar llamadas HTTP y obtener configuraciones que necesita el servicio.
</aside>

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:

```typescript
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**

```typescript
    {
      provide: HTTP_INTERCEPTORS,
      useFactory: MyInterceptorFactory,
      deps: [
        { provide: 'API_TOKEN', useValue: 'YOUR_API_TOKEN' }
      ],
      multi: true
    }
```

**LOCALE_ID**

```typescript
{ 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](https://stackblitz.com/edit/injection-tokens-marianocodes?file=src%2Fmain.ts) 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:

[Twitter](https://twitter.com/marianocodes) | [LinkedIn](https://www.linkedin.com/in/marianocodes/) | [@marianocodes]((https://www.linkedin.com/in/marianocodes/))