简体   繁体   中英

Angular RXJS structuring observables

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM