I'm encountering a simple problem which has a hacky solution of setTimeout(...,0)
.
Looking at this simple code :
@Component({
selector: 'my-app',
template: `
<div>
<input value='Fill Data' type='button' (click)='fill()'/>
<span *ngFor="let o of Items" class='mySpan'>Span To Detect<br></span>
</div>
`,
})
export class App {
Items:Array<number> = new Array<number>();
fill()
{
this.Items = [1,2,3,4,5,6,7,8,9,10]
this.analyzeDom(); //this has to run here
}
analyzeDom()
{
alert($("div .mySpan").length) // "0"
//BUT if I set this hacky trick , it works
// setTimeout(function (){ alert($("div .mySpan").length)},0) // "10"
}
}
If I click the button , the alert shows "0". I understand why it's happening. It's becuase Angular didn't complete its cycle to actually populate the ngFor
.
However - doing this trick with setTimeout(..,0)
seems a bit hacky to me and I prefer not to trust on it.
Question:
What is the right way to "wait for operation" in Angular ? (so that I'll see "10") ?
Plnkr
1) You can force Angular to update the DOM by calling cdRef.detectChanges
constructor(private cdRef: ChangeDetectorRef) {}
analyzeDom(){
this.cdRef.detectChanges();
alert($("div .mySpan").length)
Plunker
As setTimeout
is macrotask therefore it is running next digest cycle.
It means that after calling setTimeout
all components tree will be checked
cdRef.detectChanges
doesn't call appRef.tick()
. It only executes change detection component itself and its children.
2) or you can wait until Angulat has updated DOM by subscribing to zone.onMicrotaskEmpty
import 'rxjs/add/operator/first';
constructor(private zone: NgZone) {}
...
this.zone.onMicrotaskEmpty.first().subscribe(() => this.analyzeDom());
Note: first operator can cause memory leak. See https://github.com/angular/material2/issues/6905
Subscribing to onMicrotaskEmpty
doesn't call change detection cycle
Plunker
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With