在垂直和水平步进材料之间切换

时间:2017-12-30 09:15:38

标签: angular-material2

如何在角度组件中使用相同的步进步骤在 mat-vertical-stepper mat-h​​orizo​​ntal-stepper 之间切换?

7 个答案:

答案 0 :(得分:4)

我将Teradata's Covalent组件与Google的Material组件一起使用。他们使用物料设计,甚至以与Google物料模块相同的方式导入模块。

Covalent的步进器在设置时会考虑模式输入,因此您可以像这样实现HTML模板:

<td-steps [mode]="stepperMode">
  <td-step>
    ...
  </td-step>
  ...
</td-steps>

然后在组件的打字稿文件中,您可以根据需要将变量设置为水平或垂直:

if (condition) {
  stepperMode = 'horizontal';
} else {
  stepperMode = 'vertical';
}

答案 1 :(得分:4)

我想做同样的事情,并最终弄清楚了如何使它工作,并完整地包含了步骤等内容,此外,您还可以在水平和垂直之间同步当前选择的索引,从而改变页面大小不会将其重置为第1步。

这是一个完整的例子。

包装器组件HTML:     

<ng-template #horizontal>
  <mat-horizontal-stepper #stepper
    [linear]="isLinear"
    (selectionChange)="selectionChanged($event)">
    <mat-step *ngFor="let step of steps; let i = index"
      [stepControl]="step.form"
      [label]="step.label"
      [optional]="step.isOptional">
      <ng-container *ngTemplateOutlet="step.template"></ng-container>
      <div class="actions">
        <div class="previous">
          <button *ngIf="i > 0" 
            type="button" mat-button
            color="accent"
            (click)="reset()"
            matTooltip="All entries will be cleared">Start Over</button>
          <button *ngIf="i > 0"
            type="button" mat-button
            matStepperPrevious>Previous</button>
        </div>
        <div class="next">
          <button type="button" mat-button
            color="primary"
            matStepperNext
            (click)="step.submit()">{{i === steps.length - 1 ? 'Finish' : 'Next'}}</button>
        </div>
      </div>
    </mat-step>
  </mat-horizontal-stepper>
</ng-template>

<ng-template #vertical>
  <mat-vertical-stepper #stepper
    [linear]="isLinear"
    (selectionChange)="selectionChanged($event)">
    <mat-step *ngFor="let step of steps; let i = index"
      [stepControl]="step.form"
      [label]="step.label"
      [optional]="step.isOptional">
      <ng-container *ngTemplateOutlet="step.template"></ng-container>
      <div class="actions">
        <div class="previous">
          <button *ngIf="i > 0" 
            type="button" mat-button
            color="accent"
            (click)="reset()"
            matTooltip="All entries will be cleared">Start Over</button>
          <button *ngIf="i > 0"
            type="button" mat-button
            matStepperPrevious>Previous</button>
        </div>
        <div class="next">
          <button type="button" mat-button
            color="primary"
            matStepperNext
            (click)="step.submit()">{{i === steps.length - 1 ? 'Finish' : 'Next'}}</button>
        </div>
      </div>
    </mat-step>
  </mat-vertical-stepper>
</ng-template>

包装器组件ts:

import { Component, OnInit, OnDestroy, Input, ContentChildren, QueryList, ViewChild, TemplateRef } from '@angular/core';
import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout';
import { StepComponent } from './step/step.component';
import { Subscription } from 'rxjs';
import { MatStepper } from '@angular/material';

@Component({
  selector: 'stepper',
  templateUrl: './stepper.component.html',
  styleUrls: ['./stepper.component.scss']
})
export class StepperComponent implements OnInit, OnDestroy {

  public selectedIndex: number = 0;
  public isMobile: boolean;
  public template: TemplateRef<any>;
  @Input() isLinear: boolean = true;
  @Input() startAtIndex: number;
  @ContentChildren(StepComponent) private steps: QueryList<StepComponent>;
  @ViewChild('horizontal') templHorizontal: TemplateRef<any>;
  @ViewChild('vertical') templVertical: TemplateRef<any>;
  @ViewChild('stepper') stepper: MatStepper;

  private _bpSub: Subscription;

  constructor(private bpObserver: BreakpointObserver) { }

  ngOnInit() {
    this._bpSub = this.bpObserver
      .observe(['(max-width: 599px)'])
      .subscribe((state: BreakpointState) => {
        this.setMobileStepper(state.matches);
      });

    if (this.startAtIndex) {
      this.selectedIndex = this.startAtIndex;
    }
  }

