2

I have a big project, where I make a few HTTP GET calls to API to get different data. I would like to combine them in one call (I already created it and I have nested json with all the data), the problem is that I cannot pass the data between components. Let me explain the problem. This is the call:

get(entityType: string): Promise<any> {
    return this.http
        .get<any>(
            this.location.prepareExternalUrl("./assets/temp-data/LEV_page/organisation.json")
        )
        .take(1)
        .toPromise();
}

In my main component, I do this:

public entityType:string;
public dataSource;

constructor(private levsService: LinkedEntityVocabulariesService) {}

ngOnInit() {
    this.entityType = localStorage.getItem("currentEntityType");
    this.levsService.get(this.entityType).then(data => {
        this.dataSource = data;
    });
}

If I want to use dataSource in that component, it works fine, but when I try to pass it to child component like this:

<app-properties [send]="dataSource"></app-properties>

And then just access it there:

@Input("send") send;

constructor() {}

ngOnInit() {
    console.log("test", this.send);
}

All I get is test undefined because it passes it before the data is received (even though I thought using Promise instead of Observable would prevent it).

Anyway, my question is: Can I make a call to get data and then pass it between components instead of calling api again? If yes how can I do it with above example? Thanks in advance!

C.OG
  • 6,236
  • 3
  • 20
  • 38
MWPodgorni
  • 83
  • 2
  • 9
  • I had a similar problem with the `@Input` decorator. If you have to wait for the data and do something after data arrived use `OnChanges` interface to detect when the data arrives. As alternative you can use a service and store your recieved data in a subject for example a `BehaviourSubject`. – JuNe Oct 24 '19 at 08:38
  • I use this in combination with an observable from rxjs. ` mySubject: BehaviourSubject = new BehaviourSubject(null); myObservable$: Observable = mySubject.asObservable(); ` – JuNe Oct 24 '19 at 08:38
  • You mean, like in this example? https://stackoverflow.com/a/42185519/12094093 – MWPodgorni Oct 24 '19 at 08:40
  • A little different like the post. See my last comment. – JuNe Oct 24 '19 at 08:44

3 Answers3

3

No matter whether you use the promise recipe or the observable recipe, trying to access the data before it's available will yield undefined. You can, for example, create the child component conditionally only when you have the data

<app-properties [send]="dataSource" *ngIf="dataSource"></app-properties>

Or you could inside the app-properties component check whether dataSource is defined before trying to do anything with it, the right place to do that will be the ngOnChanges lifecycle method.

ngOnChanges() {
    if (this.send) {
        console.log(this.send);
    }
}
mbojko
  • 13,503
  • 1
  • 16
  • 26
1

What is making your component not displaying new data is Angular change detection.

It is not triggered, fortunately angular has built in async pipe which does that for you. Also this pipe unsubscribes from event so angular has covered you all the way round.

Use this approach:

public entityType:string;
public dataSource$: Observable<yourInterface>;

constructor(private levsService: LinkedEntityVocabulariesService) {}

ngOnInit() {
  this.entityType = localStorage.getItem("currentEntityType");
  this.datasource$ = this.levsService.get(this.entityType);
}

Then in your html use this approach:

<app-properties [send]="dataSource$ | async"></app-properties>

And you will have your data loaded asynchronously.

The async pipe subscribes to an Observable or Promise and returns the latest value it has emitted. When a new value is emitted, the async pipe marks the component to be checked for changes. When the component gets destroyed, the async pipe unsubscribes automatically to avoid potential memory leaks.

If you want to learn more, please have a look here

  • This is incorrect. In the OP's solution, change detection _is_ triggered, but only after change actually happens (API call is returned), and he accessed the input _once_ and too early, in the child's `ngOnInit`. Changing from the explicit subscription to the async pipe won't help, the results will be the same. – mbojko Oct 24 '19 at 09:03
  • I am using this approach in angular for 4+ years, in huge enterprise applications. Never had problems with it, also a lot of courses provide this way. The official angular docs show this approach here: https://angular.io/api/common/AsyncPipe – Miroslav Maksimovic Oct 24 '19 at 09:38
  • 1
    1. Angular doesn't _have_ 4+ years, its initial release was in 2016. And 2. if the async pipe solves the problem of accessing data in `ngOnInit` before it's actually available, then I'd like to see it on stackBlitz. – mbojko Oct 24 '19 at 09:41
  • Ah, then he can use @input() set(data:any) { if(data){ console.log(data) } } – Miroslav Maksimovic Oct 24 '19 at 09:44
  • @mbojko Am I correct for accessing data in .ts component, the cleanest solution? – Miroslav Maksimovic Oct 24 '19 at 09:49
  • There's absolutely nothing wrong with the async pipe approach, it's just that the subscription was not the source of the problem. – mbojko Oct 24 '19 at 09:53
0

I also came across the same situation, we have an API call that returns a user profile details, and that profile details are needed across maximum components in the application.

In Angular, we can reduce this API call by creating a sharable Object in service (shared service). I think here you can implement the Behavior Subject. An object needs to initialize or declare at the start inside service and needs to call that shared Object across all application by calling service in the component. For this, you need to import libraries from 'rxjs'

  private profileDataSource = new BehaviorSubject('');
  profileData = this.profileDataSource.asObservable();

Above declared "profileDataSource" as BehaviorSubject with ' ' value. I have called this in the first component after login and initialized this with the profile details. In our case we also updating this Object if user updates profile details, this updation will reflect as we update BehavourSubject Object. You can call this Object in components and use shared data.

learn Subjects in-depth here https://rxjs-dev.firebaseapp.com/guide/subject

Bhagvat Lande
  • 1,392
  • 3
  • 17
  • 34