我正在提交... (用“x”勾选)
[ ] bug report => search github for a similar issue or PR before submitting
[X] feature request
[ ] support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question
@DzmitryShylovich要求的 #12275 的后续问题
当前行为
延迟加载的 NgModule 的entryComponents
不能使用ComponentFactoryResolver
渲染。 错误信息: No component factory found for {{entryComponent}}
预期行为
entryComponents
应该像导入模块一样可用
使用说明最小化重现问题
http://plnkr.co/edit/9euisYeSNbEFrfzOhhzf?p=preview
我创建了一个简单的设置,类似于我们内部使用的设置。 Main
组件提供了一种渲染Type
的方法。 EntryComponent
$ 在Page1Module
$ 中声明为entryComponent
#$ 。 但是,在加载Page1Module
之后,当尝试通过ComponentFactoryResolver
渲染EntryComponent
$ 时,会抛出No component factory found for EntryComponent
。
改变行为的动机/用例是什么?
在根级别渲染entryComponents
个子模块。 这种方法的用例是
请告诉我们您的环境:
我们目前使用的是 Angular 2.1.1,但这也会影响最新版本的 Angular (2.4.6)(参见 plnkr)。
经过一番调查,我发现在你的作品中是按设计的。
入口组件遵循与延迟加载模块中的提供者相同的规则。 https://angular.io/docs/ts/latest/cookbook/ngmodule-faq.html#! #q-lazy-loaded-module-provider-visibility
它们仅在延迟加载的模块中可见。
好的,如果这是预期的行为,那么如何完成上述用例等特定任务?
我不认为炸毁主模块是一个合适的解决方案。
如果你想在MainComponent
中使用入口组件,那么你应该在AppModule
中提供它
所以你的解决方案确实是将所有内容都移到主模块中?
IMO 这完全打破了 Angular 的模块概念,它应该允许将功能块组合到一个模块中。
这基本上意味着像模态这样的东西不可能与延迟加载架构一起使用。
您需要使用类似于材料https://material.angular.io/components/component/dialog的 api
然后它应该工作
哇,所以你的意见是我们应该像 Angular Material 那样移动 DOM 内容。 见这里: https ://github.com/angular/material2/blob/master/src/lib/core/portal/dom-portal-host.ts#L30 -L55
如果这真的是在 Angular 中处理这个问题的最佳实践,那么基本上我们可以转储 Angular 并重新开始使用 JQuery。
对不起,但我不能认真对待这种方法。
我不知道模态和延迟加载的最佳方法是什么,但无论如何在你的 plunkr 中它按预期工作并且这里没有角度错误。
对于模态的讨论,最好使用支持通道,例如 gitter 和 stackoverflow
好的,在这种情况下,我将此问题更改为功能请求。
IMO 这些用例应该由 Angular 自己处理,而不是通过手动沿着 DOM 移动。
今天面临同样的问题 - 在延迟加载模块中定义的模态组件不由应用程序模态服务处理(尽管使用entryComponents
,但它无法找到它)。 被迫将其移动到破坏惰性模块结构和使用逻辑的主包:困惑:
同样的问题......不可能在惰性加载模块中使用 entryComponents 而不会破坏惰性模块逻辑:我的 LazyLoaded 模块依赖于我的 AppModule 中声明的 entryComponent :(
遇到同样的问题,尽管在查看了 ng-bootstrap 模态源代码后,我确实得出了一个解决方案。 本质上的问题(根据我的理解,如果我错了,请纠正我)是当你的应用程序被引导时你的惰性模块不包含在初始注入器中,并且初始注入器一旦设置就不能被修改。 这就是您在 OP 中收到错误的原因。 当您的惰性模块之一被加载时,它会获得自己的注入器,其中引用您在惰性模块中声明或提供的任何内容。 话虽如此,只要您创建动态组件的任何位置都引用了正确的injector
和componentFactoryResolver
,您确实可以在延迟加载模块之外引用entryComponent
。
所以,我所做的是创建一个单例服务来存储我要创建的动态组件的injector
和componentFactoryResolver
。 此服务需要在您的惰性模块之外。 然后,每当我去动态创建一个组件时,我都可以调用这个服务来获得我需要的东西。
下面的代码将在您在惰性模块之外创建动态组件的任何位置
@ViewChild('parent', {read: ViewContainerRef}) parent: ViewContainerRef;
private componentRef: ComponentRef<any>;
...
const childComponent = this.componentFactoryResolver.resolveComponentFactory(yourComponentRef);
this.refInjector = ReflectiveInjector.resolveAndCreate([{provide: yourComponentRef, useValue: yourComponentRef}], this.injector);
this.componentRef = this.parent.createComponent(childComponent, 0, this.refInjector);
```html
Then in your parent component for your entryComponent you'll inject `injector` and `componentFactoryResolver` and set their values in the shared service:
```js
constructor(private componentFactoryResolver: ComponentFactoryResolver, private injector: Injector) {}
如果这没有任何意义,请告诉我,我可以进一步详细说明。 编辑我忘了提到我正在使用 Angular 2.4.9。
谢谢@jmcclanahan。 您能告诉我们更多关于该服务的外观和设置方式吗? 我正在努力解决类似的问题,尝试将一个延迟加载模块中的组件动态加载到另一个延迟加载模块中的组件中,即使(我认为)我已经在正确的位置添加了入口组件,但我被告知我没有。 谢谢
@gcorgnet - 当然。 所以我的用例是,我的应用程序的每个页面都有一个通用工具栏,但在工具栏中,我想根据我所在的页面添加按钮和工具以实现额外的功能。 为了实现这一点,我创建了一个组件来保存这些按钮/工具及其逻辑,并且我想将这些组件保留在它们关联的模块中。 因此,在我的模块之外,基本上对这些特定的工具栏功能一无所知,同时仍然能够在公共工具栏上显示它们。 以下是我的工作解决方案,希望对您有所帮助:
我对工具栏服务所做的只是创建一个可观察对象,因此您的惰性组件可以将componentFactoryResolver
和injector
引用传递给正在侦听receiveContext
的工具栏公共组件
工具栏.service.ts
@Injectable()
export class ToolbarService {
contextReceivedSource = new Subject<any>();
contextReceived$ = this.contextReceivedSource.asObservable();
receiveContext(componentFactoryResolver: ComponentFactoryResolver, injector: Injector) {
this.contextReceivedSource.next({ resolver: componentFactoryResolver, injector: injector });
}
}
在您的延迟加载组件中,您需要注入componentFactoryResolver
和injector
并在工具栏服务中触发一个事件。
延迟加载的工具栏组件
...
constructor(private componentFactoryResolver: ComponentFactoryResolver, private injector: Injector) {}
...
ngOnInit() {
this.toolbarService.receiveContext(this.componentFactoryResolver, this.injector);
}
...
最后,在我的核心工具栏组件中,我订阅了工具栏服务中的 observable,以便在任何时候触发事件时,它都会尝试注入并创建我需要的惰性组件。 销毁您创建的componentRef
也很重要,否则最终会导致内存泄漏。
...
@ViewChild('toolbarTarget', {read: ViewContainerRef}) toolbarTarget: ViewContainerRef;
component: Type<Component>;
refInjector: ReflectiveInjector;
resolverSub: Subscription;
refInjector: ReflectiveInjector;
componentFactoryResolver: ComponentFactoryResolver;
injector: Injector;
ngOnInit() {
this.resolverSub = this.toolbarService.contextReceived$.subscribe(resolver => {
this.componentFactoryResolver = resolver.resolver;
this.injector = resolver.injector;
});
}
updateToolbar(data: any) {
this.clearComponent();
this.component = data['toolbar'];
if (this.component) {
const childComponent = this.componentFactoryResolver.resolveComponentFactory(this.component);
this.refInjector = ReflectiveInjector
.resolveAndCreate([{provide: this.component, useValue: this.component}], this.injector);
this.componentRef = this.toolbarTarget.createComponent(childComponent, 0, this.refInjector);
}
}
clearComponent() {
this.toolbarTarget.clear();
if (this.componentRef) { this.componentRef.destroy(); };
}
ngOnDestroy() {
this.resolverSub.unsubscribe();
this.clearComponent();
}
然后,您动态创建的组件将放置在您放置相应#toolbarTarget
的任何位置
<div #toolbarTarget></div>
顺便说一句,我在公共工具栏组件中使用这一行this.component = data['toolbar'];
从路由中获取延迟加载的组件引用。 如果您想查看完整的代码,我可以发布它,但这超出了本次讨论的范围。
{ path: '', component: yourComponent, data: { toolbar: yourToolbarComponentToInject } }
如果您还有其他问题,请告诉我!
@jmcclanahan我对您的代码有一个问题(可能是一个愚蠢的问题),当您提到“延迟加载的工具栏组件”时,您将服务调用代码放在 ngOnInit 部分,但是您如何设法初始化它(组件)无需将其添加到 HTML 代码并稍后将其隐藏在 DOM 中? 由于该组件只会在顶部栏中使用,因此我之前不会在其他任何地方加载它。
@Dunos - 我意识到我在上面的示例中完全忽略了这部分,对此感到抱歉! 您需要将动态组件作为entryComponent
添加到与其关联的延迟加载模块中。
然后在工具栏组件本身中,您将定义一个ViewContainerRef
,它将引用您希望动态组件显示的 html 模板中的位置。 然后 updateToolbar() 方法将负责创建您的组件,将其放置在指定位置并在工具栏上显示该组件。
@ViewChild('toolbarTarget', {read: ViewContainerRef}) toolbarTarget: ViewContainerRef;
...
updateToolbar(data: any) {
this.clearComponent();
this.component = data['toolbar'];
if (this.component) {
const childComponent = this.componentFactoryResolver.resolveComponentFactory(this.component);
this.refInjector = ReflectiveInjector
.resolveAndCreate([{provide: this.component, useValue: this.component}], this.injector);
this.componentRef = this.toolbarTarget.createComponent(childComponent, 0, this.refInjector);
}
}
然后将您动态创建的组件放置在您放置相应#toolbarTarget 的任何位置
<div #toolbarTarget></div>
每当运行 ngOnDestroy() 时都会调用 clearComponent() 方法,该方法负责在您离开页面时隐藏组件。
clearComponent() {
this.toolbarTarget.clear();
if (this.componentRef) { this.componentRef.destroy(); };
}
ngOnDestroy() {
this.resolverSub.unsubscribe();
this.clearComponent();
}
我还更新了我上面的原始帖子。 我收到了很多关于这个的问题,所以我会尽快添加一个使用这个动态工具栏的工作示例,并通过链接更新你们。
@jmcclanahan我得到了(我认为),但我仍然不明白如何从动态组件( this.toolbarService.receiveContext调用部分)调用服务,因为 ngOnInit 从未被调用(或者我没有)不知道怎么做)。 我添加到 entryComponents 但需要对其进行一些初始化以使 onInit 工作。
@Dunos - 也许一个例子会是最好的。 假设我正在从我的应用程序的索引页面导航到页面 A。您将拥有页面 A 的相应路由,它将定义页面 A 的模块/组件。 页面 A 还将定义一个单独的组件,我想在全局工具栏中显示它。 因此,在页面 A 的主要组件中,您将注入ComponentFactoryResolver
和Injector
,然后触发receiveContext
事件。 这段代码:
constructor(private componentFactoryResolver: ComponentFactoryResolver, private injector: Injector) {}
...
ngOnInit() {
this.toolbarService.receiveContext(this.componentFactoryResolver, this.injector);
}
抱歉,我知道这可能会造成混淆。 您不会将该代码放在要在工具栏中显示的组件中,而是放在与您所在页面关联的组件中。
@jmcclanahan啊完美,非常感谢!!! 现在可以工作了:)
@jmcclanahan在找到这篇文章之前我刚刚解决了同样的问题。 我注意到的一件事是,如果您想在函数声明中保存一些字符,您可以使用您传递的 Injector 获取对 ComponentFactoryResolver 的引用。 你可以这样做:
injector.get(ComponentFactoryResolver);
这应该返回您需要的参考。 但最终它给出的结果与将 ComponentFactoryResolver 作为参数传入函数中的结果相同。
这也解决了我的问题。
总之,您需要知道的是,当您想要动态创建在惰性模块中声明的组件时,您需要实际使用在该惰性模块中声明的组件中的 ComponentFactoryResolver。 根注入器(包含根 ComponentFactoryResolver)将不知道在加载的lazyModule 中声明的任何入口组件。
看起来有点奇怪,但这就是 Angular 的设计方式(这是有道理的),但这实际上在使用框架时会产生额外的复杂性,恕我直言。
Angular 团队如何利用这一点:每当激活路由时,根注入器应自动附加该lazyModule 上列出的entryComponents,并且每当更改路由时,应自动销毁先前附加的entryComponents,这将确保降低复杂性并避免过度泛滥带有额外组件工厂的根注入器。
我将为此提交功能请求。
谢谢!
非常感谢@jmcclanahan
@jmcclanahan非常感谢!! @renatoaraujoc您是否已将此功能提交给 Angular 团队?
@rraya我已经提交了关于这个“问题”的功能请求(如果你考虑一下,这并不是一个真正的问题),请参阅#17168。
只是为了补充这一点。 我的用例是我在我的 AppModule 中创建了一个 ModalManagerService,它有一个 open 方法,应用程序的所有其他部分都调用它来打开一个模式。 它传递了一个组件,然后将其作为模式打开。 我还有一个 closeAll 方法,当模式需要通过某些操作而不是用户关闭它们时使用,例如由于会话超时导致的路线更改。
@jmcclanahan ,但我仍然不理解你的,如果有一些建议,非常感谢。似乎在 'this.componentFactoryResolver.resolveComponentFactory' 出现了一些错误
@Dunos你能帮帮我吗?几周以来我都遇到了同样的问题,真的很头疼。
@werts ,
您需要了解的是,每当您导航到延迟加载的路由时,激活的路由将生成一个子注入器,其中包含该延迟模块的 entryComponents。 如果您在 AppModule(它是应用程序的根注入器)中定义了全局 modalService,则 componentFactoryResolver 将不知道任何来自任何 lazyModule 的新 entryComponents。 所以,解决这个问题的方法之一是从一个lazyModule 的组件中获取complementFactoryResolver 的实例,并以某种方式告诉modalService“嘿,使用这个注入器/componentFactoryResolver 来渲染想要的entryComponent”。
似乎有点难以理解,但事实并非如此。
@renatoaraujoc感谢您的解释,在我的用例中,我想从链接加载组件,也就是说,我单击左侧边栏中的菜单,我将从延迟加载的模块中检索模块,然后,我将创建一个动态组件,因为我想构建一个类似标签的应用程序,所以,在右侧的主要内容区域,有一些组件内容,如果我点击标签触发器,内容将显示。或者有没有任何解决方案构建标签-喜欢通过 angular2/4 的应用程序?
无论如何,lazyload 组件永远不会被初始化,我如何告诉服务它的 componentFactoryResolver?
对于我的应用程序,有一个AppModule,它包含CoreModule,所以,我的ContentTabModule(用于加载标签集)属于CoreModule,所有由ContentTabModule处理的创建组件,它有服务,组件,指令,管道,当点击一个菜单链接,ContentTabService 将从菜单参数创建一个动态组件。我已经看到在网络面板上加载了一些主干(我使用 webpack)。然后我想创建在延迟加载模块上演示的组件。
@renatoaraujoc我怎样才能从一个lazyModule 的组件中获取complementFactoryResolver 的实例?我认为这要困难得多,因为lazymodule 的组件永远不会被初始化(或者我错了)
@werts检查上面我与@jmcclanahan的对话,我们用工具栏讨论过类似的东西。
@Dunos我无法理解来自@jmcclanahan的示例,我想我遭受了同样的痛苦。lazyload 组件永远不会初始化。
this.toolbarService.receiveContext() 从延迟加载模块调用?
@werts您必须使用路由加载惰性模块的主要组件,并且该组件是使用其Injector和ComponentFactoryResolver调用辅助服务的组件。
@Dunos谢谢,如果方便的话,你能帮我一把吗,其他一些用例,我想从链接点击延迟加载模块,然后创建动态组件。可以吗?
@werts ...是的,这是可能的。 查看https://github.com/Toxicable/module-loading作为起点。
好的,现在试试,thx。 @mlc-mlapis
@werts等人:我可以建议您将此支持线程带到另一个频道吗? StackOverflow 或 Gitter 可能更合适。 事实上,我订阅了这个问题(我相信还有很多其他问题),它现在产生了很多噪音,这有点离题了。
从好的方面来说:很高兴看到这样一个乐于助人的社区!
我不敢相信这个问题也是我必须面对延迟加载模块的另一个障碍。 我一直在拼命寻找一种解决方案,以使我的惰性模块的子级对其父级(app.module)保持匿名,以便我可以在需要时轻松删除/导出惰性模块。 对于像 app.component.html 中的应用程序范围<router-outlet name="app-popup">
(我已经放弃,希望 ComponentFactory 可以取代它的工作)这样简单的事情,仅仅加载懒惰的子组件非常令人生畏。 直到现在才遇到惰性模块入口子项的入口组件问题。 如果我需要根模块来注册惰性子组件,则无法正确隔离我的模块。 我只是希望 Angular 团队尽快意识到这是一个问题并解决它。 我仍在等待他们使用适用于惰性模块的应用程序范围的路由器插座来回应我的问题。 https://github.com/angular/angular/issues/19520
一开始什么都没有
'让我们创建一个视图! ',jQuery 团队说,
'我们列个清单吧! ',Angular 团队说。
@jmcclanahan我们现在有了 ngComponentOutlet,它让事情变得简单多了。
<ng-container *ngComponentOutlet="yourComponentClass;injector: yourInjector;"></ng-container>
@crysislinux ngComponentOutlet 很棒,但它不允许我们将数据传递给组件,所以它不是一个解决方案。
有关此功能的任何消息? =/
我刚遇到这个问题。 尝试了几个建议,没有成功。 不过,我让它工作了。 事实证明,我延迟加载的模块缺少另一个依赖模块的导入——在本例中为 NGX 模态模块——但该错误指责我的组件不可解析。
我很好奇:这个问题是通过使用ui-router解决的吗? 或者甚至可能是 Angular 自己的路由器多出口功能? 他们在幕后做这个逻辑吗? 这对我来说是 angular 1.x 中的一个非常常见的情况,我正在尝试将其转换为 angular 2+
在工具栏示例中,将在更高级别的模块中设置 ui-view(可能是命名视图,或在角路由器的情况下的路由器出口)。 如果延迟加载的模块状态将工具栏 ui-view 替换为它自己的内容,则它必须只是“工作”,否则 ui-router 将无法在当前模块之外的更高级别视图需要替换的延迟加载环境中运行. 与角度 1.x 相比,这将是极其有限的。
上面的示例似乎解决了 ui-router/angular 路由器旨在解决的相同问题。 我错过了什么吗?
...我们可能完全失去了问题所在。 正如我们每天都知道和使用的那样,使用延迟加载模块中的组件没有问题......即使通过路由器或通过我们自己的代码以编程方式。
当使用上面的@jmcclanahan示例时,它似乎适用于我的用例。
我必须在this.componentRef = this.toolbarTarget.createComponent(childComponent, 0, this.refInjector);.
this.componentRef.changeDetectorRef.markForCheck();
#$
这解决了我在 HostBinding 属性未触发时遇到的问题。
我想我会尝试一个对我有用的潜在解决方案。 这是原始 plunkr 的一个分支。
http://plnkr.co/edit/x3fdwqrFDQr2og0p6gwr?p=preview
要解决此问题,您需要从具有 entryComponent (Page1Module) 的模块中提供的服务 (ResolverService) 解析组件工厂。 然后将其传递给第二个服务(ChangerService),该服务使用已解析的工厂并将组件添加到 MainComponent 的视图中。
我真的不明白这个该死的路由器。 检查这个 plunker(来自@patkoeller的示例)
http://plnkr.co/edit/keMuCdU9BwBDNcaMUAEU?p=preview @ src/page1.ts
我基本上想要做的是将一些组件注入顶级应用程序并使用[routerLink]
指令以及与其定义的延迟加载模块相关的命令,例如: [routerLink]="['./', 'sub', 'component']
生成/page-1/sub/component
。 如果我用一些ActivatedRoute
将注射器传递给createComponent
$ 并且它适用于我的情况,它会搞砸所有其他 routerLink,但是如果我将router.routerState.snapshot
重新分配给state
从解析中传递并使用此路由器实例传递注入器,它会按预期工作......非常令人困惑!
我应该提交错误吗?
我的对话单例服务的入口组件面临同样的问题。 与功能相关的模态组件应保留在各自的模块中,而不需要@jmcclanahan的解决方法。
这有什么新的预计到达时间吗? 或者至少是 Angular 团队的回应? 没有这个功能是一个真正令人沮丧的事情,因为模块封装刚刚被扔出窗外。 @angular/material 的所有对话框都需要在根模块中导入。
@xban1x我认为我们在这方面没有任何进展。 我们必须绕过它:(
@jmcclanahan是否可以使用您的方法获得注入组件的参考? 我已经成功注入了一个组件,它是一个树顺便说一句,所以现在我需要订阅组件中的树单击事件,它注入了树。 有没有办法做到这一点?
在问题 #23819 中,我询问了如何使入口组件的行为方式与providesIn: 'root'
的服务类似。 关闭了这个问题,因为这两个问题似乎都是为了解决同一件事。 我希望这个问题能很快得到解决,因为这是一个重要的特性:)
更新
从我在非缩小代码中看到的,当providesIn
在服务中定义时,它注册工厂以便它已经可以使用(不需要在模块中导入它),如果我理解的话正确:
var Injectable = makeDecorator('Injectable', undefined, undefined, undefined, function (injectableType, options) {
if (options && options.providedIn !== undefined &&
injectableType.ngInjectableDef === undefined) {
injectableType.ngInjectableDef = defineInjectable({
providedIn: options.providedIn,
factory: convertInjectableProviderToFactory(injectableType, options)
});
}
});
我假设有一种方法可以通过@Component({ ... entryComponent: true })
之类的选项在组件中应用类似的逻辑,以便在加载包含它的块时,在解析器中定义工厂(应该有一个包含entryComponents
中的组件与组件依赖关系,否则会导致编译时错误)。
@lucasbasquerotto ...我们使用以下逻辑加载模块并使用其组件类型(我们自己的字符串键 <-> 组件类型之间的映射直接存储在该模块中):
this._loader.load(url)
.then((_ngModuleFactory: NgModuleFactory<any>) => {
const _ngModuleRef: NgModuleRef<any> = _ngModuleFactory.create(this._injector);
const _cmpType: Type<any> = _ngModuleRef.instance.cmps.get('dynamic');
const _cmpFactory: ComponentFactory<any> = _ngModuleRef
.componentFactoryResolver
.resolveComponentFactory(_cmpType);
this._cmpRef = this.vc.createComponent(_cmpFactory, 0, this._injector, []);
});
大家好,现在我也在使用单例类的解决方法,该类维护对几个延迟加载的模块解析器的引用。
使用新版本的 Angular 有没有更好的方法来处理这个问题?
谢谢
嘿大家,
只是面临同样的问题,我使用解决方法作为我的解决方案的引导程序。
更进一步并减少对全局服务的需求的一种方法是公开 RouteConfigLoadEnd 事件的加载配置私有变量。 这个 LoadedRouterConfig 类型的私有变量包含加载的模块、它的注入器和它的 componentFactoryResolver。
我应该打开一个新问题来将其作为功能请求提出吗?
--------- 编辑后
没关系我的问题,我发现了这个功能请求https://github.com/angular/angular/issues/24069
我想我有同样的问题,但我的额外模块不是lazy loaded
。 我必须将我的entryModules
从SharedFeatureModule
(仅在需要时才加载)移动到CoreModule
。
现在它起作用了。 但是我将与功能相关的组件移到了核心,我认为这不好。
这个问题还没有解决?
@jmcclanahan的解决方案
{ path: '', component: yourComponent, data: { toolbar: yourToolbarComponentToInject } }
在末尾添加一个额外的空白路线。 当我尝试创建动态面包屑时,这已成为一个问题。
如果不将这个空白路由默认添加到组件中,我更喜欢在惰性子模块中使用类似于引导程序的东西。
对此有何更新?
我们的自定义解决方案包括将ComponentFactoryResolver
和Injector
构造函数注入到可以打开全局侧边栏的每个组件中。 然后调用组件将这些引用(它自己的)连同所需的Component
一起传递给服务。 该服务使用这些实例来创建组件。 过于复杂和冗长。
只需将您的入口组件放入他们自己的子模块中,然后将其导入应用程序,而不是其他任何地方。
@broweratcognitecdotcom我认为这个问题的重点是延迟加载模块中的entryComponents
。 在AppModule
中包含您想要用作入口组件的每个组件的所有子模块将强制所有此类组件急切加载,这可能会导致启动时出现性能问题。
并不是说这些组件应该与使用它们的组件(消费者)无关,这样他们的模块不应该知道它们是用作入口组件还是普通组件(在 html 中声明),只有它的消费者应该知道(因此,将它们放在 AppModule 中会很难知道它们为什么在那里,您需要在其他地方搜索才能知道)。
@lucasbasquerotto ...以编程方式加载此类惰性模块...然后您可以完全控制它,并且可以正常工作。
在遇到一个奇怪的问题后,我偶然发现了这个线程,我无法通过使用字符串键和相应组件的映射从根解析器解析组件。 我可以从字面上看到 _factories 地图中的组件,但不知何故,它无法正确比较并返回未定义。
@jmcclanahan @Dunos你不认为不是功能模块交出它的注入器引用(向主应用程序模块注册它的上下文),如果它公开一个服务,比如 FactoryResolver,它实际上会为其返回正确解析的工厂组件,因为它将正确引用其自己的模块的注入器。 这样,它就像为您的组件公开一个公共 api。
我正在尝试做类似的事情,但我的模块不是延迟加载的,我遇到了我上面描述的问题。 从理论上讲,它应该可以工作,除非我在这里遗漏了一些明显的东西?
@h-arora 我注意到了同样的问题,看到了比赛。 在 _factories 中,但问题是该模块实际上有一个构建错误,没有出现
从 #17168 开始(作为本次的副本关闭)
当前行为
如果你有一个单例服务,它使用 App 的 RootInjector 提供的 ComponentFactoryResolver 来动态创建组件,并且你希望创建一个在惰性模块中声明的 entryComponent,这将失败,因为根注入器不知道它。
预期行为
考虑到根注入器不知道子注入器已知的 entryComponents,这实际上是预期的行为。
改变行为的动机/用例是什么?
尽管 Angular 的模块化设计是正确的,但其动机是利用复杂性来创建动态组件。
角度版本:2.4.9
建议的解决方案:尽管这与 Angular 本身采用的模块化设计背道而驰,但我认为可以通过简单地将 LazyModule 的 entryComponents 附加到 RootInjector 的 entryComponents 并避免在您导航时淹没 RootInjector 来减轻创建动态组件的复杂性(这会破坏 LazyModule),之前注入的 entryComponents 将从数组中删除。
简单说说:
页面已加载 -> RootInjector 已创建。
发出一个激活 LazyModule -> ChildInjector 的导航。
RootInjector.entryComponents.append(childInjector.entryComponents)
发出到其他地方的导航 -> LazyModule 不再需要,因此被破坏。
RootInjector.entryComponents.destroyLastAppendedEntryComponents()
希望我能提出一些好的建议。
我花了几天时间分解我的公司项目并实施延迟加载。 直到最后我才意识到我们的模态无法正常工作,因为它们是各自模块中的条目。 我们已经恢复并正在等待修复...
这是计划好的事情还是我们应该探索解决方法。 正如人们在上面所做的那样。
@ravtakhar您可能想查看解决方法,因为它在积压工作中并且此时没有分配优先级。
我花了几天时间分解我的公司项目并实施延迟加载。 直到最后我才意识到我们的模态无法正常工作,因为它们是各自模块中的条目。 我们已经恢复并正在等待修复...
这是计划好的事情还是我们应该探索解决方法。 正如人们在上面所做的那样。
同样的问题在这里
@tmirun ...如果您以编程方式加载此类惰性模块...那么您可以完全控制,...这意味着您最终可以获得模块引用并调用moduleRef.componentFactoryResolver.resolveComponentFactory(entryComponentType)
有与上面许多相同的问题,并通过创建一个独立的模块来解决,该模块声明和导出所有 entryComponents,包括 appModule 中的条目并导出它,以便每个单独的lazyLoaded 模块都将加载这些组件。
我发现的唯一其他可行的解决方案也是通过根据托管entryComponent
的模块提供自定义ComponentFactoryResolver
来生成对话框。
注意:请记住,根目录中提供的单例模块只会看到主模块入口组件,这就是我决定提出第一个解决方案的原因,否则您可能会考虑为应用程序的每个子模块制作一个对话框生成器,并且在该子模块最终包括具有其他entryComponents
的共享模块,方法是向生成模式的服务提供所述模块的ComponentFactoryResolver
。
...由于 Jason 在 Angular 7.2 中的演示http://www.youtube.com/watch?v=2wMQTxtpvoY&t=131m24s ... 应该提供一般支持的解决方案(Ivy 中的一些新 API + 支持)。 ..但我不确定是否正确提到了 7.2 版。
我对CDK Overlay有同样的问题,我有一个构建在 CDK 覆盖之上的画廊插件,但由于这个问题,我必须将它导入根模块中。
顺便说一句,我的情况有什么解决方法吗?
@MurhafSousli ...大声笑,我不得不再说一遍,这是设计使然。 这件事被解释了一百次......有一个根注入器,任何延迟加载的模块都是一层深,所以你不能假设你能够从根级别触及它的组件。 但正如多次所说,您可以以编程方式加载该模块......所以您将获得该模块引用......然后您就可以访问它的组件。
也将我添加到列表中😄。 我有模态组件,我在其中动态加载我的组件,我不想将它们全部加载到应用程序中,因为它们不是应用程序组件的一部分,这将是糟糕的设计。 但这就是我在阅读完所有这些后必须做的事情。
@msarica今天向我指出,如果您创建一个延迟加载的服务并让它扩展您打开弹出窗口的全局服务,然后使用延迟加载的服务打开弹出窗口,它将按预期工作:
全球PopupService
@Injectable({ providedIn: 'root' })
export class PopupService {
constructor(
private injector: Injector,
private overlay: Overlay,
) {}
open<T = any>(component: ComponentType<T>) { ... }
}
特定服务仅在MyFeatureModule
中延迟加载
@Injectable()
export class MyFeaturePopupService extends PopupService {}
在延迟加载的组件中使用MyFeatureModule
@Component({ ... })
export class MyFeatureComponent {
constructor(
private myFeaturePopupService: MyFeaturePopupService,
) {}
openPopup() {
this.myFeaturePopupService.open(MyFeaturePopupComponent);
}
}
延迟加载模块的示例定义
@NgModule({
imports: [MyFeatureRoutingModule],
declarations: [
MyFeatureComponent,
MyFeaturePopupComponent,
],
entryComponents: [MyFeaturePopupComponent],
providers: [MyFeaturePopupService],
})
export class MyFeatureModule {}
在 Angular 7 中测试。
注意:我们的代码利用了 Angular CDK 的Overlay
,但概念是相同的(根据该线程的其他部分,关注的是Injector
)。
...应该可以使用一般支持的解决方案(Ivy 中的一些新 API + 支持)...在 Angular 7.2 中
... @mlc-mapis 您能否详细说明一般支持的解决方案是什么?
@cedvdb ...我刚刚提到了该演示文稿中的重要时刻,它实际上看起来像是一个解决方案...这意味着支持以编程方式加载延迟加载的模块,然后使用它的任何部分,如组件,指令, ...
即使在今天也可以使用这种方式......但是使用Angular CLI,您总是需要定义必要的路由......即使您从未通过路由器使用它们。
哇。 这是非常出乎意料的。
我基于@angular/cdk 门户和覆盖实现了我自己的对话系统: https://stackblitz.com/edit/cdk-dialog-example-p1。 也遇到了这个问题。
@CWSpear提到的解决方法有效:我必须在我的功能模块中扩展服务,这对我有用。 多么奇怪!
有没有更优雅的解决方案?
有没有更优雅的解决方案?
我一直在尝试的是在惰性模块级别重新提供启动器包装器。 让这个包装器在构造函数中使用一个注入器,并将其传递给真正的启动器(从根注入)。 它是一条线修复。 不过,我不确定它有多稳定,
@NgModule({
declarations: [LazyRootComponent, LazyEntryComponent],
entryComponents:[LazyEntryComponent],
providers:[LauncherWrapper], //<---- reprovision here
imports: [
CommonModule,
LazyModRoutingModule
]
})
export class LazyModModule { }
@Injectable()
export class LauncherWrapper {
constructor(private injector: Injector, private launcher:ActualLaucherClass) {
}
我的问题是因为我使用@Injectable({providedIn: 'root'})
引起的。 将其更改为@Injectable({providedIn: MyModule})
解决了它。 😄
这是有道理的,因为服务需要访问延迟加载的模块入口组件。 当在根目录中提供时,它无法访问它们,因为模块暂时与根注入器断开连接。
非常感谢@jmcclanahan和@dirkluijk ,我花了好几个小时摸不着头脑,你的描述帮助我找到了解决方案。
我只是想知道什么是正确的方法。
需要@angular/cdk 吗? 为什么 ?
如果它需要您正在集成的代码来了解它,我认为任何解决方法都不可行,因为有很多第 3 方代码只使用ComponentFactoryResolver
而不知道您的特殊替代注册表。
考虑到这一点,这是我的“解决方案”: CoalescingComponentFactoryResolver 。 这是一个应该由 app 模块提供并在其构造函数中初始化的服务,如下所示:
@NgModule({
providers: [CoalescingComponentFactoryResolver]
})
class AppModule {
constructor(coalescingResolver: CoalescingComponentFactoryResolver) {
coalescingResolver.init();
}
}
然后,延迟加载的模块应该注入它并用它注册自己的ComponentFactoryResolver
实例。 像这样:
@NgModule({})
export class LazyModule {
constructor(
coalescingResolver: CoalescingComponentFactoryResolver,
localResolver: ComponentFactoryResolver
) {
coalescingResolver.registerResolver(localResolver);
}
}
完成后,延迟加载模块中的入口组件应该可以从非延迟加载的服务中获得。
工作原理:初始化时,它会注入根应用程序的ComponentFactoryResolver
并猴子修补resolveComponentFactory
方法以调用它自己的实现。 这个实现首先尝试在所有注册的惰性模块解析器上解析组件工厂,然后回退到根应用程序解析器(有一些额外的逻辑来避免循环调用,但这就是要点)。
所以,是的,一个非常严重的黑客攻击。 但它现在在 Angular 7 中有效。也许它会对某人有用。
基于@CWSpear的解决方案,我只需将现有的 PopupService 添加到组件的 providers 数组中即可。 所以我不需要创建一个单独的“特定于功能”的 PopupService。
就像这样:
@Component({
...
providers: [PopupService] //<---- generic PopupService
})
export class MedicationPortletComponent implements OnInit, OnDestroy {
...
或者,您也可以将 PopupService 添加到延迟加载模块的 NgModule 的 providers 数组中。 例如:
@NgModule({
declarations: [
...
],
imports: [
...
],
providers: [PopupService] //<--- here's the PopupService!
})
export class SharedFeaturePatientModule {}
@jonrimmer最佳解决方案
@jonrimmer这是最小的侵入性,非常适合我们的用例。 非常感谢我和我的团队:)
我只是好奇它在惰性路由器的情况下如何顺利工作,但是当我删除惰性路由并使用 ngModuleFactoryLoader 手动加载模块时会引发错误。
//Ignore the syntax
CompA {
openDialog() {//invoked on button click of this comp
matDialog.open(FilterComponent);//works fine in case of lazy route but throws error when manually loaded
}
}
ModuleA {//lazy module
imports: [MatDialog, FilterModule],
declaration: [CompA]
}
FilterModule {
declaration: [FilterComponent],
entryComponent: [FilterComponent]
}
FilterComponent { ...
}
@jmcclanahan @MartinJHItInstituttet @splincode
async showBar() {
const module = await import("../bar/bar.module");
const compiled = this._compiler.compileModuleAndAllComponentsSync(
module.BarModule
);
compiled.ngModuleFactory.create(this._injector);
let factory: any = compiled.componentFactories.find(
componentFactory => componentFactory.selector === "app-dialog"
);
this.modal.show(factory.componentType);
}
@Jimmysh这是用于 JIT 模式的。 您需要为 AOT 模式加载import("../bar/bar.module.ngfactory")
(其中compileModuleAndAllComponentsSync
不可用)。
@mlc-mlapis
深受https://github.com/angular/angular/blob/master/aio/src/app/custom-elements/elements-loader.ts的启发
https://github.com/Jimmysh/ng-lazy-component-load
它可以使用 AOT JIT。 对我来说成功。
import { InjectionToken, Type } from '@angular/core';
import { LoadChildrenCallback } from '@angular/router';
export const ELEMENT_MODULE_LOAD_CALLBACKS_AS_ROUTES = [
{
path: 'aio-foo',
loadChildren: () => import('../foo/foo.module').then(mod => mod.FooModule)
}
];
export interface WithCustomElementComponent {
customElementComponent: Type<any>;
}
export const ELEMENT_MODULE_LOAD_CALLBACKS_TOKEN = new InjectionToken<Map<string, LoadChildrenCallback>>(
'aio/elements-map'
);
export const ELEMENT_MODULE_LOAD_CALLBACKS = new Map<string, LoadChildrenCallback>();
ELEMENT_MODULE_LOAD_CALLBACKS_AS_ROUTES.forEach(route => {
ELEMENT_MODULE_LOAD_CALLBACKS.set(route.path, route.loadChildren);
});
```打字稿
从'@angular/core'导入{编译器,注入,可注入,NgModuleFactory,NgModuleRef,类型,组件工厂};
从“@angular/router”导入 { LoadChildrenCallback };
从'./lazy-component-registry'导入{ ELEMENT_MODULE_LOAD_CALLBACKS_TOKEN };
@可注射({
提供在:“根”
})
导出类 LazyComponentLoader {
私有模块ToLoad:地图
私有模块加载 = 新地图
构造函数(
私有模块引用:NgModuleRef
@Inject(ELEMENT_MODULE_LOAD_CALLBACKS_TOKEN) elementModulePaths:映射
私有编译器:编译器
) {
this.modulesToLoad = new Map(elementModulePaths);
}
加载(路径:字符串,选择器?:字符串):承诺
如果(this.modulesLoading.has(路径)){
返回 this.modulesLoading.get(path);
}
if (this.modulesToLoad.has(path)) {
const modulePathLoader = this.modulesToLoad.get(path);
const loadedAndRegistered = (modulePathLoader() as Promise<NgModuleFactory<any> | Type<any>>)
.then(elementModuleOrFactory => {
if (elementModuleOrFactory instanceof NgModuleFactory) {
return elementModuleOrFactory;
} else {
return this.compiler.compileModuleAsync(elementModuleOrFactory);
}
})
.then(elementModuleFactory => {
const elementModuleRef = elementModuleFactory.create(this.moduleRef.injector);
const factories: Map<any, ComponentFactory<any>> = (elementModuleRef.componentFactoryResolver as any)
._factories;
if (selector) {
const find = Array.from(factories.keys()).find(type => {
const factory = factories.get(type);
return factory.selector === selector;
});
if (find) {
return find;
} else {
return Promise.reject(new Error(`not found selector:${selector}`));
}
}
this.modulesToLoad.delete(path);
return;
})
.catch(err => {
this.modulesLoading.delete(path);
return Promise.reject(err);
});
this.modulesLoading.set(path, loadedAndRegistered);
return loadedAndRegistered;
}
return Promise.resolve();
}
}
``` typescript
async showFoo() {
await this.lazyComponentLoader.load('aio-foo');
this.modal.show(FooDialogComponent);
}
async showFoo2() {
const aaa = await this.lazyComponentLoader.load('aio-foo', 'app-dialog');
this.modal.show(aaa);
}
@Jimmysh ...然后正确。 它仅在 JIT 模式下使用编译器。
constructor(private compiler: Compiler) {}
...
if (elementModuleOrFactory instanceof NgModuleFactory) {
return elementModuleOrFactory;
} else {
return this.compiler.compileModuleAsync(elementModuleOrFactory);
}
@mlc-mlapis AOT 将是 NgModuleFactory。 您可以测试https://github.com/Jimmysh/ng-lazy-component-load
FWIW,与@CWSpear和@dirkluijk类似,我从我的服务中删除了providedIn: 'root'
(在延迟加载的模块中定义并负责打开entryComponent
),而是指定了服务在我的惰性模块定义的providers
中。 我相信最大/唯一的缺点是该服务不再是可摇树的。
使用providedIn: MyLazyModule
会导致循环依赖警告。
@jonrimmer我不能不感谢你就离开这个话题!! 你拯救了我的一天。
对于每个阅读我的评论的人来说,如果你看到这个精彩的答案,它可能会帮助你,以免浪费你的时间。
确认这在 Angular 8.2.0 中尚未解决。 自 2.5 年前创建 OP 以来,Angular 的格局发生了很大变化,Angular Material 2 已成为 Angular Components。
正如其他评论员所指出的,Angular Material 使用 DOM,因此应用程序开发人员在 MatDialog 中加载应用程序组件时不必使用 DOM,即使该应用程序组件是在延迟加载的子模块的 entryComponents 中声明的。
但是,在业务应用程序中执行相同的 DOM 操作看起来很奇怪,这违反了 Angular 的应用程序编程指南。
很高兴可以实现 OP 的功能请求,以便ComponentFactoryResolver
可以“看到”在延迟加载的子模块中声明的入口组件,以一种或另一种方式。 在 app.module 中声明一个惰性模块的入口组件是有效的,但很难看。
除了上面提到的脏工作解决方案:1. DOM,2. 在 app.module 中声明组件,我将在下面提供另一种不那么脏的解决方案。
第三种解决方案,不那么脏。
我有多个延迟加载的模块使用的共享服务。
export interface DataComponent {
data: any;
}
/**
* Display an NG component in a dialog, and this dialog has not need to answer but close.
*/
@Injectable()
export class DataComponentDialogService {
modalRef: MatDialogRef<DataComponentDialog>;
constructor(private dialog: MatDialog) { }
open(title: string, externalComponentType: Type<DataComponent>, componentData: any, autofocus = true): Observable<any> {
const isSmallScreen = window.innerWidth < 640 || window.innerHeight < 640;
this.modalRef = this.dialog.open(DataComponentDialog,
{
disableClose: true,
minWidth: isSmallScreen ? '98vw' : undefined,
autoFocus: autofocus,
data: {
title: title,
externalComponentType: externalComponentType,
componentData: componentData,
isSmallScreen: isSmallScreen
}
});
return this.modalRef.afterClosed();
}
}
该服务可以查看未延迟加载的模块的入口组件。 我在一些延迟加载的模块中实现了一些入口组件。 所以我在每个惰性模块中提供了这个服务的派生类:
import { DataComponentDialogService } from '../_ui_services/dataComponentDialog.component';
@Injectable()
export class LazyComponentDialogService extends DataComponentDialogService {
constructor(dialog: MatDialog) {
super(dialog);
}
}
由于 LazyComponentDialogService 与惰性入口组件在同一个惰性模块中提供,因此可以看到组件。
根据您的应用架构,您可能不需要派生类,而只需在每个模块中提供 DataComponentDialogService 或类似的。
由于 ivy 实现(使用 9.0.0-next.7 测试),Angular 9 中似乎不再存在该问题。 您可以从延迟模块外部动态创建延迟加载模块的组件。 对于 Ivy 中的动态组件,似乎不再需要入口组件声明。 Ivy 不需要以前由编译器生成的单独的组件工厂。 组件工厂信息通过 Ivy(?) 嵌入到组件定义中。 一旦你有了一个组件类型,你应该能够得到它的组件工厂,然后创建它的一个实例。 这只是我的印象。 信守诺言
您需要做的就是创建一个单独的小部件模块,该模块声明所有动态组件并将它们作为 entryComponents 包含在内。 然后从 AppModule 中导入这个模块。 这将确保您的入口组件已注册到根注入器。
这个解决方案是干净的,不会破坏 Lazy 封装。
@NgModule({
declarations: [
ModalComponent,
... more dynamic components ...
],
entryComponents: [
ModalComponent,
... more dynamic components ...
]
})
export class DynamicModule {
}
应用模块:
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule,
BrowserAnimationsModule,
DynamicModule
],
providers: [],
bootstrap: [
AppComponent
]
})
export class AppModule { }
@pixelbits-mk 这将使所有动态组件(如 modal 等)急切加载,但这里的重点是使它们能够延迟加载以不减慢应用程序的初始加载速度(尤其是随着项目的增长) ),就像调用它们的组件一样。
假设延迟加载的ComponentA
动态调用延迟加载的动态ComponentB
。 如果ComponentA
知道它会调用ComponentB
你可以在ComponentAModule
中导入ComponentBModule
#$ 。 这不是理想的情况,但我可以忍受。
当ComponentA
不知道ComponentB
将被加载时,主要问题出现(在我看来)。 例如,如果ComponentB
是模态的并且ComponentA
$ 在服务中调用方法showModal()
,并且服务在后台动态加载ComponentB
, ComponentA
不会知道的。
我现在要做的是在方法中包含ComponentB
引用作为参数,例如showModal(ComponentB)
,这样ComponentA
现在知道ComponentB
将被加载,但在我看来这很糟糕(对方法的每次调用都必须传递组件引用)。
如果入口组件无需显式导入它们的模块就可用(我认为这个问题是关于什么的),那将解决它(也许 Angular 可以检测到ComponentA
调用导入ComponentB
的服务ComponentAModule
隐式导入ComponentBModule
,虽然它可能会产生不希望的效果,即导入其引用的组件的模块,但导入的原因不是动态创建它们,我会更多地将其视为边缘情况,并且可以接受)。
我还没有测试过@jonrimmer 的建议,但这似乎很有希望。
如果ComponentA
$ 仅在显示ComponentA
时自动使用ComponentB
延迟加载模块,那就太棒了:
ModuleA
有组件ComponentA1
, ComponentA2
。
html ComponentB
中的ComponentA1
引用(在模块ModuleB
中),但ComponentA2
没有。
ModuleB
只有在显示ComponentA1
时才会自动延迟加载,但如果显示ComponentA2
则不会。
这将非常适合主页上的仪表板场景。 Dahsboards 来自许多不同的模块,但如果不包括仪表板(比如说通过用户设置),则无需下载该模块。
@jonrimmer包装主 ComponentFactoryResolver 的 resolveComponentFactory 函数并搜索之前注册的延迟加载模块的好主意。
它与注入根中提供的服务的入口组件一起使用。 但是我无法注入仅在延迟加载模块中提供的服务。 在这种情况下,我得到了一个 StaticInjectorError。 你能检查一下吗?
我想确认常春藤似乎可以解决这个问题。 我今天偶然发现了这个问题,当时我将我的模块重写为延迟加载,其中包含入口组件和一个共享服务来实例化它们,只是发现一个新的错误表明找不到组件工厂。 我实际上很震惊地读到这是一个问题。
无论如何,我刚刚在我现有的 angular 8.2 项目中启用了常春藤,一切似乎都按预期工作。
我在延迟加载模块中使用Overlay
的解决方案:
您必须像这样将工厂解析器传递给ComponentPortal
的构造函数
@Injectable()
export class OverlayService {
constructor(
private overlay: Overlay,
private componentFactoryResolver: ComponentFactoryResolver
) {
}
open<T>(component: ComponentType<T>) {
const overlayRef = this.overlay.create();
const filePreviewPortal = new ComponentPortal(component, null, null, this.componentFactoryResolver);
overlayRef.attach(filePreviewPortal);
}
}
但是您还必须在延迟加载的模块中OverlayService
。 OverlayService 需要注入ComponentFactoryResolver
的提供 OverlayService 的模块
@NgModule({
declarations: [
SomeComponent,
],
entryComponents: [
SomeComponent, // <-------- will be opened via this.overlayService.open(SomeComponent)
],
providers: [
OverlayService, // <--------
]
})
export class LazyLoadedModule {}
@mixrich这种方法将有效[在我的应用程序中,我正在这样做,并且它工作正常,直到我需要关闭某些事件(如注销)的所有模式对话框],但请记住,这将在每次其他模块时实例化 OverlayService导入此模块,即应用程序不会像以前那样拥有单个实例。
因为不会有覆盖服务的信号,所以不容易知道打开了多少个模态对话框。
当然,这是应用程序的要求,但请注意这种方法。
@mixrich这种方法将有效[在我的应用程序中,我正在这样做,并且它工作正常,直到我需要关闭某些事件(如注销)的所有模式对话框],但请记住,这将在每次其他模块时实例化 OverlayService导入此模块,即应用程序不会像以前那样拥有单个实例。
因为不会有覆盖服务的信号,所以不容易知道打开了多少个模态对话框。
当然,这是应用程序的要求,但请注意这种方法。
例如,在我的情况下,我有ModalDialogModule
并提供了OverlayService
并且当我调用OverlayService.open(SomeComponent)
它还为我创建模态窗口模板,在里面插入SomeComponent
它并返回一些带有有用的可观察对象(用于关闭和成功事件)、组件实例和手动close
方法的数据结构。
所以,当我需要在我的 LazyModule 中使用模态时,我只需要将ModalDialogModule
导入它以获得使用OverlayService
的能力。 我发现这种方法很方便,因为你总是知道要使用 modal 你必须导入ModalDialogModule
,就像你总是知道要使用反应式表单你必须导入ReactiveFormModule
我在 Angular 8.3.6 中得到了这个工作。 我有一个带有入口组件(垫子对话框)的库模块,如果我将它们添加到库模块入口组件中,它将不会加载。 当我尝试打开它时,它说在控制台日志中找不到 MyCustomDialog 的组件工厂。
解决方案是在库模块的 NgModule 类中创建一个静态方法,该方法返回一个包含所有模块入口组件的数组。 然后从应用模块 NgModule 中调用此方法。
@NgModule({
declarations: [
MyCustomDialogComponent
],
imports: [
CommonModule
],
exports: [
MyCustomDialogComponent
]
// no need to add the dialog component to entryComponents here.
})
export class MyCustomModule {
static getEntryComponents() {
const entryComponents = [];
entryComponents.push(MyCustomDialogComponent);
return entryComponents;
}
}
import {MyCustomModule} from 'my-custom-library'; // import library from node_modules
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule,
BrowserAnimationsModule,
MyCustomModule
],
providers: [],
bootstrap: [AppComponent],
entryComponents: [].concat(MyCustomModule.getEntryComponents())
})
export class AppModule {
}
我知道这仍然不能修复库中的 entryComponents 不起作用,但至少使用静态方法,应用程序模块不必知道自定义库中的条目组件。 它只是调用该方法并获取库中列出的任何内容。 应用程序没有责任知道库中的哪些组件是入口组件。
@asmith2306为什么不使用扩展运算符? 它看起来很丑
@asmith2306为什么不使用扩展运算符? 它看起来很丑
因为它不适合它。 某处有一个关于它的错误。
看起来它不适用于 --prod 模式
即使它确实有效,它也不能解决真正的问题。 问题是如果我将一个组件添加到 NgModule 的 entryComponents 中,并且该模块被导入到我的应用程序中延迟加载的模块中,那么 entryComponents 应该会自动注册到应用程序的入口组件中。 应该不需要进一步的工作。 我会进一步说,任何 NgModule 的 entryComponents 中的任何组件都应该自动在应用程序模块的 entryComponents 中结束。 目前情况并非如此。 如果是的话,我们就不会在这里了。
@broweratcognitecdotcom不同注入级别的冲突和优先级如何?
在我看来,@mlc-mapis 的顺序应该是先到先得。
@mlc-mlapis 你的意思是不同的组件是否具有相同的名称? 如果有多个匹配项,则第一次匹配或错误。 我可以接受条目组件名称必须是唯一的要求。
@broweratcognitecdotcom嗨,是的。 因为如果我们谈论一般原则,则必须涵盖所有变体。
@mlc-mlapis 我认为许多人想要克服的角度限制是入口组件只能由应用程序实例化并放置在 dom 中。 该应用程序不需要知道我导入到延迟加载模块中的模块使用的对话框。 当前入口组件的概念打破了 Angular 的模块化模式。
不再需要使用 Ivy entryComponents 的 AFAIK,对吧? 由于组件将具有局部性,因此仅动态导入它们将始终有效吗?
@Airblader你是对的,我们知道。 这只是对历史的一个小小的回忆。 😄
对于那些仍然在这个问题上苦苦挣扎的人来说,这里是真正有效的解决方案: https ://github.com/angular/angular/issues/14324#issuecomment -481898762
我为这个问题发布了一个包。 来自 jornimmer 的一些代码副本。
看起来这个问题在 Angular 9 中不存在。如果你现在需要解决这个问题。 你可以使用@aiao/lazy-component
示例代码在这里
https://github.com/aiao-io/aiao/tree/master/integration/lazy-component
我相信这在运行 ivy 的 Angular v9 中已修复。 如果有人仍然面临类似的问题,请以最小的重现场景打开一个新问题。 谢谢!
最有用的评论
好的,在这种情况下,我将此问题更改为功能请求。
IMO 这些用例应该由 Angular 自己处理,而不是通过手动沿着 DOM 移动。