How do you manage configuration variables/constants for different environments?
如何为不同的环境管理配置变量/常量?
This could be an example:
这可以是一个例子:
My rest API is reachable on localhost:7080/myapi/
, but my friend that works on the same code under Git version control has the API deployed on his Tomcat on localhost:8099/hisapi/
.
我的rest API在localhost:7080/myapi/上是可访问的,但是我在Git版本控制下工作的朋友在他的Tomcat上部署了这个API:8099/hisapi/。
Supposing that we have something like this :
假设我们有这样的东西
angular
.module('app', ['ngResource'])
.constant('API_END_POINT','<local_end_point>')
.factory('User', function($resource, API_END_POINT) {
return $resource(API_END_POINT + 'user');
});
How do I dynamically inject the correct value of the API endpoint, depending on the environment?
如何根据环境动态注入API端点的正确值?
In PHP I usually do this kind of stuff with a config.username.xml
file, merging the basic configuration file (config.xml) with the local environment configuration file recognised by the name of the user. But I don't know how to manage this kind of thing in JavaScript?
在PHP中,我通常用config.username做这些事情。xml文件,将基本配置文件(config.xml)与通过用户名识别的本地环境配置文件合并在一起。但是我不知道如何在JavaScript中管理这类东西?
10 个解决方案
#1
208
I'm a little late to the thread, but if you're using Grunt I've had great success with grunt-ng-constant
.
我有点晚了,但是如果你用咕哝的话,我在垃圾邮件-ng-constant中取得了巨大的成功。
The config section for ngconstant
in my Gruntfile.js
looks like
我的Gruntfile中ngconstant的配置部分。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'
}
}
}
The tasks that use ngconstant
look like
使用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'
]);
So running grunt server
will generate a config.js
file in app/scripts/
that looks like
因此运行grunt服务器将生成一个配置。在app/script /中显示的js文件
"use strict";
angular.module("config", []).constant("ENV", "development");
Finally, I declare the dependency on whatever modules need it:
最后,我声明对任何模块的依赖:
// the 'config' dependency is generated via grunt
var app = angular.module('myApp', [ 'config' ]);
Now my constants can be dependency injected where needed. E.g.,
现在我的常量可以在需要时注入依赖项。例如,
app.controller('MyController', ['ENV', function( ENV ) {
if( ENV === 'production' ) {
...
}
}]);
#2
75
One cool solution might be separating all environment-specific values into some separate angular module, that all other modules depend on:
一个很酷的解决方案可能是将所有特定于环境的值分离成一些单独的角模块,所有其他模块都依赖于这些角模块:
angular.module('configuration', [])
.constant('API_END_POINT','123456')
.constant('HOST','localhost');
Then your modules that need those entries can declare a dependency on it:
然后需要这些条目的模块可以声明对它的依赖:
angular.module('services',['configuration'])
.factory('User',['$resource','API_END_POINT'],function($resource,API_END_POINT){
return $resource(API_END_POINT + 'user');
});
Now you could think about further cool stuff:
现在你可以考虑一些更酷的东西:
The module, that contains the configuration can be separated into configuration.js, that will be included at your page.
包含配置的模块可以分为配置。js,这将包含在您的页面中。
This script can be easily edited by each of you, as long as you don’t check this separate file into git. But it's easier to not check in the configuration if it is in a separate file. Also, you could branch it locally.
这个脚本可以很容易地由每个人编辑,只要您不将这个单独的文件检查到git中。但如果配置在单独的文件中,则更容易不检入。此外,您还可以在本地对其进行分支。
Now, if you have a build-system, like ANT or Maven, your further steps could be implementing some placeholders for the values API_END_POINT, that will be replaced during build-time, with your specific values.
现在,如果您有一个构建系统,如ANT或Maven,您的进一步步骤可能是为API_END_POINT值实现一些占位符,这些占位符将在构建时用特定的值替换。
Or you have your configuration_a.js
and configuration_b.js
and decide at the backend which to include.
或者你有一个configuration_a。js和configuration_b。然后在后端决定包含哪个。
#3
30
For Gulp users, gulp-ng-constant is also useful combined with gulp-concat, event-stream and yargs.
对于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() { });
});
In my config folder I have these files:
在我的配置文件夹中,我有这些文件:
ls -l config
total 8
-rw-r--r--+ 1 .. ci.json
-rw-r--r--+ 1 .. development.json
-rw-r--r--+ 1 .. production.json
Then you can run gulp config --env development
and that will create something like this:
然后你可以运行gulp config -env开发,这会创建如下内容:
angular.module("app.config", [])
.constant("foo", "bar")
.constant("ngConstant", true);
I also have this spec:
我也有这个规格:
beforeEach(module('app'));
it('loads the config', inject(function(config) {
expect(config).toBeTruthy();
}));
#4
16
To achieve that, I suggest you to use AngularJS Environment Plugin: https://www.npmjs.com/package/angular-environment
为此,我建议您使用AngularJS环境插件:https://www.npmjs.com/package/angular-environment
Here's an example:
这里有一个例子:
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();
});
And then, you can call the variables from your controllers such as this:
然后,你可以从控制器中调用变量,比如:
envService.read('apiUrl');
Hope it helps.
希望它可以帮助。
#5
13
You could use lvh.me:9000
to access your AngularJS app, (lvh.me
just points to 127.0.0.1) and then specify a different endpoint if lvh.me
is the host:
您可以使用lvh。我:9000来访问你的AngularJS应用(lvh)。我只指向127.0.0.1),然后在lvh中指定一个不同的端点。我是主持人:
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/';
}
});
And then inject the Configuration service and use Configuration.API
wherever you need to access the API:
然后注入配置服务并使用配置。在任何需要访问该API的地方:
$resource(Configuration.API + '/endpoint/:id', {
id: '@id'
});
A tad clunky, but works fine for me, albeit in a slightly different situation (API endpoints differ in production and development).
虽然有点笨拙,但对我来说还不错,尽管情况略有不同(API端点在生产和开发上有所不同)。
#6
5
Good question!
好问题!
One solution could be to continue using your config.xml file, and provide api endpoint information from the backend to your generated html, like this (example in php):
一种解决方案是继续使用配置。xml文件,并将api端点信息从后端提供给生成的html,比如这个(php中的例子):
<script type="text/javascript">
angular.module('YourApp').constant('API_END_POINT', '<?php echo $apiEndPointFromBackend; ?>');
</script>
Maybe not a pretty solution, but it would work.
也许这不是一个很好的解决方案,但它会起作用。
Another solution could be to keep the API_END_POINT
constant value as it should be in production, and only modify your hosts-file to point that url to your local api instead.
另一种解决方案是将API_END_POINT常量值保持在生产环境中,并且只修改您的主机文件以将该url指向您的本地api。
Or maybe a solution using localStorage
for overrides, like this:
或者使用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
5
We could also do something like this.
我们也可以这样做。
(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];
}
}
});
})();
And in your controller/service
, we can inject the dependency and call the get method with property to be accessed.
在您的控制器/服务中,我们可以注入依赖项并调用具有要访问的属性的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')
would return the url based on the host environment.
$http.get(env.get('apiroot'))将基于主机环境返回url。
#8
3
Very late to the thread, but a technique I've used, pre-Angular, is to take advantage of JSON and the flexibility of JS to dynamically reference collection keys, and use inalienable facts of the environment (host server name, current browser language, etc.) as inputs to selectively discriminate/prefer suffixed key names within a JSON data structure.
很晚的线程,但我使用的技巧,pre-Angular,是利用JSON和JS的灵活性动态参考收集钥匙,和使用环境的不可剥夺的事实(主机服务器名称、当前浏览器语言等)作为输入选择性歧视/喜欢JSON数据结构中关键的名字作为后缀。
This provides not merely deploy-environment context (per OP) but any arbitrary context (such as language) to provide i18n or any other variance required simultaneously, and (ideally) within a single configuration manifest, without duplication, and readably obvious.
这不仅提供了部署环境上下文(每个OP),还提供了任何任意上下文(如语言),以同时提供i18n或任何其他所需的差异,并且(理想情况下)在一个配置清单中(没有重复,而且可读性明显)。
IN ABOUT 10 LINES VANILLA JS
大概有10行
Overly-simplified but classic example: An API endpoint base URL in a JSON-formatted properties file that varies per environment where (natch) the host server will also vary:
过度简化但经典的示例:json格式的属性文件中的API端点基本URL,每个环境(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/'
},
...
A key to the discrimination function is simply the server hostname in the request.
识别函数的关键是请求中的服务器主机名。
This, naturally, can be combined with an additional key based on the user's language settings:
当然,这可以根据用户的语言设置与附加的键结合:
...
'app': {
'NAME': 'Ferry Reservations',
'NAME@fr': 'Réservations de ferry',
'NAME@de': 'Fähren Reservierungen'
},
...
The scope of the discrimination/preference can be confined to individual keys (as above) where the "base" key is only overwritten if there's a matching key+suffix for the inputs to the function -- or an entire structure, and that structure itself recursively parsed for matching discrimination/preference suffixes:
识别/偏好的范围可以局限于单个键(如上所述),只有当函数的输入有匹配的键+后缀时,“base”键才被覆盖,或者是整个结构,该结构本身被递归解析以匹配识别/偏好后缀:
'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, if a visiting user to the production website has German (de) language preference setting, the above configuration would collapse to:
因此,如果访问产品网站的用户具有德语(de)语言首选项设置,则上述配置将崩溃为:
'help': {
'BLURB': 'Bitte kontaktieren Sie unseren Kundendienst!!1!',
'PHONE': '1-800-CUS-TOMR',
'EMAIL': 'customer.service@productionwebsite.com'
},
What does such a magical preference/discrimination JSON-rewriting function look like? Not much:
如此神奇的偏好/歧视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);
};
};
In our implementations, which include Angular and pre-Angular websites, we simply bootstrap the configuration well ahead of other resource calls by placing the JSON within a self-executing JS closure, including the prefer() function, and fed basic properties of hostname and language-code (and accepts any additional arbitrary suffixes you might need):
在我们的实现(包括有角的和有角的网站)中,我们只需将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]) ] );
A pre-Angular site would now have a collapsed (no @ suffixed keys) window.app_props to refer to.
预角点现在将有一个折叠(无@后缀键)窗口。app_props引用。
An Angular site, as a bootstrap/init step, simply copies the dead-dropped props object into $rootScope, and (optionally) destroys it from global/window scope
作为引导/初始化步骤,一个有棱角的站点只需要将死去的道具对象复制到$rootScope中,然后(可选地)从全局/窗口作用域中销毁它
app.constant('props',angular.copy(window.app_props || {})).run( function ($rootScope,props) { $rootScope.props = props; delete window.app_props;} );
to be subsequently injected into controllers:
随后注入控制器:
app.controller('CtrlApp',function($log,props){ ... } );
or referred to from bindings in views:
或从视图中的绑定中引用:
<span>{{ props.help.blurb }} {{ props.help.email }}</span>
Caveats? The @ character is not valid JS/JSON variable/key naming, but so far accepted. If that's a deal-breaker, substitute for any convention you like, such as "__" (double underscore) as long as you stick to it.
警告?@字符不是有效的JS/JSON变量/键名,但到目前为止被接受。如果这是一种破坏交易的行为,那就用“__”(双下划线)来代替你喜欢的任何约定,只要你坚持下去。
The technique could be applied server-side, ported to Java or C# but your efficiency/compactness may vary.
该技术可以应用于服务器端,移植到Java或c#,但是您的效率/紧凑性可能有所不同。
Alternately, the function/convention could be part of your front-end compile script, so that the full gory all-environment/all-language JSON is never transmitted over the wire.
或者,函数/约定可以是前端编译脚本的一部分,这样就不会通过网络传输完整的gory全环境/全语言JSON。
UPDATE
更新
We've evolved usage of this technique to allow multiple suffixes to a key, to avoid being forced to use collections (you still can, as deeply as you want), and as well to honor the order of the preferred suffixes.
我们已经发展了这种技术的用法,允许对一个键使用多个后缀,以避免*使用集合(您仍然可以随心所欲地使用集合),并尊重首选后缀的顺序。
Example (also see working jsFiddle):
示例(也参见工作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 (basic usage) prefers '@dev' keys, discards all other suffixed keys
1/2(基本用法)喜欢'@dev'键,丢弃所有其他后缀的键
3 prefers '@dev' over '@fr', prefers '@dev&fr' over all others
3更喜欢“@dev”而不是“@fr”,更喜欢“@dev&fr”。
4 (same as 3 but prefers '@fr' over '@dev')
4(与3相同,但更喜欢“@fr”而不是“@dev”)
5 no preferred suffixes, drops ALL suffixed properties
没有首选后缀,删除所有后缀属性
It accomplishes this by scoring each suffixed property and promoting the value of a suffixed property to the non-suffixed property when iterating over the properties and finding a higher-scored suffix.
它通过为每个后缀属性打分,并在遍历属性并找到得分更高的后缀时将后缀固定属性的值提升到非后缀固定属性。
Some efficiencies in this version, including removing dependence on JSON to deep-copy, and only recursing into objects that survive the scoring round at their depth:
这个版本的一些效率,包括消除对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
2
If you're using Brunch, the plugin Constangular helps you to manage variables for different environments.
如果您正在使用Brunch,插件constangle可以帮助您管理不同环境的变量。
#10
-8
Have you seen this question and its answer?
你看到这个问题和它的答案了吗?
You can set a globally valid value for you app like this:
您可以为您的应用程序设置一个全局有效值,如下所示:
app.value('key', 'value');
and then use it in your services. You could move this code to a config.js file and execute it on page load or another convenient moment.
然后在你的服务中使用它。您可以将此代码移动到配置中。js文件并在页面加载或其他方便的时刻执行。
#1
208
I'm a little late to the thread, but if you're using Grunt I've had great success with grunt-ng-constant
.
我有点晚了,但是如果你用咕哝的话,我在垃圾邮件-ng-constant中取得了巨大的成功。
The config section for ngconstant
in my Gruntfile.js
looks like
我的Gruntfile中ngconstant的配置部分。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'
}
}
}
The tasks that use ngconstant
look like
使用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'
]);
So running grunt server
will generate a config.js
file in app/scripts/
that looks like
因此运行grunt服务器将生成一个配置。在app/script /中显示的js文件
"use strict";
angular.module("config", []).constant("ENV", "development");
Finally, I declare the dependency on whatever modules need it:
最后,我声明对任何模块的依赖:
// the 'config' dependency is generated via grunt
var app = angular.module('myApp', [ 'config' ]);
Now my constants can be dependency injected where needed. E.g.,
现在我的常量可以在需要时注入依赖项。例如,
app.controller('MyController', ['ENV', function( ENV ) {
if( ENV === 'production' ) {
...
}
}]);
#2
75
One cool solution might be separating all environment-specific values into some separate angular module, that all other modules depend on:
一个很酷的解决方案可能是将所有特定于环境的值分离成一些单独的角模块,所有其他模块都依赖于这些角模块:
angular.module('configuration', [])
.constant('API_END_POINT','123456')
.constant('HOST','localhost');
Then your modules that need those entries can declare a dependency on it:
然后需要这些条目的模块可以声明对它的依赖:
angular.module('services',['configuration'])
.factory('User',['$resource','API_END_POINT'],function($resource,API_END_POINT){
return $resource(API_END_POINT + 'user');
});
Now you could think about further cool stuff:
现在你可以考虑一些更酷的东西:
The module, that contains the configuration can be separated into configuration.js, that will be included at your page.
包含配置的模块可以分为配置。js,这将包含在您的页面中。
This script can be easily edited by each of you, as long as you don’t check this separate file into git. But it's easier to not check in the configuration if it is in a separate file. Also, you could branch it locally.
这个脚本可以很容易地由每个人编辑,只要您不将这个单独的文件检查到git中。但如果配置在单独的文件中,则更容易不检入。此外,您还可以在本地对其进行分支。
Now, if you have a build-system, like ANT or Maven, your further steps could be implementing some placeholders for the values API_END_POINT, that will be replaced during build-time, with your specific values.
现在,如果您有一个构建系统,如ANT或Maven,您的进一步步骤可能是为API_END_POINT值实现一些占位符,这些占位符将在构建时用特定的值替换。
Or you have your configuration_a.js
and configuration_b.js
and decide at the backend which to include.
或者你有一个configuration_a。js和configuration_b。然后在后端决定包含哪个。
#3
30
For Gulp users, gulp-ng-constant is also useful combined with gulp-concat, event-stream and yargs.
对于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() { });
});
In my config folder I have these files:
在我的配置文件夹中,我有这些文件:
ls -l config
total 8
-rw-r--r--+ 1 .. ci.json
-rw-r--r--+ 1 .. development.json
-rw-r--r--+ 1 .. production.json
Then you can run gulp config --env development
and that will create something like this:
然后你可以运行gulp config -env开发,这会创建如下内容:
angular.module("app.config", [])
.constant("foo", "bar")
.constant("ngConstant", true);
I also have this spec:
我也有这个规格:
beforeEach(module('app'));
it('loads the config', inject(function(config) {
expect(config).toBeTruthy();
}));
#4
16
To achieve that, I suggest you to use AngularJS Environment Plugin: https://www.npmjs.com/package/angular-environment
为此,我建议您使用AngularJS环境插件:https://www.npmjs.com/package/angular-environment
Here's an example:
这里有一个例子:
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();
});
And then, you can call the variables from your controllers such as this:
然后,你可以从控制器中调用变量,比如:
envService.read('apiUrl');
Hope it helps.
希望它可以帮助。
#5
13
You could use lvh.me:9000
to access your AngularJS app, (lvh.me
just points to 127.0.0.1) and then specify a different endpoint if lvh.me
is the host:
您可以使用lvh。我:9000来访问你的AngularJS应用(lvh)。我只指向127.0.0.1),然后在lvh中指定一个不同的端点。我是主持人:
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/';
}
});
And then inject the Configuration service and use Configuration.API
wherever you need to access the API:
然后注入配置服务并使用配置。在任何需要访问该API的地方:
$resource(Configuration.API + '/endpoint/:id', {
id: '@id'
});
A tad clunky, but works fine for me, albeit in a slightly different situation (API endpoints differ in production and development).
虽然有点笨拙,但对我来说还不错,尽管情况略有不同(API端点在生产和开发上有所不同)。
#6
5
Good question!
好问题!
One solution could be to continue using your config.xml file, and provide api endpoint information from the backend to your generated html, like this (example in php):
一种解决方案是继续使用配置。xml文件,并将api端点信息从后端提供给生成的html,比如这个(php中的例子):
<script type="text/javascript">
angular.module('YourApp').constant('API_END_POINT', '<?php echo $apiEndPointFromBackend; ?>');
</script>
Maybe not a pretty solution, but it would work.
也许这不是一个很好的解决方案,但它会起作用。
Another solution could be to keep the API_END_POINT
constant value as it should be in production, and only modify your hosts-file to point that url to your local api instead.
另一种解决方案是将API_END_POINT常量值保持在生产环境中,并且只修改您的主机文件以将该url指向您的本地api。
Or maybe a solution using localStorage
for overrides, like this:
或者使用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
5
We could also do something like this.
我们也可以这样做。
(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];
}
}
});
})();
And in your controller/service
, we can inject the dependency and call the get method with property to be accessed.
在您的控制器/服务中,我们可以注入依赖项并调用具有要访问的属性的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')
would return the url based on the host environment.
$http.get(env.get('apiroot'))将基于主机环境返回url。
#8
3
Very late to the thread, but a technique I've used, pre-Angular, is to take advantage of JSON and the flexibility of JS to dynamically reference collection keys, and use inalienable facts of the environment (host server name, current browser language, etc.) as inputs to selectively discriminate/prefer suffixed key names within a JSON data structure.
很晚的线程,但我使用的技巧,pre-Angular,是利用JSON和JS的灵活性动态参考收集钥匙,和使用环境的不可剥夺的事实(主机服务器名称、当前浏览器语言等)作为输入选择性歧视/喜欢JSON数据结构中关键的名字作为后缀。
This provides not merely deploy-environment context (per OP) but any arbitrary context (such as language) to provide i18n or any other variance required simultaneously, and (ideally) within a single configuration manifest, without duplication, and readably obvious.
这不仅提供了部署环境上下文(每个OP),还提供了任何任意上下文(如语言),以同时提供i18n或任何其他所需的差异,并且(理想情况下)在一个配置清单中(没有重复,而且可读性明显)。
IN ABOUT 10 LINES VANILLA JS
大概有10行
Overly-simplified but classic example: An API endpoint base URL in a JSON-formatted properties file that varies per environment where (natch) the host server will also vary:
过度简化但经典的示例:json格式的属性文件中的API端点基本URL,每个环境(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/'
},
...
A key to the discrimination function is simply the server hostname in the request.
识别函数的关键是请求中的服务器主机名。
This, naturally, can be combined with an additional key based on the user's language settings:
当然,这可以根据用户的语言设置与附加的键结合:
...
'app': {
'NAME': 'Ferry Reservations',
'NAME@fr': 'Réservations de ferry',
'NAME@de': 'Fähren Reservierungen'
},
...
The scope of the discrimination/preference can be confined to individual keys (as above) where the "base" key is only overwritten if there's a matching key+suffix for the inputs to the function -- or an entire structure, and that structure itself recursively parsed for matching discrimination/preference suffixes:
识别/偏好的范围可以局限于单个键(如上所述),只有当函数的输入有匹配的键+后缀时,“base”键才被覆盖,或者是整个结构,该结构本身被递归解析以匹配识别/偏好后缀:
'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, if a visiting user to the production website has German (de) language preference setting, the above configuration would collapse to:
因此,如果访问产品网站的用户具有德语(de)语言首选项设置,则上述配置将崩溃为:
'help': {
'BLURB': 'Bitte kontaktieren Sie unseren Kundendienst!!1!',
'PHONE': '1-800-CUS-TOMR',
'EMAIL': 'customer.service@productionwebsite.com'
},
What does such a magical preference/discrimination JSON-rewriting function look like? Not much:
如此神奇的偏好/歧视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);
};
};
In our implementations, which include Angular and pre-Angular websites, we simply bootstrap the configuration well ahead of other resource calls by placing the JSON within a self-executing JS closure, including the prefer() function, and fed basic properties of hostname and language-code (and accepts any additional arbitrary suffixes you might need):
在我们的实现(包括有角的和有角的网站)中,我们只需将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]) ] );
A pre-Angular site would now have a collapsed (no @ suffixed keys) window.app_props to refer to.
预角点现在将有一个折叠(无@后缀键)窗口。app_props引用。
An Angular site, as a bootstrap/init step, simply copies the dead-dropped props object into $rootScope, and (optionally) destroys it from global/window scope
作为引导/初始化步骤,一个有棱角的站点只需要将死去的道具对象复制到$rootScope中,然后(可选地)从全局/窗口作用域中销毁它
app.constant('props',angular.copy(window.app_props || {})).run( function ($rootScope,props) { $rootScope.props = props; delete window.app_props;} );
to be subsequently injected into controllers:
随后注入控制器:
app.controller('CtrlApp',function($log,props){ ... } );
or referred to from bindings in views:
或从视图中的绑定中引用:
<span>{{ props.help.blurb }} {{ props.help.email }}</span>
Caveats? The @ character is not valid JS/JSON variable/key naming, but so far accepted. If that's a deal-breaker, substitute for any convention you like, such as "__" (double underscore) as long as you stick to it.
警告?@字符不是有效的JS/JSON变量/键名,但到目前为止被接受。如果这是一种破坏交易的行为,那就用“__”(双下划线)来代替你喜欢的任何约定,只要你坚持下去。
The technique could be applied server-side, ported to Java or C# but your efficiency/compactness may vary.
该技术可以应用于服务器端,移植到Java或c#,但是您的效率/紧凑性可能有所不同。
Alternately, the function/convention could be part of your front-end compile script, so that the full gory all-environment/all-language JSON is never transmitted over the wire.
或者,函数/约定可以是前端编译脚本的一部分,这样就不会通过网络传输完整的gory全环境/全语言JSON。
UPDATE
更新
We've evolved usage of this technique to allow multiple suffixes to a key, to avoid being forced to use collections (you still can, as deeply as you want), and as well to honor the order of the preferred suffixes.
我们已经发展了这种技术的用法,允许对一个键使用多个后缀,以避免*使用集合(您仍然可以随心所欲地使用集合),并尊重首选后缀的顺序。
Example (also see working jsFiddle):
示例(也参见工作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 (basic usage) prefers '@dev' keys, discards all other suffixed keys
1/2(基本用法)喜欢'@dev'键,丢弃所有其他后缀的键
3 prefers '@dev' over '@fr', prefers '@dev&fr' over all others
3更喜欢“@dev”而不是“@fr”,更喜欢“@dev&fr”。
4 (same as 3 but prefers '@fr' over '@dev')
4(与3相同,但更喜欢“@fr”而不是“@dev”)
5 no preferred suffixes, drops ALL suffixed properties
没有首选后缀,删除所有后缀属性
It accomplishes this by scoring each suffixed property and promoting the value of a suffixed property to the non-suffixed property when iterating over the properties and finding a higher-scored suffix.
它通过为每个后缀属性打分,并在遍历属性并找到得分更高的后缀时将后缀固定属性的值提升到非后缀固定属性。
Some efficiencies in this version, including removing dependence on JSON to deep-copy, and only recursing into objects that survive the scoring round at their depth:
这个版本的一些效率,包括消除对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
2
If you're using Brunch, the plugin Constangular helps you to manage variables for different environments.
如果您正在使用Brunch,插件constangle可以帮助您管理不同环境的变量。
#10
-8
Have you seen this question and its answer?
你看到这个问题和它的答案了吗?
You can set a globally valid value for you app like this:
您可以为您的应用程序设置一个全局有效值,如下所示:
app.value('key', 'value');
and then use it in your services. You could move this code to a config.js file and execute it on page load or another convenient moment.
然后在你的服务中使用它。您可以将此代码移动到配置中。js文件并在页面加载或其他方便的时刻执行。