Comment configurer différents environnements dans Angular.js?


220

Comment gérez-vous les variables / constantes de configuration pour différents environnements?

Cela pourrait être un exemple:

Mon API de repos est accessible localhost:7080/myapi/, mais mon ami qui travaille sur le même code sous le contrôle de version Git a déployé l'API sur son Tomcat localhost:8099/hisapi/.

Supposons que nous ayons quelque chose comme ça:

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

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

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

Comment injecter dynamiquement la valeur correcte du point de terminaison API, en fonction de l'environnement?

En PHP, je fais généralement ce genre de choses avec un config.username.xmlfichier, en fusionnant le fichier de configuration de base (config.xml) avec le fichier de configuration de l'environnement local reconnu par le nom de l'utilisateur. Mais je ne sais pas comment gérer ce genre de chose en JavaScript?

Réponses:


209

Je suis un peu en retard sur le sujet, mais si vous utilisez Grunt, j'ai eu beaucoup de succès grunt-ng-constant.

La section de configuration de ngconstantmon Gruntfile.jsapparence

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

Les tâches qui utilisent ngconstantressemblent à

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

Donc, l'exécution grunt servergénérera un config.jsfichier app/scripts/qui ressemble à

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

Enfin, je déclare la dépendance à l'égard des modules qui en ont besoin:

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

Maintenant, mes constantes peuvent être injectées dans les dépendances si nécessaire. Par exemple,

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

10
Plutôt que de mettre 'ngconstant:development'en 'serve'- si vous le mettre dans la configuration de la montre sous 'gruntfile'que tasks: ['ngconstant:development']- vous ne devez redémarrer grunt servelorsque vous mettez à jour les variables de développement dans le gruntfile.
2014

10
Au lieu d'ajouter vos constantes dans le gruntfile.js, vous pouvez mettre des fichiers séparés comme ceci:package: grunt.file.readJSON('development.json')
Guilhem Soulas

3
Il existe une syntaxe mise à jour pour Gruntfile.js dans la version 0.5 de grunt-ng-constant: github.com/werk85/grunt-ng-constant/issues/31 . Excellente réponse, merci!
pherris

10
Pour ceux qui utilisent gulp, il y a gulp-ng-constant .
Dheeraj Vepakomma

4
J'ai trouvé qu'il était également nécessaire d'inclure le fichier scripts / config.js dans angular pour trouver le module, comme ceci: <script src = "scripts / config.js"> </script>
Toni Gamez

75

Une solution intéressante pourrait être de séparer toutes les valeurs spécifiques à l'environnement dans un module angulaire distinct, sur lequel tous les autres modules dépendent:

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

Ensuite, vos modules qui ont besoin de ces entrées peuvent en déclarer une dépendance:

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

Maintenant, vous pourriez penser à d'autres trucs sympas:

Le module, qui contient la configuration, peut être séparé en configuration.js, qui sera inclus sur votre page.

Ce script peut être facilement édité par chacun de vous, tant que vous ne consignez pas ce fichier séparé dans git. Mais il est plus facile de ne pas archiver la configuration si elle se trouve dans un fichier séparé. En outre, vous pouvez le ramifier localement.

Maintenant, si vous avez un système de build, comme ANT ou Maven, vos étapes supplémentaires pourraient être l'implémentation de certains espaces réservés pour les valeurs API_END_POINT, qui seront remplacés pendant la construction, avec vos valeurs spécifiques.

Ou vous avez votre configuration_a.jset configuration_b.jset décidez au backend lequel inclure.


30

Pour les utilisateurs de Gulp , gulp-ng-constant est également utile en combinaison avec gulp-concat , event-stream et 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() { });
});

Dans mon dossier de configuration, j'ai ces fichiers:

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

Ensuite, vous pouvez exécuter gulp config --env developmentet cela créera quelque chose comme ceci:

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

J'ai aussi cette spécification:

beforeEach(module('app'));

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

Existe-t-il un moyen de supprimer le tableau de dépendances avec gulp ng constant? Je n'ai aucune dépendance à mes constantes comme vous l'avez par exemple dans "ngAnimate". Si je ne l'inclus pas, j'obtiens un tableau de dépendances vide sous angular.module ("my.module.config", []) mais je veux la sortie sous angular.module ("my.module.config"). Je ne vois aucune option dans gulp ng constant mais je vois que vous pouvez passer deps: false dans grunt ng constant package. De l'aide?
Arun Gopalpuri

