Posso mover programaticamente as etapas de um tapete deslizante horizontal em Angular / Angular Material

88

Tenho uma pergunta em relação ao material angular (com Angular 4+). Digamos que em meu modelo de componente eu adicione um <mat-horizontal-stepper>componente e, em cada etapa <mat-step>, tenha botões de passo para navegar no componente. Igual a...

<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>

Agora estou me perguntando se é possível remover os botões de cada etapa e tê-los em outro lugar <mat-horizontal-stepper>em uma posição estática ou mesmo fora do <mat-horizontal-stepper>e posso navegar para trás e para frente usando o código dentro do meu arquivo typecript do componente. Para se ter uma ideia, gostaria que meu HTML fosse algo assim

<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
fonte

Respostas:

182

Sim. É possível pular para um stepper específico usando a selectedIndexpropriedade do MatStepper. Além disso, MatStepperexpõe métodos públicos next()e previous(). Você pode usá-los para se mover para frente e para trás.

Em seu modelo:

Adicione um id ao seu stepper, por exemplo #stepper. Então, em seus métodos goBack()e goForward(), passe o id de passo:

<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>

.. e em seu texto datilografado:

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

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

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

Link para demonstração stackblitz .


Você também pode usar ViewChildpara obter uma referência ao componente de passo em seu TypeScript, conforme mostrado abaixo:

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

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

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

Nesse caso, você não precisa passar a referência de passo no método no html do seu componente. Link para a demonstração com ViewChild


Você pode ativar / desativar os botões Backe Nextusando o seguinte:

<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
fonte
8
Eu estava apenas olhando ViewChilde vendo como poderia fazer referência ao Stepper - mas você me venceu! Adorei o fato de você ter adicionado a funcionalidade ativar / desativar também! Tenha alguns pontos!
Mike Sav
ViewChildtambém é uma boa opção para obter o stepper. Mas eu preferiria passar uma identificação. Também adicionei a ViewChildsolução na demonstração \ o /
Faisal
Olá Faisal, obrigado por uma resposta tão boa, mais uma ajuda, em vez de passar um formulário para tapete, podemos passar componentes angulares e, dependendo se esse componente for válido, posso passar para o próximo tapete, como isso pode ser feito , obrigado
Enthu
Ótima solução. Mas como poderíamos restringir a área de rolagem apenas ao conteúdo do stepper e não ao cabeçalho real. Se você adicionar montes de conteúdo, o cabeçalho rolará para fora de vista. Como o cabeçalho dá uma indicação de onde o usuário está em algum processo, é importante que o cabeçalho esteja visível independentemente da quantidade de conteúdo em cada etapa.
Wayne Riesterer
1
Se estiver usando Angular 8 e @ViewChild, você deve usar static: false @ViewChild('stepper', {static: false}) private myStepper: MatStepper; Por Angular, este valor será padronizado como false no link
rorvis
29

Além @ Faisal resposta 's, esta é a minha opinião sobre fazer o salto MatStepper sem a necessidade de passar a passo nos argumentos.

Isso é útil quando você precisa de mais flexibilidade para manipular o stepper, por exemplo, de um Serviceou algo mais.

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>

Arquivo TS:

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

Aqui está a demonstração do stackblitz .

Altus
fonte
21

Se você deseja navegar programaticamente para a próxima etapa e se estiver usando um stepper linear , siga as etapas abaixo:

  • Crie um steppercomo este: <mat-horizontal-stepper linear #matHorizontalStepper>
  • Defina mat-stepassim:<mat-step [completed]="isThisStepDone">
  • De dentro, mat-stepcrie um botão para ir para a próxima etapa como este: <button (click)="next(matHorizontalStepper)">NEXT STEP</button>
  • No .tsarquivo, declare uma MatStepperreferência chamada stepper :
    @ViewChild('matHorizontalStepper') stepper: MatStepper;
  • Além disso, dentro do .tsarquivo inicialize isThisStepDonecomo falso :isThisStepDone: boolean = false;
  • Em seguida, escreva o método para o botão NEXT STEP chamado next():

    submit(stepper: MatStepper) {
     this.isThisStepDone = true;
     setTimeout(() => {           // or do some API calls/ Async events
      stepper.next();
     }, 1);
    }
    
Barba Negra
fonte
3
A parte assíncrona ( setTimeout()) é necessária devido à propagação de estado via isThisStepDone.
Yuri
2

Você também pode fazer isso especificando o índice real do stepper 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
fonte
1
Estou apenas usando @ViewChild('stepper') private myStepper: MatStepper;e do que this.matStepper.next();na minha função. Funcionando perfeitamente
Máx.