【问题标题】:Dynamically loaded Angular 10 component cannot access CommonModule动态加载的 Angular 10 组件无法访问 CommonModule
【发布时间】:2021-02-14 14:52:22
【问题描述】:

我目前正在开发一个 Angular 应用程序。在我的一种方法中,我动态创建了一个组件,但是我无法使用组件中的CommonModule 中的ngClassngIf 和其他此类指令。

以下是错误示例

Error when I use ngIf or ngClass inside the dynamically loaded logo component

我已经做了什么:

  • 我已经在我的项目 app.module.ts 中导入了 commonModule
  • 我已经在我的显示组件中导入了 commonModule,它可以正常工作,因为我可以在所有其他组件中使用 ngIf 和 ngClass 而没有任何问题
  • 此外,只要我没有在我的 html 中使用 CommonModule 中的任何指令,我就可以毫无错误地导入任何组件
  • 我尝试通过组件工厂 createComponent 函数导入 NgModule 的实例,如angular documentation 所示:

Quick view of angular documentation on component factory createcomponent

我已经为此花费了数小时,并认为这与我使用 createComponent 方法有关。

请帮忙!

这是我的 app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule, HammerGestureConfig, HammerModule, 
 HAMMER_GESTURE_CONFIG } from '@angular/platform-browser';
import { GoogleAnalyticsService } from 
 './shared/services/googleanalytics'; // import our Google Analytics 
 service
import { BrowserAnimationsModule } from '@angular/platform- 
browser/animations';
import { HttpClientModule } from '@angular/common/http';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { CommonModule } from '@angular/common';


@NgModule({
  declarations: [AppComponent],
  imports: [
   CommonModule,
   BrowserModule,
   AppRoutingModule,
   HttpClientModule,
   BrowserAnimationsModule,
   ],
  providers: [GoogleAnalyticsService], 
  bootstrap: [AppComponent],
 })
 export class AppModule {}

这是我的显示组件 (Bdoc-a.component.ts)

import {
  Component, OnInit, Input, Output, Renderer2, AfterViewInit, 
 AfterContentChecked, ViewChild, ElementRef,
  ComponentFactoryResolver, ViewContainerRef, ViewChildren, QueryList
  } from '@angular/core';
 import { StorageService } from 
 '../../../../shared/services/storage.service';
import { AuthService } from '../../../../auth/auth.service';

 @Component({
  selector: 'app-bdoc-a',
  templateUrl: './bdoc-a.component.html',
  styleUrls: ['./bdoc-a.component.scss'],
 })
 export class BdocAComponent implements OnInit, AfterViewInit, 
 AfterContentChecked {
  @Input() docConfig = { aspectRatio: '4:3', width: 800, height: 500 };
  @Input() bgStyles = { shadow: true, bgClr: '#ffcc00' };
  @Input() showBtns = false;

  fWidth = this.docConfig.width;
  fMaxWidth = this.docConfig.width;
  fHeight = this.docConfig.height;
  fMaxHeight = this.docConfig.height;

  @Input() url = '';
  @Input() settings = { ...  };


  ...

  @ViewChildren('loadDynAssetElEditItms', { read: ViewContainerRef }) 
  biEditEls: QueryList<ViewContainerRef>;
  @ViewChildren('loadDynAssetElViewItms', { read: ViewContainerRef }) 
  biViewEls: QueryList<ViewContainerRef>;
  loadDynAssetEl: any;


  @ViewChild('bieditorFloat', { read: ElementRef }) bieditorFloat: 
  ElementRef;
  @ViewChild('bieditorFloatViewer', { read: ElementRef }) 
  bieditorFloatViewer: ElementRef;

  // variable to hold all document page elements
  allDocPages: any;
  allDocPageComp = [];


  constructor(private storage: StorageService,
          private resolver: ComponentFactoryResolver,
          private authService: AuthService,
          private elmRef: ElementRef) {
   this.url = 'templates/logo/1/logo1a/logo1a.component';
   }


   ngAfterViewInit(): void {
    this.initBrandAsset();
   }

  initBrandAsset(): void {
    this.allDocPageComp = [];

    setTimeout(() => {
      for (let i = 0; i < this.pages; i++) {
        this.loadDynAsset(this.url, i);
      }
      this.setDocPageVar();
    }, 200);
  }

  async loadDynAsset(url, pgIndex) {
    const impEl = await import( 'src/app/' + url);
    const allKeys = Object.keys(impEl);


    this.biEditEls.forEach((itm, i) => {
      if (i === pgIndex) {
      itm.clear();
      const newComp = itm.createComponent(
      this.resolver.resolveComponentFactory(impEl[allKeys[0]]));
      newComp.instance['bol']['test'] = 'LOGO TEST TEXT HERE... ' + pgIndex;

      this.allDocPageComp.push(newComp.instance);
    }
    });

    }
   }

