Trigger Event When Element Becomes Visible With Ngif

Trigger event when element becomes visible with 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. I suggest AfterViewInit.

Now that you know how, let's see what you should do.

In your case, you should create div with template references. This will allow you to have a reference to the element and make you able to check which div is shown or hidden.

Here is a stackblitz that shows you how it works, and here is the code :

import { Component, ViewChildren, QueryList, ElementRef } from '@angular/core';

@Component({
selector: 'my-app',
template: `
<div *ngFor="let item of [0, 1, 2, 3, 4, 5]; let i = index">
<span *ngIf="i === show" #shownDiv [id]="'div-' + i">{{ item }}</span>
</div>
`
})
export class AppComponent {
name = 'Angular 6';
show = 0;

@ViewChildren('shownDiv') divs: QueryList<ElementRef>;

ngOnInit() {
setInterval(() => {
this.show++;
if (this.show > 5) {
this.show = 0;
}
}, 1000);
}

ngAfterViewChecked() {
let shown = this.divs.find(div => !!div);
console.log('DIV shown is ' + (shown.nativeElement as HTMLSpanElement).id);
// Now that you know which div is shown, you can run whatever piece of code you want
}
}

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

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>

Event each time component becomes visible

What I finally did (which is not very beautiful but works while I don't have a better way to do it...) is to use the ngAfterContentChecked() callback and handle the change myself.

@ViewChild('map') m;
private isVisible: boolean = false;
ngAfterContentChecked(): void
{
if (this.isVisible == false && this.m.nativeElement.offsetParent != null)
{
console.log('isVisible switched from false to true');
this.isVisible = true;
this.Refresh();
}
else if (this.isVisible == true && this.m.nativeElement.offsetParent == null)
{
console.log('isVisible switched from true to false');
this.isVisible = false;
}
}

Trigger function when ngIf turns true to set focus on input which gets revealed

You can set the focus in the AfterViewChecked lifecycle hook, as shown in this plunker. I added a check to make sure that the focus is set only when the input element becomes visible, not every time AfterViewChecked is triggered.

import { Component, ViewChild, AfterViewChecked, ElementRef } from '@angular/core';

export class MyComponent implements AfterViewChecked {

@ViewChild("myInput") private myInput: ElementRef;

public showFirst = true;
private prevShowFirst = true;

public ngAfterViewChecked(): void {
if (!this.showFirst && this.prevShowFirst) {
this.myInput.nativeElement.focus();
}
this.prevShowFirst = this.showFirst;
}
}

Ng-if not hiding element when watched variable turns false

Your problem is that jQuery events are triggered outside of Angular digest cycle, so you need to call $scope.$apply manually.

$(window).load(jqCheckWindowSize);
$(window).resize(jqCheckWindowSize);

function jqCheckWindowSize() {
$scope.$apply($scope.checkWindowSize);
}

Or in a poweruser two lines way:

$(window).load  ($scope.$apply.bind($scope, $scope.checkWindowSize));
$(window).resize($scope.$apply.bind($scope, $scope.checkWindowSize));

Alternatively, you can call $scope.$apply directly in checkWindowSize or wrap the code using $timeout, etc. Many answers here already cover this.

How to check whether ngIf has taken effect

If you flip the boolean value to true and in the next line of code you try to get a reference to the component or DOM element controlled by NgIf... well, that component or DOM element doesn't exist yet. Angular doesn't run in parallel with your code. Your JavaScript callback has to finish, then Angular (change detection) runs, which will notice the boolean value change and create the component or DOM element and insert it into the DOM.

To fix your issue, call setTimeout(callbackFn, 0) after you flip the boolean value. This adds your callbackFn to the JavaScript message queue. This will ensure that Angular (change detection) runs before your callback function. Hence, when your callbackFn executes, the element you want to focus should now exist. Using setTimeout(..., 0) ensures that your callbackFn gets called in the next turn of the JavaScript event loop.

This technique of using setTimeout(..., 0) is used in the LifeCycle hooks dev guide when discussing the AfterView* hooks.

Here are a few other examples, if you need more details:

  • https://stackoverflow.com/a/35752722/215945 - uses focus()
  • https://stackoverflow.com/a/34757864/215945 - uses Renderer to call focus
  • https://stackoverflow.com/a/36338181/215945 - uses a directive to set the focus
  • https://stackoverflow.com/a/34503163/215945 - shows 4 different ways to do it

html element inside ng-if appear when the page is loading

Take a look at the documentation for ng-cloak.

From the documentation:

The ngCloak directive is used to prevent the Angular html template
from being briefly displayed by the browser in its raw (uncompiled)
form while your application is loading. Use this directive to avoid
the undesirable flicker effect caused by the html template display.

Also:

The directive can be applied to the element, but the preferred
usage is to apply multiple ngCloak directives to small portions of the
page to permit progressive rendering of the browser view.

<div  id = "menu"  ng-if="someCondition" ng-cloak>
<ul class="user-menu">
<li>
menu item 1
</li>
<li>
menu item 2
</li>
</ul>
</div>

How it works:

The following styles are applied to the document (the angular.js file is essentially acting as a .css file) when angular.js is loaded (which is recommended to be in the head of your page). When angular actually runs on DOM load, it removes the ng-cloak attribute, uncloaking the elements.

[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
display: none !important;
}

If you don't load angular in the head of your document, then you can add the styles manually to the head and everything will still work. Just add

<style type="text/css">
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
display: none !important;
}
</style>


Related Topics



Leave a reply



Submit