Herencia de environments en Angular

Mar 20, 2019

Las aplicaciones grandes tienen una configuración enorme que a veces es difícil de mantener. Angular proporciona un sistema de configuración para diferentes entornos, pero, en la gran mayoría de los casos, no es suficiente, siendo necesario repetir la misma configuración para varios entornos. Entonces, ¿qué podríamos hacer para solucionar eso?

Árbol representando la herencia

Configuración base

En este ejemplo, vamos a usar la siguiente configuración:

// environment.ts configuration
export const environment = {
  production: false,
  appVersion: '1.0'
  endpoint: 'mysite.dev/rest',
  browserSupport: [
    {
      name: 'Chrome',
      version: 45
    },
    {
      name: 'Firefox',
      version: 40
    },
    {
      name: 'Safari',
      version: 9
    }
  ],
}
// environment.prod.ts configuration
export const environment = {
  production: true;
  appVersion: '1.0',
  endpoint: 'mysite.prod/rest',
  browserSupport: [
    {
      name: 'Chrome',
      version: 45
    },
    {
      name: 'Firefox',
      version: 40
    },
    {
      name: 'Safari',
      version: 9
    }
  ],
}

Como puedes ver, estamos repitiendo configuración común en ambos entornos, por lo cometer un error cambiando algún valor en uno de los entornos y olvidando el otro es bastante fácil de que ocurra.

División e herencia

Para solucionar este problema, tenemos que dividir la configuración en varios archivos y usar una herramienta para unirlos. La herramienta que usaré en este ejemplo es deepMerge. Para instalarlo solo agrega a tu package.json.

"deepmerge": "^2.1.0"

Estamos listos para dividir la configuración siguiendo las siguientes reglas. Vamos a suponer que existe la necesidad de tener varios entornos de desarrollo y producción.

  • appVersion y browserSupport son comunes a todos los entornos, por lo que los ponemos en un archivo de configuración global.
  • La producción va ligado al entorno en qué estemos (dev o prod), por lo que crearemos archivos dev.ts y prod.ts.
  • Si suponemos que tenemos endpoint diferente para cada entorno, colocaremos la variable endpoint directamente en el archivo de entorno.

La configuración resultante es la siguiente:

// global.ts
export const ENVIRONMENT_GLOBAL = {
  appVersion: '1.0',
  browserSupport: [
    {
      name: 'Chrome',
      version: 45
    },
    {
      name: 'Firefox',
      version: 40
    },
    {
      name: 'Safari',
      version: 9
    }
  ]
};
// dev.ts
import {ENVIRONMENT_GLOBAL} from './global';
import * as deepmerge from 'deepmerge';

export const ENVIRONMENT_DEV = deepmerge.all([{}, ENVIRONMENT_GLOBAL, {
  production: false,
});
// prod.ts
import {ENVIRONMENT_GLOBAL} from './global';
import * as deepmerge from 'deepmerge';

export const ENVIRONMENT_PROD = deepmerge.all([{}, ENVIRONMENT_GLOBAL, {
  production: true,
});
// environment.ts
import {ENVIRONMENT_DEV} from './dev';
import * as deepmerge from 'deepmerge';

export const environment = deepmerge.all([{}, ENVIRONMENT_DEV, {
  endpoint: 'mysite.dev/rest'
});
// environment.prod.ts
import {ENVIRONMENT_PROD} from './prod';
import * as deepmerge from 'deepmerge';

export const environment = deepmerge.all([{}, ENVIRONMENT_PROD, {
  endpoint: 'mysite.prod/rest'
});

Y ahora tenemos una configuración más legible, en una estructura jerárquica y fácil de mantener.

Me gustaría aclarar que esta solución funciona correctamente tanto con angular-cli como con ahead of time compiler.