異なる環境のための設定変数/定数をどのように管理しますか?
これは一例です。
私の残りの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');
});
環境に応じて、APIエンドポイントの正しい値を動的に挿入する方法を教えてください。
PHPでは、私は通常config.username.xml
ファイルを使ってこの種のことを行い、基本設定ファイル(config.xml)とユーザの名前で認識されるローカル環境設定ファイルをマージします。しかし、JavaScriptでこのようなことを管理する方法がわかりませんか?
スレッドには少し遅れていますが、 Grunt を使用している場合は grunt-ng-constant
で大成功を収めています。
私のGruntfile.js
のngconstant
のconfigセクションは以下のようになります。
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' ) {
...
}
}]);
1つの優れた解決策は、すべての環境固有の値をいくつかの個別の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に分けることができます。
この個別のファイルをgitにチェックインしない限り、このスクリプトは各自が簡単に編集できます。しかし、別のファイルにある場合は設定をチェックインしない方が簡単です。また、ローカルに分岐することもできます。
さて、ANTやMavenのようなビルドシステムがあるなら、あなたのさらなるステップは値API_END_POINTのためのプレースホルダーを実装することでしょう。そしてそれはあなたの特定の値で、ビルド時に置き換えられます。
あるいは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() { });
});
私の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();
}));
そのためには、AngularJS Environment Pluginを使用することをお勧めします。 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');
それが役に立てば幸い。
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にアクセスする必要がある場合はいつでもConfiguration.API
を使用します。
$resource(Configuration.API + '/endpoint/:id', {
id: '@id'
});
少し不格好ですが、状況は少し異なりますが、私にとってはうまく機能します(APIエンドポイントは本番と開発で異なります)。
私たちもこのようなことをすることができました。
(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
の中で、依存関係を注入し、アクセスされるべきpropertyと共に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を返します。
良い質問!
1つの解決策は、config.xmlファイルを使い続け、このようにバックエンドから生成されたHTMLにAPIエンドポイント情報を提供することです(phpの例)。
<script type="text/javascript">
angular.module('YourApp').constant('API_END_POINT', '<?php echo $apiEndPointFromBackend; ?>');
</script>
たぶんきれいな解決策ではないかもしれませんが、それはうまくいくでしょう。
もう1つの解決策は、本番環境ではAPI_END_POINT
定数値を維持し、そのURLをローカルAPIに変更するようにhostsファイルを変更することです。
あるいは、次のように、オーバーライドにlocalStorage
を使用する解決策もあります。
.factory('User',['$resource','API_END_POINT'],function($resource,API_END_POINT){
var myApi = localStorage.get('myLocalApiOverride');
return $resource((myApi || API_END_POINT) + 'user');
});
スレッドには非常に遅くなりましたが、以前のAngular以前に使用した手法は、JSONとJSの柔軟性を利用してコレクションキーを動的に参照し、不可抗力な環境の事実(ホストサーバー名、現在のブラウザ言語)を使用することです。 JSONデータ構造内の接尾辞付きキー名を選択的に識別/選択するための入力として。
これは、展開環境のコンテキスト(OPごと)だけでなく、国際化や同時に(理想的には)単一の構成マニフェスト内で必要な任意のコンテキスト(言語など)を複製することなく提供します。
10行についてバニラJS
非常に単純化された、しかし典型的な例:JSON形式のプロパティーファイル内のAPIエンドポイントのベースURL。ホストサーバーも(ナッチする)環境によって異なります。
...
'svcs': {
'VER': '2.3',
'API@localhost': 'http://localhost:9090/',
'[email protected]': 'https://www.uat.productionwebsite.com:9090/res/',
'[email protected]': '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': '[email protected]'
},
'[email protected]': {
'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': '[email protected]'
},
つまり、本番Webサイトにアクセスするユーザーにドイツ語(de)の言語設定がある場合、上記の設定は次のように折りたたまれます。
'help': {
'BLURB': 'Bitte kontaktieren Sie unseren Kundendienst!!1!',
'PHONE': '1-800-CUS-TOMR',
'EMAIL': '[email protected]'
},
このような不思議な好み/差別の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);
};
};
AngularやAngular以前のWebサイトなどの実装では、prefer()関数を含むJSONを自己実行型JSクロージャ内に配置することで、他のリソース呼び出しよりもはるかに先に設定をブートストラップします。ホスト名と言語コードの基本的な特性を与えます(そしてあなたが必要とするかもしれない追加の任意の接尾辞を受け入れます):
(function(prefs){ var props = {
'svcs': {
'VER': '2.3',
'API@localhost': 'http://localhost:9090/',
'[email protected]': 'https://www.uat.productionwebsite.com:9090/res/',
'[email protected]': '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]) ] );
Angular以前のサイトは、折りたたまれた(@接尾辞のないキー)window.app_propsを参照するようになりました。
ブートストラップ/ initステップとしてのAngularサイトは、単にドロップされた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/JSON変数/キーの命名ではありませんが、これまでのところ受け入れられています。それが対処法違反の場合は、固執する限り「__」(二重下線)などの好きな規則に置き換えてください。
このテクニックはサーバーサイドに適用することも、JavaやC#に移植することもできますが、効率やコンパクトさは異なるかもしれません。
あるいは、関数/規約がフロントエンドのコンパイルスクリプトの一部になっている可能性があります。そのため、全環境/全言語の全JSON JSONがネットワーク経由で送信されることはありません。
UPDATE
私たちはこのテクニックの使用法を進化させて、複数の接尾辞をキーに許し、コレクションを使わざるを得ないようにし(そしてあなたが望む限り深くすることができます)、そして好ましい接尾辞の順序を守ります。
例(working 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優先サフィックスなし、すべてのサフィックス付きプロパティを削除します
これは、各接尾辞付きプロパティをスコア付けし、プロパティを反復処理してより高いスコアの接尾辞を見つけるときに、接尾辞付きプロパティの値を非接尾辞付きプロパティに昇格させることによって実現されます。
このバージョンでは、ディープコピーへの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());
}