当由于*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-div
、nested-div-1
和my-nested-component
。当您执行以下操作时,您将获得
undefined
异常:@ViewChild() myNestedComponent : MyNestedComponent; ngAfterViewInit() { restService.get().then((result) => { this.dto = result; this.something = true; this.myNestedComponent.theMethod(); } }
这是完全合理的,因为Angular还没有更新视图,所以
myNestedComponent
是未定义的
坏/破解解决方案
- 使用
ChangeDetectorRef.detectChanges()
强制Angular更新视图 - 将
this.myNestedComponent.theMethod()
封装在setTimeout
中,这将延迟该行的执行,直到Angular完成他的循环
更好的解决方案
给
my-nested-component
一个@Output
事件,以便my-nested-component
在完成初始化(例如在my-nested-component
的ngAfterViewInit
中)时可以执行RootComponent
中的方法<my-nested-component (afterInit)="nestedComponentInitialized()"></my-nested-component> nestedComponentInitialized() { this.myNestedComponentIsInitalized = true; this.myNestedComponent.theMethod(); // Works now! }
这种方法的问题是,当RootComponent想用myNestedComponent 做一些事情时,他需要检查
this.myNestedComponentIsInitalized
当您使用
@ViewChild()
的setter时,可以避免这种初始化方法。但每个@ViewChild
仍需要一些代码myNestedComponent: MyNestedComponent; @ViewChild() set myNestedComponentSetter(myNestedComponent: MyNestedComponent) { this.myNestedComponentIsInitalized = true; this.myNestedComponent = myNestedComponent; }
将
@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种布尔值用于嵌套组件的状态。管理这一切变得非常复杂。
不要使用
*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-component
和anotherChild
- 当
anotherChild
被初始化时,将anotherChild
的组件引用传递给my-nested-component
(因此dto
和somethingElse
为真),并且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/
我知道这是一个巨大的话题,我无法在一篇文章中解释它的复杂性,但也许它会给你一些灵感。