Come configuro diversi ambienti in Angular.js?


220

Come gestite le variabili / costanti di configurazione per ambienti diversi?

Questo potrebbe essere un esempio:

L'API di riposo è raggiungibile localhost:7080/myapi/, ma il mio amico che lavora sullo stesso codice sotto il controllo della versione di Git ha l'API distribuita sul suo Tomcat attivo localhost:8099/hisapi/.

Supponendo che abbiamo qualcosa del genere:

angular
    .module('app', ['ngResource'])

    .constant('API_END_POINT','<local_end_point>')

    .factory('User', function($resource, API_END_POINT) {
        return $resource(API_END_POINT + 'user');
    });

Come si inserisce dinamicamente il valore corretto dell'endpoint API, a seconda dell'ambiente?

In PHP di solito faccio questo tipo di cose con un config.username.xmlfile, unendo il file di configurazione di base (config.xml) con il file di configurazione dell'ambiente locale riconosciuto dal nome dell'utente. Ma non so come gestire questo genere di cose in JavaScript?

Risposte:


209

Sono un po 'in ritardo sul thread, ma se stai usando Grunt ho avuto un grande successo grunt-ng-constant.

La sezione di configurazione per ngconstantnel mio Gruntfile.jssembra

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

Le attività che usano ngconstantsembrano

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

Quindi l'esecuzione grunt servergenererà un config.jsfile app/scripts/simile al seguente

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

Infine, dichiaro la dipendenza da qualunque modulo ne abbia bisogno:

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

Ora le mie costanti possono essere iniettate dove necessario. Per esempio,

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

10
Piuttosto che mettere 'ngconstant:development'in 'serve'- se lo metti in config di orologio sotto 'gruntfile'come tasks: ['ngconstant:development']- non sarà necessario riavviare grunt servequando si aggiornano le variabili di sviluppo nel gruntfile.
spenthil

10
Invece di aggiungere le tue costanti in gruntfile.js, puoi inserire file separati come questo:package: grunt.file.readJSON('development.json')
Guilhem Soulas,

3
Esiste una sintassi aggiornata per Gruntfile.js nella versione 0.5 di grunt-ng-constant: github.com/werk85/grunt-ng-constant/issues/31 . Ottima risposta, grazie!
pherris,

10
Per coloro che usano gulp, c'è gulp-ng-constant .
Dheeraj Vepakomma,

4
Ho scoperto che è anche necessario includere il file script / config.js in angolare per trovare il modulo, in questo modo: <script src = "scripts / config.js"> </script>
Toni Gamez

75

Una soluzione interessante potrebbe essere quella di separare tutti i valori specifici dell'ambiente in un modulo angolare separato, da cui dipendono tutti gli altri moduli:

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

Quindi i moduli che necessitano di tali voci possono dichiarare una dipendenza da esso:

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

Ora potresti pensare a ulteriori cose interessanti:

Il modulo, che contiene la configurazione, può essere separato in configuration.js, che verrà incluso nella tua pagina.

Questo script può essere facilmente modificato da ognuno di voi, purché non si controlli questo file separato in git. Ma è più facile non controllare nella configurazione se si trova in un file separato. Inoltre, potresti dirigerlo localmente.

Ora, se hai un sistema di compilazione, come ANT o Maven, i tuoi ulteriori passi potrebbero essere l'implementazione di alcuni segnaposto per i valori API_END_POINT, che verranno sostituiti durante il tempo di compilazione, con i tuoi valori specifici.

Oppure hai il tuo configuration_a.jse configuration_b.jsdecidi nel backend quale includere.


30

Per gli utenti di Gulp , gulp-ng-constant è utile anche in combinazione con 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() { });
});

Nella mia cartella di configurazione ho questi file:

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

Quindi puoi eseguire gulp config --env developmente questo creerà qualcosa del genere:

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

Ho anche questa specifica:

beforeEach(module('app'));

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

C'è un modo per rimuovere l'array di dipendenze con costante gulp ng? Non ho alcuna dipendenza dalle mie costanti come in questo ad esempio "ngAnimate". Se non lo includo, ottengo un array di dipendenze vuoto come angular.module ("my.module.config", []) ma voglio l'output come angular.module ("my.module.config"). Non vedo alcuna opzione nella costante di gulp ma vedo che puoi passare deps: false nel pacchetto di grunt ng costante. Qualsiasi aiuto?
Arun Gopalpuri,

