Angular 远程加载的模块没有将入口组件加载到视图中

Angular Remotely loaded Module does not load entry component into view

背景:

我已经按照多个教程远程加载模块,以便尝试使用 Angular 创建插件架构。特别是:

手头的问题:

代码如下:

https://github.com/rickszyr/angular-plugins/

如何运行它:

  1. npm 安装
  2. npm 运行 build:init //编译公共服务
  3. npm 运行 build:plugins // 为两个插件生成 umd 包
  4. npm 运行 start:all // 启动服务器和客户端
  5. 使用默认字段值单击“加载”
  6. 出现错误。

错误:

我发现由于某种原因,组件宿主视图没有初始化 _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