Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Event to fire when an angular *ngIf statement evaluates in template

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?

like image 751
Sammy Avatar asked Jun 10 '17 11:06

Sammy


People also ask

Can ng-template have ngIf?

ng-template should be used along with structural directives like [ngIf],[ngFor],[NgSwitch] or custom structural directives. ng-template never meant to be used like other HTML elements.

What is the use of * in ngIf?

The *ngIf directive is most commonly used to conditionally show an inline template, as seen in the following example. The default else template is blank.

What happens when component is made visible again using ngIf?

Your div will be rendered and visible once the change detection is triggered. When a change is detected, the whole lifecycle is ran again. If you want to run something, you should hook on one of the events of the lifecycle.

What is the difference between ngIf and * ngIf in Angular?

ngIf is the directive. Because it's a structural directive (template-based), you need to use the * prefix to use it into templates. *ngIf corresponds to the shortcut for the following syntax (“syntactic sugar”): <template [ngIf]="condition">


2 Answers

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>
like image 86
Reactgular Avatar answered Sep 18 '22 17:09

Reactgular


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 last way because to me it was easier to handle observables than validations inside setter's, also this approach is closer to the "Event" concept.


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.

like image 36
luiscla27 Avatar answered Sep 19 '22 17:09

luiscla27