17

A tale scopo, ti suggerisco di utilizzare il plug-in per l'ambiente AngularJS: https://www.npmjs.com/package/angular-environment

Ecco un esempio:

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 quindi, puoi chiamare le variabili dai tuoi controller come questo:

envService.read('apiUrl');

Spero che sia d'aiuto.


1
come passa dallo sviluppo alla produzione?
Mawg dice di ripristinare Monica il

Ciao Juan Pablo, o @Mawg se l'hai capito. Prima di porre una domanda su SO / sollevare un problema su Github; come angular-environmentrileva l'ambiente? cioè cosa devi fare sul tuo computer / web server locale in modo che sappia che è rispettivamente dev / prod?
StevieP,

Rileggere nuovamente i documenti ... " envServiceProvider.check()... imposterà automaticamente l'ambiente appropriato in base a determinati domini". Quindi ritengo che rilevi il dominio corrente e imposta l'ambiente in modo appropriato - è tempo di provarlo!
StevieP,

13

È possibile utilizzare lvh.me:9000per accedere all'app AngularJS ( lvh.mepunta solo a 127.0.0.1) e quindi specificare un endpoint diverso se lvh.meè l'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/';
  }
});

Quindi iniettare il servizio di configurazione e utilizzare Configuration.APIovunque sia necessario accedere all'API:

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

Un po 'goffo, ma funziona bene per me, anche se in una situazione leggermente diversa (gli endpoint API differiscono per produzione e sviluppo).


1
quindi penso spesso che le persone siano troppo complicate. L'uso semplice di è window.location.hoststato più che sufficiente per me.
joseym,

7

Potremmo anche fare qualcosa del genere.

(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 nel tuo controller/service, possiamo iniettare la dipendenza e chiamare il metodo get con la proprietà a cui accedere.

(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') restituirebbe l'URL in base all'ambiente host.


5

Buona domanda!

Una soluzione potrebbe essere quella di continuare a utilizzare il tuo file config.xml e fornire informazioni sull'endpoint API dal backend al tuo html generato, come questo (esempio in php):

<script type="text/javascript">
angular.module('YourApp').constant('API_END_POINT', '<?php echo $apiEndPointFromBackend; ?>');
</script>

Forse non è una soluzione carina, ma funzionerebbe.

Un'altra soluzione potrebbe essere quella di mantenere il API_END_POINTvalore costante come dovrebbe essere in produzione e modificare solo il file hosts per indicare invece l'URL all'API locale.

O forse una soluzione che utilizza localStorageper le sostituzioni, in questo modo:

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

Ciao Joakimbeng, ho scritto la soluzione che uso in php per spiegare il punto. Stiamo cercando di codificare un client javascript puro con un backend java RESTful puro, quindi il mix php / js non è il mio caso e anche quando scrivo in php cerco sempre di mantenere php e js non mescolati. ma grazie per la risposta. Penso che la soluzione di risposta @kfis potrebbe funzionare: un file configuration.js non sotto controllo di versione che contiene un modulo di configurazione. Con questo approccio posso iniettare / caricare anche un modulo di configurazione diverso a scopo di test, se necessario. Grazie ragazzi.
rbarilani,

@ hal9087 Sono totalmente d'accordo sulla parte relativa al mix delle lingue, va evitata a tutti i costi :) Mi piace anche la soluzione configuration.js, la terrò a mente quando avrò bisogno di qualcosa di simile!
Joakimbeng,

4

Molto tardi nel thread, ma una tecnica che ho usato, pre-angolare, è quella di sfruttare JSON e la flessibilità di JS per fare riferimento in modo dinamico alle chiavi di raccolta e utilizzare fatti inalienabili dell'ambiente (nome del server host, lingua corrente del browser , ecc.) come input per discriminare / preferire selettivamente i nomi delle chiavi con suffisso all'interno di una struttura di dati JSON.

Ciò fornisce non solo un contesto di distribuzione (per PO) ma qualsiasi contesto arbitrario (come il linguaggio) per fornire i18n o qualsiasi altra varianza richiesta simultaneamente e (idealmente) all'interno di un singolo manifest di configurazione, senza duplicazioni e chiaramente ovvio.

IN CIRCA 10 LINEE VANIGLIA JS

Esempio eccessivamente semplificato ma classico: un URL di base dell'endpoint API in un file delle proprietà in formato JSON che varia in base all'ambiente in cui (natch) il server host varierà anche:

    ...
    '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/'
    },
    ...

Una chiave per la funzione di discriminazione è semplicemente il nome host del server nella richiesta.

Questo, naturalmente, può essere combinato con una chiave aggiuntiva basata sulle impostazioni della lingua dell'utente:

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

L'ambito della discriminazione / preferenza può essere limitato ai singoli tasti (come sopra) in cui il tasto "base" viene sovrascritto solo se esiste un tasto corrispondente + suffisso per gli input della funzione - o un'intera struttura e quella struttura stessa analizzato ricorsivamente per abbinare i suffissi di discriminazione / preferenza:

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

Quindi, se un utente che visita il sito Web di produzione ha un'impostazione delle preferenze della lingua tedesca ( de ), la configurazione di cui sopra crollerebbe in:

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

Che aspetto ha una tale magica preferenza / discriminazione come funzione di riscrittura JSON? Non tanto:

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

    };
};

