Angular.jsでさまざまな環境を構成するにはどうすればよいですか?


220

さまざまな環境の構成変数/定数をどのように管理しますか?

これは例であるかもしれません:

私の残りのAPIはで到達可能ですがlocalhost:7080/myapi/、Gitバージョン管理下で同じコードで作業している私の友人は、彼のTomcatにAPIをデプロイしています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'as で置く場合、gruntfileの開発変数を更新するときtasks: ['ngconstant:development']に再起動grunt serveする必要はありません。
2014年

10
代わりにgruntfile.jsであなたの定数を追加するのは、このような個別のファイルに入れることができます:package: grunt.file.readJSON('development.json')
ギレムSoulas

3
grunt-ng-constantの0.5バージョンにGruntfile.jsの更新された構文があります:github.com/werk85/grunt-ng-constant/issues/31。ありがとうございます。
フェリス14

10
gulpを使用する人のために、gulp-ng-constantがあります。
Dheeraj Vepakomma 14年

4
次のように、scripts / config.jsファイルを角度を付けてモジュールを検索するために含めることも必要です。<script src = "scripts / config.js"> </ script>
Toni Gamez

75

1つの優れたソリューションは、環境固有のすべての値をいくつかの個別の角度モジュールに分離し、他のすべてのモジュールがそれに依存することです。

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.jsandがconfiguration_b.jsあり、含めるバックエンドで決定します。


30

ガルプのユーザー、ゴクゴク-NG-定数と組み合わせることも有用であるゴクゴク化連結イベント・ストリーム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")として必要です。不変定数にはオプションがありませんが、不変定数パッケージではdeps:falseを渡すことができます。何か助けは?
アルンゴパルプリ2015年

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はモニカを2015

こんにちはファンパブロ、またはあなたがそれを理解した場合は@モーグ。SOについて質問する前に、Githubで問題を提起します。どのようにangular-environment環境を検出しますか?つまり、ローカルマシン/ Webサーバーでそれぞれdev / prodであることを認識させるために何をする必要がありますか?
StevieP 2016年

ドキュメントをもう一度読んで... " envServiceProvider.check()...は、指定されたドメインに基づいて適切な環境を自動的に設定します"。だから私はそれが現在のドメインを検出して環境を適切に設定していると思います-それをテストする時間です!
StevieP 2016年

13

を使用lvh.me:9000してAngularJSアプリにアクセスし(lvh.me127.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 2014

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

良い質問!

1つの解決策は、config.xmlファイルを引き続き使用し、次のように、バックエンドから生成されたhtmlにAPIエンドポイント情報を提供することです(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でポイントを説明するために使用するソリューションを書きました。私たちは純粋なRESTful Javaバックエンドで純粋なJavaScriptクライアントをコーディングしようとしているので、php / jsの混合は私のケースではありません。また、phpで書くときは常にphpとjsを混合しないようにします。しかし、答えてくれてありがとう。@kfis回答ソリューションはうまくいくと思います:構成モジュールを含むバージョン管理下にないconfiguration.jsファイル。このアプローチでは、必要に応じて、テスト目的で別の構成モジュールも挿入/ロードできます。みんなありがとう。
rbarilani 2013年

@ hal9087混合言語の部分については完全に同意します。絶対に避けてください:) configuration.jsソリューションも好きです。同様のものが必要なときは覚えておきます。
joakimbeng 2013年

4

スレッドに非常に遅れていますが、私が以前に使用した手法(Angularより前)は、JSONとJSの柔軟性を利用して動的にコレクションキーを参照し、環境の不可解な事実(ホストサーバー名、現在のブラウザー言語)を使用することですなど)。JSONデータ構造内の接尾辞付きのキー名を選択的に区別/優先するための入力として。

これにより、(OPごとに)単に展開環境コンテキストが提供されるだけでなく、i18nまたは(理想的には)単一の構成マニフェスト内で重複する必要のない、同時に必要なi18nまたはその他の差異を提供するための任意のコンテキスト(言語など)が提供されます。

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

識別機能の鍵となるのは、リクエスト内のサーバーのホスト名だけです。

これは当然、ユーザーの言語設定に基づいて追加のキーと組み合わせることができます。

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

したがって、本番Webサイトにアクセスするユーザーがドイツ語(de)言語設定を持っている場合、上記の構成は次のように折りたたまれます。

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

そのような不思議な設定/識別JS​​ON書き換え関数はどのように見えますか?あまりない:

// 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およびPre-Angular Webサイトを含む私たちの実装では、prefer()関数を含む自己実行JSクロージャ内にJSONを配置し、ホスト名と言語コード(必要に応じて追加の任意のサフィックスを受け入れます):

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

Angularより前のサイトには、参照するために折りたたまれた(@サフィックスの付いたキーがない)window.app_propsがあります。

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がネットワーク経由で送信されることはありません。

更新

この手法の使用法を進化させて、キーに複数のサフィックスを使用できるようにし、コレクションの使用を強制されないようにして(必要に応じてさらに深く)、優先されるサフィックスの順序を尊重できるようにしました。

例(動作している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と同じですが、「@ 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());
}


-8

この質問を見たことがありますとその答えましたか?

次のように、アプリにグローバルに有効な値を設定できます。

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

そして、それをサービスで使用します。このコードをconfig.jsファイルに移動して、ページの読み込み時または別の都合のよいときに実行できます。


7
なぜこれがそんなに悪い答えなのか誰かが説明してくれませんか?それは大幅に反対投票されましたが、単一のコメントではありません...
aendrew

5
これは地獄のように古いですが、ダウン投票の理由を推測する必要があった場合、それは環境固有の構成の問題に対処していないためです。古い値のアプリで.value()を使用してグローバル値を設定することをお勧めします。envまたは元の質問パラメーターに沿った何かに応じて、これをどのように使用するかについては言及されていません。
coblr 2014
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.