When calling
this.stepper.next()
using a button on the child component, the stepper is not progressed until the second time the button is clicked.
Parent Component Html
<mat-card>
<mat-card-header>Test</mat-card-header>
<mat-card-content>
<mat-horizontal-stepper #stepper linear>
<mat-step [completed]='this.CheckIfStepCompleted(1)'>
<mat-card>
<mat-card-content>
<app-comp1 [steps]='this.steps'></app-comp1>
</mat-card-content>
<mat-card-actions align='start'>
<!-- <button mat-raised-button matStepperNext color='primary' [disabled]='!this.CheckIfStepCompleted(1)'>Next</button> -->
</mat-card-actions>
</mat-card>
</mat-step>
<mat-step [completed]='this.CheckIfStepCompleted(2)'>
<mat-card>
<mat-card-content>
<app-comp2 [steps]='this.steps'></app-comp2>
</mat-card-content>
<mat-card-actions align='start'>
<button mat-raised-button matStepperNext color='primary' [disabled]='!this.CheckIfStepCompleted(2)'>Next</button>
</mat-card-actions>
</mat-card>
</mat-step>
<mat-step [completed]='this.CheckIfStepCompleted(2)'>
<mat-card>
<mat-card-content>
<app-comp3 [steps]='this.steps'></app-comp3>
</mat-card-content>
<mat-card-actions align='start'>
<button mat-raised-button matStepperPrevious color='primary' [disabled]='!this.CheckIfStepCompleted(2)'>Back</button>
</mat-card-actions>
</mat-card>
</mat-step>
</mat-horizontal-stepper>
</mat-card-content>
</mat-card>
Child Component Html
<button (click)='this.Complete()' mat-raised-button color='accent'>Complete</button>
Child Component ts file
import { Component, Input, NgZone, OnInit, ViewChild, ViewChildren } from '@angular/core';
import { MatHorizontalStepper } from '@angular/material/stepper';
import { Step, SteppperService } from '../../services/stepper/steppper.service';
@Component({
selector: 'app-comp1',
templateUrl: './comp1.component.html',
styleUrls: ['./comp1.component.css']
})
export class Comp1Component implements OnInit {
@Input() steps: Step[] = [];
constructor(private _stepSvc: SteppperService, private readonly stepper: MatHorizontalStepper, private ngZone: NgZone) { }
selectedIndex: number = this.stepper.selectedIndex;
ngOnInit(): void {
}
Complete() {
this._stepSvc.CompleteStep(this.steps, 1).then(() => this.stepper.next());
// this.ProgressStep();
}
ProgressStep() {
this.ngZone.run(() => {
this.stepper.next();
});
}
}
We have also tried creating a function on the parent and having a event be emitted from the child component to trigger the next().
TLDR: I need to trigger the next() for the stepper located on the parent component, but I need to trigger it from the child component.
So I got it working. I was able to use Akkonrad's answer but that wasn't what finally worked.
Ultimately I had to not use the "completed" attribute on the template tile.
What I found was on the ngAfterViewInit() that I would access the stepper as a ViewChild and then iterate through the steps and set them all to Completed=false.
Then if I used a EventEmitter on the child component I could trigger the completion of the step on the parent. They key is that I CANNOT use the attribute for it to work this way. It can only be controlled by the ts file.
Now to me, this seems like a bug. That being said I've only had a year in angular and I'm by far not an expert. If someone can explain why this happens this way I would love to know.
Working Code is below:
Parent html:
<mat-card>
<mat-card-header>Test</mat-card-header>
<mat-card-content>
<mat-horizontal-stepper #stepper linear>
<mat-step>
<mat-card>
<mat-card-content>
<app-comp1 (CompleteStep)='this.CompleteStep(0)'></app-comp1>
</mat-card-content>
</mat-card>
</mat-step>
<mat-step>
<mat-card>
<mat-card-content>
<app-comp2 (CompleteStep)='this.CompleteStep(1)'></app-comp2>
</mat-card-content>
</mat-card>
</mat-step>
<mat-step>
<mat-card>
<mat-card-content>
<app-comp3 (CompleteStep)='this.CompleteStep(2)'></app-comp3>
</mat-card-content>
</mat-card>
</mat-step>
</mat-horizontal-stepper>
</mat-card-content>
</mat-card>
Parent TS:
import { Component, OnInit, ViewChild, AfterViewInit, ViewEncapsulation } from '@angular/core';
import { MatStepper } from '@angular/material/stepper';
@Component({
selector: 'app-step',
templateUrl: './step.component.html',
styleUrls: ['./step.component.css'],
encapsulation: ViewEncapsulation.None
})
export class StepComponent implements OnInit, AfterViewInit {
@ViewChild('stepper', { static: false }) stepper: MatStepper;
constructor() { }
ngOnInit() {
}
ngAfterViewInit() {
this.Initialize()
}
Initialize() {
this.stepper.steps.forEach(Step => {
Step.completed = false;
});
}
CompleteStep(stepNumer: number) {
debugger;
this.stepper.steps.get(stepNumer).completed = true;
this.stepper.next();
}
}
Child Html:
<button (click)='this.Complete()' mat-raised-button color='accent'>Complete</button>
Child TS:
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
@Component({
selector: 'app-comp1',
templateUrl: './comp1.component.html',
styleUrls: ['./comp1.component.css']
})
export class Comp1Component implements OnInit {
@Output() CompleteStep = new EventEmitter<any>();
constructor() { }
ngOnInit(): void {
}
Complete() {
this.CompleteStep.emit();
}
}