Implementing Feature Flags in an Angular

Implementing Feature Flags in an Angular

Implementing Feature Flags in an Angular. After deep-diving into some of the more complex aspects of Angular and TypeScript, I thought it’s about time to dedicate a post to something a little lighter.

A Brief Overview

Feature flagging is a well-known technique that improves development speed and allows teams to test new features before they’re stable. It can be used for several reasons: A/B testing for specific features, deploying a feature to a small target audience to get feedback on it, or continuous code delivery.

In this article, we’ll learn how we can leverage various Angular features in order to prevent entering or viewing prohibited areas. Let’s get started.

Using a Feature Flag Provider

In our application, we use a commercial service for feature flag management, which exposes a GUI, where we can add or update our application feature flags. I won’t mention the service name, as advertising it isn’t the point of this article.

Our backend is responsible for the communication with the provider, and provides us with the user’s features flag values upon login. A typical response would look like this:

{
      "name": "Netanel",
      "featureFlags": {
        "data-canvas-radar-widget": true
        "data-canvas-scatter-plot-widget": true
      }
    }

user.js

Preload the User’s Feature Flags

We need to preemptively fetch the user’s data from the server, and make it available to our components before we allow the user to interact with them. Luckily, Angular makes doing that it a breeze, by exposing the APP_INITIALIZER injection token.

The APP_INITIALIZER provider can take a function that returns a promise, and only when that promise is resolved, Angular will bootstrap the application.

For the purpose of our demo we’ll use Akita, although you can employ any state management solution for this purpose. Let’s quickly scaffold a UserStore:

@Injectable({ providedIn: 'root' })
    export class UserQuery extends Query<UserState> {


      constructor(protected store: UserStore) {
        super(store);
      }


      hasFlags(flags: string | string[]): boolean {
        const userFlags = this.getValue().featureFlags;
        return coerceArray(flags).every(current => userFlags[current]);
      }
    }

user.query.ts

@Injectable({ providedIn: 'root' })
    @StoreConfig({ name: 'user' })
    export class UserStore extends Store<UserState> {


      constructor() {
        super(createInitialState());
      }


    }

user.store.ts

The getValue() query method returns the current state. In our case, we need the user featureFlags in order to check if we can render the view.

Now, let’s create the UserService:

@Injectable({ providedIn: 'root' })
    export class UserService {


      constructor(private userStore: UserStore) {}


      getUser() {
        const fakeUser = {
          name: 'Netanel',
          featureFlag: {
            a: true,
            b: false
          }
        };


        return timer(300).pipe(
          mapTo(fakeUser),
          tap(user => this.userStore.update(user))
        );
      }


    }

ff-service.ts

We expose a getUser method which is responsible for initializing the store with the user’s data via a server request ( in this case it’s simulated).

Now, let’s use our UserService with the APP_INITIALIZER token to load the user’s data before the app loads:

export function preloadUser(userService: UserService) {
      return function() {
        return userService.getUser().toPromise();
      };
    }


    @NgModule({
      ...
      providers: [
        {
          provide: APP_INITIALIZER,
          multi: true,
          useFactory: preloadUser,
          deps: [UserService]
        }
      ]
    })
    export class AppModule {}

app.module.ts 

We provide the APP_INITIALIZER with the preloadUser function, which calls the service’s getUser method. This will ensure that the user’s data will be in our store before the application loads.

Now, that we have the user’s data, we can move forward and create our structural directive.

Creating the *featureFlag Directive

Structural directives are responsible for HTML layout. They shape or reshape the DOM’s structure, typically by adding, removing, or manipulating elements, and that’s exactly what we need.

The featureFlag structural directive will be in charge of displaying a provided template, in a DRY way, based on whether the user is authorized that template. Let’s create it:

@Directive({
      selector: '[featureFlag]'
    })
    export class FeatureFlagDirective {
      @Input() featureFlag: string | string[];


      constructor(
        private vcr: ViewContainerRef,
        private tpl: TemplateRef<any>,
        private userQuery: UserQuery
      ) {
      }


      ngOnInit() {
        if (this.userQuery.hasFlags(this.featureFlag)) {
          this.vcr.createEmbeddedView(this.tpl);
        }
      }


    }

featureFlag.directive.ts

A structural directive creates an embedded view from the Angular-generated <ng-template> and inserts that view in a view container adjacent to the directive’s original host element.

We pass the provided flags from the input to the hasFlags() query method, and based on the value it returns, we can determine whether or not we should render the template. Let’s use the directive:

<div *featureFlag="'a'">...</div>


    <div *featureFlag="['a', 'd']">...</div>

c.html

Notice that the directive can support an array of flags. We can even take it one step further and add support for an or condition. Here’s some quick pseudo-code for this functionality:

<div *featureFlag="'a' or 'c'">...</div>

withOr.html

export class FeatureFlagDirective {
      @Input() featureFlag: string | string[];
      @Input() featureFlagOr: string = '';

      ...


      ngOnInit() {
        if (this.userQuery.hasFlags(this.featureFlag) || 
            this.userQuery.hasFlags(this.featureFlagOr)
         ) {
          this.vcr.createEmbeddedView(this.tpl);
        }
      }


    }

withOr.ts

The can be done through the template directives. You can read more about this syntax in this great blog post.

Let’s move forward and implement the routing guards, which will prevent navigation from unauthorized users.

Implementing the Can Activate Guard

Applications often restrict access to certain areas based on the user’s identity. For that purpose, we can implement a guard which only permits access to authorized users, based on their feature flags.

