
¿Alguna vez has pensado en configurar distintos entornos de desarrollo de Angular para consumir recursos o servicios según la configuración? ¿Generar builds de la misma aplicación que consuman recursos de distintas fuentes? ¡Sigue leyendo!
En algunos escenarios de desarrollo de aplicaciones web que trabajan con APIs, puede que no tengamos acceso a ellas cuando queramos. En esos casos suelen usarse soluciones como proxies, servidores fake/mock…
Todas son buenas soluciones, pero… ¿podemos aprovechar la inyección de dependencias de Angular y los builders de la CLI para crear y proveer servicios según el entorno? Veámoslo :)
Antes que nada, si no conoces bien las configuraciones de Angular, te recomiendo echar un vistazo a la documentación oficial.
Nuestro objetivo es conseguir el siguiente comportamiento:
# Should init dev-server with HTTP Providers*
ng serve
# Should init dev-server with Faker Providers*
ng serve --configuration faker
Configuración del entorno
# Create a new Angular Project
ng new env-providers
# Install needed dependences
npm install --save-dev @angular-builders/custom-webpack
npm install --save faker
Modelado de proveedores
Necesitamos obtener la siguiente estructura:
- FooInterface: define la interfaz que implementarán los distintos servicios (Http y Faker)
foo-service/foo-service.interface.ts
import { Observable } from 'rxjs';
export interface IFooService {
retrieve(id: number): Observable<Foo>;
}
- FooService (impl. HTTP): implementación concreta del servicio con acceso a datos por protocolo HTTP.
foo-service/foo.servicice.ts
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { IFooService } from './foo-service.interface';
import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: 'root'
})
export class FooService implements IFooService {
constructor(
private http: HttpClient
) {}
retrieve(id: number): Observable<Foo> {
return this.http.get(`${environment.API_URL}/foo`);
}
}
- FooService (impl. Faker): implementación concreta del servicio con objetos generados por Faker.
foo-service/foo*.servicice.faker.ts
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { IFooService } from './foo-service.interface';
import * as faker from 'faker';
@Injectable({
providedIn: 'root'
})
export class FooService implements IFooService {
retrieve(id: number): Observable<Foo> {
return of(new Foo({
id: id,
name: faker.name
}));
}
}
Ya tenemos los proveedores listos para usar, así que… ¡manos a la obra!
app.component.ts
import { Component, OnInit } from '@angular/core';
import { FooService } from './foo-service/foo.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
public foo$: Observable<Foo>;
constructor(
private *fooService*: FooService
) {}
ngOnInit() {
this.foo$ = this.fooService.retrieve(1);
}
}
Observa que el servicio se importa desde /foo.service, es decir, el servicio que se usará por defecto al ejecutar ng serve sin parámetros adicionales.
Configuración y personalización de los builders de la CLI
Ahora que tenemos los servicios creados y consumidos desde un componente, configuraremos el proyecto para que la Angular CLI se comporte como se describió al inicio del artículo.
Configuración del artefacto y del builder
Debemos modificar angular.json con una configuración similar a la siguiente:
angular.json
"projects": {
. . .
"architect": {
/* <build> artifact config */
"build": {
/* Custom artifact builder */
"builder": "@angular-builders/custom-webpack:browser",
"options": {
/* Our custom builder adjustment */
"customWebpackConfig": {
"path": "./providers.config.ts"
},
. . .
},
"configurations": {
"production": {
. . .
},
/* Our build configuration */
"faker": {
"tsConfig": "tsconfig.app.faker.json"
}
}
}
}
}
}
En este archivo hacemos 3 cambios importantes:
Sustituimos el builder por defecto de Angular por el que instalamos de @angular-builders/custom-webpack.
Con la propiedad customWebpackConfig indicamos el archivo que aplicará las modificaciones necesarias para inyectar unos u otros proveedores. Lo construiremos más adelante.
Creamos una nueva configuración llamada
faker. Realizará la transpilación de TypeScript con la configuración definida entsconfig.app.faker.json.
El nuevo archivo tsconfig:
tsconfig.app.faker.json
{
"extends": "./tsconfig.app.json",
"include": [
"src//*.d.ts",
"src/**/*.service.faker.ts"
]
}
Con esta configuración le decimos explícitamente a TypeScript que transpile los archivos que terminan en *.service.faker.ts. Si no se encuentran importaciones de esos archivos, el compilador no los compilará salvo que se indique en include.
Extender el comportamiento del builder
Por último, extendemos la funcionalidad del builder de la CLI con la siguiente configuración.
providers.config.ts
import * as webpack from 'webpack';
const PROVIDER_FILE_CASING = '.service';
// Retrieve '--configuration' parameter*
const configurationIndex =
process.argv.indexOf('--configuration') > -1
? process.argv.indexOf('--configuration') + 1
: 0;
const configuration = configurationIndex
? `${process.argv[configurationIndex]}`
: '';
export default (*config*: webpack.Configuration) => {
config.plugins.push(
/*
* Using webpack plugin to replacement:
* We are searching all '.service' occurrences
* & replace it to `.service.<configuration>`
*/
new webpack.NormalModuleReplacementPlugin(
new RegExp(`(.*)${PROVIDER_FILE_CASING}(\.*)`),
resource => {
resource.request = resource.request.replace(
new RegExp(`${PROVIDER_FILE_CASING}`),
`${PROVIDER_FILE_CASING}.${configuration}`
);
}
)
);
// New config is returned to Builder
return config;
};
Estamos creando una regla de sustitución que reemplaza:
.service=>*.service.faker
Es decir, sustituye los proveedores HTTP por los de Faker.
¡Y listo! Al ejecutar ng serve, la aplicación se construirá en el servidor de desarrollo con los proveedores HTTP; si ejecutamos ng serve --configuration faker, usará los proveedores Faker.
Cuándo aplicarlo
Podemos usar este enfoque si…
Necesitamos configurar distintos entornos de desarrollo que accedan a datos de fuentes o protocolos de comunicación diferentes.
Necesitamos generar distintos builds de la aplicación según la capa de acceso a datos.
Queremos reducir el tiempo de ejecución de tests por latencia del servidor en entornos de prueba o no podemos acceder al servidor.
Publicado originalmente en dev.to.