可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have an odd requirement and was hoping for some help.
I need to focus on the first found invalid input of a form after clicking a button (not submit). The form is rather large, and so the screen needs to scroll to the first invalid input.
This AngularJS answer would be what I would need, but didn't know if a directive like this would be the way to go in Angular 2:
Set focus on first invalid input in AngularJs form
What would be the Angular 2 way to do this? Thanks for all the help!
回答1:
This works for me. Not the most elegant solution, but given the constraints in Angular we are all experiencing for this particular task, it does the job.
scrollTo(el: Element): void {
if(el) {
el.scrollIntoView({ behavior: 'smooth' });
}
}
scrollToError(): void {
const firstElementWithError = document.querySelector('.ng-invalid');
this.scrollTo(firstElementWithError);
}
async scrollIfFormHasErrors(form: FormGroup): Promise <any> {
await form.invalid;
this.scrollToError();
}
This works, allowing you to evade manipulating the DOM. It simply goes to the first element with .ng-invalid
on the page through the document.querySelector()
which returns the first element in the returned list.
To use it:
this.scrollIfFormHasErrors(this.form).then(() => {
// Run any additional functionality if you need to.
});
I also posted this on Angular's Github page: https://github.com/angular/angular/issues/13158#issuecomment-432275834
回答2:
Unfortunately I can't test this at the moment, so might be a few bugs, but should be mostly there.
Just add it to your form.
import {Directive, Input, HostListener} from '@angular/core';
import {NgForm} from '@angular/forms';
@Directive({ selector: '[scrollToFirstInvalid]' })
export class ScrollToFirstInvalidDirective {
@Input('scrollToFirstInvalid') form: NgForm;
constructor() {
}
@HostListener('submit', ['$event'])
onSubmit(event) {
if(!this.form.valid) {
let target;
for (var i in this.form.controls) {
if(!this.form.controls[i].valid) {
target = this.form.controls[i];
break;
}
}
if(target) {
$('html,body').animate({scrollTop: $(target.nativeElement).offset().top}, 'slow');
}
}
}
}
回答3:
I don't know if this is valid approach or not but this is working great for me.
import { Directive, Input, HostListener, ElementRef } from '@angular/core';
import { NgForm } from '@angular/forms';
import * as $ from 'jquery';
@Directive({ selector: '[accessible-form]' })
export class AccessibleForm {
@Input('form') form: NgForm;
constructor(private el: ElementRef) {
}
@HostListener('submit', ['$event'])
onSubmit(event) {
event.preventDefault();
if (!this.form.valid) {
let target;
target = this.el.nativeElement.querySelector('.ng-invalid')
if (target) {
$('html,body').animate({ scrollTop: $(target).offset().top }, 'slow');
target.focus();
}
}
}
}
In HTML
<form [formGroup]="addUserForm" class="form mt-30" (ngSubmit)="updateUser(addUserForm)" accessible-form [form]="addUserForm"></form>
I have mixed the approach of angularjs accessible form directive in this.
Improvements are welcomed!!!
回答4:
If you are using AngularMaterial, the MdInputDirective has a focus() method which allow you to directly focus on the input field.
In your component, just get a reference to all the inputs with the @ViewChildren annotation, like this:
@ViewChildren(MdInputDirective) inputs: QueryList<MdInputDirective>;
Then, setting focus on the first invalid input is as simple as this:
this.inputs.find(input => !input._ngControl.valid).focus()
回答5:
I've created an Angular directive to solve this problem. You can check it here ngx-scroll-to-first-invalid.
Steps:
1.Install the module:
npm i @ismaestro/ngx-scroll-to-first-invalid --save
2.Import the NgxScrollToFirstInvalidModule
:
import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
import {NgxScrollToFirstInvalidModule} from '@ismaestro/ngx-scroll-to-first-invalid';
@NgModule({
imports: [
BrowserModule,
NgxScrollToFirstInvalidModule
],
bootstrap: [AppComponent]
})
export class AppModule { }
3.Use the directive inside a form:
<form [formGroup]="testForm" ngxScrollToFirstInvalid>
<input id="test-input1" type="text" formControlName="someText1">
<button (click)="saveForm()"></button>
</form>
Hope it helps!
:)
回答6:
Plain HTML solution.
If you don't need to be scrolling , just focus on first valid input, I use :
public submitForm() {
if(this.form.valid){
// submit form
} else {
let invalidFields = [].slice.call(document.getElementsByClassName('ng-invalid'));
invalidFields[1].focus();
}
}
This is for template driven form here. We focus on second element of invalidFields cuz first is the whole form which is invalid too.
回答7:
@HostListener('submit', ['$event'])
onSubmit(event) {
event.preventDefault();
if (!this.checkoutForm.valid) {
let target;
target = $('input[type=text].ng-invalid').first();
if (target) {
$('html,body').animate({ scrollTop: $(target).offset().top }, 'slow', ()=> {
target.focus();
});
}
}
}
回答8:
I recommend putting this in a service, for me it worked like this:
if (this.form.valid) {
//submit
} else {
let control;
Object.keys(this.form.controls).reverse().forEach( (field) => {
if (this.form.get(field).invalid) {
control = this.form.get(field);
control.markAsDirty();
}
});
if(control) {
let el = $('.ng-invalid:not(form):first');
$('html,body').animate({scrollTop: (el.offset().top - 20)}, 'slow', () => {
el.focus();
});
}
}
回答9:
For Angular Material ,
The below worked for me
@ViewChildren(MatInput) inputs: QueryList <MatInput>;
this.inputs.find(input => !input.ngControl.valid).focus();