  selectionChanged(event: any): void {
    this.selectedIndex = event.selectedIndex;
  }

  setMobileStepper(isMobile: boolean): void {
    this.isMobile = isMobile;
    if (isMobile) {
      this.template = this.templVertical;
    }
    else {
      this.template = this.templHorizontal;
    }
    setTimeout(() => {
      // need async call since the ViewChild isn't ready
      // until after this function runs, thus the setTimeout hack
      this.stepper.selectedIndex = this.selectedIndex;
    });
  }

  reset(): void {
    this.stepper.reset();
  }

  ngOnDestroy(): void {
    this._bpSub.unsubscribe();
  }

}

步骤组件HTML:

<ng-template #template>
  <ng-content></ng-content>
</ng-template>

步骤ts:

import { Component, Input, Output, TemplateRef, ViewChild, EventEmitter } from '@angular/core';
import { FormGroup } from '@angular/forms';

@Component({
  selector: 'stepper-step',
  templateUrl: './step.component.html',
  styleUrls: ['./step.component.scss']
})
export class StepComponent {

  @Input() isOptional: boolean = false;
  @Input() label: string;
  @Input() form: FormGroup;
  @ViewChild('template') template: TemplateRef<any>;
  @Output() formSubmitted: EventEmitter<any> = new EventEmitter();

  constructor() { }

  submit(): void {
    this.formSubmitted.emit(this.form.value);
  }

}

在组件HTML中使用自适应Angular Material Stepper:

<stepper>
  <stepper-step
    label="Step 1 label"
    [form]="step1form"
    (formSubmitted)="form1Submit($event)">
    content
    <form [formGroup]="frmStep1">
      <mat-form-field>
        <input matInput name="firstname" formControlName="firstname" placeholder="First Name" />
      </mat-form-field>
      content
    </form>
  </stepper-step>
  <stepper-step
    label="Step 2 label"
    [form]="step2form">
    step 2 content
  </stepper-step>
</stepper>

以及表单所需的组件功能:

form1Submit(formValues: any): void {
  console.log(formValues);
}

答案 2 :(得分:3)

为避免重写相同的html内容,请执行以下操作。创建模板,并使用#hashtag给他们提供参考。那么您可以使用ng-container *ngTemplateOutlet="hashtag"></ng-container>插入它们。

这是一个制作响应式stepepr(有角度的物质方式)的示例。

<ng-template #stepOne>
  <div>step one</div>
</ng-template>

<ng-template #stepTwo>
  <div>step two</div>
</ng-template>

<ng-template #stepThree>
  <div>step three</div>
</ng-template>

<ng-template #stepFour>
  <div>step four</div>
</ng-template>

<ng-template [ngIf]="smallScreen" [ngIfElse]="bigScreen">
  <mat-vertical-stepper linear #stepper >
    <mat-step>
      <ng-container *ngTemplateOutlet="stepOne"></ng-container>
    </mat-step>
    <mat-step>
      <ng-container *ngTemplateOutlet="stepTwo"></ng-container>
    </mat-step>
    <mat-step>
      <ng-container *ngTemplateOutlet="stepThree"></ng-container>
    </mat-step>
    <mat-step>
      <ng-container *ngTemplateOutlet="stepFour"></ng-container>
    </mat-step>
  </mat-vertical-stepper>
</ng-template>

<ng-template #bigScreen>
  <mat-horizontal-stepper linear #stepper >
    <mat-step>
      <ng-container *ngTemplateOutlet="stepOne"></ng-container>
    </mat-step>
    <mat-step >
      <ng-container *ngTemplateOutlet="stepTwo"></ng-container>
    </mat-step>
    <mat-step>
      <ng-container *ngTemplateOutlet="stepThree"></ng-container>
    </mat-step>
    <mat-step>
      <ng-container *ngTemplateOutlet="stepFour"></ng-container>
    </mat-step>
  </mat-horizontal-stepper>
</ng-template>

您可以使用cdk角布局来跟踪屏幕尺寸,

import { Component } from '@angular/core';
import {BreakpointObserver, Breakpoints} from '@angular/cdk/layout';

@Component({
  selector: 'app-responsive-stepper',
  templateUrl: './responsive-stepper.component.html',
  styleUrls: ['./responsive-stepper.component.scss']
})
export class ResponsiveStepperComponent implements OnInit {

    smallScreen: boolean;

