Como eu configuro diferentes ambientes no Angular.js?

Como você gerencia variables ​​de configuração / constantes para diferentes ambientes?

Este poderia ser um exemplo:

Minha API de descanso é acessível em localhost:7080/myapi/ , mas meu amigo que trabalha no mesmo código sob version control do Git tem a API implementada em seu Tomcat em localhost:8099/hisapi/ .

Supondo que tenhamos algo assim:

 angular .module('app', ['ngResource']) .constant('API_END_POINT','') .factory('User', function($resource, API_END_POINT) { return $resource(API_END_POINT + 'user'); }); 

Como injetar dinamicamente o valor correto do endpoint da API, dependendo do ambiente?

No PHP eu costumo fazer esse tipo de coisa com um arquivo config.username.xml , mesclando o arquivo de configuração básico (config.xml) com o arquivo de configuração do ambiente local reconhecido pelo nome do usuário. Mas eu não sei como gerenciar esse tipo de coisa em JavaScript?

Estou um pouco atrasado para o tópico, mas se você está usando o Grunt, eu tive um grande sucesso com o grunt-ng-constant .

A seção de configuração para o ngconstant no meu Gruntfile.js parece

 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' } } } 

As tarefas que usam ngconstant parecem

 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' ]); 

Então, executando o grunt server irá gerar um arquivo config.js em app/scripts/ que se parece com

 "use strict"; angular.module("config", []).constant("ENV", "development"); 

Finalmente, eu declaro a dependência de qualquer módulo que precise:

 // the 'config' dependency is generated via grunt var app = angular.module('myApp', [ 'config' ]); 

Agora minhas constantes podem ser injetadas por dependência quando necessário. Por exemplo,

 app.controller('MyController', ['ENV', function( ENV ) { if( ENV === 'production' ) { ... } }]); 

Uma solução interessante pode ser separar todos os valores específicos do ambiente em algum módulo angular separado, em que todos os outros módulos dependem:

 angular.module('configuration', []) .constant('API_END_POINT','123456') .constant('HOST','localhost'); 

Então seus módulos que precisam dessas inputs podem declarar uma dependência:

 angular.module('services',['configuration']) .factory('User',['$resource','API_END_POINT'],function($resource,API_END_POINT){ return $resource(API_END_POINT + 'user'); }); 

Agora você poderia pensar em outras coisas legais:

O módulo, que contém a configuração, pode ser separado em configuration.js, que será incluído em sua página.

Este script pode ser facilmente editado por cada um de vocês, contanto que você não cheque este arquivo separado no git. Mas é mais fácil não verificar a configuração se estiver em um arquivo separado. Além disso, você pode ramificá-lo localmente.

Agora, se você tiver um sistema de criação, como ANT ou Maven, suas etapas adicionais podem estar implementando alguns marcadores para os valores API_END_POINT, que serão substituídos durante o período de criação, com seus valores específicos.

Ou você tem seu configuration_a.js e configuration_b.js e decide no backend que include.

Para usuários Gulp , gulp-ng-constant também é útil combinado com gulp-concat , event-stream e 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() { }); }); 

Na minha pasta de configuração eu tenho esses arquivos:

 ls -l config total 8 -rw-r--r--+ 1 .. ci.json -rw-r--r--+ 1 .. development.json -rw-r--r--+ 1 .. production.json 

Então você pode executar gulp config --env development e isso criará algo parecido com isto:

 angular.module("app.config", []) .constant("foo", "bar") .constant("ngConstant", true); 

Eu também tenho essa especificação:

 beforeEach(module('app')); it('loads the config', inject(function(config) { expect(config).toBeTruthy(); })); 

Para conseguir isso, sugiro que você use o AngularJS Environment Plugin: https://www.npmjs.com/package/angular-environment

Aqui está um exemplo:

 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(); }); 

E então, você pode chamar as variables ​​de seus controladores como este:

 envService.read('apiUrl'); 

