Як налаштувати різні середовища в Angular.js?


220

Як ви керуєте змінними / константами конфігурації для різних середовищ?

Це може бути приклад:

Мій API відпочинку доступний localhost:7080/myapi/, але мій друг, який працює над тим самим кодом під керуванням версії Git, API розгорнуто на його Tomcat localhost:8099/hisapi/.

Припустимо, що у нас є щось подібне:

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

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

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

Як я динамічно вводять правильне значення кінцевої точки API, залежно від середовища?

У PHP я зазвичай роблю цей матеріал з config.username.xmlфайлом, об'єднуючи базовий файл конфігурації (config.xml) з локальним файлом конфігурації середовища, розпізнаваним іменем користувача. Але я не знаю, як керувати подібними речами в JavaScript?

Відповіді:


209

Я трохи запізнююся на тему, але якщо ви використовуєте Grunt, я мав великий успіх grunt-ng-constant.

Розділ конфігурації на ngconstantмій Gruntfile.jsвигляд виглядає так

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

Завдання, які використовують, ngconstantвиглядають так

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

Таким чином, запущений файл grunt serverбуде генерувати config.jsфайл у app/scripts/такому вигляді

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

Нарешті, я заявляю про залежність від необхідних модулів:

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

Тепер мої константи можуть бути введені залежністю там, де потрібно. Наприклад,

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

10
Замість того , щоб помістити 'ngconstant:development'в 'serve'- якщо ви помістіть його в конфіги вахти під , 'gruntfile'як tasks: ['ngconstant:development']- ви не повинні перезавантаження grunt serveпри оновленні змінних розвитку в gruntfile.
spenthil

10
Замість того, щоб додавати константи в gruntfile.js, ви можете помістити окремі файли, як це:package: grunt.file.readJSON('development.json')
Guilhem Soulas

3
Існує оновлений синтаксис для Gruntfile.js у версії 0.5 grunt-ng-константи: github.com/werk85/grunt-ng-constant/isissue/31 . Чудова відповідь, дякую!
феріс

10
Для тих, хто використовує gulp, є gulp-ng-константа .
Dheeraj Vepakomma

4
Я виявив, що також потрібно включити файл script / config.js до кутового, щоб знайти модуль, як-от так: <script src = "script / config.js"> </script>
Toni Gamez

75

Одним класним рішенням може бути розділення всіх значень, що стосуються середовища, на окремий кутовий модуль, від якого залежать усі інші модулі:

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

Тоді ваші модулі, які потребують цих записів, можуть оголосити залежність від нього:

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

Тепер ви можете подумати про подальші класні речі:

Модуль, що містить конфігурацію, можна розділити на config.js, який буде включений у вашу сторінку.

Цей скрипт може легко редагувати кожен з вас, якщо ви не перевірите цей окремий файл у git. Але простіше не перевірити конфігурацію, якщо вона знаходиться в окремому файлі. Крім того, ви можете поділити його локально.

Тепер, якщо у вас є система збірки, наприклад ANT або Maven, вашими подальшими кроками може бути реалізація деяких заповнювачів для значень API_END_POINT, які будуть замінені під час збирання вашими конкретними значеннями.

Або ви маєте своє configuration_a.jsі configuration_b.jsвирішуєте в бекенде, що його включити.


30

Для користувачів Gulp також потрібна константа gulp-ng у поєднанні з gulp-concat , event-stream та 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() { });
});

У моїй папці config я маю ці файли:

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

Тоді ви можете запустити, gulp config --env developmentі це створить щось подібне:

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

У мене також є ця специфікація:

beforeEach(module('app'));

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

Чи є спосіб видалити масив залежності за допомогою постійної gulp ng? У мене немає такої залежності від моїх констант, як у вас, наприклад, "ngAnimate". Якщо я не включаю його, я отримую порожній масив залежності як angular.module ("my.module.config", []), але я хочу, щоб результат був як angular.module ("my.module.config"). Я не бачу жодної опції в константі gulp ng, але я бачу, що ви можете передавати deps: false in grunt ng постійний пакет. Будь-яка допомога?
Арун Гопалпурі

