如何在Angular 2中强制重新渲染组件?

225
如何在Angular 2中强制重新渲染组件? 为了调试Redux,我想强制一个组件重新渲染其视图,这是否可能?

"Re-rendering"是什么意思?更新绑定? - Günter Zöchbauer
只是一个快速的问题,为什么您需要强制重新渲染? - Tuong Le
5
可能是手动触发 Angular2 变更检测的重复问题。 - blo0p3r
6个回答

255

渲染发生在变更检测之后。要强制进行变更检测,以便将已更改的组件属性值传播到DOM(然后浏览器将在视图中呈现这些更改),以下是一些选项:

  • ApplicationRef.tick() - 类似于 Angular 1 中的 $rootScope.$digest() -- 即检查整个组件树
  • NgZone.run(callback) - 类似于 $rootScope.$apply(callback) -- 即在 Angular 2 区域内评估回调函数。我认为,但不确定,这最终会在执行回调函数后检查整个组件树。
  • ChangeDetectorRef.detectChanges() - 类似于 $scope.$digest() -- 即仅检查此组件及其子级

您需要导入并注入ApplicationRefNgZoneChangeDetectorRef到您的组件中。

对于您的特定情况,如果只有一个组件发生了更改,我建议使用最后一个选项。


2
有没有关于Angular2最终版本的ChangeDetectorRef的可用代码?我现在遇到了这样一种情况:在使用http的post请求创建新用户后,将新对象推送到现有旧用户列表(用于在视图中迭代)后,视图没有更新。很奇怪,这是我第一次在ng2中遇到更新不起作用的情况。更改检测策略是默认的,所以我知道我没有搞砸更改检测策略。 - Gary
1
@Gary,你应该发布一个新问题,并包括你的组件和服务代码(最好包括一个演示问题的最小plunker)。我经常看到的一个常见问题是在POST回调中没有使用正确的this上下文。 - Mark Rajcok
你知道我是否可以在每次进行更改时手动触发管道吗?我尝试过触发变更检测,但管道没有更新... 我也尝试了在管道中使用 pure:false。它可以工作,但对于我的用例来说太昂贵(低效)了。 - Nate
1
@ncohen, 我不知道如何手动触发管道更新。 您可以使用纯管道并在需要触发更新时更改对象引用。 这在Pipes文档的“纯管道”部分中进行了讨论。 根据您的用例,您可能希望使用组件属性而不是管道。 这种技术在Pipes文档的末尾简要讨论过。 - Mark Rajcok
1
@N-ate,所有的链接都已修复。 - Mark Rajcok
1
ChangeDetectorRef.detectChanges() 对我不起作用。我认为这是因为没有任何更改。我只想触发重绘/计算。更新管道值。我想通过 setInterval 每 x 秒触发它。我认为重新分配变量目前是一种简单的方法来实现这一点。但也许不是最好的方法。 - Domske

53

tx,我找到了我需要的解决方法:

  constructor(private zone:NgZone) {
    // enable to for time travel
    this.appStore.subscribe((state) => {
        this.zone.run(() => {
            console.log('enabled time travel');
        });
    });

运行 zone.run 将强制组件重新渲染。


8
在这个上下文中,"appStore"是哪种类型的变量?它似乎是可观察的...但我的可观察对象在组件内部,我希望在单击按钮时刷新该组件...我不知道如何从父组件/当前位置访问子组件的方法/变量。 - Abdeali Chandanwala

52

变更检测器参考方法

import { Component, OnInit, ChangeDetectorRef } from '@angular/core';

export class MyComponent {

    constructor(private cdr: ChangeDetectorRef) { }

    selected(item: any) {
        if (item == 'Department')
            this.isDepartment = true;
        else
            this.isDepartment = false;
        this.cdr.detectChanges();
    }

}

36

我使用 *ngIf 强制重新加载我的组件。

容器内的所有组件都会重新经历完整的生命周期钩子。

在模板中:

<ng-container *ngIf="_reload">
    components here 
</ng-container>

然后在 ts 文件中:

public _reload = true;

private reload() {
    setTimeout(() => this._reload = false);
    setTimeout(() => this._reload = true);
}

感谢您,@loonis! 我觉得这应该可以实现,而且我已经准备好了除setTimeout()之外的所有内容。 现在我的问题通过简单而轻巧的解决方案得到了解决! - LHM
需要注意的一点是 - 容器的消失和再次出现可能会导致大小变化,页面可能会闪烁。 - Eggcellentos
1
这对我来说是最好的解决方案。使用我的自定义翻译器管道,解决了我的翻译问题。 - supernerd
谢谢,有点hacky,但这是我这种情况下唯一可行的解决方案(视频滑块)。 - Pierre
除非您在此组件上使用@ViewChild()来获取它,否则它将为未定义。 - herve

17

这里的其他答案提供了触发变化检测周期以更新组件视图的解决方案(这与完全重新渲染不同)。

完全重新渲染可以通过以下方式使用ng-templateng-containerViewContainerRef实现,从而销毁并重新初始化组件(调用所有生命周期钩子并重建视图):

<div>
  <ng-container #outlet >
  </ng-container>
</div>

<ng-template #content>
  <child></child>
</ng-template>

那么,在引用#outlet#content的组件中,我们可以清除插座内容并插入另一个子组件实例:

@ViewChild("outlet", {read: ViewContainerRef}) outletRef: ViewContainerRef;
@ViewChild("content", {read: TemplateRef}) contentRef: TemplateRef<any>;

private rerender() {
    this.outletRef.clear();
    this.outletRef.createEmbeddedView(this.contentRef);
}

此外,初始内容应在AfterContentInit钩子上插入:

ngAfterContentInit() {
    this.outletRef.createEmbeddedView(this.contentRef);
}

可以在这里找到完整的工作解决方案 https://stackblitz.com/edit/angular-component-rerender


1
我猜在之前的 Angular 版本中,这个钩子可能是正确的(不确定)。但是在新版本(对我来说是 v13)中,它不起作用,任何对此解决方案感兴趣的人都应该使用 ngAfterViewInit。 顺便说一句,当输入保留了先前的值而表单模型是正确的时候,这是唯一有效的解决方案。这发生在我在通过 :id 路由区分的路由之间切换时。 最后,我不得不在每次导航结束时调用 rerender 函数。 - Marcin Wojtach

12

ChangeDetectorRef.detectChanges()一般是最专注的方法。ApplicationRef.tick()则通常过于粗暴。

要使用ChangeDetectorRef.detectChanges(),您需要在组件顶部添加以下代码:

import {  ChangeDetectorRef } from '@angular/core';

通常情况下,你会在构造函数中使用别名来注入它,像这样:

constructor( private cdr: ChangeDetectorRef ) { ... }

然后,在适当的位置调用它,像这样:

this.cdr.detectChanges();

ChangeDetectorRef.detectChanges()的调用位置非常重要,与应用程序组件的生命周期和呈现方式密切相关。在此处,完全了解Angular生命周期是必不可少的,没有什么替代方法。确保你理解内部细节后,就可以适当地使用ChangeDetectorRef.detectChanges()(有时很容易理解何时使用它,但有时可能会非常复杂)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接