I have a FeatureToggle guard that calls a FeatureToggle service to get an array of feature toggles, but I am struggling to get my head around how to structure the observables similar to how I would do it with promises.
guard -
canActivate() {
if(this.toggleService.hasToggles){
return this.toggleService.isOn('my toggle');
} else {
return this.toggleService.getToggles().subscribe(() => {
return this.toggleService.isOn('my toggle')
})
}
}
service -
@Injectable
export class ToggleService {
private _toggles: string[];
private _hasToggles: boolean;
getToggles() {
return this.http.get('...').subscribe((toggles) => {
this._toggles = toggles;
})
}
isOn(toggle) {
// return if toggle is in this._toggles;
}
}
However because inside the service I am already subscribing, what I would be returning is a subscription, not an observable. I can create a local reference to the observable in getToggles
and return that, but is that the pattern I should be using?
You can use a ReplaySubject
to repeat the last emitted values for new subscriptions, and then have getToggles()
call http.get()
only once.
https://www.learnrxjs.io/subjects/replaysubject.html
@Injectable({providedIn: 'root'})
export class ToggleService {
private _toggles: ReplaySubject<string[]>;
getToggles(): ReplaySubject<string[]> {
if (!this._toggles) {
this._toggles = new ReplaySubject();
this.http.get('...').subscribe((toggles) => this._toggles.next(toggles));
}
return this._toggles;
}
isOn(toggle): Observable<boolean> {
return this.getToggles().pipe(map(toggles => toggles.includes(toggle)));
}
}
You can also use a shareReply
operator:
https://www.learnrxjs.io/operators/multicasting/sharereplay.html
@Injectable({providedIn: 'root'})
export class ToggleService {
private _toggles: Observable<string[]>;
getToggles(): Observable<string[]> {
if (!this._toggles) {
this._toggles = this.http.get('...').pipe(shareReplay(1));
}
return this._toggles;
}
isOn(toggle): Observable<boolean> {
return this.getToggles().pipe(map(toggles => toggles.includes(toggle)));
}
}
When working with Observables, there is always the edge where you cannot pass an Observable
any further. In Angular they can be passed into as far as the view template, subscribing to them via the | async
| async
pipe. Or, in your case, as the returned value of the canActivate
. Therefore it is very handy to use Observables in your service.
In you service, if you want to cache the results, you could store them using BehaviourSubject
and then pass them further as Observable. Or you could create an observable with shareReplay
operator and re-use it. Or, simply, you could just store them away and return them using the of
function from 'rxjs'
. I'm going to illustrate the last option.
@Injectable
export class ToggleService {
private _toggles: string[];
private _hasToggles: boolean;
getToggles(refresh?: boolean) {
if (!refresh || !this.toggles) {
return this.http.get('...').pipe(
tap((toggles) => {
this._toggles = toggles;
});
);
} else {
return of(this._toggles);
}
}
From the isOn
you can also return an Observable
- of boolean
type. That way you can pass it as far as possible.
isOn(toggle): Observable<boolean> {
return this.getToggles().pipe(
map(toggles => toggles.includes(toggle))
)
}
The canActivate
becomes very simple:
canActivate(): Observable<boolean> {
return this.toggleService.isOn('my toggle');
}
Angular subscribes to the returned observable. There's no need to check whether the toggles have already been loaded - the service already takes care of that.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.