Reputation: 167
I am trying to create a generic stepper component using Angular CDK Stepper as mention here https://material.angular.io/guide/creating-a-custom-stepper-using-the-cdk-stepper.
I want to pass stepper's header and footer template through template references by using ngTemplateOutlet.
Templates are not working when I pass cdkStepperPrevious,cdkStepperNext in the buttons to them.
Code can be found here https://stackblitz.com/edit/angular-bgyqqz-r8axmr
<ng-template #stepperHeader>
<header>
<h2> Header</h2>
</header>
</ng-template>
<ng-template #stepperFooter>
<button class="example-nav-button" cdkStepperPrevious>Next</button>
<button class="example-nav-button" cdkStepperNext>Previous</button>
</ng-template>
<example-custom-stepper [headerTemplate]="stepperHeader" [footerTemplate]="stepperFooter">
<cdk-step> <p>This is any content of "Step 1"</p> </cdk-step>
<cdk-step> <p>This is any content of "Step 2"</p> </cdk-step>
</example-custom-stepper>
and in my example-custom-stepper component
<section class="example-container">
<ng-container [ngTemplateOutlet]="headerTemplate"></ng-container>
<div [style.display]="selected ? 'block' : 'none'">
<ng-container [ngTemplateOutlet]="selected.content"></ng-container>
</div>
<footer class="example-step-navigation-bar">
<ng-container [ngTemplateOutlet]="footerTemplate"></ng-container>
</footer>
</section>
Error
preview-4adb70f742b91f09679fb.js:1 ERROR Error: StaticInjectorError(AppModule)[CdkStep -> CdkStepper]: StaticInjectorError(Platform: core)[CdkStep -> CdkStepper]: NullInjectorError: No provider for CdkStepper! at NullInjector.get (injector.ts:43) at resolveToken (injector.ts:346) at tryResolveToken (injector.ts:288) at StaticInjector.get (injector.ts:168) at resolveToken (injector.ts:346) at tryResolveToken (injector.ts:288) at StaticInjector.get (injector.ts:168) at resolveNgModuleDep (ng_module.ts:125) at NgModuleRef_.get (refs.ts:507) at resolveDep (provider.ts:423)
Upvotes: 4
Views: 8361
Reputation: 49
Please note that ng-template, ng-container or ngTemplateOutlet are NOT THE CULPRITS TO BE BLAMED.
You're using cdkStepperPrevious and cdkStepperNext directives in cdk-custom-stepper-without-form-example.html. Check out the component that backs the html file. There is no provider for CdkStepper in CdkCustomStepperWithoutFormExample component. cdk directives are available to use only after you provide cdkStepper. A work around may be to add the following code to the @Component directive: providers: [{ provide: CdkStepper }]
However, that won't solve the problem. Whenever you declare ng-template, all directives and events inside the template get tied to the component in which the html file is defined. Open the developer options and inspect the button that is being displayed inside the ng-container. You'll see html markup of the pattern _ngcontent-xxx-c1. If you look at the html markup of other elements they will read _ngcontent-xxx-c0. It implies that the content of the ng-container does not belong to the component in which it is rendered. (Angular marks every element of a component with _ngcontent-xxx-c# where # is the number of component.) More info can be found at the following link:
What does _ngcontent-c# mean in Angular?
This means that cdkStepperPrevious and cdkStepperNext directives specified in the buttons will try to invoke implementation in the CdkCustomStepperWithoutFormExample class even though they are rendered inside the CustomStepper component. However, that class doesn't extend CdkStepper. Therefore, the _stepper instance will remain undefined. Have a look at the source code of CdkStepperNext/Previous directives.
https://github.com/angular/components/blob/master/src/cdk/stepper/stepper-button.ts
The constructors of both those directives take a _stepper instance of type CdkStepper. That is available only inside the CustomStepper class.
So, if you want your code to work you should define ng-template containing the footer anywhere inside example-custom-stepper.html file. Then, if you try to render that template inside an ng-container it will work as expected because _stepper instance needed by the constructor of cdkStepperPrevious/Next directive is available inside the CustomStepper class.
1) Remove the footer from CdkCustomStepperWithoutFormExample component
2) Add it to CustomStepper
Upvotes: 2