问题
How can I programmatically set focus to "Address" input after clicking "Next" button on step one, so after going form step 1 ("Fill out your name") to step 2 ("Fill out your address").
https://stackblitz.com/angular/onnbkqrydrg?file=app%2Fstepper-overview-example.ts https://material.angular.io/components/stepper/overview
This is what I want to achieve:
I was already experimenting with MatStepper's selectionChange event, but it seems that it is fired before step 2 is visible, so it doesn't work.
回答1:
You were on the right path using the selection Change event. See the forked StackBlitz:
StackBlitz
What you want to do is assign an id
to your input elements that can be used in correlation with the selected index of the stepper, such as:
<mat-step [stepControl]="firstFormGroup">
<form [formGroup]="firstFormGroup">
<ng-template matStepLabel>Fill out your name</ng-template>
<mat-form-field>
<input id="input0" matInput placeholder="Last name, First name" formControlName="firstCtrl" required>
</mat-form-field>
<div>
<button mat-button matStepperNext>Next</button>
</div>
</form>
</mat-step>
<mat-step [stepControl]="secondFormGroup">
<form [formGroup]="secondFormGroup">
<ng-template matStepLabel>Fill out your address</ng-template>
<mat-form-field>
<input id="input1" matInput placeholder="Address" formControlName="secondCtrl" required>
</mat-form-field>
<div>
<button mat-button matStepperPrevious>Back</button>
<button mat-button matStepperNext>Next</button>
</div>
</form>
</mat-step>
Then by using the selection (selectionChange) event you can set focus on the input element after waiting for it to be displayed in the DOM:
export class StepperOverviewExample implements AfterViewInit {
isLinear = false;
firstFormGroup: FormGroup;
secondFormGroup: FormGroup;
public targetInput = 'input0';
constructor(private _formBuilder: FormBuilder) { }
ngOnInit() {
this.firstFormGroup = this._formBuilder.group({
firstCtrl: ['', Validators.required]
});
this.secondFormGroup = this._formBuilder.group({
secondCtrl: ['', Validators.required]
});
}
ngAfterViewInit() {
this.setFocus();
}
private setFocus() {
let targetElem = document.getElementById(this.targetInput);
setTimeout(function waitTargetElem() {
if (document.body.contains(targetElem)) {
targetElem.focus();
} else {
setTimeout(waitTargetElem, 100);
}
}, 100);
}
onChange(event: any) {
let index = String(event.selectedIndex);
this.targetInput = 'input' + index;
this.setFocus();
}
}
回答2:
I have created my own alternate version to the one proposed by Narm. The problem that I had was that had rendering of actual stepper was postponed (eg. loading data from the server) ngAfterViewInit
fired too soon. To combat that I am setting focus after step animation has finished. It creates slight delay but allows component to settle. StackBlitz and the code:
export class StepperOverviewExample implements OnInit {
isLinear = false;
firstFormGroup: FormGroup;
secondFormGroup: FormGroup;
private targetId = 'input0';
constructor(private _formBuilder: FormBuilder) {}
ngOnInit() {
this.firstFormGroup = this._formBuilder.group({
firstCtrl: ['', Validators.required]
});
this.secondFormGroup = this._formBuilder.group({
secondCtrl: ['', Validators.required]
});
}
setFocus() {
const targetElem = document.getElementById(this.targetId);
targetElem.focus();
}
setTargetId(event: any) {
this.targetId = `input${event.selectedIndex}`;
}
}
<mat-horizontal-stepper (animationDone)="setFocus()" (selectionChange)="setTargetId($event)" [linear]="isLinear" #stepper>
<mat-step [stepControl]="firstFormGroup">
<form [formGroup]="firstFormGroup">
<ng-template matStepLabel>Fill out your name</ng-template>
<mat-form-field>
<input id="input0" matInput placeholder="Last name, First name" formControlName="firstCtrl" required>
</mat-form-field>
<div>
<button mat-button matStepperNext>Next</button>
</div>
</form>
</mat-step>
<mat-step [stepControl]="secondFormGroup">
<form [formGroup]="secondFormGroup">
<ng-template matStepLabel>Fill out your address</ng-template>
<mat-form-field>
<input id="input1" matInput placeholder="Address" formControlName="secondCtrl" 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>
You are now done.
<div>
<button mat-button matStepperPrevious>Back</button>
<button mat-button (click)="stepper.reset()">Reset</button>
</div>
</mat-step>
</mat-horizontal-stepper>
I think it still would be good to get rid of document
for server side rendering compatibility.
回答3:
The solution with input ids and selectionChange listener works and you can add also (animationDone) listener so you can call setFocus() without setTimeout() logic.
来源:https://stackoverflow.com/questions/49011701/focus-input-after-going-to-the-next-step-in-matstepper