17

Pour y parvenir, je vous suggère d'utiliser le plugin AngularJS Environment: https://www.npmjs.com/package/angular-environment

Voici un exemple:

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

Et puis, vous pouvez appeler les variables de vos contrôleurs comme ceci:

envService.read('apiUrl');

J'espère que ça aide.


1
comment passe-t-il entre le développement et la production?
Mawg dit réintégrer Monica

Salut Juan Pablo, ou @Mawg si vous l'avez compris. Avant de poser une question sur SO / soulever un problème sur Github; comment angular-environmentdétecte l'environnement? c'est-à-dire que devez-vous faire sur votre machine / serveur Web local pour qu'il sache qu'il s'agit respectivement de dev / prod?
StevieP

En lisant à nouveau les documents ... " envServiceProvider.check()... définira automatiquement l'environnement approprié en fonction des domaines donnés". Je pense donc qu'il détecte le domaine actuel et définit l'environnement de manière appropriée - il est temps de le tester!
StevieP

13

Vous pouvez utiliser lvh.me:9000pour accéder à votre application AngularJS, ( lvh.mepointe simplement vers 127.0.0.1), puis spécifiez un point de terminaison différent s'il lvh.mes'agit de l'hôte:

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

Ensuite, injectez le service de configuration et utilisez-le Configuration.APIpartout où vous en avez besoin pour accéder à l'API:

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

Un peu maladroit, mais fonctionne très bien pour moi, bien que dans une situation légèrement différente (les points de terminaison API diffèrent en production et en développement).


1
donc je pense que souvent les gens compliquent les choses. La simple utilisation de window.location.hostme suffisait largement.
joseym

7

Nous pourrions également faire quelque chose comme ça.

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

    });

})();

Et dans votre controller/service, nous pouvons injecter la dépendance et appeler la méthode get avec la propriété à laquelle accéder.

(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') retournerait l'URL en fonction de l'environnement hôte.


5

Bonne question!

Une solution pourrait être de continuer à utiliser votre fichier config.xml et de fournir des informations de point de terminaison api du backend à votre html généré, comme ceci (exemple en php):

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

Ce n'est peut-être pas une jolie solution, mais cela fonctionnerait.

Une autre solution pourrait être de conserver la API_END_POINTvaleur constante telle qu'elle devrait être en production et de modifier uniquement votre fichier d'hôtes pour pointer cette URL vers votre API locale à la place.

Ou peut-être une solution utilisant localStoragedes remplacements, comme ceci:

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

Salut joakimbeng, j'ai écrit la solution que j'utilise en php pour expliquer le point. Nous essayons de coder un client javascript pur avec un backend java pur RESTful, donc le mélange php / js ce n'est pas mon cas et aussi quand j'écris en php j'essaie toujours de garder php et js non mélangés. mais merci pour la réponse. Je pense que la solution de réponse @kfis pourrait fonctionner: un fichier configuration.js non sous contrôle de version qui contient un module de configuration. Avec cette approche, je peux injecter / charger également un module de configuration différent à des fins de test, si cela est nécessaire. Merci les gars.
rbarilani

@ hal9087 Je suis totalement d'accord sur la partie des langages de mixage, cela devrait être évité à tout prix :) J'aime aussi la solution configuration.js, je la garderai à l'esprit quand j'ai besoin de quelque chose de similaire!
joakimbeng

4

Très tard dans le fil, mais une technique que j'ai utilisée, pré-angulaire, est de tirer parti de JSON et de la flexibilité de JS pour référencer dynamiquement les clés de collection et utiliser des faits inaliénables de l'environnement (nom du serveur hôte, langue actuelle du navigateur , etc.) comme entrées pour discriminer / préférer sélectivement les noms de clés suffixés dans une structure de données JSON.

Cela fournit non seulement un contexte d'environnement de déploiement (par OP), mais tout contexte arbitraire (tel que le langage) pour fournir i18n ou tout autre écart requis simultanément, et (idéalement) dans un manifeste de configuration unique, sans duplication et clairement visible.

EN ENVIRON 10 LIGNES VANILLE JS

Exemple trop simplifié mais classique: une URL de base de point de terminaison API dans un fichier de propriétés au format JSON qui varie selon l'environnement où (natch) le serveur hôte variera également:

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

Une clé de la fonction de discrimination est simplement le nom d'hôte du serveur dans la demande.

Ceci, naturellement, peut être combiné avec une clé supplémentaire basée sur les paramètres de langue de l'utilisateur:

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

L'étendue de la discrimination / préférence peut être limitée à des clés individuelles (comme ci-dessus) où la clé "de base" n'est écrasée que s'il y a une clé correspondante + suffixe pour les entrées de la fonction - ou une structure entière, et cette structure elle-même récursivement analysé pour faire correspondre les suffixes de discrimination / préférence:

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

Donc, si un utilisateur visitant le site Web de production a un paramètre de préférence de langue allemande ( de ), la configuration ci-dessus se réduirait à:

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

À quoi ressemble une telle fonction de réécriture JSON de préférence / discrimination magique? Pas tant:

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

    };
};

