Как настроить разные среды в Angular.js?

213

Как вы управляете конфигурационными переменными/константами для разных сред?

Это может быть пример:

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?

Теги:
angularjs-service

10 ответов

196
Лучший ответ

Я немного опоздал на поток, но если вы используете 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' ) {
    ...
  }
}]);
  • 0
    Это восхитительно!
  • 10
    Вместо того, чтобы помещать 'ngconstant:development' в 'serve' - если вы помещаете его в конфигурацию часов в 'gruntfile' качестве tasks: ['ngconstant:development'] - вам не нужно перезапускать grunt serve при обновлении переменных разработки в gruntfile.
Показать ещё 11 комментариев
78

Одним из замечательных решений может быть разделение всех значений, относящихся к среде, на отдельный модуль 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, и вы решите включить в него бэкэнд.

30

Для пользователей 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();
}));
  • 0
    Есть ли способ удалить массив зависимостей с константой gulp ng? У меня нет никакой зависимости от моих констант, как у вас в этом, например, "ngAnimate". Если я не включаю его, я получаю пустой массив зависимостей как angular.module ("my.module.config", []), но я хочу выводить как angular.module ("my.module.config"). Я не вижу никакой опции в константе gulp ng, но вижу, что вы можете передать deps: false в пакет константы grunt ng. Любая помощь?
  • 0
    Я просто опустил это. Это было из примера в документах.
16

Чтобы достичь этого, я предлагаю вам использовать плагин окружения 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
    как он переключается между разработкой и производством?
  • 0
    Привет, Хуан Пабло, или @Mawg, если ты это понял. Прежде чем я задам вопрос о SO / поднять вопрос о Github; как angular-environment обнаруживает окружающую среду? т.е. что вам нужно сделать на вашем локальном компьютере / веб-сервере, чтобы он знал, что это соответственно dev / prod?
Показать ещё 1 комментарий
13

Вы можете использовать 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 отличаются производством и развитием).

  • 1
    поэтому я часто думаю, что люди слишком усложняют вещи. Простое использование window.location.host было более чем достаточно для меня.
5

Мы могли бы также сделать что-то подобное.

(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

Хороший вопрос!

Одним из решений может быть продолжение использования вашего файла 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');
});
  • 0
    Привет joakimbeng, я написал решение, которое я использую в php, чтобы объяснить суть. Мы пытаемся закодировать чистый клиент javascript с чистым Java-бэкэндом RESTful, поэтому сочетание php / js не в моем случае, а также, когда я пишу на php, я всегда стараюсь не смешивать php и js. но спасибо за ответ. Я думаю, что решение для ответа @kfis может сработать: файл configuration.js не находится под управлением версией и содержит модуль конфигурации. При таком подходе я могу ввести / загрузить также другой модуль конфигурации для тестирования, если это необходимо. Спасибо, парни.
  • 0
    @ hal9087 Я полностью согласен с языком смешивания, его следует избегать любой ценой :) Мне также нравится решение configuration.js, я буду помнить его, когда мне понадобится нечто подобное!
3

Очень поздно для потока, но метод, который я использовал, pre- Angular, должен использовать JSON и гибкость JS для динамического сравнения ключей коллекции и использования неотъемлемых фактов среды (хост-сервер имя, текущий язык браузера и т.д.) в качестве входных данных для выборочного распознавания/предпочтения имен суффикса ключей в структуре данных JSON.

Это обеспечивает не просто контекст среды развертывания (для каждого OP), а любой произвольный контекст (например, язык) для предоставления i18n или любой другой дисперсии, требуемой одновременно, и (в идеале) в рамках одного манифеста конфигурации без дублирования и с очевидной очевидностью.

В ДО 10 ЛИНИЙ VANILLA JS

Слишком упрощенный, но классический пример: URL-адрес конечной точки API в файле свойств в формате JSON, который изменяется для каждой среды, где (natch) также будет изменяться сервер:

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

SO, если посетитель на веб-сайте производства имеет настройку предпочтения языка (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 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/',
        '[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])  ] );

У сайта 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());
}
2

Если вы используете Brunch, плагин Constangular помогает вам управлять переменными для разных сред.

-7

Вы видели этот question и его ответ?

Вы можете установить глобально допустимое значение для вашего приложения следующим образом:

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

а затем использовать его в своих службах. Вы можете переместить этот код в файл config.js и выполнить его при загрузке страницы или в другой удобный момент.

  • 6
    Может кто-нибудь объяснить, почему это такой плохой ответ? Это было массово опровергнуто, но ни одного комментария ...
  • 5
    Это чертовски старо, но, если бы мне пришлось угадывать, почему отрицательные голоса, это потому, что они не решают проблему конфигураций, специфичных для среды, а просто предлагают использовать .value () для установки глобального значения в любом старом приложении. Нет никакого упоминания о том, как можно использовать это в зависимости от env или чего-либо в исходных параметрах вопросов.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню