20

If I have the following:

<div *ngIf="user$ | async as user" class="container"> <p>{{user.name}}</p> </div> 

Is there a way I can execute code when the div above finally appears on screen?

3
  • 1
    Cant you Just subscribe in Code to the user$ observable? Commented Jun 10, 2017 at 11:34
  • What kind of code do you want to execute? Commented Jun 10, 2017 at 11:38
  • I want to hide my loader (spinner)! I hide/show it via a service that emits a boolean value using a ReplaySubject. The actual spinner component is placed at the app's root component with an *ngIf statement hooked to that boolean value. Commented Jun 10, 2017 at 11:40

2 Answers 2

35
+50

The *ngIf will remove that DOM element and all attached components/directives. So you can just write a simple directive that executes an event when it's first created. When the *ngIf transitions from false to true the directive will be created (again, and again, etc...)

@Directive({selector: '[after-if]'}) export class AfterIfDirective implements AfterContentInit { @Output('after-if') public after: EventEmitter<void> = new EventEmitter<void>(); public ngAfterContentInit(): void { // timeout helps prevent unexpected change errors setTimeout(()=> this.after.next()); } } 

Sample HTML:

<div *ngIf="user$ | async as user" (after-if)="your expression"> <p>{{user.name}}</p> </div> 
Sign up to request clarification or add additional context in comments.

Comments

22

A solution without the creation of a new directive is to take advange of @ViewChild and @ViewChildren behaviour:

Property decorator that configures a view query. The change detector looks for the first element or the directive matching the selector in the view DOM. If the view DOM changes, and a new child matches the selector, the property is updated.


1. ViewChild

The important part is If the view DOM changes wich means that in this case this'll only be triggered when the element is created or destroyed.

First declare a variable name for the element, for the sample i used #userContent

<div #userContent *ngIf="user$ | async as user" class="container"> <p>user.name</p> </div> 

Then add a @ViewChild reference inside your component:

@ViewChild('userContent') set userContent(element) { if (element) { // here you get access only when element is rendered (or destroyed) } } 

This solution was provided inside another question, also @ViewChild behaviour detail is available here.


2. ViewChildren

Another solution without using a new directive is to subscribe to @ViewChildren change observable, instead of using @ViewChild put it like this:

@ViewChildren('userContent') private userContent: QueryList<any>; 

And then subscribe to it change observable:

userContent.changes.pipe(takeUntil(this.$d)).subscribe((d: QueryList<any>) => { if (d.length) { // here you get access only when element is rendered } }); 

I've preferred the @ViewChildren way because to me it was easier to handle observables than validations inside setter's, also this approach is closer to the "Event" concept.


3. ngAfterViewInit()

NOTE: this option doesn't apply to OP question, as he's rendering a div, not a component.

As a general recommendation, when possible, avoid using any approach listed in this question. Instead, add an event triggered inside the ngAfterViewInit method of your child component. This is way more natural as Angular is intended to be used.

Child component:

// declare the event @Output() public created= new EventEmitter<any>(); // later on, trigger the event every time the component is rendered ngAfterViewInit() { this.created.next(); } 

Parent component:

<my-user-content-component #userContent (created)="onUserContentCreated($event)"></my-user-content-component> 
@ViewChild('userContent') userContent: MyUserContentComponent; onUserContentCreated() { // here you get access only when element is rendered using `this.userContent` } 

Note about Observables:

All observables need to be unsubscribed, otherwise you'll provoke memory leaks; there's a lot of ways to prevent that, as a recommendation, my favorite way is the RxJs function takeUntil, this part: pipe(takeUntil(this.$d)) and the following at your ngOnDestroy method:

private $d = new Subject(); ngOnDestroy() { this.$d.next(); this.$d.complete(); } 

The reason I recommend this way is because the amount of extra code to implement it is very low, also; you can use the same variable for all of your subscriptions in the component (this.$d). For more details/options about unsubscription approaches see this other related question/answer.

6 Comments

The subscribe function returns a subscription that you must store a reference to and unsubscribe to when the component is destroyed (e.g. in ngOnDestroy). Is there a way to make this less verbose?
There's a lot of ways @Ryan, my favorite is the RxJs function takeUntil, there's a lot more detail in this answer: stackoverflow.com/a/41177163/1657465
I think what I'm looking for is something like the "custom ngSubscribe directive". IOW, something to manage registration and removal of the subscription (similar to the async pipe). The amount of boilerplate code for each subscription is undesirable. The code you have written above does not unsubscribe. When the component is destroyed, the subscription lives on. You can prove this by logging inside the subscription.
@RyanHaney, thank you for your feedback, I've just added the unsubscribe method for the sake of developers that might not know that all observables need to be unsubscribed
Looks the same than the takeUntil approach, but with the ngOnDestroy method inside the extended class
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.