Espero que ajude.

Você poderia usar lvh.me:9000 para acessar seu aplicativo AngularJS, ( lvh.me apenas aponta para 127.0.0.1) e então especificar um endpoint diferente se lvh.me for o 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/'; } }); 

E, em seguida, injetar o serviço de configuração e usar Configuration.API sempre que você precisar acessar a API:

 $resource(Configuration.API + '/endpoint/:id', { id: '@id' }); 

Um pouco desajeitado, mas funciona bem para mim, embora em uma situação um pouco diferente (endpoints da API diferem em produção e desenvolvimento).

Boa pergunta!

Uma solução poderia ser continuar usando seu arquivo config.xml e fornecer informações de pontos de extremidade da API a partir do back-end para o seu html gerado, como este (exemplo em php):

  

Talvez não seja uma solução bonita, mas funcionaria.

Outra solução poderia ser manter o valor da constante API_END_POINT como deveria estar em produção e modificar apenas seu arquivo hosts para apontar esse URL para sua API local.

Ou talvez uma solução usando localStorage para substituições, assim:

 .factory('User',['$resource','API_END_POINT'],function($resource,API_END_POINT){ var myApi = localStorage.get('myLocalApiOverride'); return $resource((myApi || API_END_POINT) + 'user'); }); 

Nós também poderíamos fazer algo assim.

 (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]; } } }); })(); 