Nelle nostre implementazioni, che includono siti Web angolari e pre-angolari, avviamo semplicemente la configurazione ben prima di altre chiamate di risorse posizionando il JSON in una chiusura JS autoeseguente, inclusa la funzione prefer (), e alimentando le proprietà di base di nome host e codice lingua (e accetta eventuali suffissi arbitrari aggiuntivi di cui potresti aver bisogno):

(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 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 sito pre-angolare ora avrebbe una finestra compressa (no @ suffixed keys) window.app_props a cui fare riferimento.

Un sito angolare, come un passo bootstrap / init, copia semplicemente l'oggetto oggetti di scena scartati in $ rootScope e (facoltativamente) lo distrugge dall'ambito globale / della finestra

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

da iniettare successivamente nei controller:

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

o indicato da associazioni in viste:

<span>{{ props.help.blurb }} {{ props.help.email }}</span>

Avvertenze? Il carattere @ non è un nome variabile / chiave JS / JSON valido, ma finora accettato. Se questo è un rompicapo, sostituisci qualsiasi convenzione ti piaccia, come "__" (doppio trattino basso) finché ti attieni.

La tecnica può essere applicata sul lato server, trasferita su Java o C # ma l'efficienza / compattezza può variare.

In alternativa, la funzione / convenzione potrebbe far parte del tuo script di compilazione front-end, in modo che il JSON tutto-ambiente / tutto-linguaggio gory completo non sia mai trasmesso via cavo.

AGGIORNARE

Abbiamo evoluto l'uso di questa tecnica per consentire più suffissi a una chiave, per evitare di essere costretti a usare le raccolte (puoi ancora, nel modo che desideri) e anche per onorare l'ordine dei suffissi preferiti.

Esempio (vedi anche jsFiddle funzionante ):

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 (utilizzo di base) preferisce i tasti '@dev', scarta tutti gli altri tasti con suffisso

3 preferisce '@dev' rispetto a '@fr', preferisce '@ dev & fr' rispetto a tutti gli altri

4 (uguale a 3 ma preferisce '@fr' rispetto a '@dev')

5 nessun suffisso preferito, elimina TUTTE le proprietà suffissate

Ciò si ottiene assegnando un punteggio a ciascuna proprietà con suffisso e promuovendo il valore di una proprietà con suffisso sulla proprietà senza suffisso durante l'iterazione delle proprietà e la ricerca di un suffisso con punteggio più elevato.

Alcune efficienze in questa versione, inclusa la rimozione della dipendenza da JSON per il deep-copy e il ricorrere solo in oggetti che sopravvivono al round di punteggio in profondità:

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


-8

Hai visto questa domanda e la sua risposta?

Puoi impostare un valore valido a livello globale per la tua app in questo modo:

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

e poi usalo nei tuoi servizi. È possibile spostare questo codice in un file config.js ed eseguirlo al caricamento della pagina o in un altro momento opportuno.


7
Qualcuno potrebbe spiegare perché questa è una risposta così negativa? È stato ampiamente ridimensionato, ma non un singolo commento ...
pubblicato il

5
È vecchio come l'inferno, ma se dovessi indovinare perché i downvotes, è perché non affronta il problema delle configurazioni specifiche dell'ambiente, è solo un suggerimento per usare .value () per impostare un valore globale in qualsiasi vecchia app. Non si fa menzione di come si utilizzerebbe questo a seconda di env o altro lungo i parametri delle domande originali.
coblr,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.