javascript - services - ¿Cómo configuro diferentes entornos en Angular.js?
http angularjs (10)
¿Cómo maneja las variables / constantes de configuración para diferentes entornos?
Esto podría ser un ejemplo:
Se puede localhost:7080/myapi/
mi API de descanso en localhost:7080/myapi/
, pero mi amigo que trabaja con el mismo código bajo el control de versiones de Git tiene la API implementada en su Tomcat en localhost:8099/hisapi/
.
Suponiendo que tenemos algo como esto:
angular
.module(''app'', [''ngResource''])
.constant(''API_END_POINT'',''<local_end_point>'')
.factory(''User'', function($resource, API_END_POINT) {
return $resource(API_END_POINT + ''user'');
});
¿Cómo inyecto dinámicamente el valor correcto del punto final de API, dependiendo del entorno?
En PHP, normalmente hago este tipo de cosas con un archivo config.username.xml
, combinando el archivo de configuración básica (config.xml) con el archivo de configuración del entorno local reconocido por el nombre del usuario. ¿Pero no sé cómo manejar este tipo de cosas en JavaScript?
¡Buena pregunta!
Una solución podría ser continuar usando su archivo config.xml y proporcionar información de puntos finales de API desde el backend a su html generado, como este (ejemplo en php):
<script type="text/javascript">
angular.module(''YourApp'').constant(''API_END_POINT'', ''<?php echo $apiEndPointFromBackend; ?>'');
</script>
Tal vez no sea una solución bonita, pero funcionaría.
Otra solución podría ser mantener el valor de API_END_POINT
constante como debería estar en producción, y solo modificar su archivo de hosts para apuntar esa url a su api local en su lugar.
O tal vez una solución que usa localStorage
para anulaciones, como esta:
.factory(''User'',[''$resource'',''API_END_POINT''],function($resource,API_END_POINT){
var myApi = localStorage.get(''myLocalApiOverride'');
return $resource((myApi || API_END_POINT) + ''user'');
});
¿Has visto esta question y su respuesta?
Puede establecer un valor válido globalmente para su aplicación de esta manera:
app.value(''key'', ''value'');
y luego usarlo en sus servicios. Puede mover este código a un archivo config.js y ejecutarlo en la carga de la página u otro momento conveniente.
Llego un poco tarde al tema, pero si estás usando Grunt , he tenido un gran éxito con Grunt grunt-ng-constant
.
La sección de configuración para ngconstant
en mi Gruntfile.js
parece a
ngconstant: {
options: {
name: ''config'',
wrap: ''"use strict";/n/n{%= __ngModule %}'',
space: '' ''
},
development: {
options: {
dest: ''<%= yeoman.app %>/scripts/config.js''
},
constants: {
ENV: ''development''
}
},
production: {
options: {
dest: ''<%= yeoman.dist %>/scripts/config.js''
},
constants: {
ENV: ''production''
}
}
}
Las tareas que usan ngconstant
parecen
grunt.registerTask(''server'', function (target) {
if (target === ''dist'') {
return grunt.task.run([
''build'',
''open'',
''connect:dist:keepalive''
]);
}
grunt.task.run([
''clean:server'',
''ngconstant:development'',
''concurrent:server'',
''connect:livereload'',
''open'',
''watch''
]);
});
grunt.registerTask(''build'', [
''clean:dist'',
''ngconstant:production'',
''useminPrepare'',
''concurrent:dist'',
''concat'',
''copy'',
''cdnify'',
''ngmin'',
''cssmin'',
''uglify'',
''rev'',
''usemin''
]);
Entonces, ejecutar el grunt server
generará un archivo config.js
en app/scripts/
que parece
"use strict";
angular.module("config", []).constant("ENV", "development");
Finalmente, declaro la dependencia de cualquier módulo que lo necesite:
// the ''config'' dependency is generated via grunt
var app = angular.module(''myApp'', [ ''config'' ]);
Ahora mis constantes pueden ser inyectadas de dependencia donde sea necesario. P.ej,
app.controller(''MyController'', [''ENV'', function( ENV ) {
if( ENV === ''production'' ) {
...
}
}]);
Muy tarde en el tema, pero una técnica que he utilizado, pre-Angular, es aprovechar JSON y la flexibilidad de JS para hacer referencia dinámica a las claves de recopilación, y utilizar datos inalienables del entorno (nombre del servidor host, idioma actual del navegador). , etc.) como entradas para discriminar / preferir selectivamente los nombres de las claves con sufijo dentro de una estructura de datos JSON.
Esto no solo proporciona un contexto de entorno de despliegue (por OP), sino cualquier contexto arbitrario (como el lenguaje) para proporcionar i18n o cualquier otra varianza requerida simultáneamente, e (idealmente) dentro de un único manifiesto de configuración, sin duplicación y de manera legible.
EN UNO 10 LINEAS VANILLA JS
Ejemplo demasiado simplificado pero clásico: una URL de base de punto final de API en un archivo de propiedades con formato JSON que varía según el entorno donde (natch) el servidor host también variará:
...
''svcs'': {
''VER'': ''2.3'',
''API@localhost'': ''http://localhost:9090/'',
''[email protected]'': ''https://www.uat.productionwebsite.com:9090/res/'',
''[email protected]'': ''https://www.productionwebsite.com:9090/api/res/''
},
...
Una clave para la función de discriminación es simplemente el nombre de host del servidor en la solicitud.
Esto, naturalmente, se puede combinar con una clave adicional basada en la configuración de idioma del usuario:
...
''app'': {
''NAME'': ''Ferry Reservations'',
''NAME@fr'': ''Réservations de ferry'',
''NAME@de'': ''Fähren Reservierungen''
},
...
El alcance de la discriminación / preferencia se puede limitar a las teclas individuales (como se muestra arriba) donde la tecla "base" solo se sobrescribe si hay una tecla + sufijo correspondiente para las entradas a la función, o una estructura completa, y esa estructura en sí misma. analizado de forma recursiva para la coincidencia de sufijos de discriminación / preferencia:
''help'': {
''BLURB'': ''This pre-production environment is not supported. Contact Development Team with questions.'',
''PHONE'': ''808-867-5309'',
''EMAIL'': ''[email protected]''
},
''[email protected]'': {
''BLURB'': ''Please contact Customer Service Center'',
''BLURB@fr'': ''S/'il vous plaît communiquer avec notre Centre de service à la clientèle'',
''BLURB@de'': ''Bitte kontaktieren Sie unseren Kundendienst!!1!'',
''PHONE'': ''1-800-CUS-TOMR'',
''EMAIL'': ''[email protected]''
},
Por lo tanto, si un usuario visitante del sitio web de producción tiene una configuración de preferencia de idioma ( de ) alemán, la configuración anterior se contraería para:
''help'': {
''BLURB'': ''Bitte kontaktieren Sie unseren Kundendienst!!1!'',
''PHONE'': ''1-800-CUS-TOMR'',
''EMAIL'': ''[email protected]''
},
¿Cómo se ve esa función de reescritura JSON de preferencia / discriminación mágica? No mucho:
// prefer(object,suffix|[suffixes]) by/par/durch storsoc
// prefer({ a: ''apple'', a@env: ''banana'', b: ''carrot'' },''env'') -> { a: ''banana'', b: ''carrot'' }
function prefer(o,sufs) {
for (var key in o) {
if (!o.hasOwnProperty(key)) continue; // skip non-instance props
if(key.split(''@'')[1]) { // suffixed!
// replace root prop with the suffixed prop if among prefs
if(o[key] && sufs.indexOf(key.split(''@'')[1]) > -1) o[key.split(''@'')[0]] = JSON.parse(JSON.stringify(o[key]));
// and nuke the suffixed prop to tidy up
delete o[key];
// continue with root key ...
key = key.split(''@'')[0];
}
// ... in case it''s a collection itself, recurse it!
if(o[key] && typeof o[key] === ''object'') prefer(o[key],sufs);
};
};
En nuestras implementaciones, que incluyen sitios web Angular y pre-Angular, simplemente iniciamos la configuración mucho antes que otras llamadas de recursos al colocar el JSON dentro de un cierre de JS autoejecutable, incluida la función prefer (), y alimentamos las propiedades básicas del nombre de host y código de idioma (y acepta cualquier sufijo arbitrario adicional que pueda necesitar):
(function(prefs){ var props = {
''svcs'': {
''VER'': ''2.3'',
''API@localhost'': ''http://localhost:9090/'',
''[email protected]'': ''https://www.uat.productionwebsite.com:9090/res/'',
''[email protected]'': ''https://www.productionwebsite.com:9090/api/res/''
},
...
/* yadda yadda moar JSON und bisque */
function prefer(o,sufs) {
// body of prefer function, broken for e.g.
};
// convert string and comma-separated-string to array .. and process it
prefs = [].concat( ( prefs.split ? prefs.split('','') : prefs ) || []);
prefer(props,prefs);
window.app_props = JSON.parse(JSON.stringify(props));
})([location.hostname, ((window.navigator.userLanguage || window.navigator.language).split(''-'')[0]) ] );
Un sitio pre-Angular ahora tendría un window.app_props colapsado (sin @ sufijo de teclas) para referirse.
Un sitio Angular, como un paso bootstrap / init, simplemente copia el objeto de accesorios caído en $ rootScope, y (opcionalmente) lo destruye del ámbito global / ventana
app.constant(''props'',angular.copy(window.app_props || {})).run( function ($rootScope,props) { $rootScope.props = props; delete window.app_props;} );
Para ser inyectado posteriormente en los controladores:
app.controller(''CtrlApp'',function($log,props){ ... } );
o referido de enlaces en vistas:
<span>{{ props.help.blurb }} {{ props.help.email }}</span>
Advertencias? El carácter @ no es un nombre de variable / clave JS / JSON válido, pero hasta ahora aceptado. Si eso es un factor decisivo, sustituya cualquier convención que le guste, como "__" (subrayado doble), siempre y cuando se adhiera a ella.
La técnica podría aplicarse en el lado del servidor, portarse a Java o C #, pero su eficiencia / compacidad puede variar.
Alternativamente, la función / convención podría ser parte de su script de compilación de front-end, de modo que el JSON completo para todos los entornos / idiomas nunca se transmita a través del cable.
ACTUALIZAR
Hemos evolucionado el uso de esta técnica para permitir varios sufijos a una clave, para evitar que nos vean obligados a usar colecciones (todavía puede, tan profundamente como desee), y también para respetar el orden de los sufijos preferidos.
Ejemplo (ver también trabajo jsFiddle ):
var o = { ''a'':''apple'', ''a@dev'':''apple-dev'', ''a@fr'':''pomme'',
''b'':''banana'', ''b@fr'':''banane'', ''b@dev&fr'':''banane-dev'',
''c'':{ ''o'':''c-dot-oh'', ''o@fr'':''c-point-oh'' }, ''c@dev'': { ''o'':''c-dot-oh-dev'', ''o@fr'':''c-point-oh-dev'' } };
/*1*/ prefer(o,''dev''); // { a:''apple-dev'', b:''banana'', c:{o:''c-dot-oh-dev''} }
/*2*/ prefer(o,''fr''); // { a:''pomme'', b:''banane'', c:{o:''c-point-oh''} }
/*3*/ prefer(o,''dev,fr''); // { a:''apple-dev'', b:''banane-dev'', c:{o:''c-point-oh-dev''} }
/*4*/ prefer(o,[''fr'',''dev'']); // { a:''pomme'', b:''banane-dev'', c:{o:''c-point-oh-dev''} }
/*5*/ prefer(o); // { a:''apple'', b:''banana'', c:{o:''c-dot-oh''} }
1/2 (uso básico) prefiere las claves ''@dev'', descarta todas las demás claves con sufijo
3 prefiere ''@dev'' sobre ''@fr'', prefiere ''@ dev & fr'' sobre todos los demás
4 (igual que 3 pero prefiere ''@fr'' sobre ''@dev'')
5 sin sufijos preferidos, cae TODAS las propiedades con sufijo
Esto se logra al calificar cada propiedad sufijada y al promover el valor de una propiedad sufijada a la propiedad no sufijada al iterar sobre las propiedades y encontrar un sufijo con una puntuación más alta.
Algunas eficiencias en esta versión, incluida la eliminación de la dependencia de JSON para copiar en profundidad, y solo recurrir a objetos que sobrevivan a la ronda de puntuación en su profundidad:
function prefer(obj,suf) {
function pr(o,s) {
for (var p in o) {
if (!o.hasOwnProperty(p) || !p.split(''@'')[1] || p.split(''@@'')[1] ) continue; // ignore: proto-prop OR not-suffixed OR temp prop score
var b = p.split(''@'')[0]; // base prop name
if(!!!o[''@@''+b]) o[''@@''+b] = 0; // +score placeholder
var ps = p.split(''@'')[1].split(''&''); // array of property suffixes
var sc = 0; var v = 0; // reset (running)score and value
while(ps.length) {
// suffix value: index(of found suffix in prefs)^10
v = Math.floor(Math.pow(10,s.indexOf(ps.pop())));
if(!v) { sc = 0; break; } // found suf NOT in prefs, zero score (delete later)
sc += v;
}
if(sc > o[''@@''+b]) { o[''@@''+b] = sc; o[b] = o[p]; } // hi-score! promote to base prop
delete o[p];
}
for (var p in o) if(p.split(''@@'')[1]) delete o[p]; // remove scores
for (var p in o) if(typeof o[p] === ''object'') pr(o[p],s); // recurse surviving objs
}
if( typeof obj !== ''object'' ) return; // validate
suf = ( (suf || suf === 0 ) && ( suf.length || suf === parseFloat(suf) ) ? suf.toString().split('','') : []); // array|string|number|comma-separated-string -> array-of-strings
pr(obj,suf.reverse());
}
Para lograrlo, le sugiero que use el complemento de entorno AngularJS: https://www.npmjs.com/package/angular-environment
Aquí hay un ejemplo:
angular.module(''yourApp'', [''environment'']).
config(function(envServiceProvider) {
// set the domains and variables for each environment
envServiceProvider.config({
domains: {
development: [''localhost'', ''dev.local''],
production: [''acme.com'', ''acme.net'', ''acme.org'']
// anotherStage: [''domain1'', ''domain2''],
// anotherStage: [''domain1'', ''domain2'']
},
vars: {
development: {
apiUrl: ''//localhost/api'',
staticUrl: ''//localhost/static''
// antoherCustomVar: ''lorem'',
// antoherCustomVar: ''ipsum''
},
production: {
apiUrl: ''//api.acme.com/v2'',
staticUrl: ''//static.acme.com''
// antoherCustomVar: ''lorem'',
// antoherCustomVar: ''ipsum''
}
// anotherStage: {
// customVar: ''lorem'',
// customVar: ''ipsum''
// }
}
});
// run the environment check, so the comprobation is made
// before controllers and services are built
envServiceProvider.check();
});
Y luego, puede llamar a las variables desde sus controladores como este:
envService.read(''apiUrl'');
Espero eso ayude.
Para los usuarios de Gulp , gulp-ng-constant también es útil combinado con gulp-concat , event-stream y yargs .
var concat = require(''gulp-concat''),
es = require(''event-stream''),
gulp = require(''gulp''),
ngConstant = require(''gulp-ng-constant''),
argv = require(''yargs'').argv;
var enviroment = argv.env || ''development'';
gulp.task(''config'', function () {
var config = gulp.src(''config/'' + enviroment + ''.json'')
.pipe(ngConstant({name: ''app.config''}));
var scripts = gulp.src(''js/*'');
return es.merge(config, scripts)
.pipe(concat(''app.js''))
.pipe(gulp.dest(''app/dist''))
.on(''error'', function() { });
});
En mi carpeta de configuración tengo estos archivos:
ls -l config
total 8
-rw-r--r--+ 1 .. ci.json
-rw-r--r--+ 1 .. development.json
-rw-r--r--+ 1 .. production.json
Luego puedes ejecutar gulp config --env development
y eso creará algo como esto:
angular.module("app.config", [])
.constant("foo", "bar")
.constant("ngConstant", true);
También tengo esta especificación:
beforeEach(module(''app''));
it(''loads the config'', inject(function(config) {
expect(config).toBeTruthy();
}));
Podría usar lvh.me:9000
para acceder a su aplicación AngularJS, ( lvh.me
solo apunta a 127.0.0.1) y luego especificar un punto final diferente si lvh.me
es el host:
app.service("Configuration", function() {
if (window.location.host.match(/lvh/.me/)) {
return this.API = ''http://localhost//:7080/myapi/'';
} else {
return this.API = ''http://localhost//:8099/hisapi/'';
}
});
Y luego inyecte el servicio de configuración y use Configuration.API
donde sea que necesite acceder a la API:
$resource(Configuration.API + ''/endpoint/:id'', {
id: ''@id''
});
Un poco torpe, pero funciona bien para mí, aunque en una situación ligeramente diferente (los puntos finales de API difieren en producción y desarrollo).
Si está utilizando Brunch , el complemento Constangular ayuda a administrar variables para diferentes entornos.
También podríamos hacer algo como esto.
(function(){
''use strict'';
angular.module(''app'').service(''env'', function env() {
var _environments = {
local: {
host: ''localhost:3000'',
config: {
apiroot: ''http://localhost:3000''
}
},
dev: {
host: ''dev.com'',
config: {
apiroot: ''http://localhost:3000''
}
},
test: {
host: ''test.com'',
config: {
apiroot: ''http://localhost:3000''
}
},
stage: {
host: ''stage.com'',
config: {
apiroot: ''staging''
}
},
prod: {
host: ''production.com'',
config: {
apiroot: ''production''
}
}
},
_environment;
return {
getEnvironment: function(){
var host = window.location.host;
if(_environment){
return _environment;
}
for(var environment in _environments){
if(typeof _environments[environment].host && _environments[environment].host == host){
_environment = environment;
return _environment;
}
}
return null;
},
get: function(property){
return _environments[this.getEnvironment()].config[property];
}
}
});
})();
Y en su controller/service
, podemos inyectar la dependencia y llamar al método de obtención con propiedad para acceder.
(function() {
''use strict'';
angular.module(''app'').service(''apiService'', apiService);
apiService.$inject = [''configurations'', ''$q'', ''$http'', ''env''];
function apiService(config, $q, $http, env) {
var service = {};
/* **********APIs **************** */
service.get = function() {
return $http.get(env.get(''apiroot'') + ''/api/yourservice'');
};
return service;
}
})();
$http.get(env.get(''apiroot'')
devolvería la url en función del entorno host.
Una solución interesante podría ser separar todos los valores específicos del entorno en algún módulo angular separado, del que dependen todos los demás módulos:
angular.module(''configuration'', [])
.constant(''API_END_POINT'',''123456'')
.constant(''HOST'',''localhost'');
Entonces, los módulos que necesitan esas entradas pueden declarar una dependencia en él:
angular.module(''services'',[''configuration''])
.factory(''User'',[''$resource'',''API_END_POINT''],function($resource,API_END_POINT){
return $resource(API_END_POINT + ''user'');
});
Ahora puedes pensar en más cosas interesantes:
El módulo, que contiene la configuración, se puede separar en configuration.js, que se incluirá en su página.
Este script puede ser editado fácilmente por cada uno de ustedes, siempre y cuando no compruebe este archivo separado en git. Pero es más fácil no verificar la configuración si está en un archivo separado. Además, podrías ramificarlo localmente.
Ahora, si tiene un sistema de compilación, como ANT o Maven, sus pasos adicionales podrían ser implementar algunos marcadores de posición para los valores API_END_POINT, que se reemplazarán durante el tiempo de compilación, con sus valores específicos.
O tiene su configuration_a.js
y configuration_b.js
y decide en el backend qué incluir.