Angular: how to handle Observables and use result in different methods

Viewed 297

I'm having troubles to understand how I should work with Observables to meet my requirements. As an example, lets say that I have an application and for internationalization, I'm using REST service with all the labels and translations. Some labels can change based on performed actions on the page. And I'm not sure how to retrieve data from the service and use it in different methods.

Let's look at this pseudo-code:

labels: KeyValuePair[];
userDetails: UserDetails;
displayedColumns: string[];

// services below generated thanks to ng-swagger-gen
constructor(
   labelService: LabelService, 
   userService: UserService
){}

ngOnInit() {
   this.userService.get().subscribe(
      (userData) => {
        this.userDetails = userData; 
        console.log(userData);
      },
      (error) => {
        this.errorMessage = error;
      }
    );

   this.labelService.get(this.userDetails.getLanguage()).subscribe(
      (labelsResponse) => {
        this.labels = labelsResponse; // use the labels in HTML template
        console.log(labelsResponse);
      },
      (error) => {
        this.errorMessage = error;
      }
    );

   this.retrieveTableColumns();
}

retrieveTableColumns() {
   this.displayedColumns = this.labels.filter(/* filter columns */);
}

executeMethods() { // method executed from HTML template
   this.doSomethingWithUserDetails();
   this.doSomethingWithLabels();
}

doSomethingWithUserDetails() {
   // 1. do differnet things and use it in HTML template
   // 2. check one of properties and based on this 
   this.setColumns();
}

setColumns() { // columns used in mat-table
    if (this.userDetails.allowedForAction) {
        this.displayedColumns = this.columnsDef.concat(['action']);
        this.hiddenColumns.push(this.displayedColumns.length);
    } else {
        this.displayedColumns = this.columnsDef;
    }
}

doSomethingWithLabels() {
   // do some magic using retrieved labels
}

For generating web client service, I've used ng-swagger-gen.

How to handle the Observables to achieve this? So to be able to use data in different places. I've heard about converting it with .toPromise and use async await, but I've also heard that this is antipattern... So not sure what to do here... I'm working with Angular 9 if it matters.

1 Answers

Basics

You just keep the response as an observable:

labels$ = this.labelService.get(this.userDetails.getLanguage())

And in the Html you use an async pipe:

*ngFor="let label of labels$ | async"

If you want to transform the data in your typescript, you use ´.pipe()´ on the observable and create a new one:

newLabels$ = labels$.pipe(
    map(labels => labels.filter(label => label.id > 5)),
    // Or whatever you want to filter, sorry no inspiration
    tap(labels => console.log(labels)),
    // more pipes ...
)

You can create you own functions to be used in these pipes.

You can also just write a function taking whatever object is inside the observable as parameter and just use the function in your html:

*ngFor="let label of labelsSortedByName((labels$ | async)!)"

I'm using an ngFor as example because it's used a lot, but you can use it anywhere. Basically try to not subscribe in you code, just use async pipes. You'll have lots of fun learning about higher order observables, combining multiple observables together.

Merging Observables

Example:

first$ = of("en") // An observable representing the result of your first request

second(lang: string): Observable<string> { // Request requiring a string argument
  return of(lang + " language")
}

result$ = this.first$.pipe( // The resulting observable of the second request
  mergeMap(r => {
    return this.second(r)
  })
)

In template:

{{ result$ | async }}  <!-- Result: 'en language' -->

In this example first$ and second() are just mocks of your requests. I realize your second request does something else than just concatenating strings, but the idea is the same. It takes an argument of the type held by the first Observable and a new Observable is returned.

Here is a Stackblitz for you to play with.

Related