为什么angular2会多次执行方法? [英] Why angular2 executes methods several times?
问题描述
我的应用程序结构如下:
ts:
...
export class TodoListComponent {
get sortedTodos():ITodo[] {
console.log(this.counter++);
...
}
....
html:
<div class="todo-item" *ngFor="let todo of sortedTodos" [class.completed]="todo.completed">
<todo-list-item [todo]="todo" class="todo-item" (deleted)="onTodoDeleted(todo)"
(toggled)="onTodoUpdated($event)"></todo-list-item>
</div>
如果我启动应用程序,我会在控制台中看到:
1
2
3
4
5
6
我真的对这种行为感到困惑.对我来说,它看起来很奇怪,我认为它可能会导致错误和性能问题.请说明为什么一次加载页面时 6!次执行.
我不确定我是否提供了主题中所有需要的信息.随意提出其他要求.还可以在 bitbucket存储库链接
中找到所有代码库PS
ts文件的全部内容:
import {Component, Input, Output, EventEmitter} from "@angular/core"
import {ITodo} from "../../shared/todo.model";
import {TodoService} from "../../shared/todoService";
@Component({
moduleId: module.id,
selector: "todo-list",
templateUrl: "todo-list.component.html",
styleUrls: ["todo-list.component.css"],
})
export class TodoListComponent {
@Input() todos:ITodo[];
@Output() updated:EventEmitter<ITodo> = new EventEmitter<ITodo>();
@Output() deleted:EventEmitter<ITodo> = new EventEmitter<ITodo>();
get sortedTodos():ITodo[] {
return !this.todos ? [] :
this.todos.map((todo:ITodo)=>todo)
.sort((a:ITodo, b:ITodo)=> {
if (a.title > b.title) {
return 1;
} else if (a.title < b.title) {
return -1;
}
return 0;
})
.sort((a:ITodo, b:ITodo)=> (+a.completed - (+b.completed)));
}
onTodoDeleted(todo:ITodo):void {
this.deleted.emit(todo);
}
onTodoUpdated(todo:ITodo):void {
this.updated.emit(todo);
}
constructor(private todoService:TodoService) {
}
}
它执行了6次,因为:
在ApplicationRef
类中有一个tick
方法,它被2个更改检测周期执行了3次.如果通过调用enableProdMode()
启用生产模式,它将执行3次
ApplicationRef
是对在页面上运行的Angular应用程序的引用. tick
方法正在从根到叶运行更改检测.
这是tick
方法的外观( Angular 2中的更改检测)
所以我们知道我们的sortedTodos
吸气剂被一个tick
为什么tick
方法执行3次?
1):第一个tick
正在通过引导应用程序手动运行.
private _loadComponent(componentRef: ComponentRef<any>): void {
this.attachView(componentRef.hostView);
this.tick();
https: //github.com/angular/angular/blob/2.3.0/modules/%40angular/core/src/application_ref.ts#L479
2) Angular2在zonejs中运行,因此它是管理变更检测的主要工具.在ApplicationRef
上面提到的内容已订阅zone.onMicrotaskEmpty
.
this._zone.onMicrotaskEmpty.subscribe(
{next: () => { this._zone.run(() => { this.tick(); }); }});
https: //github.com/angular/angular/blob/2.3.0/modules/%40angular/core/src/application_ref.ts#L433
onMicrotaskEmpty 事件是区域 变得稳定
时的指示器private checkStable() {
if (this._nesting == 0 && !this._hasPendingMicrotasks && !this._isStable) {
try {
this._nesting++;
this._onMicrotaskEmpty.emit(null); // notice this
} finally {
this._nesting--;
if (!this._hasPendingMicrotasks) {
try {
this.runOutsideAngular(() => this._onStable.emit(null));
} finally {
this._isStable = true;
}
}
}
}
}
https://github.com/angular/in-memory-web-api/blob/0.0.20/src/in-memory-backend.service.ts#L136-L155 >
它开始定期的zonejs任务周期,使任务进入unStable
区域,最后在执行上述onMicrotaskEmpty
事件所描述的任务执行后再次发出
您可以在此处找到有关zonejs的更多详细信息
- http://blog.kwintenp.com/地狱如何真正工作/
- http://blog.thoughtram .io/angular/2016/02/01/zones-in-angular-2.html
回顾:
因此您可以看到您的解决方案有点错误.您不应在模板中使用getter或函数作为绑定.可能的解决方案,您可以在这里找到
my application structure looks like this:
ts:
...
export class TodoListComponent {
get sortedTodos():ITodo[] {
console.log(this.counter++);
...
}
....
html:
<div class="todo-item" *ngFor="let todo of sortedTodos" [class.completed]="todo.completed">
<todo-list-item [todo]="todo" class="todo-item" (deleted)="onTodoDeleted(todo)"
(toggled)="onTodoUpdated($event)"></todo-list-item>
</div>
If I start application I see in console:
1
2
3
4
5
6
I really confusing about this behaviour. for me it looks very strange and I think it can lead to bugs and performance issues. Please explain why it executes 6! times when I load page at once.
I am not sure that I provided all needed information in topic. Feel free to request womething else. Also all code base can be found bitbucket repo link
P.S.
full ts file content:
import {Component, Input, Output, EventEmitter} from "@angular/core"
import {ITodo} from "../../shared/todo.model";
import {TodoService} from "../../shared/todoService";
@Component({
moduleId: module.id,
selector: "todo-list",
templateUrl: "todo-list.component.html",
styleUrls: ["todo-list.component.css"],
})
export class TodoListComponent {
@Input() todos:ITodo[];
@Output() updated:EventEmitter<ITodo> = new EventEmitter<ITodo>();
@Output() deleted:EventEmitter<ITodo> = new EventEmitter<ITodo>();
get sortedTodos():ITodo[] {
return !this.todos ? [] :
this.todos.map((todo:ITodo)=>todo)
.sort((a:ITodo, b:ITodo)=> {
if (a.title > b.title) {
return 1;
} else if (a.title < b.title) {
return -1;
}
return 0;
})
.sort((a:ITodo, b:ITodo)=> (+a.completed - (+b.completed)));
}
onTodoDeleted(todo:ITodo):void {
this.deleted.emit(todo);
}
onTodoUpdated(todo:ITodo):void {
this.updated.emit(todo);
}
constructor(private todoService:TodoService) {
}
}
It executes 6 times because:
There is a tick
method in ApplicationRef
class and it is executed 3 times by 2 change detection cycles. If you will enable production mode by calling enableProdMode()
it will be executed 3 times
ApplicationRef
is an reference to an Angular application running on a page. The tick
method is running change detection from the root to leaves.
Here is how tick
method looks (https://github.com/angular/angular/blob/2.3.0/modules/%40angular/core/src/application_ref.ts#L493-L509):
tick(): void {
if (this._runningTick) {
throw new Error('ApplicationRef.tick is called recursively');
}
const scope = ApplicationRef_._tickScope();
try {
this._runningTick = true;
this._views.forEach((view) => view.ref.detectChanges()); // check
if (this._enforceNoNewChanges) {
this._views.forEach((view) => view.ref.checkNoChanges()); // check only for debug mode
}
} finally {
this._runningTick = false;
wtfLeave(scope);
}
}
For debug mode tick
starts two change detection cycles. So detectChangesInternal
within compiled view will be called twice.
And as your sortedTodos
property is a getter so it will be executed everytime as a function.
Read more about it here (Change Detection in Angular 2)
So then we know that our sortedTodos
getter is called twice for one tick
Why is the tick
method executed 3 times?
1) First tick
is running manually by bootstrapping application.
private _loadComponent(componentRef: ComponentRef<any>): void {
this.attachView(componentRef.hostView);
this.tick();
https://github.com/angular/angular/blob/2.3.0/modules/%40angular/core/src/application_ref.ts#L479
2) Angular2 is running within zonejs so it's the main thing which manages change detection. Mentioned above ApplicationRef
is subscribed to zone.onMicrotaskEmpty
.
this._zone.onMicrotaskEmpty.subscribe(
{next: () => { this._zone.run(() => { this.tick(); }); }});
https://github.com/angular/angular/blob/2.3.0/modules/%40angular/core/src/application_ref.ts#L433
onMicrotaskEmpty event is an indicator when zone gets stable
private checkStable() {
if (this._nesting == 0 && !this._hasPendingMicrotasks && !this._isStable) {
try {
this._nesting++;
this._onMicrotaskEmpty.emit(null); // notice this
} finally {
this._nesting--;
if (!this._hasPendingMicrotasks) {
try {
this.runOutsideAngular(() => this._onStable.emit(null));
} finally {
this._isStable = true;
}
}
}
}
}
https://github.com/angular/angular/blob/2.3.0/modules/%40angular/core/src/zone/ng_zone.ts#L195-L211
So after some zonejs task this event is emitted
3) You're using angular2-in-memory-web-api
package and when you're trying to get mock data it does following:
createConnection(req: Request): Connection {
let res = this.handleRequest(req);
let response = new Observable<Response>((responseObserver: Observer<Response>) => {
if (isSuccess(res.status)) {
responseObserver.next(res);
responseObserver.complete();
} else {
responseObserver.error(res);
}
return () => { }; // unsubscribe function
});
response = response.delay(this.config.delay || 500); // notice this
return {
readyState: ReadyState.Done,
request: req,
response
};
}
https://github.com/angular/in-memory-web-api/blob/0.0.20/src/in-memory-backend.service.ts#L136-L155
It start regular zonejs task cycle which makes zone unStable
and finally after task execution is emitted described above onMicrotaskEmpty
event again
You can find more details about zonejs here
- http://blog.kwintenp.com/how-the-hell-do-zones-really-work/
- http://blog.thoughtram.io/angular/2016/02/01/zones-in-angular-2.html
Recap:
So as you can see your solution a bit wrong. You shouldn't use getter or function as binding within your template. Possible solution you can find here
这篇关于为什么angular2会多次执行方法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!