diff --git a/demo/scripts/directives.js b/demo/scripts/directives.js index c7fe4cf..2c23c55 100644 --- a/demo/scripts/directives.js +++ b/demo/scripts/directives.js @@ -31,6 +31,7 @@ angular.module('my-app') replace: true }; }) - .run(function(featureFlags, $http) { - featureFlags.set($http.get('../data/flags.json')); + .run(function($http, featureFlagConfig) { + featureFlagConfig.setAppName('myapp'); + featureFlagConfig.setInitialFlags($http.get('../data/flags.json')); }); diff --git a/dist/featureFlags.js b/dist/featureFlags.js index a9b479a..bb1f173 100644 --- a/dist/featureFlags.js +++ b/dist/featureFlags.js @@ -76,10 +76,8 @@ angular.module('feature-flags').directive('featureFlagOverrides', ['featureFlags }; }]); -angular.module('feature-flags').service('featureFlagOverrides', ['$rootElement', function($rootElement) { - var appName = $rootElement.attr('ng-app'), - keyPrefix = 'featureFlags.' + appName + '.', - +angular.module('feature-flags').service('featureFlagOverrides', ['$rootElement', 'featureFlagConfig', function($rootElement, featureFlagConfig) { + var keyPrefix = 'featureFlags.', localStorageAvailable = (function() { try { localStorage.setItem('featureFlags.availableTest', 'test'); @@ -116,6 +114,9 @@ angular.module('feature-flags').service('featureFlagOverrides', ['$rootElement', } }; + if (featureFlagConfig.getAppName().length > 0) { + keyPrefix += featureFlagConfig.getAppName() + '.'; + } return { isPresent: function(key) { var value = get(key); @@ -143,7 +144,7 @@ angular.module('feature-flags').service('featureFlagOverrides', ['$rootElement', }; }]); -function FeatureFlags($q, featureFlagOverrides, initialFlags) { +angular.module('feature-flags').service('featureFlags', ['$q', 'featureFlagOverrides', 'featureFlagConfig', function($q, featureFlagOverrides, featureFlagConfig) { var serverFlagCache = {}, flags = [], @@ -205,8 +206,9 @@ function FeatureFlags($q, featureFlagOverrides, initialFlags) { }, init = function() { - if (initialFlags) { - set(initialFlags); + var iniFlags = featureFlagConfig.getInitialFlags(); + if (iniFlags) { + set(iniFlags); } }; init(); @@ -221,18 +223,56 @@ function FeatureFlags($q, featureFlagOverrides, initialFlags) { isOnByDefault: isOnByDefault, isOverridden: isOverridden }; +}]); + +function FeatureFlagConfig(initialFlags, userAppName) { + var initFlags = [], + appName = '', + + getInitialFlags = function() { + return initFlags; + }, + setInitialFlags = function(value) { + initFlags = value; + }, + getAppName = function() { + return appName; + }, + setAppName = function(value) { + appName = value; + }, + init = function() { + if (initialFlags) { + initFlags = initialFlags; + } + if (userAppName) { + appName = userAppName; + } + }; + init(); + + return { + getInitialFlags: getInitialFlags, + setInitialFlags: setInitialFlags, + getAppName: getAppName, + setAppName: setAppName + }; } -angular.module('feature-flags').provider('featureFlags', function() { - var initialFlags = []; +angular.module('feature-flags').provider('featureFlagConfig', function() { + var initialFlags = [], + appName = ''; this.setInitialFlags = function(flags) { initialFlags = flags; }; - this.$get = ['$q', 'featureFlagOverrides', function($q, featureFlagOverrides) { - return new FeatureFlags($q, featureFlagOverrides, initialFlags); - }]; + this.setAppName = function(name) { + appName = name; + }; + this.$get = function() { + return new FeatureFlagConfig(initialFlags, appName); + }; }); }()); \ No newline at end of file diff --git a/dist/featureFlags.min.js b/dist/featureFlags.min.js index 9b113d4..c25fdd5 100644 --- a/dist/featureFlags.min.js +++ b/dist/featureFlags.min.js @@ -1,2 +1,2 @@ /*! Angular Feature Flags v1.6.1 © 2017 Michael Taranto */ -!function(){function e(e,a,t){var r={},n=[],i=function(a){var t=e.defer();return t.resolve(a),t.promise},l=function(e){return a.isPresent(e)},f=function(e){return l(e)?"true"===a.get(e):r[e]},u=function(e){return r[e]},s=function(e){return e.forEach(function(e){r[e.key]=e.active,e.active=f(e.key)}),angular.copy(e,n),n},c=function(e){return e.then(function(e){return s(e.data||e)})},g=function(){return n},o=function(e){return angular.isArray(e)?i(s(e)):c(e)},d=function(e){e.active=!0,a.set(e.key,!0)},v=function(e){e.active=!1,a.set(e.key,!1)},y=function(e){e.active=r[e.key],a.remove(e.key)},m=function(){t&&o(t)};return m(),{set:o,get:g,enable:d,disable:v,reset:y,isOn:f,isOnByDefault:u,isOverridden:l}}angular.module("feature-flags",[]),angular.module("feature-flags").directive("featureFlag",["featureFlags","$interpolate",function(e,a){return{transclude:"element",priority:599,terminal:!0,restrict:"A",$$tlb:!0,compile:function(t,r){var n="featureFlagHide"in r;return t[0].textContent=" featureFlag: "+r.featureFlag+" is "+(n?"on":"off")+" ",function(t,r,i,l,f){var u,s;t.$watch(function(){var r=a(i.featureFlag)(t);return e.isOn(r)},function(e){var a=n?!e:e;a?(s=t.$new(),f(s,function(e){u=e,r.after(u).remove()})):(s&&(s.$destroy(),s=null),u&&(u.after(r).remove(),u=null))})}}}}]),angular.module("feature-flags").directive("featureFlagOverrides",["featureFlags",function(e){return{restrict:"A",link:function(a){a.flags=e.get(),a.isOn=e.isOn,a.isOverridden=e.isOverridden,a.enable=e.enable,a.disable=e.disable,a.reset=e.reset,a.isOnByDefault=e.isOnByDefault},template:'

Feature Flags

{{flag.name || flag.key}}
ON
OFF
DEFAULT ({{isOnByDefault(flag.key) ? \'ON\' : \'OFF\'}})
{{flag.description}}
',replace:!0}}]),angular.module("feature-flags").service("featureFlagOverrides",["$rootElement",function(e){var a=e.attr("ng-app"),t="featureFlags."+a+".",r=function(){try{return localStorage.setItem("featureFlags.availableTest","test"),localStorage.removeItem("featureFlags.availableTest"),!0}catch(e){return!1}}(),n=function(e){return t+e},i=function(e){return 0===e.indexOf(t)},l=function(e,a){r&&localStorage.setItem(n(a),e)},f=function(e){return r?localStorage.getItem(n(e)):void 0},u=function(e){r&&localStorage.removeItem(n(e))};return{isPresent:function(e){var a=f(e);return"undefined"!=typeof a&&null!==a},get:f,set:function(e,a){angular.isObject(e)?angular.forEach(e,l):l(a,e)},remove:u,reset:function(){var e;if(r)for(e in localStorage)i(e)&&localStorage.removeItem(e)}}}]),angular.module("feature-flags").provider("featureFlags",function(){var a=[];this.setInitialFlags=function(e){a=e},this.$get=["$q","featureFlagOverrides",function(t,r){return new e(t,r,a)}]})}(); \ No newline at end of file +!function(){function e(e,t){var a=[],n="",r=function(){return a},i=function(e){a=e},l=function(){return n},f=function(e){n=e},u=function(){e&&(a=e),t&&(n=t)};return u(),{getInitialFlags:r,setInitialFlags:i,getAppName:l,setAppName:f}}angular.module("feature-flags",[]),angular.module("feature-flags").directive("featureFlag",["featureFlags","$interpolate",function(e,t){return{transclude:"element",priority:599,terminal:!0,restrict:"A",$$tlb:!0,compile:function(a,n){var r="featureFlagHide"in n;return a[0].textContent=" featureFlag: "+n.featureFlag+" is "+(r?"on":"off")+" ",function(a,n,i,l,f){var u,s;a.$watch(function(){var n=t(i.featureFlag)(a);return e.isOn(n)},function(e){var t=r?!e:e;t?(s=a.$new(),f(s,function(e){u=e,n.after(u).remove()})):(s&&(s.$destroy(),s=null),u&&(u.after(n).remove(),u=null))})}}}}]),angular.module("feature-flags").directive("featureFlagOverrides",["featureFlags",function(e){return{restrict:"A",link:function(t){t.flags=e.get(),t.isOn=e.isOn,t.isOverridden=e.isOverridden,t.enable=e.enable,t.disable=e.disable,t.reset=e.reset,t.isOnByDefault=e.isOnByDefault},template:'

Feature Flags

{{flag.name || flag.key}}
ON
OFF
DEFAULT ({{isOnByDefault(flag.key) ? \'ON\' : \'OFF\'}})
{{flag.description}}
',replace:!0}}]),angular.module("feature-flags").service("featureFlagOverrides",["$rootElement","featureFlagConfig",function(e,t){var a="featureFlags.",n=function(){try{return localStorage.setItem("featureFlags.availableTest","test"),localStorage.removeItem("featureFlags.availableTest"),!0}catch(e){return!1}}(),r=function(e){return a+e},i=function(e){return 0===e.indexOf(a)},l=function(e,t){n&&localStorage.setItem(r(t),e)},f=function(e){return n?localStorage.getItem(r(e)):void 0},u=function(e){n&&localStorage.removeItem(r(e))};return t.getAppName().length>0&&(a+=t.getAppName()+"."),{isPresent:function(e){var t=f(e);return"undefined"!=typeof t&&null!==t},get:f,set:function(e,t){angular.isObject(e)?angular.forEach(e,l):l(t,e)},remove:u,reset:function(){var e;if(n)for(e in localStorage)i(e)&&localStorage.removeItem(e)}}}]),angular.module("feature-flags").service("featureFlags",["$q","featureFlagOverrides","featureFlagConfig",function(e,t,a){var n={},r=[],i=function(t){var a=e.defer();return a.resolve(t),a.promise},l=function(e){return t.isPresent(e)},f=function(e){return l(e)?"true"===t.get(e):n[e]},u=function(e){return n[e]},s=function(e){return e.forEach(function(e){n[e.key]=e.active,e.active=f(e.key)}),angular.copy(e,r),r},g=function(e){return e.then(function(e){return s(e.data||e)})},c=function(){return r},o=function(e){return angular.isArray(e)?i(s(e)):g(e)},v=function(e){e.active=!0,t.set(e.key,!0)},d=function(e){e.active=!1,t.set(e.key,!1)},m=function(e){e.active=n[e.key],t.remove(e.key)},F=function(){var e=a.getInitialFlags();e&&o(e)};return F(),{set:o,get:c,enable:v,disable:d,reset:m,isOn:f,isOnByDefault:u,isOverridden:l}}]),angular.module("feature-flags").provider("featureFlagConfig",function(){var t=[],a="";this.setInitialFlags=function(e){t=e},this.setAppName=function(e){a=e},this.$get=function(){return new e(t,a)}})}(); \ No newline at end of file diff --git a/src/featureFlagOverrides.service.js b/src/featureFlagOverrides.service.js index 14b8f96..1601adf 100644 --- a/src/featureFlagOverrides.service.js +++ b/src/featureFlagOverrides.service.js @@ -1,7 +1,5 @@ -angular.module('feature-flags').service('featureFlagOverrides', function($rootElement) { - var appName = $rootElement.attr('ng-app'), - keyPrefix = 'featureFlags.' + appName + '.', - +angular.module('feature-flags').service('featureFlagOverrides', function($rootElement, featureFlagConfig) { + var keyPrefix = 'featureFlags.', localStorageAvailable = (function() { try { localStorage.setItem('featureFlags.availableTest', 'test'); @@ -38,6 +36,9 @@ angular.module('feature-flags').service('featureFlagOverrides', function($rootEl } }; + if (featureFlagConfig.getAppName().length > 0) { + keyPrefix += featureFlagConfig.getAppName() + '.'; + } return { isPresent: function(key) { var value = get(key); diff --git a/src/featureFlags.provider.js b/src/featureFlags.service.js similarity index 80% rename from src/featureFlags.provider.js rename to src/featureFlags.service.js index db7ac99..50acaf1 100644 --- a/src/featureFlags.provider.js +++ b/src/featureFlags.service.js @@ -1,4 +1,4 @@ -function FeatureFlags($q, featureFlagOverrides, initialFlags) { +angular.module('feature-flags').service('featureFlags', function($q, featureFlagOverrides, featureFlagConfig) { var serverFlagCache = {}, flags = [], @@ -60,8 +60,9 @@ function FeatureFlags($q, featureFlagOverrides, initialFlags) { }, init = function() { - if (initialFlags) { - set(initialFlags); + var iniFlags = featureFlagConfig.getInitialFlags(); + if (iniFlags) { + set(iniFlags); } }; init(); @@ -76,16 +77,4 @@ function FeatureFlags($q, featureFlagOverrides, initialFlags) { isOnByDefault: isOnByDefault, isOverridden: isOverridden }; -} - -angular.module('feature-flags').provider('featureFlags', function() { - var initialFlags = []; - - this.setInitialFlags = function(flags) { - initialFlags = flags; - }; - - this.$get = function($q, featureFlagOverrides) { - return new FeatureFlags($q, featureFlagOverrides, initialFlags); - }; }); diff --git a/src/featureFlagsConfig.provider.js b/src/featureFlagsConfig.provider.js new file mode 100644 index 0000000..8cbaedd --- /dev/null +++ b/src/featureFlagsConfig.provider.js @@ -0,0 +1,49 @@ +function FeatureFlagConfig(initialFlags, userAppName) { + var initFlags = [], + appName = '', + + getInitialFlags = function() { + return initFlags; + }, + setInitialFlags = function(value) { + initFlags = value; + }, + getAppName = function() { + return appName; + }, + setAppName = function(value) { + appName = value; + }, + init = function() { + if (initialFlags) { + initFlags = initialFlags; + } + if (userAppName) { + appName = userAppName; + } + }; + init(); + + return { + getInitialFlags: getInitialFlags, + setInitialFlags: setInitialFlags, + getAppName: getAppName, + setAppName: setAppName + }; +} + +angular.module('feature-flags').provider('featureFlagConfig', function() { + var initialFlags = [], + appName = ''; + + this.setInitialFlags = function(flags) { + initialFlags = flags; + }; + + this.setAppName = function(name) { + appName = name; + }; + this.$get = function() { + return new FeatureFlagConfig(initialFlags, appName); + }; +}); diff --git a/test/featureFlagOverrides.service.spec.js b/test/featureFlagOverrides.service.spec.js index 6291d26..f744c24 100644 --- a/test/featureFlagOverrides.service.spec.js +++ b/test/featureFlagOverrides.service.spec.js @@ -5,9 +5,21 @@ inject = angular.mock.inject; describe('Service: featureFlagOverrides', function() { - var service, appName = ''; - - beforeEach(module('feature-flags')); + var service, appName = 'myapp'; + + beforeEach(module('feature-flags', function($provide) { + $provide.provider('featureFlagConfig', function() { + return { + $get: function() { + return { + getAppName: function() { + return appName; + } + }; + } + }; + }); + })); beforeEach(inject(function(featureFlagOverrides) { service = featureFlagOverrides; diff --git a/test/featureFlags.provider.spec.js b/test/featureFlags.service.spec.js similarity index 92% rename from test/featureFlags.provider.spec.js rename to test/featureFlags.service.spec.js index 99df006..e999a4e 100644 --- a/test/featureFlags.provider.spec.js +++ b/test/featureFlags.service.spec.js @@ -354,7 +354,7 @@ }); }); - describe('Provider: featureFlags', function() { + describe('Service with provider config: featureFlags', function() { var featureFlags, flags = [{ active: true, @@ -362,11 +362,25 @@ }, { active: false, key: 'FLAG_KEY_2' - }]; + }], + appName = 'myapp'; describe('When no flags are set in the config phase', function() { - beforeEach(module('feature-flags', function(featureFlagsProvider) { - featureFlagsProvider.setInitialFlags(null); + beforeEach(module('feature-flags', function($provide) { + $provide.provider('featureFlagConfig', function() { + return { + $get: function() { + return { + getInitialFlags: function() { + return null; + }, + getAppName: function() { + return appName; + } + }; + } + }; + }); })); beforeEach(inject(function(_featureFlags_) { @@ -379,8 +393,21 @@ }); describe('When flags are set in the config phase', function() { - beforeEach(module('feature-flags', function(featureFlagsProvider) { - featureFlagsProvider.setInitialFlags(flags); + beforeEach(module('feature-flags', function($provide) { + $provide.provider('featureFlagConfig', function() { + return { + $get: function() { + return { + getInitialFlags: function() { + return flags; + }, + getAppName: function() { + return appName; + } + }; + } + }; + }); })); beforeEach(inject(function(_featureFlags_) { diff --git a/test/featureFlagsConfig.provider.spec.js b/test/featureFlagsConfig.provider.spec.js new file mode 100644 index 0000000..1f675f1 --- /dev/null +++ b/test/featureFlagsConfig.provider.spec.js @@ -0,0 +1,69 @@ +(function(angular) { + 'use strict'; + + var module = angular.mock.module, + inject = angular.mock.inject; + + describe('Provider: featureFlags', function() { + var appName = 'myapp', + featureFlagConfig, + flags = [{ + active: true, + key: 'FLAG_KEY' + }, { + active: false, + key: 'FLAG_KEY_2' + }]; + + describe('When no flags are set in the config phase', function() { + beforeEach(module('feature-flags', function(featureFlagConfigProvider) { + featureFlagConfigProvider.setInitialFlags(null); + featureFlagConfigProvider.setAppName(null); + })); + + beforeEach(inject(function(_featureFlagConfig_) { + featureFlagConfig = _featureFlagConfig_; + })); + + it('should return an empty array for current feature flags', function() { + expect(featureFlagConfig).not.toBeUndefined(); + expect(featureFlagConfig.getInitialFlags()).toEqual([]); + expect(featureFlagConfig.getAppName()).toEqual(''); + }); + }); + + describe('When flags are set in the config phase', function() { + beforeEach(module('feature-flags', function(featureFlagConfigProvider) { + featureFlagConfigProvider.setInitialFlags(flags); + featureFlagConfigProvider.setAppName(appName); + })); + + beforeEach(inject(function(_featureFlagConfig_) { + featureFlagConfig = _featureFlagConfig_; + })); + + it('should init the flags with the ones set in the config phase', function() { + expect(featureFlagConfig).not.toBeUndefined(); + expect(featureFlagConfig.getInitialFlags()).toEqual(flags); + expect(featureFlagConfig.getAppName()).toEqual(appName); + }); + }); + + describe('When flags are set in the run phase', function() { + beforeEach(module('feature-flags', function(featureFlagConfigProvider) { + featureFlagConfigProvider.setInitialFlags(null); + })); + beforeEach(inject(function(_featureFlagConfig_) { + featureFlagConfig = _featureFlagConfig_; + })); + + it('should init the flags with the ones set in the run phase', function() { + expect(featureFlagConfig).not.toBeUndefined(); + featureFlagConfig.setInitialFlags(flags); + featureFlagConfig.setAppName(appName); + expect(featureFlagConfig.getInitialFlags()).toEqual(flags); + expect(featureFlagConfig.getAppName()).toEqual(appName); + }); + }); + }); +}(window.angular));