Angular 自定义焦点指令。聚焦表单的第一个无效输入
Angular Custom focus Directive. Focus a form's first invalid input
我已经创建了一个指令来聚焦无效的输入
import { Directive, Input, Renderer2, ElementRef, OnChanges } from '@angular/core';
@Directive({
// tslint:disable-next-line:directive-selector
selector: '[focusOnError]'
})
export class HighlightDirective implements OnChanges {
@Input() submitted: string;
constructor(private renderer: Renderer2, private el: ElementRef) { }
ngOnChanges(): void {
const el = this.renderer.selectRootElement(this.el.nativeElement);
if (this.submitted && el && el.classList.contains('ng-invalid') && el.focus) {
setTimeout(() => el.focus());
}
}
}
我确实有一个带有两个输入的反应形式,并且我已将指令应用于两个输入
<form>
...
<input type="text" id="familyName" focusOnError />
...
<input type="text" id="appointmentCode" focusOnError />
...
</form>
提交表单后它工作正常,但我正在努力实现以下目标:
预期结果:
- 提交表单后,如果两个输入均无效,则应只关注第一个。
当前结果:
- 如果两个输入都无效,则提交表单后,第二个输入将获得焦点。
我不知道如何指定 "only do this if it's the first child",我已经尝试使用指令的选择器,但没有成功。
有什么想法吗?
非常感谢。
好吧,只是为了好玩 stackblitz。如果我们有一个 formControl,我们可以注入 ngControl,它就是控件本身。这样我们就可以得到formGroup。我控制 "submited" 在 app.component
中制作 work-around
<button (click)="check()">click</button>
check() {
this.submited = false;
setTimeout(() => {
this.submited = true;
})
}
指令就像
export class FocusOnErrorDirective implements OnInit {
@HostListener('input')
onInput() {
this._submited = false;
}
//I used "set" to avoid ngChanges, but then I need the "ugly" work-around in app.component
@Input('focusOnError')
set submited(value) {
this._submited = value;
if (this._submited) { ((is submited is true
if (this.control && this.control.invalid) { //if the control is invalid
if (this.form) {
for (let key of this.keys) //I loop over all the
{ //controls ordered
if (this.form.get(key).invalid) { //If I find one invalid
if (key == this.control.name) { //If it's the own control
setTimeout(() => {
this.el.nativeElement.focus() //focus
});
}
break; //end of loop
}
}
}
else
this.el.nativeElement.focus()
}
}
}
private form: FormGroup;
private _submited: boolean;
private keys: string[];
constructor(@Optional() private control: NgControl, private el: ElementRef) { }
ngOnInit() {
//in this.form we has the formGroup.
this.form = this.control?this.control.control.parent as FormGroup:null;
//we need store the names of the control in an array "keys"
if (this.form)
this.keys = JSON.stringify(this.form.value)
.replace(/[&\/\#+()$~%.'"*?<>{}]/g, '')
.split(',')
.map(x => x.split(':')[0]);
}
}
为了控制表单的输入,我认为更好的解决方案是使用 ViewChildren 来获取所有元素。所以,我们可以遍历这些元素并关注第一个元素。
所以,我们可以有一个简单的辅助指令:
@Directive({
selector: '[focusOnError]'
})
export class FocusOnErrorDirective {
public get invalid()
{
return this.control?this.control.invalid:false;
}
public focus()
{
this.el.nativeElement.focus()
}
constructor(@Optional() private control: NgControl, private el: ElementRef) { }
}
而且,在我们的组件中,我们有一些像
@ViewChildren(FocusOnErrorDirective) fields:QueryList<FocusOnErrorDirective>
check() {
const fields=this.fields.toArray();
for (let field of fields)
{
if (field.invalid)
{
field.focus();
break;
}
}
}
您可以在 stackblitz
中查看实际操作
更新 事情总是可以改进的:
为什么不创建一个应用于表单的指令?
@Directive({
selector: '[focusOnError]'
})
export class FocusOnErrorDirective {
@ContentChildren(NgControl) fields: QueryList<NgControl>
@HostListener('submit')
check() {
const fields = this.fields.toArray();
for (let field of fields) {
if (field.invalid) {
(field.valueAccessor as any)._elementRef.nativeElement.focus();
break;
}
}
}
所以,我们的.html就像
<form [formGroup]="myForm" focusOnError>
<input type="text" formControlName="familyName" />
<input type="text" formControlName="appointmentCode" />
<button >click</button>
</form>
甚至更多,如果我们使用 as 选择器形式
@Directive({
selector: 'form'
})
甚至我们可以去掉表单中的focusOnError
<form [formGroup]="myForm" (submit)="submit(myForm)">
..
</form>
更新 2 formGroup 与 formGroup 的问题。 已解决
NgControl 只考虑有[(ngModel)]、formControlName 和[formControl] 的控件,所以。如果我们可以使用像
这样的形式
myForm = new FormGroup({
familyName: new FormControl('', Validators.required),
appointmentCode: new FormControl('', Validators.required),
group: new FormGroup({
subfamilyName: new FormControl('', Validators.required),
subappointmentCode: new FormControl('', Validators.required)
})
})
我们可以使用如下形式:
<form [formGroup]="myForm" focusOnError (submit)="submit(myForm)">
<input type="text" formControlName="familyName" />
<input type="text" formControlName="appointmentCode" />
<div >
<input type="text" [formControl]="group.get('subfamilyName')" />
<input type="text" [formControl]="group.get('subappointmentCode')" />
</div>
<button >click</button>
</form>
在 .ts 中我们有
get group()
{
return this.myForm.get('group')
}
Update 3 with Angular 8 可以得到children的后代,所以直接写
@ContentChildren(NgControl,{descendants:true}) fields: QueryList<NgControl>
我已经创建了一个指令来聚焦无效的输入
import { Directive, Input, Renderer2, ElementRef, OnChanges } from '@angular/core';
@Directive({
// tslint:disable-next-line:directive-selector
selector: '[focusOnError]'
})
export class HighlightDirective implements OnChanges {
@Input() submitted: string;
constructor(private renderer: Renderer2, private el: ElementRef) { }
ngOnChanges(): void {
const el = this.renderer.selectRootElement(this.el.nativeElement);
if (this.submitted && el && el.classList.contains('ng-invalid') && el.focus) {
setTimeout(() => el.focus());
}
}
}
我确实有一个带有两个输入的反应形式,并且我已将指令应用于两个输入
<form>
...
<input type="text" id="familyName" focusOnError />
...
<input type="text" id="appointmentCode" focusOnError />
...
</form>
提交表单后它工作正常,但我正在努力实现以下目标:
预期结果: - 提交表单后,如果两个输入均无效,则应只关注第一个。
当前结果: - 如果两个输入都无效,则提交表单后,第二个输入将获得焦点。
我不知道如何指定 "only do this if it's the first child",我已经尝试使用指令的选择器,但没有成功。
有什么想法吗?
非常感谢。
好吧,只是为了好玩 stackblitz。如果我们有一个 formControl,我们可以注入 ngControl,它就是控件本身。这样我们就可以得到formGroup。我控制 "submited" 在 app.component
中制作 work-around<button (click)="check()">click</button>
check() {
this.submited = false;
setTimeout(() => {
this.submited = true;
})
}
指令就像
export class FocusOnErrorDirective implements OnInit {
@HostListener('input')
onInput() {
this._submited = false;
}
//I used "set" to avoid ngChanges, but then I need the "ugly" work-around in app.component
@Input('focusOnError')
set submited(value) {
this._submited = value;
if (this._submited) { ((is submited is true
if (this.control && this.control.invalid) { //if the control is invalid
if (this.form) {
for (let key of this.keys) //I loop over all the
{ //controls ordered
if (this.form.get(key).invalid) { //If I find one invalid
if (key == this.control.name) { //If it's the own control
setTimeout(() => {
this.el.nativeElement.focus() //focus
});
}
break; //end of loop
}
}
}
else
this.el.nativeElement.focus()
}
}
}
private form: FormGroup;
private _submited: boolean;
private keys: string[];
constructor(@Optional() private control: NgControl, private el: ElementRef) { }
ngOnInit() {
//in this.form we has the formGroup.
this.form = this.control?this.control.control.parent as FormGroup:null;
//we need store the names of the control in an array "keys"
if (this.form)
this.keys = JSON.stringify(this.form.value)
.replace(/[&\/\#+()$~%.'"*?<>{}]/g, '')
.split(',')
.map(x => x.split(':')[0]);
}
}
为了控制表单的输入,我认为更好的解决方案是使用 ViewChildren 来获取所有元素。所以,我们可以遍历这些元素并关注第一个元素。
所以,我们可以有一个简单的辅助指令:
@Directive({
selector: '[focusOnError]'
})
export class FocusOnErrorDirective {
public get invalid()
{
return this.control?this.control.invalid:false;
}
public focus()
{
this.el.nativeElement.focus()
}
constructor(@Optional() private control: NgControl, private el: ElementRef) { }
}
而且,在我们的组件中,我们有一些像
@ViewChildren(FocusOnErrorDirective) fields:QueryList<FocusOnErrorDirective>
check() {
const fields=this.fields.toArray();
for (let field of fields)
{
if (field.invalid)
{
field.focus();
break;
}
}
}
您可以在 stackblitz
中查看实际操作更新 事情总是可以改进的:
为什么不创建一个应用于表单的指令?
@Directive({
selector: '[focusOnError]'
})
export class FocusOnErrorDirective {
@ContentChildren(NgControl) fields: QueryList<NgControl>
@HostListener('submit')
check() {
const fields = this.fields.toArray();
for (let field of fields) {
if (field.invalid) {
(field.valueAccessor as any)._elementRef.nativeElement.focus();
break;
}
}
}
所以,我们的.html就像
<form [formGroup]="myForm" focusOnError>
<input type="text" formControlName="familyName" />
<input type="text" formControlName="appointmentCode" />
<button >click</button>
</form>
甚至更多,如果我们使用 as 选择器形式
@Directive({
selector: 'form'
})
甚至我们可以去掉表单中的focusOnError
<form [formGroup]="myForm" (submit)="submit(myForm)">
..
</form>
更新 2 formGroup 与 formGroup 的问题。 已解决
NgControl 只考虑有[(ngModel)]、formControlName 和[formControl] 的控件,所以。如果我们可以使用像
这样的形式myForm = new FormGroup({
familyName: new FormControl('', Validators.required),
appointmentCode: new FormControl('', Validators.required),
group: new FormGroup({
subfamilyName: new FormControl('', Validators.required),
subappointmentCode: new FormControl('', Validators.required)
})
})
我们可以使用如下形式:
<form [formGroup]="myForm" focusOnError (submit)="submit(myForm)">
<input type="text" formControlName="familyName" />
<input type="text" formControlName="appointmentCode" />
<div >
<input type="text" [formControl]="group.get('subfamilyName')" />
<input type="text" [formControl]="group.get('subappointmentCode')" />
</div>
<button >click</button>
</form>
在 .ts 中我们有
get group()
{
return this.myForm.get('group')
}
Update 3 with Angular 8 可以得到children的后代,所以直接写
@ContentChildren(NgControl,{descendants:true}) fields: QueryList<NgControl>