Switch between vertical and horizontal stepper material

亡梦爱人 提交于 2019-12-06 17:00:56

问题


How to switch between mat-vertical-stepper and mat-horizontal-stepper from angular component with same stepper steps?


回答1:


to avoid rewriting identical html content, do like this. Create the template and give them a reference using a #hashtag. then you can instert them using ng-container *ngTemplateOutlet="hashtag"></ng-container>.

here is an example of making a responsive stepepr, the angular material way.

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

You can use the angular cdk layout to track screen size like this.

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



回答2:


I use Teradata's Covalent components alongside Google's Material components. They use material design and the modules are even imported in the same manner as Google's material modules.

Covalent's stepper is setup with a mode input in mind so you would implement the HTML template like this:

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

Then in your component's typescript file you can setup the variable to be either horizontal or vertical according to your needs:

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



回答3:


I wanted to do the same thing and finally figured out how to get it to work, with complete transclusion for the steps, etc, plus you can sync the currently-selected index between the horizontal and vertical so that the page size changing won't reset the person back to step 1.

Here's a complete example.

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

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

}

Step component HTML:

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

Step component 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);
  }

}

Using responsive Angular Material Stepper in component HTML:

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

And the component function needed for forms:

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



回答4:


you might want to create two separate steppers and use *ngIf to switch between them

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

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



回答5:


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


来源:https://stackoverflow.com/questions/48032538/switch-between-vertical-and-horizontal-stepper-material

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!