如何在Angular.js中configuration不同的环境?
你如何pipe理不同环境的configurationvariables/常量?
这可能是一个例子:
我的rest API在localhost:7080/myapi/
上是可以访问的,但是我的朋友在Git版本控制下的相同代码上已经在localhost:8099/hisapi/
上的Tomcat上部署了API。
假设我们有这样的事情:
angular .module('app', ['ngResource']) .constant('API_END_POINT','<local_end_point>') .factory('User', function($resource, API_END_POINT) { return $resource(API_END_POINT + 'user'); });
我如何根据环境dynamic地注入API端点的正确值?
在PHP中,我通常使用config.username.xml
文件来完成这种工作,将基本configuration文件(config.xml)与由用户名称识别的本地环境configuration文件进行合并。 但我不知道如何在JavaScript中pipe理这种事情?
我对线程有点晚了,但是如果你使用Grunt,我已经用grunt-ng-constant
取得了很大的成功。
在我的Gruntfile.js
configuration部分看起来像
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
会在app/scripts/
生成一个config.js
文件
"use strict"; angular.module("config", []).constant("ENV", "development");
最后,我声明依赖于任何需要它的模块:
// the 'config' dependency is generated via grunt var app = angular.module('myApp', [ 'config' ]);
现在我的常量可以在需要时dependency injection。 例如,
app.controller('MyController', ['ENV', function( ENV ) { if( ENV === 'production' ) { ... } }]);
一个很酷的解决scheme可能是将所有特定于环境的值分离成一个独立的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的模块可以分解为configuration.js,这些将包含在您的页面中。
这个脚本可以很容易地由你们每个人编辑,只要你不把这个单独的文件检入到git中。 但是,如果configuration文件位于单独的文件中,则无法检入configuration。 另外,你可以在本地分支。
现在,如果你有一个构build系统,比如ANT或者Maven,那么你的进一步步骤就是实现API_END_POINT值的一些占位符,这些值将在构build时被你的具体值所替代。
或者你有你的configuration_a.js
和configuration_b.js
并在后端决定包含哪些内容。
对于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() { }); });
在我的configuration文件夹我有这些文件:
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
,这将创build如下所示:
angular.module("app.config", []) .constant("foo", "bar") .constant("ngConstant", true);
我也有这个规范:
beforeEach(module('app')); it('loads the config', inject(function(config) { expect(config).toBeTruthy(); }));
为了达到这个目的,我build议你使用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(); });
然后,你可以调用你的控制器的variables,例如:
envService.read('apiUrl');
希望能帮助到你。
您可以使用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服务并使用Configuration.API
访问API所需的任何地方:
$resource(Configuration.API + '/endpoint/:id', { id: '@id' });
有点笨重,但对我来说工作得很好,虽然情况略有不同(APIterminal在生产和开发上有所不同)。
好问题!
一个解决scheme可能是继续使用你的config.xml文件,并提供从后端到你生成的html的API端点信息,就像这样(在php中的例子):
<script type="text/javascript"> angular.module('YourApp').constant('API_END_POINT', '<?php echo $apiEndPointFromBackend; ?>'); </script>
也许不是一个漂亮的解决scheme,但它会工作。
另一个解决scheme可能是保留API_END_POINT
常量值,因为它应该在生产环境中,并且只修改您的hosts文件以将该url指向您的本地api。
或者可能是一个解决scheme使用localStorage
覆盖,如下所示:
.factory('User',['$resource','API_END_POINT'],function($resource,API_END_POINT){ var myApi = localStorage.get('myLocalApiOverride'); return $resource((myApi || API_END_POINT) + 'user'); });
我们也可以做这样的事情。
(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。
线程很晚,但是我使用的一种技术,就是利用JSON和JS的灵活性来dynamic引用收集键,并使用环境中不可分割的事实(主机服务器名称,当前浏览器语言等等)作为input来select性地区分/偏好JSON数据结构中的后缀键名称。
这不仅提供了部署环境上下文(每个OP),而且还提供任意上下文(比如语言)来提供国际化或任何其他需要同时并且(理想情况下)在单个configuration清单中的差异,而且没有重复,并且显而易见。
在关于10行VANILLA JS
过分简化但经典的示例:JSON格式的属性文件中的API端点基本URL,在每个环境中(主机)的主机服务器也不尽相同:
... '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/' }, ...
鉴别function的关键就是请求中的服务器主机名。
当然,这可以与基于用户的语言设置的附encryption钥相结合:
... 'app': { 'NAME': 'Ferry Reservations', 'NAME@fr': 'Réservations de ferry', 'NAME@de': 'Fähren Reservierungen' }, ...
区分/偏好的范围可以局限于单个键(如上所述),其中如果存在与该function的input相匹配的键+后缀或者整个结构以及该结构本身,则仅“基”键被覆盖recursionparsing匹配歧视/偏好后缀:
'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' },
因此,如果访问生产网站的用户具有德语( 德 )语言首选项设置,则上述configuration将折叠为:
'help': { 'BLURB': 'Bitte kontaktieren Sie unseren Kundendienst!!1!', 'PHONE': '1-800-CUS-TOMR', 'EMAIL': 'customer.service@productionwebsite.com' },
这样一个神奇的偏好/歧视JSON重写function是什么样的? 不多:
// 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); }; };
在我们的包括Angular和Angular网站的实现中,我们简单地通过将JSON放置在一个自执行的JS闭包(包括prefer()函数,以及提供的hostname和语言代码(并接受您可能需要的任何附加的任意后缀):
(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]) ] );
一个pre-Angular网站现在会有一个折叠的(没有@后缀的键) window.app_props来引用。
一个Angular站点,作为bootstrap / init步骤,简单地将dead-dropped props对象复制到$ 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 / JSONvariables/键命名,但迄今为止被接受。 如果这是一个交易断路器,只要你坚持,就可以替代你喜欢的任何约定,比如“__”(双下划线)。
该技术可以应用于服务器端,移植到Java或C#,但您的效率/紧凑性可能会有所不同。
或者,函数/约定可以是前端编译脚本的一部分,这样全部的环境/全部语言的JSON就不会通过线路传输。
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比'@fr'更喜欢“@dev”,喜欢“@ dev&fr”
4 (与3相同,但比'@dev'更喜欢'@fr')
5没有优选的后缀,掉落ALL后缀的属性
它通过对每个后缀属性进行评分并在迭代属性和查找更高分值的后缀时将后缀属性的值提升为非后缀属性来实现此目的。
在这个版本中有一些效率,包括消除对JSON的依赖以及深度复制,并且只能recursion到在深度进行评分的对象中:
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()); }
如果您使用的是Brunch , Constangular插件可以帮助您pipe理不同环境的variables。
你见过这个问题和答案吗?
你可以像这样为你设置一个全球有效的值:
app.value('key', 'value');
然后在你的服务中使用它。 您可以将此代码移至config.js文件,并在页面加载或其他方便时刻执行。