1

Here is the simplified demo.

Is there a simply way to print in template the value of specific element from Array in Observable? The thing is that I have a list of key-value pairs where keys have some specific structure and they can be different between each key. And I'm wondering if is there any way to create some kind of converter to object where fields would be the keys' literals. Or is there nice solution to retrieve value by passing the key, but so it could be used on the template and component. So for example extract value for given key directly from labels$.

In given demo, I have method filterLabels(key: string) which works fine when I'm using it in component. But it would lead to a lot of variables for each label etc. When I tried to call this method from template then it entered endless loop...

Disclaimer: for some reason, the json doesn't want to be downloaded inside labels.service.ts with commented code, where similar code works on my original application.

EDIT:

New demo.

I've tried to reduce the json to map. But don't understand why it is working for console.log(mapAccumulator['labels.header.defaultTitle']); and doesn't work in the template <h1>{{ convertedLabels$['labels.header.defaultTitle'] | async }}</h1>.

3
  • Whats the reason behind setting convertedLabels$ an observable? Commented Nov 22, 2021 at 20:58
  • So I could use it in async pipe in the template. At least that's what I thought. I'm still getting confused with this observable approach here... Commented Nov 22, 2021 at 21:05
  • I added an update to your answer with half a code review from the demo you provided. Cheers Commented Nov 25, 2021 at 22:07

1 Answer 1

0

Original Answer

The async pipe is what returns you the object.

Right now you are trying to get a member of the observable that does not exist, you should get the member from the result of the async pipe, like this:

<h1>{{ (convertedLabels$ | async)['labels.header.defaultTitle'] }}</h1>

Or with a ternary providing a default value:

<h1>{{ (convertedLabels$ | async) ? (convertedLabels$ | async)['labels.header.defaultTitle'] : 'Default' }}</h1>

Update

The problem is that you are using a value from an async call in your html. This value has not yet been retrieved when the component is created, that is why the async pipe fails for a second until the value has been retrieved.

You can create a Subject with an initial value to be used by the async pipe until your call has retrieved the new value, and then push that new value to the subject.

You can then create an observable from that subject and use that in your component using a getter.

Example service:

private labelsSubject = new BehaviorSubject<LabelPair[]>(this.defaultLabelPairArray)

get labels$() {
  return this.labelsSubject.asObservable()
}

constructor(private http: HttpClient) {}

getLabels() {
  this.http.get<LabelPair[]>('/assets/labels.json').subscribe(
    labelPair => this.labelsSubject.next(labelPair)
  )
}

Here is a reviewed and cleaned up version of your Stackblitz.

Another Option

You can also use a defined object instead of a map, to allow for type checking by changing your mapping of the observable a little:

Html:

<h1>{{ (convertedLabels$ | async) ? (convertedLabels$ | async)?.defaultTitle : 'Default' }}</h1>

Class an mapping:

class Labels {
  defaultTitle: string;
  otherTitle: string;
  name: string;
}

convertedLabels$ = this.labelService.getLabels().pipe(
  map((m) =>
    m.reduce((mapAccumulator, obj) => {
      const keyParts = obj.key.split('.')
      const key = keyParts[keyParts.length - 1]
      mapAccumulator[key] = obj.value;
      return mapAccumulator;
    }, new Labels())
  )
);

With yet another Stackblitz.

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

4 Comments

Let me know if that helped or if you need me to look into anything else specific from the demo's. Cheers
Oh, I didn't think about it. Unfortunately, it doesn't work very well... Firstly it throws an error ERROR Error: Cannot read properties of null (reading 'labels.header.defaultTitle') and later it's properly loaded. Also I was wondering if its the best way to achieve this? And how to properly change the header? I need to do something like that: <h1>{{ header$ | (convertedLabels$ | async)['labels.header.defaultTitle'] }}</h1>? So at first it would try to load undefined header$ then defaultTitle. And when I click button to change header, it would firstly loaded the header$.
Oh, my idea doesn't work. And I know that there is a mistake with missing |. But it still doesn't work like I thought it would...
But did you check the console? Because it throws this error and then the header is properly loaded. Is it correct syntax for null check? It breaks the app and gives that error in the preview: Conditional expression requires all 3 expressions at the end of the expression.

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.