0

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.

BondAddict
  • 599
  • 5
  • 12

2 Answers2

0

Try to add output on the child component and handle click in the parent. Input is for passing data down from the parent to the child. So you would have something like:

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[] = [];
  @Output() handleChange = new EventEmitter<boolean>(); // create the event

  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();
      this.handleChange.emit(true); // trigger the event
    });
  }
}

And then in the parent you can handle that change, for example:

<Comp1Component (handleChange)="processTheChangeFromTheParent($event)">...
akkonrad
  • 1,043
  • 1
  • 10
  • 25
0

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();
  }
}

BondAddict
  • 599
  • 5
  • 12