如何在角度组件中使用相同的步进步骤在 mat-vertical-stepper 和 mat-horizontal-stepper 之间切换?
答案 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/21940 和 https://github.com/angular/components/pull/22139)开始,您现在可以只使用 MatStepper
(注意 MatHorizontalStepper
和 MatVerticalStepper
都已弃用在这些 PR 中)并根据需要设置方向输入:
<mat-stepper [orientation]="orientation">
<mat-step>Step 1</mat-step>
<mat-step>Step 2</mat-step>
</mat-stepper>