17

Для цього я пропоную скористатися плагіном для навколишнього середовища AngularJS: https://www.npmjs.com/package/angular-environment

Ось приклад:

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

А потім ви можете викликати змінні зі своїх контролерів, такі як:

envService.read('apiUrl');

Сподіваюся, це допомагає.


1
як він переключається між розвитком і виробництвом?
Мауг каже, що поверніть Моніку

Привіт Хуан Пабло, або @Mawg, якщо ти це зрозумів. Перш ніж я поставити запитання щодо SO / підняти питання про Github; як angular-environmentвиявляє навколишнє середовище? тобто що вам потрібно зробити на локальній машині / веб-сервері, щоб він знав, що це відповідно dev / prod?
StevieP

Повторне читання документів ... " envServiceProvider.check()... автоматично встановить відповідне середовище на основі заданих доменів". Тому я вважаю, що він виявляє поточний домен і встановлює навколишнє середовище відповідним чином - час перевірити його!
StevieP

13

Ви можете використовувати lvh.me:9000для доступу до програми AngularJS ( lvh.meлише вказує на 127.0.0.1), а потім вказати іншу кінцеву точку, якщо lvh.meхост:

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

А потім введіть службу конфігурації та використовуйте Configuration.APIтам, де вам потрібно отримати доступ до API:

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

Тад незграбний, але працює для мене чудово, хоча і в дещо іншій ситуації (кінцеві точки API відрізняються виробництвом та розвитком).


1
тому я думаю, що часто люди переплутують речі. Просте використання window.location.hostдля мене було більш ніж достатньо.
joseym

7

Ми також могли б зробити щось подібне.

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

    });

})();

І у вашому випадку controller/serviceми можемо ввести залежність та викликати метод get із властивістю, до якого можна отримати доступ.

(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') повертає URL-адресу на основі хост-середовища.


5

Гарне питання!

Одним з рішень може бути продовження використання файлу config.xml та надання інформації про кінцеву точку api з бекенда до створеного HTML-коду, наприклад, такий (наприклад, у php):

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

Можливо, не гарне рішення, але воно спрацювало б.

Іншим рішенням може бути збереження API_END_POINTпостійного значення таким, яким воно повинно бути у виробництві, і лише змінити файл хостів, щоб вказати цей URL на локальний api.

Або, можливо, рішення, що використовує localStorageдля заміщення, наприклад, такого:

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

Привіт joakimbeng, я написав рішення, яке я використовую в php, щоб пояснити точку. Ми намагаємося зашифрувати чистий javascript-клієнт із чистим резервним Java-сервером RESTful, тому змішується php / js - це не мій випадок, а також, коли я пишу в php, я завжди намагаюся не змінювати php та js не змішуватися. але дякую за відповідь. Я думаю, що рішення відповіді @kfis може працювати: файл config.js не перебуває під контролем версій, який містить модуль конфігурації. При такому підході я можу вводити / завантажувати також інший модуль конфігурації для тестування, якщо це необхідно. Спасибі, хлопці.
rbarilani

@ hal9087 Я повністю погоджуюся з частиною мов змішування, цього слід уникати будь-якою ціною :) Мені також подобається рішення config.js, я пам’ятаю, коли мені потрібно щось подібне!
joakimbeng

4

Дуже пізно до теми, але використовувана нами методика, передвугольна, - це скористатися JSON та гнучкістю JS для динамічного посилання ключів колекції та використання невід'ємних фактів оточення (ім'я сервера хоста, поточна мова браузера тощо) як вхідні дані для вибіркової дискримінації / переваги суфіксальних імен ключів у структурі даних JSON.

Це забезпечує не просто контекст середовища розгортання (на ОП), але будь-який довільний контекст (наприклад, мова), щоб забезпечити i18n або будь-яку іншу необхідну дисперсію одночасно, і (в ідеалі) в рамках одного маніфесту конфігурації, без дублювання, і зрозуміло очевидним.

ВІД 10 ЛІНІЙ VANILLA JS

