即使分离了ChangeDetectorRef,也会执行插值

时间:2018-01-04 13:46:38

标签: angular performance

我有以下情况:

  • root-component有一个带有click-handler的<h1>(root-component使用ChangeDetectionStrategy.Default)
  • 在可点击的<h1>下方 - 元素是另一个组件,它呈现一些行。指定了一个模板,它定义了如何呈现一行(并且函数调用用于获取要呈现的值。我知道,对于这种情况,函数没有意义,但它显示了问题)
  • 呈现行的组件与change-Detection
  • 分离
  • 如果我单击<H1> - 元素,则会再次为组件中的每一行调用该函数,尽管它与更改检测分离。这是怎么回事?

app.component.html:

<h1 (click)="onClick()" style="cursor:pointer">Klick me</h1>

<app-rows [rows]="rows">
    <ng-template let-row="rowData">
        {{getName(row)}}
    </ng-template>
</app-rows>

app.component.ts:

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent {

  rows: any[];

  constructor(private changeDetectorRef: ChangeDetectorRef, private renderer: Renderer2) {
    this.rows = this.createRows(100);
  }

  getName(row:any): string {
    console.log("getName called")
    return row.name;
  }

  onClick() {
    console.log("clicked");
  }

  private createRows(count:number): any[] {
    let rows: any[] = [];

    for (let i=0; i < count; i++) {
      rows.push({
        name: "TEST " + (i+1)
      })
    }
    return rows;
  }
}

rows.component.html

<div *ngFor="let row of rows">
    <ng-container *ngTemplateOutlet="template; context: {'rowData': row}">
    </ng-container>
</div>

rows.component.ts:

@Component({
  selector: 'app-rows',
  templateUrl: './rows.component.html'
})
export class RowsComponent  {

  @Input()
  rows: any[];

  @ContentChild(TemplateRef)
  template: TemplateRef<any>;

  constructor(private changeDetector: ChangeDetectorRef) {
  }

  ngAfterViewInit() {
    this.changeDetector.detach();
  }
}

可以在stackblitz

找到正在运行的示例

我已将CLI项目上传到DropBox

更新:我尝试在其间添加一个额外的组件来稍微改变组件层次结构。但它并没有改变这种行为。此版本可在enter link description here

找到

3 个答案:

答案 0 :(得分:3)

实际上有点复杂,你所看到的行为的罪魁祸首是内容投射。并且根据Web组件域,它似乎是通常组件的正确行为。托管组件内的每个预计节点都属于托管组件。因此,如果您有根my-app组件的以下模板:

<a-comp>
    <b-comp>
        <d-comp></d-comp>
    </b-comp>
</a-comp>

b-compd-comp都属于my-app组件,并且都被视为投影。在这里,您可以直观地看到它(使用原生阴影根):

enter image description here

您的案例有点不同,因为您正在投射模板。在Angular中,当模板投影到另一个组件时,使用它创建的所有视图也被视为投影。并且在检查模板所属的组件时检查它们。

AppComponent模板中,您有以下内容:

<app-rows [rows]="rows">
    <ng-template let-row="rowData">
        {{getName(row)}}
    </ng-template>
</app-rows>

此处ng-templateAppComponent视图的子项,并投影在AppRowsComponent内。此模板也绑定到AppComponent,但上下文可以更改 - 您可以从RowsComponent进行设置。

Here is a much simpler example您可以在其中看到此行为。当更新绑定的父组件属性时,将更新分离组件内的投影视图。

当Angular在AppComponent上运行更改检测时,它会检查是否应检查其子项。由于AppRowsComponent已分离,因此它不会对其进行更改检测。但是,应该检查使用绑定到AppComponent的模板创建的投影视图,因此检查投影视图的变更检测实现中存在逻辑:

// `view` here is a container for `AppRowsComponent`
case ViewAction.CheckAndUpdateProjectedViews:
    if ((viewState & 128 /* Destroyed */) === 0) {
        if (viewState & 32 /* CheckProjectedView */) {
            checkAndUpdateView(view);  <----- skipped for AppRowsComponent sicne it's not projected
        }
        else if (viewState & 64 /* CheckProjectedViews */) {
            execProjectedViewsAction(view, action);  <---- this is executed
        }
    }
    break;

要了解有关更改检测的更多信息,请参阅:

答案 1 :(得分:0)

@ AngularInDepth.com

所以没有办法告诉棱角分明不要这样做?我在util.ts中找到了函数markParentViewsForCheckProjectedViews。

在调试期间,我将函数的行为更改为:

export function markParentViewsForCheckProjectedViews(view: ViewData, endView: ViewData) {
  let currView: ViewData|null = view;
  while (currView && currView !== endView) {
    if (currView.state & ViewState.Attached) {
        currView.state |= ViewState.CheckProjectedViews;
    } else {
        currView.state &= ~ViewState.CheckProjectedViews;
    }
    currView = currView.viewContainerParent || currView.parent;
  }
}

这可以解决我的问题但当然我不知道,如果这引入了新的问题。也许ChangeDetectorRef可以通过类似detachProjectedViews()(以及新的ViewState ProjectedViewsDetached)扩展?

答案 2 :(得分:0)

这是与角度内容投影的设计相关的另一个问题。

预计内容实际上属于定义它的组件。所有内容,包括DI,更改检测,事件处理,从模板创建视图都是在所有者组件的上下文中完成的,在您的情况下是AppComponent。不幸的是,模板没有解决方法。

至少还有2个问题涉及不同方面的同一问题:https://github.com/angular/angular/issues/14935https://github.com/angular/angular/issues/14842