¿Puedo mover programáticamente los pasos de un mat-horizontal-stepper en Angular / Angular Material?

85

Tengo una pregunta sobre Angular Material (con Angular 4+). Digamos que en mi plantilla de componente agrego un <mat-horizontal-stepper>componente, y dentro de cada paso <mat-step>tengo botones paso a paso para navegar por el componente. Al igual que...

<mat-horizontal-stepper>
  <mat-step>
    Step 1
    <button mat-button matStepperPrevious type="button">Back</button>
    <button mat-button matStepperNext type="button">Next</button>
  </mat-step>
  <mat-step>
    Step 2
    <button mat-button matStepperPrevious type="button">Back</button>
    <button mat-button matStepperNext type="button">Next</button>
  </mat-step>
  <mat-step>
    Step 3
    <button mat-button matStepperPrevious type="button">Back</button>
    <button mat-button matStepperNext type="button">Next</button>
  </mat-step>
</mat-horizontal-stepper>

Ahora me pregunto si es posible quitar los botones de cada paso y tenerlos en otro lugar <mat-horizontal-stepper>en una posición estática o incluso fuera de él <mat-horizontal-stepper>y puedo navegar hacia atrás y hacia adelante usando código dentro de mi archivo de mecanografía de componente. Para dar una idea, me gustaría que mi HTML fuera algo como esto

<mat-horizontal-stepper>
    <mat-step>
        Step 1
    </mat-step>
    <mat-step>
        Step 2
    </mat-step>
    <mat-step>
        Step 3
    </mat-step>
    <!-- one option -->
    <div>
       <button mat-button matStepperPrevious type="button">Back</button>
       <button mat-button matStepperNext type="button">Next</button>
    </div>
</mat-horizontal-stepper>

<!-- second option -->
<div>
   <button (click)="goBack()" type="button">Back</button>
   <button (click)="goForward()" type="button">Next</button>
</div>
Mike Sav
fuente

Respuestas:

173

Si. Es posible saltar a un paso a paso específico usando la selectedIndexpropiedad de MatStepper. Además, MatStepperexpone métodos públicos next()y previous(). Puede usarlos para moverse hacia adelante y hacia atrás.

En tu plantilla:

Agregue una identificación a su paso a paso, por ejemplo #stepper. Luego, en sus métodos goBack()y goForward(), pase el id paso a paso:

<mat-horizontal-stepper #stepper>
    <!-- Steps -->
</mat-horizontal-stepper>    
<!-- second option -->
<div>
   <button (click)="goBack(stepper)" type="button">Back</button>
   <button (click)="goForward(stepper)" type="button">Next</button>
</div>

.. y en su texto mecanografiado:

import { MatStepper } from '@angular/material/stepper';

goBack(stepper: MatStepper){
    stepper.previous();
}

goForward(stepper: MatStepper){
    stepper.next();
}

Enlace a la demostración de stackblitz .


También puede usar ViewChildpara obtener una referencia al componente paso a paso en su TypeScript como se muestra a continuación:

@ViewChild('stepper') private myStepper: MatStepper;

goBack(){
    this.myStepper.previous();
}

goForward(){
    this.myStepper.next();
}

En este caso, no tiene que pasar la referencia paso a paso en el método en el html de su componente. Enlace a la demostración con ViewChild


Puede habilitar / deshabilitar los botones Backy Nextutilizando lo siguiente:

<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>
Faisal
fuente
8
Solo estaba mirando ViewChildy viendo cómo podía hacer referencia al Stepper, ¡pero me has adelantado! ¡Me encanta el hecho de que también agregó la función de deshabilitar / habilitar! ¡Ten algunos puntos!
Mike Sav
ViewChildTambién es una buena opción para conseguir el paso a paso. Pero preferiría pasar una identificación. También agregué una ViewChildsolución en la demostración \ o /
Faisal
Hola Faisal, gracias por una respuesta tan excelente, una ayuda más, en lugar de pasar un formulario a mat-step, ¿podemos pasar componentes angulares y luego, dependiendo de que ese componente sea válido, puedo pasar al siguiente mat-step, cómo se puede lograr? , gracias
Enthu
Buena solucion. Pero, ¿cómo podríamos restringir el área de desplazamiento solo al contenido del paso a paso y no al encabezado real? Si agrega una gran cantidad de contenido, el encabezado se desplazará fuera de la vista. Dado que el encabezado da una indicación de dónde se encuentra el usuario en algún proceso, entonces es importante que el encabezado sea visible independientemente de la cantidad de contenido en cada paso.
Wayne Riesterer
Aquí, los íconos de pasos completados se configuran en "EDITAR" y el ícono de pasos actuales se establece en "NÚMERO". ¿Puedo tener el ícono de pasos completados como "HECHO (tick)" y cuando se presiona el botón de retroceso, el ícono de paso completado cambia a "Editar"? .... EX: supongamos que completé el paso 2 y hice clic en el botón siguiente, ahora el icono del paso 2 se muestra como "EDITAR icono (símbolo de lápiz)" y ahora estoy en el paso 3, que muestra "Número" como icono ... ¿Qué Lo que quiero hacer es Cuando haga clic en el siguiente btn del paso 2, el icono debería ser "HECHO" y el icono del tercer paso debería ser "Número", si hice clic en el botón anterior del tercer paso, el icono del tercer paso debería ser "HECHO", el segundo es "EDITAR".
Zhu
29