E no seu controller/service , podemos injetar a dependência e chamar o método get com a propriedade a ser acessada.

 (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') retornaria o URL com base no ambiente do host.

Muito tarde para o segmento, mas uma técnica que usei, pré-Angular, é aproveitar o JSON e a flexibilidade do JS para referenciar dinamicamente chaves de coleção e usar fatos inalienáveis ​​do ambiente (nome do servidor host, idioma atual do navegador , etc.) como inputs para seletivamente discriminar / preferir nomes de chaves com sufixo dentro de uma estrutura de dados JSON.

Isso fornece não apenas contexto de ambiente de implementação (por OP), mas qualquer contexto arbitrário (como linguagem) para fornecer i18n ou qualquer outra variação necessária simultaneamente e (idealmente) dentro de um único manifesto de configuração, sem duplicação e de forma legível óbvia.

EM CERCA DE 10 LINHAS VANILLA JS

Exemplo excessivamente simplificado, mas clássico: uma URL base do terminal da API em um arquivo de propriedades formatadas em JSON que varia de acordo com o ambiente em que (natch) o servidor host também varia:

  ... 'svcs': { 'VER': '2.3', 'API@localhost': 'http://localhost:9090/', 'API@www.uat.productionwebsite.com': 'https://www.uat.productionwebsite.com:9090/res/', 'API@www.productionwebsite.com': 'https://www.productionwebsite.com:9090/api/res/' }, ... 

Uma chave para a function de discriminação é simplesmente o nome do host do servidor na solicitação.

Isso, naturalmente, pode ser combinado com uma chave adicional com base nas configurações de idioma do usuário:

  ... 'app': { 'NAME': 'Ferry Reservations', 'NAME@fr': 'Réservations de ferry', 'NAME@de': 'Fähren Reservierungen' }, ... 

O escopo da discriminação / preferência pode ser confinado a chaves individuais (como acima) onde a chave “base” é sobrescrita apenas se houver uma chave correspondente + sufixo para as inputs da function – ou uma estrutura inteira e essa estrutura analisado recursivamente para correspondência de sufixos de discriminação / preferência:

  'help': { 'BLURB': 'This pre-production environment is not supported. Contact Development Team with questions.', 'PHONE': '808-867-5309', 'EMAIL': 'coder.jen@lostnumber.com' }, 'help@www.productionwebsite.com': { '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': 'customer.service@productionwebsite.com' }, 

Portanto, se um usuário visitante do site de produção tiver uma configuração de preferência de idioma alemão ( de ), a configuração acima será reduzida para:

  'help': { 'BLURB': 'Bitte kontaktieren Sie unseren Kundendienst!!1!', 'PHONE': '1-800-CUS-TOMR', 'EMAIL': 'customer.service@productionwebsite.com' }, 

Como é a function de rewrite JSON de preferência / discriminação mágica? Não muito:

 // 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); }; }; 

Em nossas implementações, que incluem sites Angular e pré-Angular, simplesmente inicializamos a configuração bem antes de outras chamadas de resources, colocando o JSON dentro de um encerramento JS auto-executável, incluindo a function prefer () e alimentando as propriedades básicas do hostname e código de idioma (e aceita quaisquer sufixos arbitrários adicionais que você possa precisar):

 (function(prefs){ var props = { 'svcs': { 'VER': '2.3', 'API@localhost': 'http://localhost:9090/', 'API@www.uat.productionwebsite.com': 'https://www.uat.productionwebsite.com:9090/res/', 'API@www.productionwebsite.com': 'https://www.productionwebsite.com:9090/api/res/' }, ... /* yadda yadda moar JSON und bisque */ function prefer(o,sufs) { // body of prefer function, broken for eg }; // 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]) ] ); 

Um site pré-Angular teria agora um colapso (sem chaves com sufixo @) window.app_props para se referir.

Um site Angular, como uma etapa de bootstrap / init, simplesmente copia o object props inativo para $ rootScope e (opcionalmente) o destroi do escopo global / window

 app.constant('props',angular.copy(window.app_props || {})).run( function ($rootScope,props) { $rootScope.props = props; delete window.app_props;} ); 

para ser posteriormente injetado em controladores:

 app.controller('CtrlApp',function($log,props){ ... } ); 

ou referido a partir de ligações em visualizações:

 {{ props.help.blurb }} {{ props.help.email }} 

Ressalvas? O caractere @ não é uma nomenclatura de chave / variável JS / JSON válida, mas até o momento aceita. Se for um deal-breaker, substitua qualquer convenção de que você goste, como “__” (sublinhado duplo), desde que se atenha a ele.

A técnica pode ser aplicada no lado do servidor, portada para Java ou C #, mas sua eficiência / compactação pode variar.

Como alternativa, a function / convenção pode fazer parte do seu script de compilation de front-end, de modo que o JSON completo de todo o ambiente / todo o idioma não seja transmitido pela rede.

ATUALIZAR

Nós evoluímos o uso dessa técnica para permitir vários sufixos a uma chave, para evitar ser forçado a usar collections (você ainda pode, tão profundamente quanto quiser), e também para honrar a ordem dos sufixos preferidos.

Exemplo (veja também trabalhando 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) prefere as teclas ‘@dev’, descarta todas as outras chaves com sufixo

3 prefere ‘@dev’ over ‘@fr’, prefere ‘@ dev & fr’ a todos os outros

4 (o mesmo que 3 mas prefere ‘@fr’ over ‘@dev’)

5 sem sufixos preferidos, cai TODAS as propriedades com sufixo

Ele faz isso marcando cada propriedade com sufixo e promovendo o valor de uma propriedade com sufixo para a propriedade sem sufixo ao iterar sobre as propriedades e localizar um sufixo com pontuação mais alta.

Algumas eficiências nesta versão, incluindo a remoção da dependência do JSON para cópia profunda, e apenas a recursion em objects que sobrevivem à rodada de pontuação em sua profundidade:

 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 scolors 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()); } 

Se você estiver usando o Brunch , o plugin Constangular ajuda você a gerenciar variables ​​para diferentes ambientes.

Você já viu essa pergunta e sua resposta?

Você pode definir um valor válido globalmente para seu aplicativo da seguinte forma:

 app.value('key', 'value'); 

e depois usá-lo em seus serviços. Você poderia mover este código para um arquivo config.js e executá-lo no carregamento da página ou em outro momento conveniente.