Recently I was tasked with integrating LaunchDarkly into a couple Angular projects.
LaunchDarkly (if you didn't know) is a feature flag management system.
Setting up LaunchDarkly itself is pretty easy. The docs have clear examples, and my use case was simple.
But I'm a developer and if something is too easy, I have to find a way to make it harder.
I kid, I kid (kind of).
What I really wanted, though, was to find a way to use LaunchDarkly that felt native to Angular.
That meant:
- Use LaunchDarkly in an Angular injectable service.
- Take advantage of things like RxJS.
First Pass at Creating the Service
The first step I took was to create a LaunchDarklyService
which I then quickly renamed FeatureFlagService
(I justify this because one day we might switch our system of feature flagging to something else (maybe)).
Basic starting structure of that service:
import { Injectable, OnDestroy } from '@angular/core';
import { environment } from '@environments/environment';
import * as LaunchDarkly from 'launchdarkly-js-client-sdk';
import { LDFlagValue } from 'launchdarkly-js-client-sdk';
import { Observable, Subject } from 'rxjs';
import { map } from 'rxjs/operators';
Injectable({
providedIn: 'root'
})
export class FeatureFlagService {
client: LaunchDarkly.LDClient;
initialize(): void {
const user = {
anonymous: true
} as LaunchDarkly.LDUser;
this.client = LaunchDarkly.initialize(environment.launchDarklyClientId, user);
}
getFlag(flagKey: string, defaultValue: LDFlagValue = false): Observable<LDFlagValue> {
return of(this.client.waitUntilReady()).pipe(
map(() => {
return this.client.variation(flagKey, defaultValue);
})
);
}
}
An argument could be made for client
to be private. The reason I don't have it private is basically for ease of testing and mocking.
initialize
An initialize
function is a pretty common pattern I use for setting up of singleton services.
You want to call this as early in you're app's lifecycle as possible, so, my recommendation is to call it from your AppComponent.ngOnInit
.
Other situations I use this pattern is to start up services that watch for an app to go offline, analytics services, and some other things like that.
getFlag
The initial pass at getFlag
is fine for a lot of circumstances.
At this point, I'm just passing the parameters needed by LaunchDarkly itself.
waitUntilReady()
is a LaunchDarkly method that returns a Promise when everything has initialized (or failed to do so). There's also waitForInitialization()
which actually rejects the promise if there's an error. A possible improvement we could make in the future is to utilize that, but it didn't seem necessary to get off and running.
When I think of getting at remote data in Angular, I'm typically thinking of using RxJS and observables, so, what I'm doing here is converting the promise that LaunchDarkly gives me into an observable stream using of()
which is a handy RxJS operator that converts values into an observable.
Basically, this will safeguard our flag fetching from happening before LaunchDarkly has had time to initialize.
Once it has, we can map()
the value of our stream to fetch the flag using variation
.
Second Pass
The reason I called the code above the first pass is because we can do better.
one cool thing LaunchDarkly allows you to do is to stream in changes to flags.
Meaning, you can turn off and on flags, and users can get automatic updates without ever having to reload the page.
The LaunchDarkly SDK has a handy on
method you can use to subscribe to flag changes.
// subscribe to all flag changes
client.on('change', (settings) => {
console.log('flags changed:', settings);
});
// subscribe to a particular flag's changes
client.on('change:YOUR_FLAG_KEY', (value, previous) => {
console.log('YOUR_FLAG_KEY changed:', value, '(' + previous + ')');
});
This is well and good, but we're trying to do things in a native Angular way, so, how might we accomplish the above using RxJS in our service?
What we could do is utilize a Subject
that we can notify when we should fetch our flag.
First step would be to change the way we're currently fetching the flag.
getFlag(flagKey: string, defaultValue: LDFlagValue = false): Observable<LDFlagValue> {
const fetchFlag = new Subject<void>();
this.client.waitUntilReady().then(() => {
fetchFlag.next();
});
return fetchFlag.pipe(
map(() => {
return this.client.variation(flagKey, defaultValue);
})
);
}
Now, instead of mapping the waitUntilReady
promise to an observable value we're sending a message to our fetchFlag
subject and using that to kick off variation
.
The next step would be to hook into LaunchDarkly's event emitter.
getFlag(flagKey: string, defaultValue: LDFlagValue = false): Observable<LDFlagValue> {
...
this.client.on(`change:${flagKey}`, () => {
fetchFlag.next();
});
...
}
And there you have it. Now, when you fetch a flag you'll also get any changes to it's value live. Meaning you can turn off and on features and your users will immediately see the change.
Pretty cool!
Fin
So, that's what I've got currently. We'll see how well it expands as we start using LaunchDarkly more and more.
If you want to view it all together, here's a gist I put together:
https://gist.github.com/cookavich/a8d99e8fa37b11ce8e0f3592d7e1e0c7
I also have some more code around testing where I've created mocks and what not. Let me know if you're stuck, and I can share that, too.