在一个项目中,我想将我的库切换到ivy部分编译模式(角度12)。但现在遇到了一些令人讨厌的循环依赖错误:
错误 来自示例
✖ Compiling with Angular sources in Ivy partial compilation mode.
An unhandled exception occurred: projects/circ/src/lib/tab-container/tab-container.component.ts:4:1 - error NG3003: One or more import cycles would need to be created to compile this component, which is not supported by the current compiler configuration.
4 @Component({
~~~~~~~~~~~~
5 selector: 'my-tab-container',
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
...
42 }
~~~
43 }
~
projects/circ/src/lib/container/container.component.ts:4:1
4 @Component({
~~~~~~~~~~~~
5 selector: 'my-container',
~~~~~~~~~~~~~~~~~~~~~~~~~~~
...
41 }
~~~
42 }
~
The component 'ContainerComponent' is used in the template but importing it would create a cycle: C:/projects/ng-test-projects/circular-dep-lib/projects/circ/src/lib/tab-container/tab-container.component.ts -> C:/projects/ng-test-projects/circular-dep-lib/projects/circ/src/lib/container/container.component.ts -> C:/projects/ng-test-projects/circular-dep-lib/projects/circ/src/lib/tab-container/tab-container.component.ts
很明显为什么会出现循环,但我无法找到解决方案来使其发挥作用。组件 A 内部有组件 B,而 B 内部有组件 A。它创建了类似用户可定义的 UI 之类的东西。一个组件可以包含另一组动态添加的组件,如果您愿意,可以递归。
编辑:请考虑文档中的此信息:https://angular.io/errors/NG3003#libraries“正常”项目可以使用此循环依赖,但库则不行!在 stackblitz 中,所有示例都有效,甚至是我的,因为在 stackblitz 上它不是一个库。
原来的项目相当大,有一些这样的循环。这是一个简单的例子:
容器.component.ts
import { Component, Input, OnInit } from '@angular/core';
import { UiItemLike } from '../ui-item-like';
@Component({
selector: 'my-container',
template: `
<h2>Container</h2>
<ng-container *ngFor="let item of uiItems">
<!-- Container -->
<!-- This recursion is working! The component itself is not "importing" from an other file -->
<ng-container *ngSwitchCase="item.type === 'Container'">
<my-container [uiItems]="item.uiItems"></my-container>
</ng-container>
<!-- Tab-Container -->
<ng-container *ngSwitchCase="item.type === 'TabContainer'">
<!-- Thats the circular dependency -->
<my-tab-container [uiItems]="item.uiItems"></my-tab-container>
</ng-container>
<!-- Button -->
<ng-container *ngSwitchCase="item.type === 'Button'">
<my-button></my-button>
</ng-container>
</ng-container>
`,
styles: [``]
})
export class ContainerComponent implements OnInit, UiItemLike {
@Input() uiItems: UiItemLike[];
readonly type: "Container";
constructor() {}
ngOnInit(): void {}
}
tab-container.component.ts
import { Component, Input, OnInit } from '@angular/core';
import { UiItemLike } from '../ui-item-like';
@Component({
selector: 'my-tab-container',
template: `
<h2>TabContainer</h2>
<div>
<h3>Fake tab 1</h3>
<!-- Can contain more items -->
<!-- Thats the circular dependency -->
<my-container [uiItems]="uiItems.uiItems"></my-container>
</div>
<div>
<h3>Fake tab 2</h3>
<!-- ... -->
</div>
`,
styles: [``]
})
export class TabContainerComponent implements OnInit, UiItemLike {
@Input() uiItems: UiItemLike[];
readonly type: "TabContainer";
constructor() { }
ngOnInit(): void {
}
}
迁移到 Angular 12 后遇到同样的问题。能够通过启用 Ivy 解决循环 DI,只需很少的 hack。
我的问题是链的循环DI:
ConfirmationModalComponent
,其中通过OutputHtmlComponent
包含动态内容,通过ClickableLabelDirective
支持可点击元素,它重用ActionsService
保存业务逻辑,并且信件包含ConfirmationModalComponent
上的链接再次传入 ModalService
为 open 方法,这会导致 DI 中描述的循环以及在打开 Ivy“部分”模式的构建 lib 期间出现错误。
换句话说,我使用了高级组件
FormPlayerComponent
并将所需组件 ConfirmationModalComponent
注入到高级服务 ScreenService
中,然后通过定期导入该 ActionService
服务,在低级服务 ScreenService
上重用该组件.
因此会稍微增加运行时的内存使用量,因为到ConfirmationModalComponent的“链接”将在服务的道具中分配,但就我而言,这是可以接受的决定,因为我不必重写和重新排列整个应用程序来满足新的Angular要求 -避免递归组件,就好像它在编程世界中是不自然的一样。
可以使用
ng-template
来实现递归组件调用,您可以在此处查看 stackblitz 示例。主要要求如下:
initialData = [
{ content: 'hello2' },
{ content: 'hello3' },
{
content: 'hello4',
recursiveData: [
{ content: 'hello4.1' },
{ content: 'hello4.2' },
{
content: 'hello4.3',
recursiveData: [
{ content: 'hello4.3.1' },
{ content: 'hello4.3.1' }
]
}
]
}
];
<!-- initial call -->
<app-my-component [recursiveData]="initialData || []">helloMain</app-my-component>
您可以看到组件的递归部分
<app-my-component/>
在第8行被调用:
<!-- app-my-component html-template -->
<ng-template #myComponentTemplate>
<div>
... some content ...
<ng-content></ng-content>
</div>
<div *ngFor="let d of recursiveData">
<!-- recursive call -->
<app-my-component [recursiveData]="d.recursiveData || []">{{d.content || ''}}</app-my-component>
</div>
</ng-template>
<ng-container *ngTemplateOutlet='myComponentTemplate'></ng-container>
这样做的原因是 Angular 为每个
ng-template
创建单独的实例。我之前也遇到过类似的问题,已在this other question解决。
在
*ngIf
元素上使用 *ngSwitchCase
而不是 ng-container
可以解决您的问题。
而且您的代码中也存在很多逻辑问题。
我提供了一个 stackblits,它可以与您为这两个组件提供的任何圆形组件结构配合良好。