Además de la respuesta de @ Faisal , esta es mi opinión sobre cómo hacer que MatStepper salte sin necesidad de pasar el paso a paso en los argumentos.

Esto es útil cuando necesita más flexibilidad para manipular el paso a paso, por ejemplo, desde una Serviceo algo más.

HTML:

<div fxLayout="row" fxLayoutAlign="center center" fxLayoutGap="6px">
  <button (click)="move(0)">1st</button>
  <button (click)="move(1)">2nd</button>
  <button (click)="move(2)">3rd</button>
  <button (click)="move(3)">4th</button>
</div>

Archivo TS:

move(index: number) {
    this.stepper.selectedIndex = index;
}

Aquí está la demostración de stackblitz .

Altus
fuente
21

Si desea navegar programáticamente al siguiente paso y si está utilizando un paso a paso lineal , siga los pasos a continuación:

  • Crea una steppercomo esta: <mat-horizontal-stepper linear #matHorizontalStepper>
  • Defina mat-stepasí:<mat-step [completed]="isThisStepDone">
  • Desde adentro, mat-stepcree un botón para ir al siguiente paso como este: <button (click)="next(matHorizontalStepper)">NEXT STEP</button>
  • En el .tsarchivo, declare una MatStepperreferencia denominada paso a paso :
    @ViewChild('matHorizontalStepper') stepper: MatStepper;
  • Además, dentro del .tsarchivo, inicializar isThisStepDonecomo falso :isThisStepDone: boolean = false;
  • Luego escriba el método para el botón SIGUIENTE PASO llamado next():

    submit(stepper: MatStepper) {
     this.isThisStepDone = true;
     setTimeout(() => {           // or do some API calls/ Async events
      stepper.next();
     }, 1);
    }
    
Barba Negra
fuente
3
La parte asíncrona ( setTimeout()) es necesaria debido a la propagación del estado a través de isThisStepDone.
Yuri
2

También puede hacerlo especificando el índice real del paso a paso usando selectedIndex.

stackblitz: https://stackblitz.com/edit/angular-4rvy2s?file=app%2Fstepper-overview-example.ts

HTML:

<div class="fab-nav-container">
   <mat-vertical-stepper linear="false" #stepper>
       <mat-step *ngFor="let step of stepNodes; let i = index">
           <ng-template matStepLabel>
               <p> {{step.title}} </p>
           </ng-template>
       </mat-step>
   </mat-vertical-stepper>
</div>

<div class="button-container">
   <div class="button-grp">
      <button mat-stroked-button (click)="clickButton(1, stepper)">1</button>
      <button mat-stroked-button (click)="clickButton(2, stepper)">2</button>
      <button mat-stroked-button (click)="clickButton(3, stepper)">3</button>
   </div>
</div>

TS:

import {Component, OnInit, ViewChild} from '@angular/core';
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
import { MatVerticalStepper } from '@angular/material';
import { MatStepper } from '@angular/material';
export interface INodes {
    title: string;
    seq: number;
    flowId: string;
}
/**
 * @title Stepper overview
 */
@Component({
  selector: 'stepper-overview-example',
  templateUrl: 'stepper-overview-example.html',
  styleUrls: ['stepper-overview-example.scss'],
})
export class StepperOverviewExample implements OnInit {
  @ViewChild(MatVerticalStepper) vert_stepper: MatVerticalStepper;
  @ViewChild('stepper') private myStepper: MatStepper;

  stepNodes: INodes[] = [
    { title: 'Request Submission', seq: 1, flowId: 'xasd12'}, 
    { title: 'Department Approval', seq: 2, flowId: 'erda23'}, 
    { title: 'Requestor Confirmation', seq: 3, flowId: 'fsyq51'}, 
  ];

  ngOnInit() {
  }
  ngAfterViewInit() {
    this.vert_stepper._getIndicatorType = () => 'number';
  }
  clickButton(index: number, stepper: MatStepper) {
      stepper.selectedIndex = index - 1;
  }
}
M.Laida
fuente
1
Solo estoy usando @ViewChild('stepper') private myStepper: MatStepper;y que this.matStepper.next();en mi función. Funcionando perfectamente
Max