    constructor(
       private breakpointObserver: BreakpointObserver
      ) {
        breakpointObserver.observe([
          Breakpoints.XSmall,
          Breakpoints.Small
        ]).subscribe(result => {
          this.smallScreen = result.matches;
      });
     }
}

答案 3 :(得分:0)

您可能想要创建两个单独的步进器并使用* ngIf在它们之间切换

<mat-vertical-stepper *ngIf="verticalFlag">
  <mat-step>
  </mat-step>
</mat-vertical-stepper>

<mat-horizontal-stepper *ngIf="!verticalFlag">
  <mat-step>
  </mat-step>
</mat-horizontal-stepper>

答案 4 :(得分:0)

import { Directionality } from '@angular/cdk/bidi';
import { CdkStep, StepperSelectionEvent } from '@angular/cdk/stepper';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChildren, ElementRef, EventEmitter, forwardRef, Inject, Input, Optional, Output, QueryList, ViewChildren } from '@angular/core';
import { MatStep, MatStepper } from '@angular/material';
import { DOCUMENT } from '@angular/platform-browser';

const MAT_STEPPER_PROXY_FACTORY_PROVIDER = {
    provide: MatStepper,
    deps: [forwardRef(() => StepperComponent), [new Optional(), Directionality], ChangeDetectorRef, [new Inject(DOCUMENT)]],
    useFactory: MAT_STEPPER_PROXY_FACTORY
};

export function MAT_STEPPER_PROXY_FACTORY(component: StepperComponent, directionality: Directionality,
    changeDetectorRef: ChangeDetectorRef, docuement: Document) {
    // We create a fake stepper primarily so we can generate a proxy from it.  The fake one, however, is used until 
    // our view is initialized.  The reason we need a proxy is so we can toggle between our 2 steppers 
    // (vertical and horizontal) depending on  our "orientation" property.  Probably a good idea to include a polyfill 
    // for the Proxy class: https://github.com/GoogleChrome/proxy-polyfill.

    const elementRef = new ElementRef(document.createElement('mat-horizontal-stepper'));
    const stepper = new MatStepper(directionality, changeDetectorRef, elementRef, document);
    return new Proxy(stepper, {
        get: (target, property) => Reflect.get(component.stepper || target, property),
        set: (target, property, value) => Reflect.set(component.stepper || target, property, value)
    });
}

@Component({
    selector: 'app-stepper',
    // templateUrl: './stepper.component.html',
    // styleUrls: ['./stepper.component.scss'],
    changeDetection: ChangeDetectionStrategy.OnPush,
    providers: [MAT_STEPPER_PROXY_FACTORY_PROVIDER],
    template: `
<ng-container [ngSwitch]="orientation">
    <mat-horizontal-stepper *ngSwitchCase="'horizontal'"
                            [labelPosition]="labelPosition"
                            [linear]="linear"
                            [selected]="selected"
                            [selectedIndex]="selectedIndex"
                            (animationDone)="animationDone.emit($event)"
                            (selectionChange)="selectionChange.emit($event)">
    </mat-horizontal-stepper>


    <mat-vertical-stepper *ngSwitchDefault
                            [linear]="linear"
                            [selected]="selected"
                            [selectedIndex]="selectedIndex"
                            (animationDone)="animationDone.emit($event)"
                            (selectionChange)="selectionChange.emit($event)">
    </mat-vertical-stepper>
</ng-container>
`
})
export class StepperComponent {
    // public properties
    @Input() labelPosition?: 'bottom' | 'end';
    @Input() linear?: boolean;
    @Input() orientation?: 'horizontal' | 'vertical';
    @Input() selected?: CdkStep;
    @Input() selectedIndex?: number;

    // public events
    @Output() animationDone = new EventEmitter<void>();
    @Output() selectionChange = new EventEmitter<StepperSelectionEvent>();

    // internal properties
    @ViewChildren(MatStepper) stepperList!: QueryList<MatStepper>;
    @ContentChildren(MatStep) steps!: QueryList<MatStep>;
    get stepper(): MatStepper { return this.stepperList && this.stepperList.first; }

    // private properties
    private lastSelectedIndex?: number;
    private needsFocus = false;

