Home

Awesome

ember-feature-flags Build Status Ember Observer Score

An ember-cli addon to provide feature flags.

Note to users of ember.js >= 3.1

Referencing the features service must be done using get as it is a proxy.

Installation

ember install ember-feature-flags

Usage

This addon provides a service named features available for injection into your routes, controllers, components, etc.

For example you may check if a feature is enabled:

Native class syntax:

import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
export default class BillingPlansController extends Controller {
  @service features;
  get plans() {
    if (this.features.isEnabled('newBillingPlans')) {
      // Return new plans
    } else {
      // Return old plans
    }
  }
}

Classic Ember syntax:

import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
export default Controller.extend({
  features: service(),
  plans() {
    if (this.get('features').isEnabled('new-billing-plans')) {
      // Return new plans
    } else {
      // Return old plans
    }
  }
});

Features are also available as properties of features. They are camelized.

Native class syntax:

import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
export default class BillingPlansController extends Controller {
  @service features;
  get plans() {
    if (this.features.get('newBillingPlans')) {
      // Return new plans
    } else {
      // Return old plans
    }
  }
}

Classic Ember syntax:

import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
import { computed } from '@ember/object';
export default Controller.extend({
  features: service(),
  plans: computed('features.newBillingPlans', function(){
    if (this.get('features.newBillingPlans')) {
      // Return new plans
    } else {
      // Return old plans
    }
  })
});

Check whether a feature is enabled in a template (be sure to inject the features service into the template's backing JavaScript):

// templates/components/homepage-link.hbs
{{#if features.newHomepage}}
  {{link-to "new.homepage"}}
{{else}}
  {{link-to "old.homepage"}}
{{/if}}

NOTE: features service must be injected into the respective component:

Native class syntax:

// components/homepage-link.js
export default class HomepageLink extends Component {
  @service features;
}

Classic Ember syntax:

// components/homepage-link.js
export default Component.extend({
  features: service()
});

Alternatively you can use a template helper named feature-flag:

// templates/components/homepage-link.hbs
{{#if (feature-flag 'newHomepage')}}
  {{link-to "new.homepage"}}
{{else}}
  {{link-to "old.homepage"}}
{{/if}}

Features can be toggled at runtime, and are bound:

Native class syntax:

  this.features.enable('newHomepage');
  this.features.disable('newHomepage');

Classic Ember syntax:

this.get('features').enable('newHomepage');
this.get('features').disable('newHomepage');

Features can be set in bulk:

Native class syntax:

this.features.setup({
  "new-billing-plans": true,
  "new-homepage": false
})

Classic Ember syntax:

this.get('features').setup({
  "new-billing-plans": true,
  "new-homepage": false
});

You may want to set the flags based on the result of a fetch:

// routes/application.js
features: inject(),
beforeModel() {
   return fetch('/my-flag/api').then((data) => {
     features.setup(data.json());
  });
}

NOTE: setup methods reset previously setup flags and their state.

You can get list of known feature flags via flags computed property:

this.get('features').setup({
  "new-billing-plans": true,
  "new-homepage": false
});

this.get('features.flags') // ['newBillingPlans', 'newHomepage']

Configuration

config.featureFlags

You can configure a set of initial feature flags in your app's config/environment.js file. This is an easy way to change settings for a given environment. For example:

// config/environment.js
module.exports = function(environment) {
  var ENV = {
    featureFlags: {
      'show-spinners': true,
      'download-cats': false
    }
  };

  if (environment === 'production') {
    ENV.featureFlags['download-cats'] = true;
  }

  return ENV;
};

ENV.LOG_FEATURE_FLAG_MISS

Will log when a feature flag is queried and found to be off, useful to prevent cursing at the app, wondering why your feature is not working.

Test Helpers

enableFeature / disableFeature

Turns on or off a feature for the test in which it is called. Requires ember-cli-qunit >= 4.1.0 and the newer style of tests that use setupTest, setupRenderingTest, setupApplicationTest.

Example:

import { enableFeature, disableFeature } from 'ember-feature-flags/test-support';

module('Acceptance | Awesome page', function(hooks) {
  setupApplicationTest(hooks);

  test('it displays the expected welcome message', async function (assert) {
    enableFeature('new-welcome-message');

    await visit('/');

    assert.dom('h1.welcome-message').hasText('Welcome to the new website!');

    disableFeature('new-welcome-message');

    await settled();

    assert.dom('h1.welcome-message').hasText('This is our old website, upgrade coming soon');
  });
});

withFeature

"Old"-style acceptance tests can utilize withFeature test helper to turn on a feature for the test. To use, import into your test-helper.js: import 'ember-feature-flags/test-support/helpers/with-feature' and add to your test .jshintrc, it will now be available in all of your tests.

Example:

import 'ember-feature-flags/test-support/helpers/with-feature';

test( "links go to the new homepage", function () {
  withFeature( 'new-homepage' );

  visit('/');
  click('a.home');
  andThen(function(){
    equal(currentRoute(), 'new.homepage', 'Should be on the new homepage');
  });
});

Integration Tests

If you use this.features.isEnabled() in components under integration test, you will need to inject a stub service in your tests. Using ember-qunit 0.4.16 or later, here's how to do this:

let featuresService = Service.extend({
  isEnabled() {
    return false;
  }
});

moduleForComponent('my-component', 'Integration | Component | my component', {
  integration: true,
  beforeEach() {
    this.register('service:features', featuresService);
    getOwner(this).inject('component', 'features', 'service:features');
  }
});

Note: for Ember before 2.3.0, you'll need to use ember-getowner-polyfill.

Development

Installation

Running

Running Tests

Deploying