Saturday, October 28, 2017

RxJS Observables in Angular!

...was the name of a tech talk by Ward Bell (left) and Sander Elias (right) at the AngularMix convention. Observables are lazy and deferred by nature so if one isn't running you likely are not subscribing. One pain point that I've run into before was an inability to use Observables with a .NET Core endpoint which returned void for this very reason. I have hacked around the problem by returning a true value Boolean variable, but that is technically breaking with Roy Fielding's REST paradigm when hitting endpoints that use the PUT and DELETE verbs. (They should be fire and forget.) Is that the end of the world? No, but it did irritate me enough to ask Mr. Bell about it and he suggested that returning a 200 response with no packet as returning Ok(); is apt to do in a .NET Core controller action might be wise. You may have noticed that you are importing Observable from 'rxjs/Observable' but that you are not importing a specific type at all when importing 'rxjs/add/operator/catch' or 'rxjs/add/operator/map' so what are you really importing therein? In the second and third case you are monkey patching Observable to change up how it behaves and augment it with extra functionality. You may import 'rxjs' by itself and loop in way too much stuff, but this is not recommended as it will seriously affect your performance time. Cherry pick the operators you really need. The .debounce operator will create a delay of a third of second for new items coming in in an Observable stream and if you chase this downstream in a chain with .switchMap the switchMap will drop things in flight when hit so that there is only a mapping every third of a second reducing performance cost. Observables are always listening. This takes some getting used to and you may want observables to simply be less observant. Well, you may always just get a one-time-and-we're-done promise back from an observable by using .subscribe and, yes, if you do not then you are collecting something that is going to shepherded along until it is used with the async pipe to react to the latest data, probably inside of template markup. There is a way to do a .takeUntil which allows for listening up until a point. This could be the porridge that Goldilocks picks if you fear both a race condition and a performance issue, not too cold like .subscribe and not too hot like always listening. Here are some examples from the slides shown of the things I have just mentioned. First up is the cold porridge.

export class SimplefilmsComponent implements OnInit {
   films: Movie[];
   
   constructor(private filmsService: SimpleFilmsService) {}
   
   ngOnInit() {
      this.filmsService
         .getFilms()
         .subscribe(data => (this.films = data.results));
   }

 
 

Hot porridge:

export class SimplefilmsComponent implements OnInit {
   films$: Movie[];
   
   constructor(private filmsService: SimpleFilmsService3) {}
   
   ngOnInit() {
      this.refresh();
   }
   
   refresh() {
      this.errorMsg = '';
   
      this.films$ = this.filmsService.getFilms()
         .catch(err => {
            this.errorMsg = err.message;
            return [];
         });
   }

 
 

What is above might be interfaced with at a template like so:

<div *ngFor="let film of films$ | async">{{film.title}}</div>
<div *ngIf="errorMsg" class="error">{{errorMsg}}</div>

 
 

An example of using verbs:

@Injectable()
export class SimpleFilmsService2 {
   constructor(private http: HttpClient, private swUrlService:
         SwUrlService) {}
   
   getFilms() {
      return this.http.get<RootMovies> this.url
      
         .map(data => data.results)
         .catch(err => {
      
            console.error('GET failed', err);
      
            throw new Error('Sorry but can\'t get movies right now:
               please try again later');
      
         })
      }
   
   add(movie: Movie) {
      return this.http.post(this.url, movie);
   }
   
   get url() {
      return this.swUrlService.url;
   }
}

 
 

Fishing a URL line variable out of the URL line and using it to make a GET call:

ngOnInit(): void {
   console.log('MovieComponent initialized');
   
   this.currentMovie = this.route.paramMap
      .switchMap(params => {
         const id = +params.get('id');
         return this.filmService.getFilm(id)
            .do(movie => this.currentId = movie && movie.id);
      });
}

 
 

The debounce thing:

export class WikipediaComponent {
   
   constructor(private wikiService: WikipediaService) {}
   
   searchTerm = new FormControl();
   
   keyWords$ = this.searchTerm.valueChanges
      .debounceTime(300)
      .distinctUntilChanged()
      .switchMap(searchTerm => this.wikiService.load(searchTerm))
      .map(this.makeResultsPretty);

 
 

The .takeUntil thing:

export class TakeUntilComponent implements OnInit, OnDestroy {
   time: string;
   private onDestroy = new Subject<void>();
   
   constructor(private t: TimeService) {}
   
   ngOnInit() {
   
      this.t.time('TakeUntil')
         .takeUntil(this.onDestroy.asObservable())
         .subscribe
            time => (this.time = time),
            err => console.error(err),
            () => console.log 'TakeUntil completed')
         );
   }

 
 

Addendum 12/7/2018: The number in .debouceTime above is a number of milliseconds Again, this kinda behaves as capacitors paired with resistors might on a circuit board and things streaming in at an inconsistent rate are given a consistent rate via some buffering.

No comments:

Post a Comment