@Injectable({ providedIn: 'root' })
    export class FeatureFlagGuard implements CanActivate {
      constructor(private userQuery: UserQuery) {
      }


      canActivate(route: ActivatedRouteSnapshot): boolean {
        return this.userQuery.hasFlags(route.data.flags);
      }
    }

ffguard.ts 

const routes: Routes = [
      ...
      {
        path: '...',
        component: ProtectedComponent,
        canActivate: [FeatureFlagGuard],
        data: {
          flags: 'b' // or ['a', 'b']
        }
      }
    ];

routes.ts

In the above example we’re checking whether the user has the appropriate feature flag permissions detailed in the route data, in which case we permit the navigation; Otherwise, we redirect the user to the home page.

Implementing a Custom Preload Strategy

Many applications leverage Angular’s lazy load feature in conjunction with the preload option, in order to preload (i.e., download in advance) lazy modules, so that they’ll be available when the user navigates to them.

In our case, we don’t want to preload modules that the user isn’t authorized to view. In such cases, we can implement our own [preloadStrategy](https://angular.io/api/router/PreloadingStrategy "preloadStrategy") that provides a way to intercept and determine whether any lazy module should be preloaded.

Let’s see how can we do this:

@Injectable({ providedIn: 'root' })
    export class AppPreloadingStrategy implements PreloadingStrategy {
      constructor(private userQuery: UserQuery) {
      }


      preload(route: Route, load: () => Observable<any>): Observable<any> {
        return this.userQuery.hasFlags(route.data.flags) ? load() : of(false);
      }
    }

pl2.ts

const routes: Routes = [
      {
        path: 'bar',
        loadChildren: () => import('./bar/bar.module').then(m => m.BarModule),
        data: {
          flags: 'a'
        },
      }
    ];


    @NgModule({
      imports: [RouterModule.forRoot(routes, { preloadingStrategy: AppPreloadingStrategy })],
      exports: [RouterModule]
    })
    export class AppRoutingModule {
    }

pl3.ts

Similarly to the previous example, in this example, the flags in the route data again dictate whether the user can reach the route. If that’s the case, we return the provided load() function; Otherwise, we return an observable, indicating to the router that the user should be redirected instead.

Implementing the Can Load Guard

We’re not done yet. The previous case doesn’t cover situations where the user directly navigates to the protected URL. For these cases, we need to use the canLoad guard:

@Injectable({ providedIn: 'root' })
    export class FeatureFlagCanLoad implements CanLoad {
      constructor(private userQuery: UserQuery) {
      }


      canLoad(route: Route): boolean {
        return this.userQuery.hasFlags(route.data.flags);
      }
    }

FeatureFlagCanLoad.ts

const routes = [{
      path: 'bar',
      loadChildren: () => import('./bar/bar.module').then(m => m.BarModule),
      data: {
        flags: 'b'
      },
      canLoad: [FeatureFlagCanLoad]
    }]

routesv3.ts

Now we’ve made sure that Angular will not allow the lazy loading of a module if the user doesn’t have the required feature flags. In contrast, if we only use the canActivate guard, it would download the module, but prevent navigation.

If from some reason you couldn’t use the APP_INITIALIZER functionality, you can switch to using an observable, listen to the store value, and update the view on its initial setting.

🚀 Have You Tried Akita Yet?

One of the leading state management libraries, Akita has been used in countless production environments. It is constantly being developed and improved.

Whether it is entities arriving from the server or UI state data, Akita has custom-built stores, powerful tools, and tailor-made plugins, which all help to manage the data and negate the need for massive amounts of boilerplate code. We/I highly recommend that you try it out.

Further reading:

Listen Changes In Reactive Form Controls Using valueChanges In Angular

7 Ways to Make Your Angular App More Accessible

Angular RxJS: Observables, Observers and Operators Introduction

Real Time Apps with TypeScript: Integrating Web Sockets, Node & Angular

Angular 8 - Reactive Forms Validation Example

Angular vs React vs Vue: Which is the Best Choice for 2019?

A comparison between Angular and React

angular angular-js

Bootstrap 5 Complete Course with Examples

Bootstrap 5 Tutorial - Bootstrap 5 Crash Course for Beginners

Nest.JS Tutorial for Beginners

Hello Vue 3: A First Look at Vue 3 and the Composition API

Building a simple Applications with Vue 3

Deno Crash Course: Explore Deno and Create a full REST API with Deno

How to Build a Real-time Chat App with Deno and WebSockets

Convert HTML to Markdown Online

HTML entity encoder decoder Online

Install Angular - Angular Environment Setup Process

Install Angular in easy step by step process. Firstly Install Node.js & npm, then Install Angular CLI, Create workspace and Deploy your App.

Angular Charts: How To Add Charts In Angular 9 Example

To use charts in Angular, we will use the chart.js library to construct the charts. We will fake the backend server for data and display it on charts.

Basics of Angular: Part-1

What is Angular? What it does? How we implement it in a project? So, here are some basics of angular to let you learn more about angular. Angular is a Typesc

What are the best alternatives for angular js?

<img src="https://moriohcdn.b-cdn.net/193902114c.png">There are numerous frameworks and libraries used across the globe. If not angular, there are platforms like React, Vue, Aurelia and so on for app development.

Angular 8 Node & Express JS File Upload

In this Angular 8 and Node.js tutorial, we are going to look at how to upload files on the Node server. To create Angular image upload component, we will be using Angular 8 front-end framework along with ng2-file-upload NPM package; It’s an easy to use Angular directives for uploading the files.