这是我的 Logo1a.component.html

   <div class="baItem biLogo logo1a edit">
    <div class="null bilNull">
      <ng-container>
        <div class="bilSymb">
          <div class="null">
           <div #forTxtLogo class="forTxtLogo" *ngIf="config.symb.mode === 
         'txt'">
        <div class="symbItm"><div class="nl">B</div></div><div 
     class="symbItm"><div class="nl">S</div></div>
      </div>
      <div #forImgSvgLogo class="forImgSvgLogo" *ngIf="config.symb.mode 
     === 'svg' || config.symb.mode === 'img'">
        <div class="symbItm"><div class="nl"></div></div>
      </div>
    </div>
   </div><!-- end of bilSymb -->
   </ng-container>
   <ng-container *ngIf="config.body.show">
    <div class="bilBody">
    <div class="null">
    <ng-container *ngIf="config.body.txt.show">
      <div #forTxtArea class="forTxtArea">
        <div class="null">Logo Body Text Area</div>
      </div>
    </ng-container>
    <ng-container *ngIf="config.body.tag.show">
      <div #forTagArea class="forTagArea"><div class="null">Logo Tag 
   Area...</div></div>
    </ng-container>
  </div>
   </div><!-- end of bilBody -->
   </ng-container>
   </div><!-- end of biNull -->
   </div><!-- end of biLogo-->

这是我的 Logo1a.component.ts

import { NgModule, Component, OnInit, Input, Output, Renderer2, 
AfterViewInit, AfterContentChecked, ViewChild, ElementRef,
 ComponentFactoryResolver, ViewContainerRef, ViewChildren, QueryList
} from '@angular/core';
 import { StorageService } from 
'../../../../shared/services/storage.service';
 import { AuthService } from '../../../../auth/auth.service';

 @Component({
  selector: "app-logo1a",
   templateUrl: "./logo1a.component.html",
  styleUrls: ["./logo1a.component.scss"],
})


export class Logo1aComponent implements OnInit, AfterViewInit {
  @ViewChild('loadExtra', { read: ViewContainerRef }) loadExtra: 
 ViewContainerRef;

 @Input() bol = { test: 'LOGO 1A LOADED!' }; // breadth of life

 public config = {
   symb: {
   show: true,
   mode: 'txt', // 'txt', 'symb', 'img'
  },
 body: {
  show: true,
  txt: { show: true },
  tag: { show: true }
  }
 }

  constructor(private storage: StorageService,
          private resolver: ComponentFactoryResolver,
          private vcRef: ViewContainerRef,
          private authService: AuthService,
          private elmRef: ElementRef) { }

     ngOnInit(): void {}

     ngAfterViewInit() {}
  }

这是我的 Logo1a.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Logo1aComponent } from './logo1a.component';

@NgModule({
  imports: [CommonModule],
  declarations: [Logo1aComponent],
  exports: [Logo1aComponent]
})
export class Logo1aModule {}

但这是我在我的 Angular 10 应用程序中动态导入模块而不是组件后引发的错误,正如@jburtondev 建议的那样:

错误错误:未捕获(在承诺中):错误:断言错误:传入的类型不是 ComponentType,它没有 'ɵcmp' 属性。 ....

