结合*ngIf管理子组件的最佳实践



当由于*ngIf而不总是创建子级时,我很难找到一种管理Component的子级的好方法。

组件RootComponent:的HTML结构

<div id="root-div" *ngIf="dto">
<div id="nested-div-1" *ngIf="something">
<my-nested-component>
</my-nested-component>
</div>
<div id="nested-div-2" *ngIf="somethingElse">
<div #anotherChild>
</div>
</div>
<button (onClick)="setSomethingElse()">Button</button>
</div>

问题:

  • RootComponent被渲染
  • ngAfterViewInit中进行REST调用以获得dto,直接设置something布尔值

    @ViewChild() myNestedComponent : MyNestedComponent;
    ngAfterViewInit() {
    restService.get().then((result) => {
    this.dto = result;
    this.something = true;
    }
    }
    
  • Angular现在将渲染root-divnested-div-1my-nested-component

  • 当您执行以下操作时,您将获得undefined异常:

    @ViewChild() myNestedComponent : MyNestedComponent;
    ngAfterViewInit() {
    restService.get().then((result) => {
    this.dto = result;
    this.something = true;
    this.myNestedComponent.theMethod();
    }
    }
    
  • 这是完全合理的,因为Angular还没有更新视图,所以myNestedComponent是未定义的

坏/破解解决方案

  1. 使用ChangeDetectorRef.detectChanges()强制Angular更新视图
  2. this.myNestedComponent.theMethod()封装在setTimeout中,这将延迟该行的执行,直到Angular完成他的循环

更好的解决方案

  1. my-nested-component一个@Output事件,以便my-nested-component在完成初始化(例如在my-nested-componentngAfterViewInit中)时可以执行RootComponent中的方法

    <my-nested-component (afterInit)="nestedComponentInitialized()"></my-nested-component>
    nestedComponentInitialized() {
    this.myNestedComponentIsInitalized = true;
    this.myNestedComponent.theMethod(); // Works now!
    }
    

    这种方法的问题是,当RootComponent想用myNestedComponent 做一些事情时,他需要检查this.myNestedComponentIsInitalized

  2. 当您使用@ViewChild()的setter时,可以避免这种初始化方法。但每个@ViewChild仍需要一些代码

    myNestedComponent: MyNestedComponent;
    @ViewChild() set myNestedComponentSetter(myNestedComponent: MyNestedComponent) {
    this.myNestedComponentIsInitalized = true;
    this.myNestedComponent = myNestedComponent;
    }       
    
  3. @ViewChild更改为@ViewChildren,这样就可以侦听该列表的更改,这意味着组件已初始化

    @ViewChildren() myNestedComponents : QueryList<MyNestedComponent>;
    ngAfterViewInit() {
    restService.get().then((result) => {
    this.dto = result;
    this.something = true;
    this.myNestedComponents.changes.subscribe(() => {
    this.myNestedComponentIsInitalized = true;
    this.myNestedComponents[0].theMethod();
    }
    }
    }
    

    这种方法的问题在于,您只能侦听列表中的更改,当您已经知道RootComponent中只有一个MyNestedComponent时,这会让人感到奇怪。这样做的好处是,您可以订阅初始化事件。

例如,当您有4个不同的嵌套组件时,这三个"更好的解决方案"将成为一场噩梦。因为这意味着RootCompoment需要4种初始化方法(或4种QueryList),其中4种布尔值用于嵌套组件的状态。管理这一切变得非常复杂。

  1. 不要使用*ngIf,而是使用[hidden],这样一切都会一直存在。这听起来不错,但这意味着一切都会被渲染,有时你仍然需要检查是否存在某些东西。因为这是不可能的(因为dto.name可能引发异常,因为dto还没有被REST调用初始化):

    <div id="root-div" [hidden]="dto">
    <div id="nested-div-1" [hidden]="something">
    <my-nested-component>
    </my-nested-component>
    </div>
    <div id="nested-div-2" [hidden]="!somethingElse">
    <div #anotherChild>
    <span>{{dto.name}}</span> <!-- EXCEPTION, dto is not there yet! -->
    </div>
    </div>
    <button (onClick)="setSomethingElse()">Button</button>
    </div>
    

优雅的解决方案

我正在寻找一种解决方案,它可以管理由于*ngIf条件而不总是初始化的组件的状态。

我想做一些事情,比如:

  • 等待REST请求完成并初始化my-nested-componentanotherChild
  • anotherChild被初始化时,将anotherChild的组件引用传递给my-nested-component(因此dtosomethingElse为真),并且my-nested-component也在那里(所以something也为真)

TLDR

当嵌套的Component由于*ngIf而不总是存在时,管理它们的最佳模式或方法是什么?

我认为最好的解决方案是根据容器/组件的方法来设计有角度的项目。

这意味着您有处理数据的容器(父容器)和仅基于输入值进行渲染的子容器。所有导致数据更新的操作都应该通过子级向其父级的输出发出,父级更新数据,并通过输入将更新的数据再次传递给子级。

因此,您的数据流将始终是这样的:服务->容器->组件。

这个概念主要来自通量/冗余模式和反应,但它在角度上也提供了很多优势。好处是:

  • 很少使用viewchild(未定义总是一个问题)
  • 更好地使用ChangeDetection策略(儿童可以经常使用ChangeDetection.OnPush)
  • 它迫使我们真正思考应用程序结构
  • 它产生了可读性更强的代码,并且没有过载的组件

对于Rest请求,将异步管道与ngIf结合使用也很好,当所有数据都可用时,您的孩子就会得到渲染。看看这里:https://toddmotto.com/angular-ngif-async-pipe

容器/组件结构的良好指南可在此处找到:https://www.thegreatcodeadventure.com/the-react-plus-redux-container-pattern/

还有这里:https://blog.angular-university.io/angular-2-smart-components-vs-presentation-components-whats-the-difference-when-to-use-each-and-why/

我知道这是一个巨大的话题,我无法在一篇文章中解释它的复杂性,但也许它会给你一些灵感。