为什么angular2会多次执行方法? [英] Why angular2 executes methods several times?

查看:138
本文介绍了为什么angular2会多次执行方法?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的应用程序结构如下:

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的更多详细信息

回顾:

因此您可以看到您的解决方案有点错误.您不应在模板中使用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

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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