Не найдена фабрика компонентов для неопределенных. Вы добавили его в @ NgModule.entryComponents? - PullRequest
0 голосов
/ 24 февраля 2020

Я пишу тест жасмина для моего angular 8 приложения, которое проверяет, вызван ли метод onOverlayClicked для события click. Во время выполнения теста я получаю сообщение об ошибке

Error: No component factory found for undefined. Did you add it to @NgModule.entryComponents?
        error properties: Object({ ngComponent: undefined, ngDebugContext: DebugContext_({ view: Object({ def: Object({ factory: Function, nodeFlags: 37928961, rootNodeFlags: 33554433, nodeMatchedQueries: 0, flags: 0, nodes: [ Object({ nodeIndex: 0, parent: null, renderParent: null, bindingIndex: 0, outputIndex: 0, checkIndex: 0, flags: 33554433, childFlags: 4374528, directChildFlags: 4374528, childMatchedQueries: 0, matchedQueries: Object({  }), matchedQueryIds: 0, references: Object({  }), ngContentIndex: null, childCount: 1, bindings: [  ], bindingFlags: 0, outputs: [  ], element: 
Object({ ns: '', name: 'app-dialog', attrs: [  ], template: null, componentProvider: Object({ nodeIndex: 1, parent: <circular reference: Object>, renderParent: <circular reference: Object>, bindingIndex: 0, outputIndex: 0, checkIndex: 1, flags: 4374528, childFlags: 0, directChildFlags: 0, childMatchedQueries: 0, matchedQueries: Object, matchedQueryIds: 0, references: Object, ngContentIndex: 
-1, childCount: 0, bindings: Array, b ...

Если я добавляю InsertionDirective к элементу entryComponents, я получаю сообщение об ошибке

Cannot add directive to entryComponents.

Я не уверен, в чем проблема

Модуль

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DialogComponent } from '../../components/dialog/dialog.component';
import { InsertionDirective } from '../../shared/directives/insertion.directive';

@NgModule({
  imports: [CommonModule],
  declarations: [DialogComponent, InsertionDirective],
  entryComponents: [DialogComponent]
})

export class DialogModule {}

Компонент

import { AfterViewInit, ChangeDetectorRef, Component, ComponentFactoryResolver, ComponentRef, OnDestroy, Type, ViewChild } from '@angular/core';
import { Subject } from 'rxjs';
import { InsertionDirective } from '../../shared/directives/insertion.directive';
import { DialogRef } from './config/dialog-ref';

@Component({
  selector: 'app-dialog',
  templateUrl: './dialog.component.html'
})

export class DialogComponent implements AfterViewInit, OnDestroy {
  private readonly _onClose = new Subject<any>();

  public componentRef: ComponentRef<any>;
  public childComponentType: Type<any>;
  public onClose = this._onClose.asObservable();

  // add this:
  @ViewChild(InsertionDirective, { static: false })
  insertionPoint: InsertionDirective;

  constructor(public componentFactoryResolver: ComponentFactoryResolver,
              public cd: ChangeDetectorRef,
              public dialog: DialogRef) {
    console.log('My InsertionPoint');
    console.log(this.insertionPoint);
  }

  ngAfterViewInit() {
    this.loadChildComponent(this.childComponentType);
    this.cd.detectChanges();
  }

  ngOnDestroy() {
    if (this.componentRef) {
      this.componentRef.destroy();
    }
  }

  onOverlayClicked(evt: MouseEvent) {
    // close the dialog
  }

  onDialogClicked(evt: MouseEvent) {
    evt.stopPropagation();
  }

  loadChildComponent(componentType: Type<any>) {
    const componentFactory = this.componentFactoryResolver.resolveComponentFactory(componentType);

    const viewContainerRef = this.insertionPoint.viewContainerRef;
    console.log(viewContainerRef);
    viewContainerRef.clear();

    this.componentRef = viewContainerRef.createComponent(componentFactory);
  }

  closeModal() {
    this.dialog.close();
  }
}

Тестовый компонент

describe('DialogComponent', () => {
  let component: DialogComponent;
  let fixture: ComponentFixture<DialogComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [SharedModule, DialogModule], // DialogModule
      //  declarations: [ InsertionDirective ],
      providers: [ DialogRef ]
    })
    .overrideModule(BrowserDynamicTestingModule, { set: { entryComponents: [DialogComponent] } })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(DialogComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  fit('should set call onOverlayClicked click', () => {
   // const fixture = TestBed.createComponent(AppComponent);
    fixture.detectChanges();

    spyOn(component, 'onOverlayClicked');
    const overlay = fixture.debugElement.query(By.css('.overlay'));
    overlay.triggerEventHandler('click', {});
    fixture.detectChanges();

    expect(component.onOverlayClicked).toHaveBeenCalled();

  });
});

InsertionDirective

import { Directive, ViewContainerRef } from '@angular/core';

@Directive({
  selector: '[appInsertion]',
})
export class InsertionDirective {
  constructor(public viewContainerRef: ViewContainerRef) {}
}

1 Ответ

0 голосов
/ 24 февраля 2020

В вашем классе DialogComponent вы программно создаете компонент Angular, используя ComponentResolver, как показано во фрагменте ниже:

ngAfterViewInit() {
  this.loadChildComponent(this.childComponentType);
  this.cd.detectChanges();
}

loadChildComponent(componentType: Type<any>) {
  const componentFactory = this.componentFactoryResolver.resolveComponentFactory(componentType);

  const viewContainerRef = this.insertionPoint.viewContainerRef;
  console.log(viewContainerRef);
  viewContainerRef.clear();

  this.componentRef = viewContainerRef.createComponent(componentFactory);
}

Значение объекта childComponentType, для которого вы создаете компонент программно, никогда не инициализируется в модульном тесте и, следовательно, выдает ошибку No component factory found for undefined.

Допустим, вы хотите программно создать экземпляр для XYZComponent, вы должны установить его в модульном тесте, как показано ниже :

describe('DialogComponent', () => {
  let component: DialogComponent;
  let fixture: ComponentFixture<DialogComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [SharedModule, DialogModule], // DialogModule
      //  declarations: [ InsertionDirective ],
      /*
      * Since you want to programatically create an instance of XYZComponent
      * DialogComponent, include XYZComponent in entryComponents array
      */
      entryComponents: [XYZComponent],
      providers: [ DialogRef ]
    })
    .overrideModule(BrowserDynamicTestingModule, { set: { entryComponents: [DialogComponent] } })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(DialogComponent);
    component = fixture.componentInstance;
    // initialize childComponentType object
    component.childComponentType = XYZComponent;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  fit('should set call onOverlayClicked click', () => {
    spyOn(component, 'onOverlayClicked');
    const overlay = fixture.debugElement.query(By.css('.overlay'));
    overlay.triggerEventHandler('click', {});
    fixture.detectChanges();

    expect(component.onOverlayClicked).toHaveBeenCalled();
  });
});

Это должно исправить ваши ошибки сейчас. Обратите внимание, что вам нужно включить XYZComponent в массив entryComponents для Angular, чтобы разрешить компонент во время выполнения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...