Angular 远程加载模块不会将入口组件加载到视图中

Posted

技术标签:

【中文标题】Angular 远程加载模块不会将入口组件加载到视图中【英文标题】:Angular Remotely loaded Module does not load entry component into view 【发布时间】:2021-06-16 06:30:34 【问题描述】:

背景:

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

我将 Angular 10 用于主应用程序 用于构建插件的角度构建器 汇总以生成 UMD 模块。 SystemJS 作为模块加载器

手头的问题:

我可以成功加载远程定义的模块,并且远程模块可以成功使用公共服务(我的意思是主或核心应用程序和插件都知道) 我无法动态加载该模块中定义的组件,即使该组件是在插件模块声明、导出和模块本身中的入口组件中定义的。

代码如下:

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

如何运行:

    npm 安装 npm run build:init //编译通用服务 npm run build:plugins // 为两个插件生成 umd 包 npm run 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"
  ]

【问题讨论】:

【参考方案1】:

您在插件中禁用了 Ivy 编译器,但忘记在主项目中禁用它。

在主tsconfig.json 中添加以下内容将解决问题

"angularCompilerOptions": 
  "enableIvy": false

感谢@DWhitSlaya 在此提及这一点:https://www.reddit.com/r/angular/comments/ih7hib/how_to_use_viewcontainerref_with_dynamic/g30hpzv

【讨论】:

是的!经过大量的谷歌搜索,我能够弄清楚这一点。太感谢了。我现在还设法在没有禁用常春藤的情况下运行所有​​内容,这太棒了,因为这是前进的方向

以上是关于Angular 远程加载模块不会将入口组件加载到视图中的主要内容,如果未能解决你的问题,请参考以下文章

Angular 9 嵌套延迟加载模块,带有嵌套路由器出口

RouterLink 不会在单击时导航到组件,而是在加载同一模块中的其他组件后导航

Angular的内置模块

有没有办法在 Angular 中延迟加载组件而不是模块?

Angular 2/4:为每个模块加载 sass 变量

我可以为 Angular 6 中模块的所有子路由加载一个组件吗?