Установить фокус на элемент <input>

101

Я работаю с клиентским приложением с Angular 5, и мне нужно, чтобы поле поиска было скрыто, но при нажатии кнопки поле поиска должно отображаться и фокусироваться.

Я пробовал несколько способов найти в StackOverflow с помощью директивы или около того, но не смог.

Вот пример кода:

@Component({
   selector: 'my-app',
   template: `
    <div>
    <h2>Hello</h2>
    </div>
    <button (click) ="showSearch()">Show Search</button>
    <p></p>
    <form>
      <div >
        <input *ngIf="show" #search type="text"  />            
      </div>
    </form>
    `,
  })
  export class App implements AfterViewInit {
  @ViewChild('search') searchElement: ElementRef;

  show: false;
  name:string;
  constructor() {    
  }

  showSearch(){
    this.show = !this.show;    
    this.searchElement.nativeElement.focus();
    alert("focus");
  }

  ngAfterViewInit() {
    this.firstNameElement.nativeElement.focus();
  }

Поле поиска не настроено на фокус.

Как я могу это сделать?

Боб
источник

Ответы:

120

Измените метод поиска шоу следующим образом

showSearch(){
  this.show = !this.show;  
  setTimeout(()=>{ // this will make the execution after the above boolean has changed
    this.searchElement.nativeElement.focus();
  },0);  
}
Арвинд Мутураман
источник
14
Почему нам нужно использовать setTimeout? Разве изменение логического значения не синхронно?
Андрей Рошу
5
разве это не беспорядок с zonejs?
lawphotog
1
@AndreiRosu, без него я получал ошибку, потому что изменения не были обработаны
Ислам К.
11
Это работает, но без внятного объяснения это волшебство. Магия часто ломается.
Джон Уайт
1
Я пытался сфокусировать свой элемент внутри панели, когда панель была открыта, поэтому таймаут 0 у меня не работал, но это setTimeout(() => { this.searchElement.nativeElement.focus() }, 100)
сработало
42

Для этого вам следует использовать html- автофокус :

<input *ngIf="show" #search type="text" autofocus /> 

Примечание: если ваш компонент сохраняется и используется повторно, он будет выполнять автофокусировку только при первом присоединении фрагмента. Это можно преодолеть, имея глобальный прослушиватель dom, который проверяет атрибут автофокуса внутри фрагмента dom, когда он прикреплен, а затем повторно применяет его или фокусируется через javascript.

Н-ел
источник
42
Это будет работать только один раз при каждом обновлении страницы, а не несколько раз.
moritzg 02
22

Эта директива мгновенно сфокусируется и выделит любой текст в элементе, как только он отобразится. В некоторых случаях для этого может потребоваться setTimeout, он мало тестировался.

import { Directive, ElementRef, OnInit } from '@angular/core';

@Directive({
  selector: '[appPrefixFocusAndSelect]',
})
export class FocusOnShowDirective implements OnInit {

  constructor(private el: ElementRef) {
    if (!el.nativeElement['focus']) {
      throw new Error('Element does not accept focus.');
    }
  }

  ngOnInit(): void {
    const input: HTMLInputElement = this.el.nativeElement as HTMLInputElement;
    input.focus();
    input.select();
  }
}

И в HTML:

 <mat-form-field>
     <input matInput type="text" appPrefixFocusAndSelect [value]="'etc'">
 </mat-form-field>
ggranum
источник
спасибо, это единственное решение, которое сработало для меня в случае ожидания http-запроса
Abdelhalim FELLAGUE CHEBRA
Я считаю это решение более предпочтительным, чем принятый ответ. Это чище в случае повторного использования кода и больше ориентировано на Angular.
derekbaker783
9

Я собираюсь взвесить это (решение Angular 7)

input [appFocus]="focus"....
import {AfterViewInit, Directive, ElementRef, Input,} from '@angular/core';

@Directive({
  selector: 'input[appFocus]',
})
export class FocusDirective implements AfterViewInit {

  @Input('appFocus')
  private focused: boolean = false;

  constructor(public element: ElementRef<HTMLElement>) {
  }

  ngAfterViewInit(): void {
    // ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked.
    if (this.focused) {
      setTimeout(() => this.element.nativeElement.focus(), 0);
    }
  }
}
Рикардо Сарачино
источник
1
Кажется, это в значительной степени идентично принятому ответу
Лиам
8

Это работает в Angular 8 без setTimeout:

import {AfterContentChecked, Directive, ElementRef} from '@angular/core';

@Directive({
  selector: 'input[inputAutoFocus]'
})
export class InputFocusDirective implements AfterContentChecked {
  constructor(private element: ElementRef<HTMLInputElement>) {}

  ngAfterContentChecked(): void {
    this.element.nativeElement.focus();
  }
}

Объяснение: Хорошо, это работает из-за: Обнаружения изменений. По той же причине работает setTimout, но при запуске setTimeout в Angular он обходит Zone.js и снова запускает все проверки, и это работает, потому что, когда setTimeout завершен, все изменения завершаются. С правильным крючком жизненного цикла (AfterContentChecked) можно достичь того же результата, но с тем преимуществом, что дополнительный цикл не будет выполняться. Функция срабатывает, когда все изменения проверены и переданы, и запускается после хуков AfterContentInit и DoCheck. Если я ошибаюсь, поправьте меня.

Больше одного жизненного цикла и обнаружение изменений на https://angular.io/guide/lifecycle-hooks

ОБНОВЛЕНИЕ : я нашел еще лучший способ сделать это, если использовать Angular Material CDK, a11y-package. Сначала импортируйте A11yModule в модуль, объявляющий компонент, в котором есть поле ввода. Затем используйте директивы cdkTrapFocus и cdkTrapFocusAutoCapture и используйте это в html и установите tabIndex на входе:

<div class="dropdown" cdkTrapFocus cdkTrapFocusAutoCapture>
    <input type="text tabIndex="0">
</div>

У нас возникли некоторые проблемы с нашими раскрывающимися списками, касающимися позиционирования и скорости реакции, и мы начали использовать OverlayModule из cdk, и этот метод с использованием A11yModule работает безупречно.

SNDVLL
источник
Привет ... Я не пробую cdkTrapFocusAutoCaptureатрибут, но я изменил ваш первый пример в свой код. По неизвестным для меня причинам он не работал с ловушкой жизненного цикла AfterContentChecked, а только с OnInit. В частности, с AfterContentChecked я не могу изменить фокус и перейти (с помощью мыши или клавиатуры) на другой ввод без этой директивы в той же форме.
timhecker
@timhecker да, мы столкнулись с похожей проблемой при переносе наших компонентов на оверлей cdk (он работал при использовании только css вместо оверлея для позиционирования). Он бесконечно фокусировался на вводе, когда компонент, использующий директиву, был видим. Единственным решением, которое я нашел, было использование директив cdk, упомянутых в обновлении.
SNDVLL
6

Чтобы выполнить выполнение после изменения логического значения и избежать использования тайм-аута, вы можете сделать:

import { ChangeDetectorRef } from '@angular/core';

constructor(private cd: ChangeDetectorRef) {}

showSearch(){
  this.show = !this.show;  
  this.cd.detectChanges();
  this.searchElement.nativeElement.focus();
}
Марцин Рестел
источник
5
Я пробовал это с angular 7, это не сработало, тайм-аут работал нормально
rekiem87
у меня тоже в angular 8 не работает, к сожалению, мы должны вернуться к setTimeout
Андрей
5

В Angular, в самом HTML, вы можете установить фокус на ввод при нажатии кнопки.

<button (click)="myInput.focus()">Click Me</button>

<input #myInput></input>
Шахир Шукур
источник
Этот ответ показывает простой способ программного выбора другого элемента в HTML, который я и искал (все ответы «начальный фокус» не решают, как реагировать на событие путем изменения фокуса) - к сожалению, мне нужно было отреагировать к mat-select selectionChangedсобытию, которое происходит раньше других элементов пользовательского интерфейса, поэтому перетягивание фокуса в этот момент не сработало. Вместо этого я должен был написать метод setFocus(el:HTMLElement):void { setTimeout(()=>el.focus(),0); }и вызывать его из обработчика событий: <mat-select (selectionChanged)="setFocus(myInput)">. Не так красиво, но просто и хорошо работает. Спасибо!
Guss
3

Также есть атрибут DOM, cdkFocusInitialкоторый у меня работает с входными данными. Подробнее об этом можно прочитать здесь: https://material.angular.io/cdk/a11y/overview

Тимон
источник
«Если вы используете Материал, ....»
Эндрю Копер
1

Только с использованием Angular Template

<input type="text" #searchText>

<span (click)="searchText.focus()">clear</span>
Сандипан Митра
источник
«Если вы используете Материал ...»
Эндрю Копер
0

html компонента:

<input [cdkTrapFocusAutoCapture]="show" [cdkTrapFocus]="show">

контроллер компонента:

showSearch() {
  this.show = !this.show;    
}

..и не забываем про импорт A11yModule из @ angular / cdk / a11y

import { A11yModule } from '@angular/cdk/a11y'
Cichy
источник
-1

Более простой способ - сделать это.

let elementReference = document.querySelector('<your css, #id selector>');
    if (elementReference instanceof HTMLElement) {
        elementReference.focus();
    }
пац
источник
3
Вы действительно не хотите напрямую запрашивать дом.
Ричард Дэвенпорт,