Dans nos implémentations, qui incluent les sites Web angulaires et pré-angulaires, nous démarrons simplement la configuration bien avant les autres appels de ressources en plaçant le JSON dans une fermeture JS auto-exécutable, y compris la fonction prefer (), et en alimentant les propriétés de base du nom d'hôte et code de langue (et accepte tous les suffixes arbitraires supplémentaires dont vous pourriez avoir besoin):

(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 site pré-angulaire aurait maintenant une fenêtre.app_props réduite (pas de clés suffixées @) à laquelle faire référence.

Un site angulaire, en tant qu'étape bootstrap / init, copie simplement l'objet props perdu dans $ rootScope et (éventuellement) le détruit de la portée globale / fenêtre

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

à injecter ensuite dans les contrôleurs:

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

ou référencé à partir des liaisons dans les vues:

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

Des mises en garde? Le caractère @ n'est pas un nom de variable / clé JS / JSON valide, mais jusqu'à présent accepté. Si c'est une rupture, remplacez toute convention que vous aimez, comme "__" (double soulignement) tant que vous y tenez.

La technique pourrait être appliquée côté serveur, portée sur Java ou C # mais votre efficacité / compacité peut varier.

Alternativement, la fonction / convention pourrait faire partie de votre script de compilation frontal, de sorte que le JSON complet, tout environnement / tout langage, n'est jamais transmis sur le câble.

METTRE À JOUR

Nous avons développé l'utilisation de cette technique pour autoriser plusieurs suffixes à une clé, pour éviter d'être obligé d'utiliser des collections (vous pouvez toujours, aussi profondément que vous le souhaitez), ainsi que pour respecter l'ordre des suffixes préférés.

Exemple (voir aussi jsFiddle de travail ):

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 (utilisation de base) préfère les clés '@dev', supprime toutes les autres clés suffixées

3 préfère '@dev' à '@fr', préfère '@ dev & fr' à tous les autres

4 (identique à 3 mais préfère '@fr' à '@dev')

5 pas de suffixes préférés, supprime TOUTES les propriétés suffixées

Il accomplit cela en notant chaque propriété suffixée et en promouvant la valeur d'une propriété suffixée à la propriété non suffixée lors de l'itération sur les propriétés et de la recherche d'un suffixe ayant un score plus élevé.

Quelques efficacités dans cette version, y compris la suppression de la dépendance à JSON pour la copie en profondeur, et la récurrence uniquement dans les objets qui survivent à la ronde de notation à leur profondeur:

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

Avez-vous vu cette question et sa réponse?

Vous pouvez définir une valeur globalement valide pour votre application comme ceci:

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

puis l'utiliser dans vos services. Vous pouvez déplacer ce code dans un fichier config.js et l'exécuter au chargement de la page ou à un autre moment opportun.


7
Quelqu'un pourrait-il expliquer pourquoi c'est une si mauvaise réponse? Il a été massivement sous-voté, mais pas un seul commentaire ...
aendrew

5
C'est vieux comme l'enfer, mais si je devais deviner pourquoi les downvotes, c'est parce qu'il ne résout pas le problème des configurations spécifiques à l'environnement, c'est juste une suggestion d'utiliser .value () pour définir une valeur globale dans n'importe quelle ancienne application. Il n'y a aucune mention sur la façon dont on pourrait utiliser cela en fonction de l'env ou quoi que ce soit le long des paramètres des questions d'origine.
coblr
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.