How to init a component only when its tag is at `*ngIf=true`?

Jeahel picture Jeahel · Jul 6, 2016 · Viewed 16.6k times · Source

Update:
Günter Zöchbauer provided a very acceptable answer that works perfectly (and thank you!). But I still have a question to check if what I am doing is the correct way of getting the result I seek. Context is given below.
What I expect with my custom tag on the parent view, is that when the *ngIf condition is false, the component is not loaded at all (or maybe most part of it). Is it normal that even if it is not in the DOM (because the condition is false), it is still loaded? Is there a better way to embed components' templates (and the logic of their class) into a parent component's template?

Original post

I am new to Angular 2 and so far I'm having much fun developing a small song track metadata application based on the Spotify API.

TL;DR: How to init a component only when its corresponding HTML tag (defined with the selector property of @Component) if *ngIf="true"?


But of course I'm facing difficulties! I have this component, TrackDetailComponent, in the template of which I would like to include 2 other components. On clicking on a button, it would switch from one inner component to the other.
Here is how the template looks like (note that I only have developed one of the two inner components for now, so I used a placeholder in stead of the second one):

<div id="view">
    <tig-track-info *ngIf="childView == TrackDetailView.InfoView" [track]="track"></tig-track-info>
    <p *ngIf="childView == TrackDetailView.LyricsView">Here go the lyrics</p>
</div>

As you can see, my inner component (named TrackInfoComponent) has an input parameter. Here is an excerpt of that component:

@Component({
    selector: "tig-track-info",
    templateUrl: "app/components/track-info/track-info.component.html",
    styleUrls: ["app/components/track-info/track-info.component.css",
                "app/shared/animations.css"]
})
export class TrackInfoComponent implements OnInit {
    private _trackId: string = null;
    @Input() 
    track: Track = null;
    previewAudio: any = null; // The Audio object is not yet defined in TypeScript
    albumArtworkLoaded: boolean = false;
    ...
}

My real problem is not embedding the TrackInfoComponent inside the other one, but it is to sync them. The TrackDetailComponent calls an API asynchronously and then initializes the track property. But TrackInfoComponent also needs that track to be initialized in order to do its job or else I get a null reference exception (which is logical).

I tried setting the child component data in the ngOnInit() and setting the *ngIf of its parent's template to true only when the track is ready but it seems that ngOnInit() is called right away, even if the DOM element tig-track-info is not display with *ngIf.

I guess I could use events or my own init method in the child component, to call only when I'm ready, but I would like to know if there is a better way to do it using the Angular 2 component lifecycle hooks or any other method recommanded for that. Does anyone have any idea about that?

Thank you :-)

Answer

G&#252;nter Z&#246;chbauer picture Günter Zöchbauer · Jul 6, 2016

You can use OnChanges which is called whenever an @Input() property value is changed by a template binding `[track]="..."

  export class TrackInfoComponent implements OnChanges {
    ngOnChanges(changes: SimpleChanges) {
      if(this.track) {
        ...
      }
    }
  }