Angular 远程加载的模块没有将入口组件加载到视图中
Angular Remotely loaded Module does not load entry component into view
背景:
我已经按照多个教程远程加载模块,以便尝试使用 Angular 创建插件架构。特别是:
- 我在主应用程序中使用 Angular 10
- angular 构建插件的构建器
- 汇总以生成 UMD 模块。
- SystemJS 作为模块加载器
手头的问题:
- 我可以成功加载远程定义的模块并且远程模块可以成功使用公共服务(我所说的公共服务是指主要或核心应用程序和插件已知)
- 我无法动态加载该模块中定义的组件,即使该组件在插件模块声明、导出中定义并作为模块本身的入口组件也是如此。
代码如下:
https://github.com/rickszyr/angular-plugins/
如何运行它:
- npm 安装
- npm 运行 build:init //编译公共服务
- npm 运行 build:plugins // 为两个插件生成 umd 包
- npm 运行 start:all // 启动服务器和客户端
- 使用默认字段值单击“加载”
- 出现错误。
错误:
我发现由于某种原因,组件宿主视图没有初始化 _lview 值。但我不确定如何处理该信息或如何确保它确实正确设置了该值。
尝试创建组件并将其插入动态组件加载器时失败的行在 app.component.ts 中。
非常感谢您
主要成分:
app.component.ts
import { Compiler, Component, ComponentFactoryResolver, Injector, NgModuleFactory, ViewChild, ViewContainerRef } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { IPlugin, PluginCatalogService } from "interfaces";
import * as ngCore from "@angular/core";
import * as ngCommon from "@angular/common";
import * as ngBrowser from "@angular/platform-browser";
import * as commonInterfaces from "interfaces";
import { ModuleLoader } from "./remote-module-loader.service";
import { DynamicComponentDirective } from "./directives/dynamic-component.directive";
@Component({
selector: "app-root",
templateUrl: "app.component.html",
styles: [],
})
export class AppComponent {
title = "plugins";
loader: ModuleLoader;
@ViewChild('putStuffHere', {read: ViewContainerRef}) putStuffHere: ViewContainerRef;
constructor(
public pluginService: PluginCatalogService,
private injector: Injector,
private factoryResolver: ComponentFactoryResolver,
private compiler: Compiler,
public viewContainer: ViewContainerRef
) {
this.loader = new ModuleLoader();
}
loadModule(modulePath: string, moduleName: string) {
this.loader.register({
"@angular/core": ngCore,
"@angular/common": ngCommon,
"interfaces": commonInterfaces
}).then(ml => ml.load(modulePath).then(m => {
const moduleFactory: NgModuleFactory<any> = <NgModuleFactory<any>>m.default[moduleName+ "NgFactory"];
const moduleReference = moduleFactory.create(this.injector);
moduleReference.componentFactoryResolver.resolveComponentFactory((<IPlugin>moduleReference.instance).mainComponent);
var compFactory = moduleReference.componentFactoryResolver.resolveComponentFactory(this.getEntryComponent(moduleFactory));
this.putStuffHere.createComponent(compFactory); // <<< this fails
var component = compFactory.create(this.injector);
this.putStuffHere.insert(component.hostView);// <<< this fails
}));
}
getEntryComponent(moduleFactory: any):any {
var existModuleLoad = (<any>moduleFactory.moduleType).decorators[0].type.prototype.ngMetadataName === "NgModule"
if (!existModuleLoad) return null;
return moduleFactory.moduleType.decorators[0].args[0].entryComponents[0];
}
}
app.component.html
<h1>Welcome!</h1>
<p>
<label>Path Remote</label><input #pathRemote value="http://localhost:3000/plugin2.module.umd.js">
</p>
<p>
<label>Remote Name</label><input #remoteName value="Plugin2Module">
</p>
<p>
<button (click)="loadModule(pathRemote.value, remoteName.value)">Load</button>
</p>
<ol>
<li *ngFor="let module of pluginService.installedPlugins">{{ module.name}}</li>
</ol>
<ng-container #putStuffHere></ng-container>
编译插件代码:
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core'), require('@angular/common'), require('interfaces')) :
typeof define === 'function' && define.amd ? define(['exports', '@angular/core', '@angular/common', 'interfaces'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Plugin2Module = {}, global.i0, global.i3, global.i4));
}(this, (function (exports, i0, i3, i4) { 'use strict';
/**
* @fileoverview added by tsickle
* Generated from: lib/plugin2.component.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class Plugin2Component {
constructor() {
this.title = "Nada";
}
/**
* @return {?}
*/
ngOnInit() {
}
}
Plugin2Component.decorators = [
{ type: i0.Component, args: [{
selector: 'lib-plugin2',
template: `
<p>
plugin2 works!
</p>
`
}] }
];
/** @nocollapse */
Plugin2Component.ctorParameters = () => [];
Plugin2Component.propDecorators = {
title: [{ type: i0.Input }]
};
/**
* @fileoverview added by tsickle
* Generated from: lib/plugin2.module.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class Plugin2Module {
/**
* @param {?} pluginService
*/
constructor(pluginService) {
console.log("Se registro Plugin 2");
pluginService.installedPlugins.push(this);
}
/**
* @return {?}
*/
get name() {
return "Plugin 2";
}
/**
* @return {?}
*/
get mainComponent() {
return Plugin2Component;
}
}
Plugin2Module.decorators = [
{ type: i0.NgModule, args: [{
declarations: [Plugin2Component],
imports: [i3.CommonModule],
exports: [Plugin2Component],
entryComponents: [Plugin2Component]
},] }
];
/** @nocollapse */
Plugin2Module.ctorParameters = () => [
{ type: i4.PluginCatalogService }
];
/**
* @fileoverview This file was generated by the Angular template compiler. Do not edit.
*
* @suppress {suspiciousCode,uselessCode,missingProperties,missingOverride,checkTypes,extraRequire}
* tslint:disable
*/
var styles_Plugin2Component = [];
var RenderType_Plugin2Component = i0.ɵcrt({ encapsulation: 2, styles: styles_Plugin2Component, data: {} });
function View_Plugin2Component_0(_l) { return i0.ɵvid(0, [(_l()(), i0.ɵeld(0, 0, null, null, 1, "p", [], null, null, null, null, null)), (_l()(), i0.ɵted(-1, null, [" plugin2 works! "]))], null, null); }
function View_Plugin2Component_Host_0(_l) { return i0.ɵvid(0, [(_l()(), i0.ɵeld(0, 0, null, null, 1, "lib-plugin2", [], null, null, null, View_Plugin2Component_0, RenderType_Plugin2Component)), i0.ɵdid(1, 114688, null, 0, Plugin2Component, [], null, null)], function (_ck, _v) { _ck(_v, 1, 0); }, null); }
var Plugin2ComponentNgFactory = i0.ɵccf("lib-plugin2", Plugin2Component, View_Plugin2Component_Host_0, { title: "title" }, {}, []);
/**
* @fileoverview This file was generated by the Angular template compiler. Do not edit.
*
* @suppress {suspiciousCode,uselessCode,missingProperties,missingOverride,checkTypes,extraRequire}
* tslint:disable
*/
var Plugin2ModuleNgFactory = i0.ɵcmf(Plugin2Module, [], function (_l) { return i0.ɵmod([i0.ɵmpd(512, i0.ComponentFactoryResolver, i0.ɵCodegenComponentFactoryResolver, [[8, [Plugin2ComponentNgFactory]], [3, i0.ComponentFactoryResolver], i0.NgModuleRef]), i0.ɵmpd(4608, i3.NgLocalization, i3.NgLocaleLocalization, [i0.LOCALE_ID]), i0.ɵmpd(1073742336, i3.CommonModule, i3.CommonModule, []), i0.ɵmpd(1073742336, Plugin2Module, Plugin2Module, [i4.PluginCatalogService])]); });
exports.Plugin2ModuleNgFactory = Plugin2ModuleNgFactory;
Object.defineProperty(exports, '__esModule', { value: true });
})));
tsconfig.lib.json 对于插件:
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "../../out-tsc/lib",
"target": "es2015",
"declaration": true,
"declarationMap": true,
"inlineSources": true,
"types": [],
"lib": [
"dom",
"es2018"
]
},
"angularCompilerOptions": {
"enableIvy": false,
"skipTemplateCodegen": false,
"strictMetadataEmit": true,
"annotateForClosureCompiler": true,
"enableResourceInlining": true
},
"exclude": [
"src/test.ts",
"**/*.spec.ts"
]
}
您在插件中禁用了 Ivy 编译器,但忘记在主项目中禁用它。
在主要 tsconfig.json
中添加以下内容将解决问题
"angularCompilerOptions": {
"enableIvy": false
}
感谢@DWhitSlaya 在这里提到这一点:https://www.reddit.com/r/angular/comments/ih7hib/how_to_use_viewcontainerref_with_dynamic/g30hpzv
背景:
我已经按照多个教程远程加载模块,以便尝试使用 Angular 创建插件架构。特别是:
- 我在主应用程序中使用 Angular 10
- angular 构建插件的构建器
- 汇总以生成 UMD 模块。
- SystemJS 作为模块加载器
手头的问题:
- 我可以成功加载远程定义的模块并且远程模块可以成功使用公共服务(我所说的公共服务是指主要或核心应用程序和插件已知)
- 我无法动态加载该模块中定义的组件,即使该组件在插件模块声明、导出中定义并作为模块本身的入口组件也是如此。
代码如下:
https://github.com/rickszyr/angular-plugins/
如何运行它:
- npm 安装
- npm 运行 build:init //编译公共服务
- npm 运行 build:plugins // 为两个插件生成 umd 包
- npm 运行 start:all // 启动服务器和客户端
- 使用默认字段值单击“加载”
- 出现错误。
错误:
我发现由于某种原因,组件宿主视图没有初始化 _lview 值。但我不确定如何处理该信息或如何确保它确实正确设置了该值。
尝试创建组件并将其插入动态组件加载器时失败的行在 app.component.ts 中。
非常感谢您
主要成分:
app.component.ts
import { Compiler, Component, ComponentFactoryResolver, Injector, NgModuleFactory, ViewChild, ViewContainerRef } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { IPlugin, PluginCatalogService } from "interfaces";
import * as ngCore from "@angular/core";
import * as ngCommon from "@angular/common";
import * as ngBrowser from "@angular/platform-browser";
import * as commonInterfaces from "interfaces";
import { ModuleLoader } from "./remote-module-loader.service";
import { DynamicComponentDirective } from "./directives/dynamic-component.directive";
@Component({
selector: "app-root",
templateUrl: "app.component.html",
styles: [],
})
export class AppComponent {
title = "plugins";
loader: ModuleLoader;
@ViewChild('putStuffHere', {read: ViewContainerRef}) putStuffHere: ViewContainerRef;
constructor(
public pluginService: PluginCatalogService,
private injector: Injector,
private factoryResolver: ComponentFactoryResolver,
private compiler: Compiler,
public viewContainer: ViewContainerRef
) {
this.loader = new ModuleLoader();
}
loadModule(modulePath: string, moduleName: string) {
this.loader.register({
"@angular/core": ngCore,
"@angular/common": ngCommon,
"interfaces": commonInterfaces
}).then(ml => ml.load(modulePath).then(m => {
const moduleFactory: NgModuleFactory<any> = <NgModuleFactory<any>>m.default[moduleName+ "NgFactory"];
const moduleReference = moduleFactory.create(this.injector);
moduleReference.componentFactoryResolver.resolveComponentFactory((<IPlugin>moduleReference.instance).mainComponent);
var compFactory = moduleReference.componentFactoryResolver.resolveComponentFactory(this.getEntryComponent(moduleFactory));
this.putStuffHere.createComponent(compFactory); // <<< this fails
var component = compFactory.create(this.injector);
this.putStuffHere.insert(component.hostView);// <<< this fails
}));
}
getEntryComponent(moduleFactory: any):any {
var existModuleLoad = (<any>moduleFactory.moduleType).decorators[0].type.prototype.ngMetadataName === "NgModule"
if (!existModuleLoad) return null;
return moduleFactory.moduleType.decorators[0].args[0].entryComponents[0];
}
}
app.component.html
<h1>Welcome!</h1>
<p>
<label>Path Remote</label><input #pathRemote value="http://localhost:3000/plugin2.module.umd.js">
</p>
<p>
<label>Remote Name</label><input #remoteName value="Plugin2Module">
</p>
<p>
<button (click)="loadModule(pathRemote.value, remoteName.value)">Load</button>
</p>
<ol>
<li *ngFor="let module of pluginService.installedPlugins">{{ module.name}}</li>
</ol>
<ng-container #putStuffHere></ng-container>
编译插件代码:
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core'), require('@angular/common'), require('interfaces')) :
typeof define === 'function' && define.amd ? define(['exports', '@angular/core', '@angular/common', 'interfaces'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Plugin2Module = {}, global.i0, global.i3, global.i4));
}(this, (function (exports, i0, i3, i4) { 'use strict';
/**
* @fileoverview added by tsickle
* Generated from: lib/plugin2.component.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class Plugin2Component {
constructor() {
this.title = "Nada";
}
/**
* @return {?}
*/
ngOnInit() {
}
}
Plugin2Component.decorators = [
{ type: i0.Component, args: [{
selector: 'lib-plugin2',
template: `
<p>
plugin2 works!
</p>
`
}] }
];
/** @nocollapse */
Plugin2Component.ctorParameters = () => [];
Plugin2Component.propDecorators = {
title: [{ type: i0.Input }]
};
/**
* @fileoverview added by tsickle
* Generated from: lib/plugin2.module.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class Plugin2Module {
/**
* @param {?} pluginService
*/
constructor(pluginService) {
console.log("Se registro Plugin 2");
pluginService.installedPlugins.push(this);
}
/**
* @return {?}
*/
get name() {
return "Plugin 2";
}
/**
* @return {?}
*/
get mainComponent() {
return Plugin2Component;
}
}
Plugin2Module.decorators = [
{ type: i0.NgModule, args: [{
declarations: [Plugin2Component],
imports: [i3.CommonModule],
exports: [Plugin2Component],
entryComponents: [Plugin2Component]
},] }
];
/** @nocollapse */
Plugin2Module.ctorParameters = () => [
{ type: i4.PluginCatalogService }
];
/**
* @fileoverview This file was generated by the Angular template compiler. Do not edit.
*
* @suppress {suspiciousCode,uselessCode,missingProperties,missingOverride,checkTypes,extraRequire}
* tslint:disable
*/
var styles_Plugin2Component = [];
var RenderType_Plugin2Component = i0.ɵcrt({ encapsulation: 2, styles: styles_Plugin2Component, data: {} });
function View_Plugin2Component_0(_l) { return i0.ɵvid(0, [(_l()(), i0.ɵeld(0, 0, null, null, 1, "p", [], null, null, null, null, null)), (_l()(), i0.ɵted(-1, null, [" plugin2 works! "]))], null, null); }
function View_Plugin2Component_Host_0(_l) { return i0.ɵvid(0, [(_l()(), i0.ɵeld(0, 0, null, null, 1, "lib-plugin2", [], null, null, null, View_Plugin2Component_0, RenderType_Plugin2Component)), i0.ɵdid(1, 114688, null, 0, Plugin2Component, [], null, null)], function (_ck, _v) { _ck(_v, 1, 0); }, null); }
var Plugin2ComponentNgFactory = i0.ɵccf("lib-plugin2", Plugin2Component, View_Plugin2Component_Host_0, { title: "title" }, {}, []);
/**
* @fileoverview This file was generated by the Angular template compiler. Do not edit.
*
* @suppress {suspiciousCode,uselessCode,missingProperties,missingOverride,checkTypes,extraRequire}
* tslint:disable
*/
var Plugin2ModuleNgFactory = i0.ɵcmf(Plugin2Module, [], function (_l) { return i0.ɵmod([i0.ɵmpd(512, i0.ComponentFactoryResolver, i0.ɵCodegenComponentFactoryResolver, [[8, [Plugin2ComponentNgFactory]], [3, i0.ComponentFactoryResolver], i0.NgModuleRef]), i0.ɵmpd(4608, i3.NgLocalization, i3.NgLocaleLocalization, [i0.LOCALE_ID]), i0.ɵmpd(1073742336, i3.CommonModule, i3.CommonModule, []), i0.ɵmpd(1073742336, Plugin2Module, Plugin2Module, [i4.PluginCatalogService])]); });
exports.Plugin2ModuleNgFactory = Plugin2ModuleNgFactory;
Object.defineProperty(exports, '__esModule', { value: true });
})));
tsconfig.lib.json 对于插件:
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "../../out-tsc/lib",
"target": "es2015",
"declaration": true,
"declarationMap": true,
"inlineSources": true,
"types": [],
"lib": [
"dom",
"es2018"
]
},
"angularCompilerOptions": {
"enableIvy": false,
"skipTemplateCodegen": false,
"strictMetadataEmit": true,
"annotateForClosureCompiler": true,
"enableResourceInlining": true
},
"exclude": [
"src/test.ts",
"**/*.spec.ts"
]
}
您在插件中禁用了 Ivy 编译器,但忘记在主项目中禁用它。
在主要 tsconfig.json
中添加以下内容将解决问题
"angularCompilerOptions": {
"enableIvy": false
}
感谢@DWhitSlaya 在这里提到这一点:https://www.reddit.com/r/angular/comments/ih7hib/how_to_use_viewcontainerref_with_dynamic/g30hpzv