In Angular, is it possible to have a linear stepper where the individual steps are separate components? For example:
<mat-horizontal-stepper [linear]="isLinear">
<mat-step [stepControl]="firstFormGroup" label="Some Form">
<first-component></first-component>
</mat-step>
<mat-step [stepControl]="secondFormGroup" label="Another Form">
<second-component></second-component>
</mat-step>
<mat-step [stepControl]="thirdFormGroup" label="Review">
<third-component></third-component>
</mat-step>
</mat-horizontal-stepper>
When I try this, I receive the following error upon hitting the matStepperNext
button:
TypeError: Cannot read property 'invalid' of undefined
.
You can use sub-forms to resolve it. I actually gave a talk a few months ago in Angular-UP conference about it: https://www.youtube.com/watch?v=sb7tgsNF2Jk
The idea, in general, is to create the form in the child component, inject the controlContainer using DI and setting the local form to be the controlContainer form.
Child Component:
@Component({
selector: 'app-company-info',
templateUrl: './company-info.component.html',
styleUrls: ['./company-info.component.scss'],
viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }]
})
export class CompanyInfoComponent implements OnInit {
form: FormGroup;
constructor(
private ctrlContainer: FormGroupDirective,
private fb: FormBuilder) { }
ngOnInit() {
this.form = this.ctrlContainer.form;
this.form.addControl('company',
this.fb.group({
'companyName': this.fb.control(null, [Validators.required]),
'numOfEmployees': this.fb.control(null, [Validators.required])});
}
}
Parent Component (html):
<mat-horizontal-stepper [linear]="isLinear" #stepper>
<mat-step [stepControl]="companyInfo">
<ng-template matStepLabel>Fill out your name</ng-template>
<form [formGroup]="companyInfo">
<app-company-info></app-company-info>
</form>
<div>
<button mat-button matStepperNext>Next</button>
</div>
</mat-step>
</mat-horizontal-stepper>
Parent Component (ts):
export class WizardComponent implements OnInit {
isLinear = true;
companyInfo: FormGroup;
constructor(private _formBuilder: FormBuilder) {
}
ngOnInit() {
this.companyInfo = this._formBuilder.group({
});
}
}
Here is the solution that work for me.
<mat-horizontal-stepper [linear]="true" #stepper>
<mat-step [stepControl]="selectAdvType">
<ng-template matStepLabel>
<div class="text-center">
<mat-icon>queue_play_next</mat-icon><br /><span>Select Adv Type</span>
</div>
</ng-template>
<app-advertisement-type></app-advertisement-type>
</mat-step>
<mat-step [stepControl]="selectAdvType">
<ng-template matStepLabel>
<div class="text-center">
<mat-icon>directions_car</mat-icon><br /><span>Select Car</span>
</div>
</ng-template>
<app-select-car-adv></app-select-car-adv>
</mat-step>
<mat-step>
<ng-template matStepLabel>
<div class="text-center">
<mat-icon>description</mat-icon><br /><span>Select Features</span>
</div>
</ng-template>
<div>
<button mat-button matStepperPrevious>Back</button>
<button mat-button (click)="stepper.reset()">Reset</button>
</div>
</mat-step>
</mat-horizontal-stepper>
Parent Ts file
@Component({
selector: 'app-customer.create.advertisement',
templateUrl: './customer.create.advertisement.component.html',
styleUrls: ['./customer.create.advertisement.component.scss']
})
export class CustomerCreateAdvertisementComponent implements OnInit {
isLinear = false;
selectAdvType: FormGroup;
constructor(private _formBuilder: FormBuilder) { }
ngOnInit() {
this.selectAdvType = this._formBuilder.group({
firstCtrl: ['', Validators.required]
});
}
}
Child component
<form [formGroup]="firstFormGroup">
<ng-template matStepLabel>Fill out your name</ng-template>
<mat-form-field>
<input matInput placeholder="Last name, First name" formControlName="firstCtrl" required>
</mat-form-field>
<div>
<button mat-button matStepperNext>Next</button>
</div>
</form>
@Component({
selector: 'app-advertisement-type',
templateUrl: './advertisement-type.component.html',
styleUrls: ['./advertisement-type.component.scss']
})
export class AdvertisementTypeComponent implements OnInit {
firstFormGroup: FormGroup;
constructor(private _formBuilder: FormBuilder) { }
ngOnInit() {
this.firstFormGroup = this._formBuilder.group({
firstCtrl: ['', Validators.required]
});
}
}
Improving on @eliran-eliassy answer and @christian-steinmeyer question.
Parent.Component.ts
export class ParentComponent implements OnInit {
isLinear = true;
companyInfo: FormGroup;
constructor(private _formBuilder: FormBuilder) {
}
ngOnInit() {
this.companyInfo = this._formBuilder.group({
});
}
}
Parent.Component.html
<mat-horizontal-stepper [linear]="isLinear" #stepper>
<mat-step [stepControl]="companyInfo">
<form [formGroup]="companyInfo">
<ng-template matStepLabel>Fill out your name</ng-template>
<app-company-info></app-company-info>
</form>
<div>
<button mat-button matStepperNext>Next</button>
</div>
</mat-step>
</mat-horizontal-stepper>
Child.Component.ts -> this is the sub-form
export class ChildComponent implements OnInit {
form: FormGroup;
subForm: FormGroup;
constructor(
private ctrlContainer: FormGroupDirective,
private fb: FormBuilder
) {}
ngOnInit() {
this.subForm = this.fb.group({
companyName: [null, [Validators.required]],
numOfEmployees: [null, [Validators.required]]
});
this.form = this.ctrlContainer.form;
this.form.addControl("company", this.subForm);
}
}
Child.Component.html
<div [formGroup]="subForm">
<mat-form-field appearance="outline">
<input matInput placeholder="Your Company Name" formControlName="companyName">
</mat-form-field>
</div>
See this solution on stackblitz
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With