【问题讨论】:

  • Hey Bode,欢迎来到堆栈溢出!很高兴有你。你能发布你的代码吗?
  • 嗨@jburtondev 感谢您的帮助。我应该在重新编辑我的原始帖子时将代码发布在 cmets 中吗?
  • 很高兴,请。
  • 嗨,博德!有趣的是,几个月前我也发生过类似的事情。原来我已经过度设计了那个组件,我根本不需要使用ViewContainerRef,只需要一个简单的 ngFor 循环就可以实现我想要的。我建议您像 @jburtondev 所做的那样,使用一些代码和业务需求编辑您的问题,以便我们为您提供帮助
  • @jburtondev 好的会这样做...谢谢

标签: javascript angular typescript


【解决方案1】:

组件需要在声明CommonModule的自己的模块中。否则,Angular 无法在运行时将其与 CommonModule 关联。

1.创建组件模块

@NgModule({
  declarations: [ YourDynamicComponent ],
  imports: [ CommonModule ] // THIS IS WHAT WILL TELL ANGULAR TO LOAD IT INTO YOUR COMPONENT
  exports: [ YourDynamicComponent ]
})

export class YourDynamicModule { }

2。加载组件

constructor(private compiler: Compiler, private viewContainerRef: ViewContainerRef) {}


createDynamicComponent(): void {
  const componentModule = this.compiler.compileModuleAndAllComponentsSync(YourDynamicModule);
  const factory = componentModule.componentFactories.find(c => comp.componentType === YourDynamicComponent);  
  this.viewContainerRef.createComponent(factory);
}

3.现在应该用CommonModule

装饰它

【讨论】:

【解决方案2】:

所以我终于找到了一个不太干净的工作方法来解决我的问题。

  1. 我创建了一个新模块 LogoModules,我在其中导入了将在显示组件中显示的所有徽标模块

    从'@angular/core'导入{ NgModule }; 从'../templates/logo/1/logo1a/logo1a.module'导入{ Logo1aModule }; 从'../templates/logo/2/logo2a/logo2a.module'导入{ Logo2aModule }; 从 '../templates/logo/3/logo3a/logo3a.module' 导入 { Logo3aModule };

    @NgModule({ // 导入:[], // 导出:[], })

    导出类 LogoModules {}

这已根据@jburtondev 的建议进行了一些修改,以预先导入所有模块,但我仍然认为应该有一种更简洁的方法来完成这部分。在不预先导入模块的情况下动态加载模块和组件仍然有效,但有时也会失败。我认为这是由于 tsconfig 中的编译器和 webpack 的一些设置。我需要更多的研究来澄清这一点。

  1. 我在显示组件中导入了 LogoModules 模块

    从 '../templates/logo/logos.module' 导入 { LogoModules }; ...

但是... 所以我能够将每个徽标模板的相对路径存储在我的数据库中并根据用户点击动态显示它们,我在我的显示组件(Bdoc-a.component.ts)中更新了我的动态组件加载器,如下所示:

....
async loadDynAsset(url, pgIndex) {
  const mObj = await import('src/app/' + url2);
  const mKeys = Object.keys(mObj);
  const mName = mKeys[0];

  this.compiler.compileModuleAndAllComponentsAsync(mObj[mName])
    .then((factories) => {
      const f = factories.componentFactories[0];
      const newComp = this.testDynComp.createComponent(f);
      newComp.instance['bol']['test'] = 'LOGO TEST TEXT HERE... ' + pgIndex;
    });

  .... 

}
....

然后我能够得到一致的结果而没有错误。再次感谢@jburtondev 的一些建议。一旦我发现更好的方法来解决我的问题,我会立即更新我的答案。

【讨论】:

    猜你喜欢
    • 2019-10-19
    • 1970-01-01
    • 2013-12-09
    • 2016-08-01
    • 1970-01-01
    • 2011-02-13
    • 1970-01-01
    • 1970-01-01
    • 2019-12-04
    相关资源
    最近更新 更多