Приблизно спрощений, але класичний приклад: Базова URL-адреса кінцевої точки API у файлі властивостей, відформатованих JSON, змінюється залежно від середовища, де (natch) хост-сервер також змінюватиметься:

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

Ключовим фактором дискримінації є просто ім'я хоста сервера у запиті.

Це, природно, може поєднуватися з додатковою клавішею на основі мовних налаштувань користувача:

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

Обсяг дискримінації / уподобань може бути обмежений окремими ключами (як вище), де "базовий" ключ перезаписується лише у тому випадку, якщо для входів у функцію - або цілої структури є сама відповідна клавіша + суфікс. рекурсивно розбирається для відповідності суфіксів дискримінації / переваг:

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

Так, якщо відвідувач на виробничому веб-сайті має налаштування переваг німецької ( де ) мови, наведена вище конфігурація буде згорнута до:

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

Як виглядає така магічна перевага / дискримінація функції переписування JSON? Не багато:

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

    };
};

У наших реалізаціях, що включають кутові та передвугові веб-сайти, ми просто завантажуємо конфігурацію набагато перед іншими викликами ресурсів, розміщуючи JSON в самовиконанні JS-закриття, включаючи функцію preference (), і подаємо основні властивості імені хоста та language-code (і приймає будь-які додаткові довільні суфікси, які можуть знадобитися):

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

На веб-сайті перед кутом тепер буде згорнуте (без @ суфіксальних ключів) window.app_props для посилання.

Кутовий сайт, як етап завантаження / init, просто копіює мертвий предмет реквізиту в $ rootScope і (необов'язково) знищує його з глобальної / віконної області

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

згодом вводити в контролери:

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

або посилається на прив'язки у видах:

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

Пляшки? Символ @ не є дійсним іменуванням змінної JS / JSON / клавіш, але поки прийнято. Якщо це розрив угод, замініть будь-яку конвенцію, яка вам подобається, наприклад "__" (подвійне підкреслення), поки ви дотримуєтесь цього.

Ця методика може бути застосована на стороні сервера, перенесена на Java або C #, але ефективність / компактність можуть відрізнятися.

Крім того, функція / умова може бути частиною вашого сценарію компіляції на передньому кінці, так що повноцінне середовище / всемовний JSON з усією мовою ніколи не передається по дроту.

ОНОВЛЕННЯ

Ми розробили використання цієї методики для дозволу декількох суфіксів до ключа, щоб не змушувати використовувати колекції (ви все ще можете, наскільки глибоко хочете), а також вшановувати порядок бажаних суфіксів.

Приклад (див. Також робочий 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 (основне використання) надає перевагу клавішам "@dev", відкидає всі інші суфіксні клавіші

3 віддає перевагу '@dev' над '@fr', віддає перевагу '@ dev & fr' над усіма іншими

4 (те саме, що 3, але віддає перевагу '@fr' над '@dev')

5 немає кращих суфіксів, краплі ВСІ суфіксальні властивості

Це досягається шляхом підрахунку кожної суфіксальної властивості та піднесення значення властивості суфіксів до несуфіксного властивості під час ітерації над властивостями та пошуку суфікса з більш високим балом.

Деякі показники ефективності в цій версії, включаючи усунення залежності від JSON для глибокого копіювання та повторного повторного входження в об'єкти, які переживають ранг балів на їх глибині:

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

Ви бачили це питання та його відповідь?

Ви можете встановити глобально допустиме значення для додатка таким чином:

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

а потім використовувати його у своїх послугах. Ви можете перемістити цей код у файл config.js та виконати його під час завантаження сторінки чи іншого зручного моменту.


7
Невже хтось може пояснити, чому це така погана відповідь? Це було масово знижено, але жодного коментаря не було ...
aendrew

5
Це давно як пекло, але якщо я повинен був здогадуватися, чому голоси, це тому, що він не вирішує проблему конкретних конфігурацій середовища, це лише пропозиція використовувати .value () для встановлення глобального значення в будь-якому старому додатку. Немає жодної згадки про те, як можна було б використовувати це залежно від env або чого-небудь уздовж оригінальних параметрів питань.
coblr
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.