Как вы управляете конфигурационными переменными/константами для разных сред?
Это может быть пример:
My rest 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?
Ответ 1
Я немного опоздал на поток, но если вы используете 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' ) {
...
}
}]);
Ответ 2
Одним из замечательных решений может быть разделение всех значений, относящихся к среде, на отдельный модуль angular, который зависит от всех других модулей:
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');
});
Теперь вы могли бы подумать о других интересных вещах:
Модуль, содержащий конфигурацию, может быть разделен на configuration.js, который будет включен на вашу страницу.
Этот script может быть легко отредактирован каждым из вас, если вы не проверите этот отдельный файл на git. Но проще не проверять конфигурацию, если она находится в отдельном файле. Кроме того, вы можете разбить его локально.
Теперь, если у вас есть система сборки, например ANT или Maven, ваши дальнейшие шаги могут быть реализованы с помощью некоторых заполнителей для значений API_END_POINT, которые будут заменены во время сборки с вашими конкретными значениями.
Или у вас есть configuration_a.js
и configuration_b.js
, и вы решите включить в него бэкэнд.
Ответ 3
Для пользователей Gulp gulp-ng-constant также полезно в сочетании с 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() { });
});
В моей папке конфигурации у меня есть следующие файлы:
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();
}));
Ответ 4
Чтобы достичь этого, я предлагаю вам использовать плагин окружения 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');
Надеюсь, что это поможет.
Ответ 5
Вы можете использовать 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 отличаются производством и развитием).
Ответ 6
Хороший вопрос!
Одним из решений может быть продолжение использования вашего файла config.xml и предоставление информации о конечной точке api из бэкэнда в ваш сгенерированный html, например, это (пример в php):
<script type="text/javascript">
angular.module('YourApp').constant('API_END_POINT', '<?php echo $apiEndPointFromBackend; ?>');
</script>
Возможно, это не очень красивое решение, но оно будет работать.
Другим решением может быть сохранение значения константы API_END_POINT
, так как оно должно быть в производстве, и только изменить ваш файл hosts, чтобы указать этот 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');
});
Ответ 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 на основе среды хоста.
Ответ 8
Очень поздно для потока, но метод, который я использовал, pre- Angular, должен использовать JSON и гибкость JS для динамического сравнения ключей коллекции и использования неотъемлемых фактов среды (хост-сервер имя, текущий язык браузера и т.д.) в качестве входных данных для выборочного распознавания/предпочтения имен суффикса ключей в структуре данных JSON.
Это обеспечивает не просто контекст среды развертывания (для каждого OP), а любой произвольный контекст (например, язык) для предоставления 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'
},
SO, если посетитель на веб-сайте производства имеет настройку предпочтения языка (de), указанная выше конфигурация будет сбрасываться до:
'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 a collection itself, recurse it!
if(o[key] && typeof o[key] === 'object') prefer(o[key],sufs);
};
};
В наших реализациях, которые включают веб-сайты Angular и pre- Angular, мы просто загружаем конфигурацию задолго до других вызовов ресурсов, помещая JSON в самозавершающееся закрытие JS, включая функцию prefer() и передал основные свойства имени хоста и языка-кода (и принимает любые дополнительные произвольные суффиксы, которые могут вам понадобиться):
(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]) ] );
У сайта pre- Angular теперь будет скомпенсировано (нет @суффиксных ключей) window.app_props для ссылки.
Сайт Angular, как шаг начальной загрузки/инициализации, просто копирует объект с отложенным реквизитом в $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 или С#, но эффективность/компактность могут быть разными.
В качестве альтернативы, функция/соглашение может быть частью вашего компиляционного интерфейса script, так что полный JOON-сервер gor-all-environment/all-language никогда не передается по проводу.
UPDATE
Мы разработали использование этой техники, чтобы позволить нескольким суффиксам ключу, чтобы не быть принужденными к использованию коллекций (вы все еще можете так глубоко, как вы хотите), а также соблюдать порядок предпочтительных суффиксов.
Пример (также см. рабочий 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' over '@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());
}
Ответ 9
Если вы используете Brunch, плагин Constangular помогает вам управлять переменными для разных сред.
Ответ 10
Вы видели этот question и его ответ?
Вы можете установить глобально допустимое значение для вашего приложения следующим образом:
app.value('key', 'value');
а затем использовать его в своих службах. Вы можете переместить этот код в файл config.js и выполнить его при загрузке страницы или в другой удобный момент.