Angular 2 runOutside Angular is still changing the user interface

From my understanding of runOutsideAngular() , if I need to run something that does not lead to detecting Angular changes, I need to use this function. However, my code does not work; when i click the button, the user interface changes and the number is 2.

 @Component({selector: 'my-cmp', template: `<h1>{{num}}</h1> <button (click)="onClick()">Change number</button>`}) class MyComponent implements OnChanges { num = 1; constructor(private _ngZone: NgZone ) { } onClick() { this._ngZone.runOutsideAngular(() => { this.num = 2; }})); } } 
+5
source share
5 answers

If something causes change detection and a related event like (click)="onClick()" causes change detection, then Angular will detect the change.

runOutsideAngular does not mean that Angular will not see the change, it only means that running this code does not cause change detection, but since the click event already does this, it is pointless in your example.

+4
source

If you want to prevent change detection, you can

1) subscribe to ngZone.onMicrotaskEmpty as follows:

 import { NgZone, ChangeDetectorRef } from '@angular/core'; import 'rxjs/add/operator/first'; ... export class MyComponent { constructor(private ngZone: NgZone, private cdRef: ChangeDetectorRef) {} onClick() { // to do something this.cdRef.detach(); this.ngZone.onMicrotaskEmpty.first().subscribe(() => { // reattach changeDetector after application.tick() this.cdRef.reattach(); }); } } 

This handler will work after Application.tick

See also Example of a plunger.

2) use a custom directive as follows:

 @Directive({ selector: '[outSideEventHandler]' }) class OutSideEventHandlerDirective { private handler: Function; @Input() event: string = 'click'; // pass desired event @Output('outSideEventHandler') emitter = new EventEmitter(); constructor(private ngZone: NgZone, private elRef: ElementRef) {} ngOnInit() { this.ngZone.runOutsideAngular(() => { this.handler = $event => this.emitter.emit($event); this.elRef.nativeElement.addEventListener(this.event, this.handler); }); } ngOnDestory() { this.elRef.nativeElement.removeEventListener(this.event, this.handler); } } 

and then in the template you can write:

 <button (outSideEventHandler)="onClick()">Click outside zone</button> 

or

 <button event="mousedown" (outSideEventHandler)="onClick()">Click outside zone</button> 

Plunker

3) write your own DOM event handler as described in this article.

+3
source

[In short] you need to change one line in the current code

 onClick() { this._ngZone.runOutsideAngular(() => { setTimeout(()=>this.num = 2,0); // instead of this.num = 2; }})); } 

now, if you click on <button> , this.num will become 2 , but you will not see any changes in the user interface (temporary mismatch between the view and the model)

[Explanation] without runOutsideAngular() , asynchronous functions like addEventListener() or setTimeout() behave differently ( monkey fixed) . their callbacks will try to update the interface using Angular after user code is run. For example, you can consider (click)="onClick()" as:

 addEventListener("click",function modifiedCallback(){ onClick(); updateUIifModelChanges(); //call to Angular }) 

In order not to start updating the user interface, we need to fulfill the following two conditions:

  • don't change the model in the onClick function (so, change inside setTimeout() )
  • when the model is really modified, do not call updateUIifModelChanges (call setTimeout() inside runOutsideAngular )

[More] reasons, the explanation I gave is a very ... simplified version of what is happening. setTimeout() has the same function, regardless of whether it works inside runOutsideAngular() or not. The reason she behaves differently is because she works in another Zone.

+2
source
 ... constructor( private ngZone: NgZone ){ ngZone.runOutsideAngular(() => { setInterval(()=>{ this.num= new Date().Format('yyyy-MM-dd HH:mm:ss'); },1000); }); } ... 
0
source

Using ngZone.run slightly better than setTimeout solutions because it uses angular-specific functions. The launch is intended for use in the ngZone.runOutsideAngular functions.

From the docs:

Executing functions through run allows you to re-enter the angular zone from a task executed outside the angular zone (usually launched through {@link #runOutsideAngular}).

This is really a very practical example, for example, a button that increments a number by one, but only triggers change detection when the number is even.

  @Component({selector: 'my-cmp', template: `<h1>{{num}}</h1> <button (click)="onClick()">Change number</button>`}) class MyComponent implements OnChanges { num = 1; constructor(private _ngZone: NgZone ) { } onClick() { this._ngZone.runOutsideAngular(() => { if(this.num % 2 === 0){ // modifying the state here wont trigger change. this.num++; } else{ this._ngZone.run(() => { this.num++; }) } }})); } } 
0
source

Source: https://habr.com/ru/post/1258927/


All Articles