我们的团队正在从AngularJS迁移到Angular(4)。
在我们的Angular组件中,我们使用RxJS库来处理从Http,ActivatedRoute等服务或从用户界面上的输入接收值的主体返回的observable。当对这些可观察量产生的值进行某些处理时,我们将它们映射或组合成新的可观察量以产生“派生的”可观察量。这里没什么特别的。
我们最终得到的组件几乎所有从模板绑定的公共属性都是可观察的。
然而,这种技术似乎有很大的缺点:
举个例子,这是一个简单的案例(目前为止不是最差的):分页组件的模板。注意大量的异步管道,其中一些嵌套在一个结构指令中,本身是异步的(导致我刚刚提到的bug)。
<nav aria-label="Pagination" class="mt-5" *ngIf="($items | async)?.length">
<ul class="pagination">
<li class="page-item" [class.disabled]="(currentPage$ | async) === 1">
<a class="page-link" [routerLink]="" [queryParams]="{ page: (currentPage$ | async) - 1 }" queryParamsHandling="merge">Previous</a>
</li>
<li *ngFor="let page of (pages$ | async)" class="page-item" [class.active]="page === (currentPage$ | async)">
<a class="page-link app-page-number" [routerLink]="" [queryParams]="{ page: page }" queryParamsHandling="merge">{{ page }} <span class="sr-only" *ngIf="page === (currentPage$ | async)">(current)</span></a>
</li>
<li class="page-item" [class.disabled]="(currentPage$ | async) === (lastPage$ | async)">
<a class="page-link" [routerLink]="" [queryParams]="{ page: (currentPage$ | async) + 1 }" queryParamsHandling="merge">Next</a>
</li>
</ul>
</nav>
将“派生”可观察对象更改为简单(不可观察)属性,从.subscribe()填充有助于降低代码复杂性以及模板内异步管道的数量。但这并不是一种令人满意的解决方案。
此外,我们遇到了一些与OnPush策略相关的变更检测相关的新问题。由于我们将可观察的属性转换为“普通”属性并删除了异步管道,因此Angular不再知道我们正在更改这些属性,我们经常需要调用ChangeDetectorRef.detectChanges()。
到目前为止,我们还没有找到关于如何在非平凡组件中处理RxJS的明确最佳实践。您对如何解决这些困难有什么建议吗? Redux可以解决(某些)我们的问题吗?
编辑:事实证明,上述“错误”实际上是我们对RxJS库的误用。有关详细信息,请参阅我对(GitHub issue)的贡献。
答案 0 :(得分:2)
更改检测
*ngIf="(obs | async)"
的问题是变化检测问题。变量obs
在发射时本身不会改变,再加上这是模板中的表达式,使得变化检测难以检测。
在这种情况下,有几个原则值得考虑:
可观察变量是“管道”,即通过它们的值的包装器而不是更改值本身。这两者通常是等同的,但这类似于认为数组和数组元素是同一个东西。
RxJs是一个'外部'(非Angular)库。使用这些库,我们需要知道库处理的数据更改是否对Angular更改检测可见。
#10165的修正
<div style="background-color: green;" *ngIf="trigger">{{(val1 | async)}}</div>
<div style="background-color: green;" *ngIf="!trigger">{{(val2 | async)}}</div>
ngOnInit() {
this.trigger = this.ifObservable.subscribe();
}
处理异步数据
关于“各地可观察的”这个更广泛的问题,你是对的,但这不是网络应用程序异步性质固有的问题吗?
将旧的AngularJS代码与新的Angular代码进行对比会很有趣。同一组功能实现的复杂性是否有所增加?
我喜欢应用的原则是通过订阅来处理“{one}”可观察对象,例如http.get
,并保持通过异步管道连接的“多镜头”可观察对象。
OnPush
这实际上是一种减少自动更改检测量的方法,从而加快应用程序的速度。当然,这意味着您可能需要更频繁地手动进行火灾变化检测 - 速度与复杂性的权衡。
<强>终极版强>
虽然Redux存储通常将状态公开为可观察对象(因此您仍然需要模板中的异步管道),但它确实消除了多个状态更新点的复杂性,这可能意味着需要更少的可观察量(或者至少可以观察到可观察量)远离组件)。
我所看到的唯一不涉及在模板中去除异步数据的Redux风格是Mobex,它实际上将简单变量转换为具有getter和setter的对象,其中包含要监视的逻辑异步更改并解包它们。但它对阵列也存在问题/警告。
简化模板
可能 简化您展示的模板的一种方式(我还没有测试过)是将它包装在子组件中并传入未包装的值
<nav aria-label="Pagination" class="mt-5" *ngIf="items.length">
<ul class="pagination">
<li class="page-item" [class.disabled]="currentPage === 1">
<a class="page-link" [routerLink]="" [queryParams]="{ page: currentPage - 1 }" queryParamsHandling="merge">Previous</a>
</li>
<li *ngFor="let page of pages" class="page-item" [class.active]="page === currentPage">
<a class="page-link app-page-number" [routerLink]="" [queryParams]="{ page: page }" queryParamsHandling="merge">{{ page }} <span class="sr-only" *ngIf="page === currentPage">(current)</span></a>
</li>
<li class="page-item" [class.disabled]="currentPage === lastPage">
<a class="page-link" [routerLink]="" [queryParams]="{ page: currentPage + 1 }" queryParamsHandling="merge">Next</a>
</li>
</ul>
</nav>
export class PageComponent {
@Input() currentPage = 0;
@Input() pages = [];
@Input() items = [];
并在父母中
<page-component [pages]="pages$ | async" [items]="items$ | async" [currentPage]="currentPage$ | async" >
即使使用onPush策略,@ Input也会引发变更检测 小心给孩子输入默认值。