Reputation: 7863
I have seen a number of similar posts. I wish to be able to use @ngFor
to create components to then load into steps of the Material Stepper
I have been following an example of dynamic loading, and some other posts (which are incomplete), and come up with the following.
First I have a number of components I want to load into the steps. They are just empty apart from the string (eg "component is working"
eg
export class StepPage1Component implements OnInit, StepPage {
constructor() { }
ngOnInit() {
}
}
So I have 3 of these.
The "root" component is as follows.
export class WizardStepperComponent implements OnInit {
@ViewChild('stepper') private myStepper: MatStepper;
totalStepsCount: number;
public steps: StepPage[];
constructor(private stepsService: StepPagesService) {
this.steps = [
new StepPage1Component,
new StepPage2Component,
new StepPage3Component
]
}
Markup:
<mat-horizontal-stepper #stepper>
<mat-step *ngFor='let step of steps'>
<app-step-page-wrapper item='step'></app-step-page-wrapper>
</mat-step>
</mat-horizontal-stepper>
<!-- second option -->
<div>
<button (click)="goBack(stepper)" type="button" [disabled]="stepper.selectedIndex === 0">Back</button>
<button (click)="goForward(stepper)" type="button"
[disabled]="stepper.selectedIndex === stepper._steps.length-1">Next</button>
<!-- using totalStepsCount -->
<!-- <button (click)="goForward(stepper)" type="button" [disabled]="stepper.selectedIndex === totalStepsCount-1">Next</button> -->
</div>
So in markup above, I have the ngFor
that I am having problems with.
The wrapper is as follows...
import { Component, OnInit, Input, ViewChild, ComponentFactoryResolver } from '@angular/core';
import { directiveDef } from '@angular/core/src/view';
import { PageDirective } from '../page.directive';
@Component({
selector: 'app-step-page-wrapper',
template: '<ng-template pageDirective></ng-template>',
})
export class StepPageWrapperComponent implements OnInit {
@Input() item?: any;
@ViewChild(PageDirective) pageHost: PageDirective;
constructor(private componentFactoryResolver: ComponentFactoryResolver) { }
ngOnInit() {
if (this.item == undefined) {
console.error('Item undefined');
return
}
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.item);
const viewContainerRef = this.pageHost.viewContainerRef;
viewContainerRef.clear();
const componentRef = viewContainerRef.createComponent(componentFactory);
}
}
and finally the directive it is using is..
import { Directive, ViewContainerRef } from '@angular/core';
@Directive({
selector: '[pageDirective]'
})
export class PageDirective {
constructor(public viewContainerRef: ViewContainerRef) { }
}
So, I am trying to use the StepPageWrapperComponent
to take each component as an input (item
), and then use the componentFactoryResolver
to create the conponent and then "attach" it to the directive in the template, ie pageDirective
in <ng-template pageDirective></ng-template>
However, when I run this, I get the following error..
ERROR Error: No component factory found for step. Did you add it to @NgModule.entryComponents?
at noComponentFactoryError (core.js:9877)
at CodegenComponentFactoryResolver.push../node_modules/@angular/core/fesm5/core.js.CodegenComponentFactoryResolver.resolveComponentFactory (core.js:9915)
at StepPageWrapperComponent.push../src/app/step-page-wrapper/step-page-wrapper.component.ts.StepPageWrapperComponent.ngOnInit (step-page-wrapper.component.ts:21)
at checkAndUpdateDirectiveInline (core.js:22099)
at checkAndUpdateNodeInline (core.js:23363)
at checkAndUpdateNode (core.js:23325)
at debugCheckAndUpdateNode (core.js:23959)
at debugCheckDirectivesFn (core.js:23919)
at Object.eval [as updateDirectives] (WizardStepperComponent.html:3)
at Object.debugUpdateDirectives [as updateDirectives] (core.js:23911)
If I put a break point in StepPageWrapperComponent
, this.item
just seems to be a string!
So, this string "step" is meant to be the ngFor variable...
But it is just coming in as a string, rather than the component.
Does anyone have any ideas on what I am doing wrong here, or even if there is a better way of doing this?
For any more details the source for this sample is can be cloned from https://github.com/pjc2007/wizard-stepper1.git
The reason the input was not working was I forgot the square brackets when passing the variable to the input.. ie I had
<app-step-page-wrapper item='step'></app-step-page-wrapper>
instead of
<app-step-page-wrapper [item]='step'></app-step-page-wrapper>
I now have the component as the input for StepPageWrapperComponent
as expected.
So, my problem now is the following error that is thrown when executing the line const componentFactory=this.componentFactoryResolver.resolveComponentFactory(this.item)
ngComponent: StepPage1Component {}
message: "No component factory found for [object Object]. Did you add it to @NgModule.entryComponents?"
stack: "Error: No component factory found for [object Object]. Did you add it to @NgModule.entryComponents?↵ at noComponentFactoryError (http://localhost:4200/vendor.js:71338:17)↵ at CodegenComponentFactoryResolver.push../node_modules/@angular/core/fesm5/core.js.CodegenComponentFactoryResolver.resolveComponentFactory (http://localhost:4200/vendor.js:71376:19)↵ at StepPageWrapperComponent.push../src/app/step-page-wrapper/step-page-wrapper.component.ts.StepPageWrapperComponent.ngOnInit (http://localhost:4200/main.js:244:66)↵ at checkAndUpdateDirectiveInline (http://localhost:4200/vendor.js:83560:19)↵ at checkAndUpdateNodeInline (http://localhost:4200/vendor.js:84824:20)↵ at checkAndUpdateNode (http://localhost:4200/vendor.js:84786:16)↵ at debugCheckAndUpdateNode (http://localhost:4200/vendor.js:85420:38)↵ at debugCheckDirectivesFn (http://localhost:4200/vendor.js:85380:13)↵ at Object.eval [as updateDirectives] (ng:///AppModule/WizardStepperComponent.ngfactory.js:16:5)↵ at Object.debugUpdateDirectives [as updateDirectives] (http://localhost:4200/vendor.js:85372:21)"
__proto__: Object
I do have this as an entryComponent
ie
@NgModule({
declarations: [
AppComponent,
WizardStepperComponent,
StepPage1Component,
StepPage2Component,
StepPage3Component,
PageDirective,
StepPageWrapperComponent
],
imports: [
BrowserModule,
BrowserAnimationsModule,
MatStepperModule,
MatInputModule,
MatButtonModule,
MatAutocompleteModule
],
providers: [],
bootstrap: [AppComponent],
entryComponents: [
StepPage1Component,
StepPage2Component,
StepPage3Component,
StepPageWrapperComponent
]
Stepping into this.componentFactoryResolver.resolveComponentFactory
, I see the following...
So the component seems to be in the map, but the map.get is returning undefined?
Upvotes: 6
Views: 5587
Reputation: 214085
So the component seems to be in the map, but the map.get is returning undefined?
Yes, it's misleading. You're passing instance of class not class itself.
Consider the following example:
class Test {}
console.log(Test); // class Test {}
console.log(new Test()); // Test {}
As you can see the output is quite similar but they are two different things.
So the solution is to pass component Type instead of component instance:
this.steps = [
StepPage1Component,
StepPage2Component,
StepPage3Component
]
Upvotes: 3