Переключение между вертикальным и горизонтальным шаговым материалом

Как переключаться между матовым-вертикальным шаговым и матово-горизонтальным шагером из углового компонента с одинаковыми шагами шага?

Ответ 1

Я использую компоненты Teradata Covalent вместе с компонентами Google Material. Они используют материальный дизайн, а модули даже импортируются таким же образом, как и в материальных модулях Google.

Ковалентный шагомер настраивается с учетом ввода режима, поэтому вы должны использовать шаблон HTML следующим образом:

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

Затем в вашем файле typescript файла вы можете настроить переменную как горизонтальную, так и вертикальную в соответствии с вашими потребностями:

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

Ответ 2

Я хотел сделать то же самое и, наконец, понял, как заставить его работать, с полным переходом на шаги и т.д., А также вы можете синхронизировать текущий выбранный индекс между горизонтальным и вертикальным, чтобы изменение размера страницы не было переустановите человека на шаг 1.

Вот полный пример.

Компонент Wrapper 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:

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

Ответ 3

чтобы не переписывать идентичный HTML-контент, делайте так. Создайте шаблон и дайте им ссылку, используя #hashtag. затем вы можете вставить их, используя ng-container *ngTemplateOutlet="hashtag"></ng-container>.

Вот пример изготовления отзывчивого степпра, углового материального пути.

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

Ответ 4

вы можете создать два отдельных степпера и использовать * 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>

Ответ 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();
        });
    }
}