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


220

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

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

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?

Ответы:


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'как tasks: ['ngconstant:development']- вы не должны перезагрузки grunt serveпри обновлении переменных развития в gruntfile.
14:30

10
Вместо того, чтобы добавлять свои константы в gruntfile.js, вы можете поместить их в отдельные файлы, например так:package: grunt.file.readJSON('development.json')
Гильем Соулас

3
В версии 0.5 grunt-ng-constant обновлен синтаксис для Gruntfile.js: github.com/werk85/grunt-ng-constant/issues/31 . Отличный ответ, спасибо!
pherris

10
Для тех, кто использует gulp, есть gulp-ng-constant .
Джерадж Вепакомма

4
Я обнаружил, что также необходимо включить файл scripts / config.js в angular, чтобы найти модуль, например: <script src = "scripts / config.js"> </ script>
Toni Gamez

75

Одним из классных решений может быть разделение всех специфических для среды значений на некоторый отдельный угловой модуль, от которого зависят все остальные модули:

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и решить на бэкэнде, что включать.


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

Есть ли способ удалить массив зависимостей с константой gulp ng? У меня нет никакой зависимости от моих констант, как у вас в этом, например, "ngAnimate". Если я не включаю его, я получаю пустой массив зависимостей как angular.module ("my.module.config", []), но я хочу выводить как angular.module ("my.module.config"). Я не вижу никакой опции в константе gulp ng, но вижу, что вы можете передать deps: false в пакет константы grunt ng. Любая помощь?
Арун Гопалпури

17

Для этого я предлагаю вам использовать плагин AngularJS Environment: 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 говорит восстановить Monica

Привет Хуан Пабло, или @Mawg, если ты понял это. Прежде чем я задам вопрос о SO / подниму вопрос о Github; как angular-environmentобнаружить окружающую среду? т.е. что вам нужно сделать на вашем локальном компьютере / веб-сервере, чтобы он знал, что это соответственно dev / prod?
StevieP

Повторное чтение документов ... " envServiceProvider.check()... автоматически установит соответствующую среду на основе заданных доменов". Поэтому я считаю, что он обнаруживает текущий домен и соответствующим образом устанавливает среду - время проверить это!
StevieP

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было более чем достаточно для меня.
Джозейм

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

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

Одним из решений может быть продолжение использования файла 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');
});

Привет joakimbeng, я написал решение, которое я использую в php, чтобы объяснить суть. Мы пытаемся кодировать чистый клиент javascript с чистым Java-бэкэндом RESTful, поэтому сочетание php / js это не мой случай, и когда я пишу на php, я всегда стараюсь не смешивать php и js. но спасибо за ответ. Я думаю, что решение для ответа @kfis может сработать: файл configuration.js не находится под управлением версией и содержит модуль конфигурации. При таком подходе я могу ввести / загрузить также другой модуль конфигурации для тестирования, если это необходимо. Спасибо, парни.
rbarilani

@ hal9087 Я полностью согласен с языком смешивания, его следует избегать любой ценой :) Мне также нравится решение configuration.js, я буду помнить его, когда мне понадобится нечто подобное!
Joakimbeng

4

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

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

Около 10 строк VANILLA JS

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

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

Таким образом, если посетитель рабочего сайта имеет настройку предпочтений немецкого ( де ) языка, приведенная выше конфигурация может привести к:

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

Как выглядит такая магическая функция JSON-rewriting? Немного:

// 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, мы просто загружаем конфигурацию задолго до других вызовов ресурсов, помещая JSON в самоисполняющееся JS-закрытие, включая функцию предпочитаемое имя (), и снабжаем базовыми свойствами hostname и language-code (и принимает любые дополнительные произвольные суффиксы, которые могут вам понадобиться):

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

Предварительно англоязычный сайт теперь будет иметь свернутый (без @ суффиксированных ключей) 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, но предпочитает @fr вместо @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());
}


-8

Вы видели этот вопрос и его ответ?

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

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

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


7
Может кто-нибудь объяснить, почему это такой плохой ответ? Это было массово опровергнуто, но ни одного комментария ...
Эндрю

5
Это чертовски старо, но, если бы мне пришлось угадывать, почему это отрицательные голоса, это потому, что они не решают проблему конфигураций, специфичных для среды, а просто предлагают использовать .value () для установки глобального значения в любом старом приложении. Там нет упоминания о том, как можно использовать это в зависимости от env или чего-либо в исходных параметрах вопросов.
coblr
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.