我有以下情况:
<h1>
(root-component使用ChangeDetectionStrategy.Default)<h1>
下方 - 元素是另一个组件,它呈现一些行。指定了一个模板,它定义了如何呈现一行(并且函数调用用于获取要呈现的值。我知道,对于这种情况,函数没有意义,但它显示了问题)<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
找到答案 0 :(得分:3)
实际上有点复杂,你所看到的行为的罪魁祸首是内容投射。并且根据Web组件域,它似乎是通常组件的正确行为。托管组件内的每个预计节点都属于托管组件。因此,如果您有根my-app
组件的以下模板:
<a-comp>
<b-comp>
<d-comp></d-comp>
</b-comp>
</a-comp>
b-comp
和d-comp
都属于my-app
组件,并且都被视为投影。在这里,您可以直观地看到它(使用原生阴影根):
您的案例有点不同,因为您正在投射模板。在Angular中,当模板投影到另一个组件时,使用它创建的所有视图也被视为投影。并且在检查模板所属的组件时检查它们。
在AppComponent
模板中,您有以下内容:
<app-rows [rows]="rows">
<ng-template let-row="rowData">
{{getName(row)}}
</ng-template>
</app-rows>
此处ng-template
是AppComponent
视图的子项,并投影在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/14935,https://github.com/angular/angular/issues/14842