Angular 2 Фокусируйтесь на первом недопустимом вводе после Click/Event

У меня есть странное требование, и я надеялся на какую-то помощь.

Мне нужно сосредоточиться на первом найденном недопустимом вводе формы после нажатия кнопки (не отправлять). Форма довольно большая, поэтому экран должен прокручиваться до первого недопустимого ввода.

Этот ответ AngularJS был бы тем, что мне нужно, но не знал, будет ли такая директива такой, как это сделать в Angular 2:

Установите фокус на первый недопустимый вход в форме AngularJs

Каким будет способ Angular 2? Спасибо за помощь!

Ответ 1

Это работает для меня. Не самое элегантное решение, но учитывая ограничения в Angular, которые мы все испытываем для этой конкретной задачи, он выполняет свою работу.

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

Это работает, позволяя вам избежать манипуляций с DOM. Он просто переходит к первому элементу с .ng-invalid на странице через document.querySelector() который возвращает первый элемент в возвращенном списке.

Чтобы использовать это:

this.scrollIfFormHasErrors(this.form).then(() => {
  // Run any additional functionality if you need to. 
});

Я также разместил это на странице Angular Github: https://github.com/angular/angular/issues/13158#issuecomment-432275834

Ответ 2

К сожалению, я не могу проверить это на данный момент, так что может быть несколько ошибок, но должно быть в основном там. Просто добавьте его в свою форму.

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

Я не знаю, является ли это действительным подходом или нет, но это отлично работает для меня.

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

}

В HTML

<form [formGroup]="addUserForm" class="form mt-30" (ngSubmit)="updateUser(addUserForm)" accessible-form [form]="addUserForm"></form>

Я смешал подход директивы доступного вида angularjs в этом. Усовершенствования приветствуются!!!

Ответ 4

Если вы используете AngularMaterial, MdInputDirective имеет метод focus(), который позволяет вам напрямую сфокусироваться на поле ввода.

В своем компоненте просто получите ссылку на все входы с аннотацией @ViewChildren, например:

@ViewChildren(MdInputDirective) inputs: QueryList<MdInputDirective>;

Тогда установка фокуса на первый недопустимый вход так же проста, как это:

this.inputs.find(input => !input._ngControl.valid).focus()

Ответ 5

Я создал угловую директиву для решения этой проблемы. Вы можете проверить это здесь ngx-scroll-to-first-invalid.

шаги:

1.Установите модуль:

npm i @ismaestro/ngx-scroll-to-first-invalid --save

2.Используйте 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. Используйте директиву внутри формы:

<form [formGroup]="testForm" ngxScrollToFirstInvalid>
  <input id="test-input1" type="text" formControlName="someText1">
  <button (click)="saveForm()"></button>
</form>

Надеюсь, поможет! :)

Ответ 6

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

Ответ 7

Простое решение HTML. Если вам не нужно прокручивать, просто сосредоточьтесь на первом действительном вводе, я использую:

public submitForm() {
    if(this.form.valid){
        // submit form
    } else {
        let invalidFields = [].slice.call(document.getElementsByClassName('ng-invalid'));
        invalidFields[1].focus();
    }
}

Здесь для формы с шаблоном. Мы сосредотачиваемся на втором элементе invalidFields, потому что первая - это тоже недопустимая форма.

Ответ 8

Я рекомендую поместить это в сервис, для меня он работает следующим образом:

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

Для angular материала, Ниже работает для меня

@ViewChildren(MatInput) inputs: QueryList <MatInput>;
this.inputs.find(input => !input.ngControl.valid).focus();