3

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
  • toPromise is definitely an antipattern in angular. While there is a good answer to your question, I'd suggest you use a library for i18n. Either angular's own localize package which builds a version of the app for each language, or transloco which dynamically fetches translations from a json. Translation companies will rather want to work with standard format files than to make calls to your API Commented Nov 19, 2021 at 17:44

1 Answer 1

3

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.

Sign up to request clarification or add additional context in comments.

6 Comments

Thanks for answer. But I'm still confused. UserDetails comes from observable so how can we be sure that getLanguage() will be present when you are passing it to get labels? Also what is the purpose of exclamation mark in your last example of code?
The exclamation just ensures the compiler that an object will be present. It is there because of strict type checking and you should probably make sure that you return a valid object (e.g. empty array) even if there's no content. For the other question, you just use a pipe like mergeMap, flatMap, ... I'll update my answer with an example.
Thanks for the update. I've tried to provide some changes today and I've stuck at some point. Could you take a look at my updated post and shed some light or maybe even update my stackblitz with working example? I would really appreciate your help :)
I feel like you are asking three different questions now. If the first question was answered mark it as resolved and create new questions so this does not become a meters long post. If you link the new questions in a comment I'll be happy to take a look at those and your stackblitz later.
I get your point and you are right. Sometimes I'm forgetting about it and trying to get all answers in one place, when they are related (based on the flow only). I've asked new one here: stackoverflow.com/questions/70070009/…
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.