    // public methods
    constructor(private changeDetectorRef: ChangeDetectorRef) { }
    ngAfterViewInit() {
        this.reset();
        this.stepperList.changes.subscribe(() => this.reset());
        this.selectionChange.subscribe((e: StepperSelectionEvent) => this.lastSelectedIndex = e.selectedIndex);
    }
    ngAfterViewChecked() {
        if (this.needsFocus) {
            this.needsFocus = false;
            const { _elementRef, _keyManager, selectedIndex } = <any>this.stepper;
            _elementRef.nativeElement.focus();
            _keyManager.setActiveItem(selectedIndex);
        }
    }

    // private properties
    private reset() {
        const { stepper, steps, changeDetectorRef, lastSelectedIndex } = this;
        stepper.steps.reset(steps.toArray());
        stepper.steps.notifyOnChanges();
        if (lastSelectedIndex) {
            stepper.selectedIndex = lastSelectedIndex;
        }

        Promise.resolve().then(() => {
            this.needsFocus = true;
            changeDetectorRef.markForCheck();
        });
    }
}

答案 5 :(得分:0)

这是我的方法,有两种方法,一种是使用 css 属性,另一种是使用 angular 提供的 fxLayout。所以它同样我希望你会知道如何使用 css 所以我将向你展示如何使用 fxLayout。您可以在 https://tburleson-layouts-demos.firebaseapp.com/#/docs

中查看有关 fxLayout 的信息

<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js"></script>
<mat-horizontal-stepper linear fxHide.lt-md>
          <mat-step [stepControl]="firstFormGroup" editable="true">
            <form [formGroup]="firstFormGroup">
              <ng-template matStepLabel>Fill out your name</ng-template>
              <mat-form-field>
                <mat-label>Name</mat-label>
                <input matInput formControlName="firstCtrl" placeholder="Last name, First name" required>
              </mat-form-field>
              <div>
                <button mat-button matStepperNext>Next</button>
              </div>
            </form>
          </mat-step>
          <mat-step [stepControl]="secondFormGroup" editable="true">
            <form [formGroup]="secondFormGroup">
              <ng-template matStepLabel>Fill out your address</ng-template>
              <mat-form-field>
                <mat-label>Address</mat-label>
                <input matInput formControlName="secondCtrl" placeholder="Ex. 1 Main St, New York, NY"
                       required>
              </mat-form-field>
              <div>
                <button mat-button matStepperPrevious>Back</button>
                <button mat-button matStepperNext>Next</button>
              </div>
            </form>
          </mat-step>
          <mat-step>
            <ng-template matStepLabel>Done</ng-template>
            <p>You are now done.</p>
            <div>
              <button mat-button matStepperPrevious>Back</button>
              <button mat-button (click)="done()">done</button>
            </div>
          </mat-step>
        </mat-horizontal-stepper>
        
        
        
        
        <mat-vertical-stepper linear fxHide.gt-sm>
          <mat-step [stepControl]="firstFormGroup" editable="true">
            <form [formGroup]="firstFormGroup">
              <ng-template matStepLabel>Fill out your name</ng-template>
              <mat-form-field>
                <mat-label>Name</mat-label>
                <input matInput formControlName="firstCtrl" placeholder="Last name, First name" required>
              </mat-form-field>
              <div>
                <button mat-button matStepperNext>Next</button>
              </div>
            </form>
          </mat-step>
          <mat-step [stepControl]="secondFormGroup" editable="true">
            <form [formGroup]="secondFormGroup">
              <ng-template matStepLabel>Fill out your address</ng-template>
              <mat-form-field>
                <mat-label>Address</mat-label>
                <input matInput formControlName="secondCtrl" placeholder="Ex. 1 Main St, New York, NY"
                       required>
              </mat-form-field>
              <div>
                <button mat-button matStepperPrevious>Back</button>
                <button mat-button matStepperNext>Next</button>
              </div>
            </form>
          </mat-step>
          <mat-step>
            <ng-template matStepLabel>Done</ng-template>
            <p>You are now done.</p>
            <div>
              <button mat-button matStepperPrevious>Back</button>
              <button mat-button (click)="done()">done</button>
            </div>
          </mat-step>
        </mat-horizontal-stepper>

答案 6 :(得分:0)

从 Angular 12(特别是这些 PR:https://github.com/angular/components/pull/21940https://github.com/angular/components/pull/22139)开始,您现在可以只使用 MatStepper(注意 MatHorizontalStepperMatVerticalStepper 都已弃用在这些 PR 中)并根据需要设置方向输入:

<mat-stepper [orientation]="orientation">
  <mat-step>Step 1</mat-step>
  <mat-step>Step 2</mat-step>
</mat-stepper>

DEMO