Angular 远程加载模块不会将入口组件加载到视图中 [英] Angular Remotely loaded Module does not load entry component into view

查看:140
本文介绍了Angular 远程加载模块不会将入口组件加载到视图中的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

背景:

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

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

手头的问题:

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

代码如下:

我发现由于某种原因,组件宿主视图没有初始化 _lview 值.但我不确定如何处理这些信息或如何确保它确实正确设置了该值.

尝试创建组件并将其插入动态组件加载器时,失败的行位于 app.component.ts 中.

非常感谢您

主要组件:

app.component.ts

import { Compiler, Component, ComponentFactoryResolver, Injector, NgModuleFactory, ViewChild, ViewContainerRef } from "@angular/core";从@angular/common/http"导入{HttpClient};import { IPlugin, PluginCatalogService } from interfaces";从@angular/core"导入 * 作为 ngCore;从@angular/common"导入 * 作为 ngCommon;从@angular/platform-b​​rowser"导入 * 作为 ngBrowser;import * as commonInterfaces from "interfaces";import { ModuleLoader } from "./remote-module-loader.service";import { DynamicComponentDirective } from "./directives/dynamic-component.directive";@成分({选择器:应用程序根",templateUrl: app.component.html",样式:[],})导出类 AppComponent {标题=插件";加载器:模块加载器;@ViewChild('putStuffHere', {read: ViewContainerRef}) putStuffHere: ViewContainerRef;构造函数(公共插件服务:插件目录服务,私人注射器:注射器,私有 factoryResolver:ComponentFactoryResolver,私有编译器:编译器,公共视图容器:ViewContainerRef){this.loader = new ModuleLoader();}加载模块(模块路径:字符串,模块名称:字符串){this.loader.register({@angular/core":ngCore,@angular/common":ngCommon,接口":commonInterfaces}).then(ml => ml.load(modulePath).then(m => {const moduleFactory: NgModuleFactory= <NgModuleFactory<any>>m.default[moduleName+NgFactory"];const moduleReference = moduleFactory.create(this.injector);moduleReference.componentFactoryResolver.resolveComponentFactory((moduleReference.instance).mainComponent);var compFactory = moduleReference.componentFactoryResolver.resolveComponentFactory(this.getEntryComponent(moduleFactory));this.putStuffHere.createComponent(compFactory);//<<<这失败了var component = compFactory.create(this.injector);this.putStuffHere.insert(component.hostView);//<<<这失败了}));}getEntryComponent(moduleFactory: any):any {varexistModuleLoad = (moduleFactory.moduleType).decorators[0].type.prototype.ngMetadataName === "NgModule";if (!existModuleLoad) 返回空值;返回 moduleFactory.moduleType.decorators[0].args[0].entryComponents[0];}}

app.component.html

欢迎光临!

<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><按钮(点击)=loadModule(pathRemote.value, remoteName.value)">加载</button></p><ol><li *ngFor="let module of pluginService.installedPlugins">{{module.name}}</li></ol><ng-container #putStuffHere></ng-container>

编译的插件代码:

(function (global, factory) {typeof export === 'object' &&typeof 模块 !== 'undefined' ?factory(exports, require('@angular/core'), require('@angular/common'), require('interfaces')) :typeof 定义 === '函数' &&定义.amd ?定义(['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 由 tsickle 添加* 生成自:lib/plugin2.component.ts* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingRequire,missingReturn,unusedPrivateMembers,uselessCode} 由 tsc 检查*/类 Plugin2Component {构造函数(){this.title = "Nada";}/*** @返回 {?}*/ngOnInit() {}}Plugin2Component.decorators = [{ 类型:i0.Component,参数:[{选择器:'lib-plugin2',模板:`<p>plugin2 有效!</p>`}] }];/** @nocollapse */Plugin2Component.ctorParameters = () =>[];Plugin2Component.propDecorators = {标题:[{ 类型:i0.Input }]};/*** @fileoverview 由 tsickle 添加* 生成自:lib/plugin2.module.ts* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingRequire,missingReturn,unusedPrivateMembers,uselessCode} 由 tsc 检查*/类 Plugin2Module {/*** @param {?} pluginService*/构造函数(插件服务){console.log(Se registro Plugin 2");pluginService.installedPlugins.push(this);}/*** @返回 {?}*/获取名称(){返回插件 2";}/*** @返回 {?}*/获取 mainComponent() {返回 Plugin2Component;}}Plugin2Module.decorators = [{ 类型:i0.NgModule,参数:[{声明:[Plugin2Component],进口:[i3.CommonModule],出口:[Plugin2Component],entryComponents: [Plugin2Component]},] }];/** @nocollapse */Plugin2Module.ctorParameters = () =>[{ 类型:i4.PluginCatalogService }];/*** @fileoverview 这个文件是由 Angular 模板编译器生成的.不要编辑.** @suppress {suspiciousCode,uselessCode,missingProperties,missingOverride,checkTypes,extraRequire}* tslint:禁用*/var style_Plugin2Component = [];var RenderType_Plugin2Component = i0.ɵcrt({封装:2,样式:styles_Plugin2Component,数据:{}});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有效!"]))], 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 这个文件是由 Angular 模板编译器生成的.不要编辑.** @suppress {suspiciousCode,uselessCode,missingProperties,missingOverride,checkTypes,extraRequire}* tslint:禁用*/var Plugin2ModuleNgFactory = i0.ɵcmf(Plugin2Module, [], function (_l) { return i0.ɵmod([i0.ɵmpd(512, i0.ComponentFactoryResolver, i0.ɵCodegenComponentFactoryResolver, [[8, [Plugin2ComponentNgFactory]0], [3].ComponentFactoryResolver], i0.NgModuleRef]), i0.ɵmpd(4608, i3.NgLocalization, i3.NgLocaleLocalization, [i0.LOCALE_ID]), i0.ɵmpd(1073742336, i3.CommonModule, i3.Common) [].ɵmpd(1073742336, Plugin2Module, Plugin2Module, [i4.PluginCatalogService])]); });export.Plugin2ModuleNgFactory = Plugin2ModuleNgFactory;Object.defineProperty(exports, '__esModule', { value: true });})));

插件的 tsconfig.lib.json:

/* 要了解有关此文件的更多信息,请参阅:https://angular.io/config/tsconfig.*/{扩展":../../tsconfig.json",编译器选项":{outDir":../../out-tsc/lib",目标":es2015",声明":真实,declarationMap":真,inlineSources":真,类型":[],库":[dom",es2018"]},angularCompilerOptions":{enableIvy":假,skipTemplateCodegen":假,strictMetadataEmit":真,annotateForClosureCompiler":真,启用资源内联":真},排除":[src/test.ts",**/*.spec.ts"]}

解决方案

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

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

angularCompilerOptions":{启用常春藤":假}

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

Background:

I've followed multiple tutorials to load a module remotely in order to attempt to create a plugin architecture using Angular. In particular:

  • I'm using Angular 10 for the main application
  • angular builder to build the plugins
  • Rollup to generate a UMD module.
  • SystemJS as a module loader

Issue at hand:

  • I can successfully load the remotely defined modules and the remote modules can successfully use common services (by common I mean known by the main or core application and the plugin)
  • I cannot dynamically load a component defined in that module even though the component is defined in the plugin module declarations, exports and as an entry component in the module itself.

Here's the code:

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

How to run it:

  1. npm install
  2. npm run build:init //this compiles the common services
  3. npm run build:plugins // generates umd bundles for two plugins
  4. npm run start:all // launches server and client
  5. click on "Load" with the default field values
  6. get an error.

The error:

What I found out is that for some reason that components host view does not have the _lview value initialized. But i'm not sure what to do with that information or how to make sure it does have that value properly set.

The lines that fail are in app.component.ts when trying to create the component and insert it into the dynamic component loader.

Thank you very much in advance

Main components:

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>

compiled plugin code:

(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 for the plugin:

/* 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"
  ]
}

解决方案

You have disabled the Ivy compiler in the plugins, but forgot to disable it in the main project.

Adding the following in the main tsconfig.json will fix the issue

"angularCompilerOptions": {
  "enableIvy": false
}

Thanks to @DWhitSlaya for mentioning this here: https://www.reddit.com/r/angular/comments/ih7hib/how_to_use_viewcontainerref_with_dynamic/g30hpzv

这篇关于Angular 远程加载模块不会将入口组件加载到视图中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