Main RxJS Concepts for Angular Developers

1. Two main ways to subscribe

There are two main ways to extract data from the observable  in Angular. We can subscribe in the component typescript or in the component html. In this example we subscripe to catsh in HTML and to dogs in typescript.

  public cats!: Observable<Animals>;
  public dogs!: Animals;

  ngOnInit(): void {
    this.cats = this.testService.getCats();
    this.testService.getDogs().subscribe((data) => (this.dogs = data));
  }

And in example with dogs, we puting observables in the variable and execute subcribe in HTML using pipe “async”:

<div *ngFor="let cat of cats | async | keyvalue">
  <img [src]="cat.value.url" />
</div>

<div *ngFor="let dog of dogs | keyvalue">
  <img [src]="dog.value.url" />
</div>

The using pipe “async” when possible is usual preferred option, because it also unsubscribes to the observable, and we don’t need to do it manually.

2. map operator

Mappint observables allows to transform data before you subscribe to it.

The map operator is very powerfull. You can use it to transform any type of asynchronous data into format that you want.

Imagine we need to change domain name in all urls for dog photos. Of course this case you probably not won’t see in the real project, because most often map operator is used for change structure of response and not for change part of something value. But it’s suitable for example, and it was interesting for me to do it.

public dogs!: Observable<Animals>;

  ngOnInit(): void {
    this.dogs = this.testService.getDogs().pipe(
      map((dogs) => {
        return this.changeDomainName(dogs);
      })
    );
  }

  private changeDomainName(dogs: Animals): Animals {
    for (let dog in dogs) {
      dogs[dog].url = dogs[dog].url.replace(/^[^:]+:\/\/[^/?#]+/, 'https://my-new-domain.com');
    }
    return dogs;
  }

3. swtichMap operator

This operator allows handling data relationships between two independent streams. For example, it’ll be useful when you need to check user role before can query some other data.

So swithcMap get the first observable and then uses its data to return a second observable.

In example below in Firebase database we have collection of dogs and collection of humans. Each dog has a field with name owner. And imagine that we need to show only those dogs, those belong to particular user (human).

Example from Firebase

Wrong example with using pipe map and function for filter of object of objects:

dogs!: Observable<Animals>;

ngOnInit(): void {
  this.dogs = this.testService.getHuman().pipe(
    switchMap((human) => {
      return this.testService.getDogs().pipe(
        map((dogs) => {
          return this.filterObjByValue(dogs, 'owner', human.name);
        })
      );
    })
  );
}

private filterObjByValue(obj: any, filter: string, filterValue: string): Animals {
  return Object.keys(obj).reduce((acc, val) => {
    return obj[val][filter] !== filterValue
      ? acc
      : {
          ...acc,
          [val]: obj[val],
        };
  }, {});
}

Correct example with using query params for Firebase:

sevice.ts

getDogs(owner?: string): Observable<Animals> {
  let queryParams = new HttpParams();
  queryParams = queryParams.append('orderBy', '"owner"');
  if (owner) {
    queryParams = queryParams.append('equalTo', `"${owner}"`);
  }
  return this.http.get<Animals>(`${environment.firebase.databaseURL}/animals/dogs.json`, {
    params: queryParams,
  });
}

getHuman(): Observable<{ name: string }> {
  return this.http.get<{ name: string }>(`${environment.firebase.databaseURL}/human.json`).pipe(
    map((data) => {
      return data;
    })
  );
}

component.ts 

dogs!: Observable<Animals>;

ngOnInit(): void {
  this.dogs = this.testService.getHuman().pipe(
    switchMap((human) => {
      return this.testService.getDogs(human.name);
    })
  );
}

4. Combine Observables

Some cases needed to combine several observables into one. RxJs has several combination operators. In this case we just be focusing on forkJoin. For this example we will pull two different Observables from Firabase (cats and dogs) and then combine them in new Observable called animals. But also with help of another RxJs operator map we concatenate the elements of the resulting array into a single object for easier drawing by ngFor.

component.ts 

public animals!: Observable<Animals>;

ngOnInit(): void {
  this.animals = forkJoin([this.testService.getCats(), this.testService.getDogs()]).pipe(
    map(([cats, dogs]) => {
      const data = { ...cats, ...dogs };
      return data;
    })
  );
}

component.html

<div *ngFor="let animal of animals | async | keyvalue">
  <img [src]="animal.value.url" />
</div>

Another similar operator for combine observables is combineLatest. It’s also as forkJoint run all observables in parallel, but can to emit value many times (after getting value from all), reacting to change in at least one of passed observables in the future. That is, you have necessarily to unsubscribe for it.

5. BehaviorSubject

This case is not so much about BehaviorSubject itself, as about using it along observable inside. In this code example, with every click by block with dog, we will be to send animal data to BehaviorSubject. And then we need to render this element (dog) in template.

...

public dogs!: Observable<Animals>;
  public currentDog = new BehaviorSubject<null | { url: string; owner: string }>(null);

  ngOnInit(): void {
    this.dogs = this.testService.getDogs();
  }

  handleChangeDog(newAnimal: { url: string; owner: string }) {
    this.currentDog.next(newAnimal);
  }
}

export type Animals = { [key: string]: Animal };
export type Animal = { url: string; owner: string };

Notice how we use the question mark calling any attributes. We use it because the default value of our Behaviorsubject is null. So we don’t want to call an attribute that doesn’t exist.

<div *ngFor="let dog of dogs | async | keyvalue" (click)="handleChangeDog(dog.value)">
  <img [src]="dog.value.url" />
</div>

<div>
  <p>{{ (currentDog | async)?.owner }}</p>
  <img [src]="(currentDog | async)?.url" />
</div>

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *