Establecer el foco en el elemento <input>

101

Estoy trabajando en una aplicación front-end con Angular 5, y necesito tener un cuadro de búsqueda oculto, pero al hacer clic en un botón, el cuadro de búsqueda debería mostrarse y enfocarse.

He intentado algunas formas encontradas en StackOverflow con directiva o algo así, pero no puedo tener éxito.

Aquí está el código de ejemplo:

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

El cuadro de búsqueda no está configurado para enfocar.

¿Cómo puedo hacer eso?

Beto
fuente

Respuestas:

120

Modifique el método de búsqueda de espectáculos como este

showSearch(){
  this.show = !this.show;  
  setTimeout(()=>{ // this will make the execution after the above boolean has changed
    this.searchElement.nativeElement.focus();
  },0);  
}
Arvind Muthuraman
fuente
14
¿Por qué necesitamos usar setTimeout? ¿No es el cambio de booleano sincrónico?
Andrei Rosu
5
¿Eso no se mete con zonejs?
lawphotog
1
@AndreiRosu, sin él recibía un error porque los cambios no se procesaron
Islam Q.25 de
11
Esto funciona, pero sin una explicación clara es mágico. La magia se rompe a menudo.
John White
1
Estaba tratando de enfocar mi elemento dentro de un panel cuando se abrió el panel, así que el tiempo de espera 0 no funcionó para mí, pero esto funcionó:setTimeout(() => { this.searchElement.nativeElement.focus() }, 100)
tclark333
42

Debe usar el enfoque automático html para esto:

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

Nota: si su componente se conserva y se reutiliza, solo se enfocará automáticamente la primera vez que se adjunte el fragmento. Esto se puede superar teniendo un oyente de dom global que verifique el atributo de enfoque automático dentro de un fragmento de dom cuando se adjunta y luego lo vuelva a aplicar o se enfoque a través de javascript.

N-ate
fuente
42
Esto solo funcionará una vez por cada actualización de página, no varias veces.
moritzg
22

Esta directiva enfocará y seleccionará instantáneamente cualquier texto en el elemento tan pronto como se muestre. Esto puede requerir un setTimeout para algunos casos, no se ha probado mucho.

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

Y en el HTML:

 <mat-form-field>
     <input matInput type="text" appPrefixFocusAndSelect [value]="'etc'">
 </mat-form-field>
ggranum
fuente
gracias, esta es la única solución que funcionó para mí en el caso de esperar por solicitud http
Abdelhalim FELLAGUE CHEBRA
Encuentro esta solución más preferible que la respuesta aceptada. Es más limpio en el caso de la reutilización de código y más centrado en Angular.
derekbaker783
9

Voy a opinar sobre esto (Solución 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);
    }
  }
}
Ricardo Saracino
fuente
1
Esto parece ser bastante idéntico a la respuesta aceptada
Liam
8

Esto está funcionando en Angular 8 sin 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();
  }
}

Explicación: Ok, esto funciona debido a: Detección de cambios. Es la misma razón por la que setTimout funciona, pero cuando se ejecuta un setTimeout en Angular, omitirá Zone.js y ejecutará todas las comprobaciones nuevamente, y funciona porque cuando setTimeout se completa, todos los cambios se completan. Con el gancho de ciclo de vida correcto (AfterContentChecked) se puede alcanzar el mismo resultado, pero con la ventaja de que no se ejecutará el ciclo adicional. La función se activará cuando se verifiquen y pasen todos los cambios, y se ejecutará después de los ganchos AfterContentInit y DoCheck. Si me equivoco aquí, corrígeme.

Más ciclos de vida y detección de cambios en https://angular.io/guide/lifecycle-hooks

ACTUALIZACIÓN : encontré una forma aún mejor de hacer esto si uno está usando Angular Material CDK, el paquete a11y. Primero importe A11yModule en el módulo que declara el componente en el que tiene el campo de entrada. Luego use las directivas cdkTrapFocus y cdkTrapFocusAutoCapture y use así en html y establezca tabIndex en la entrada:

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

Tuvimos algunos problemas con nuestros menús desplegables con respecto al posicionamiento y la capacidad de respuesta y comenzamos a usar OverlayModule desde el cdk, y este método que usa A11yModule funciona perfectamente.

SNDVLL
fuente
Hola ... No intento el cdkTrapFocusAutoCaptureatributo, pero cambié tu primer ejemplo en mi código. Por razones desconocidas (para mí), no funcionó con el gancho del ciclo de vida AfterContentChecked, sino solo con OnInit. En particular con AfterContentChecked, no puedo cambiar el enfoque y moverme (con el mouse o el teclado) a otra entrada sin esa directiva en la misma forma.
timhecker
@timhecker sí, nos enfrentamos a un problema similar al migrar nuestros componentes a la superposición de cdk (funcionó cuando se usa solo css en lugar de una superposición para el posicionamiento). Se centró indefinidamente en la entrada cuando un componente que usaba la directiva estaba visible. La única solución que encontré fue usar las directivas cdk mencionadas en la actualización.
SNDVLL
6

Para realizar la ejecución después de que el booleano haya cambiado y evitar el uso del tiempo de espera, puede hacer lo siguiente:

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

constructor(private cd: ChangeDetectorRef) {}

showSearch(){
  this.show = !this.show;  
  this.cd.detectChanges();
  this.searchElement.nativeElement.focus();
}
Marcin Restel
fuente
5
Intenté esto con angular 7, no funcionó, usar el tiempo de espera funcionó bien
rekiem87
para mí también en angular 8 no funciona, es una pena que tengamos que volver a setTimeout
Andrei Chivu
5

En Angular, dentro del propio HTML, puede establecer el enfoque para ingresar al hacer clic en un botón.

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

<input #myInput></input>
shaheer shukur
fuente
Esta respuesta muestra una forma simple de seleccionar programáticamente otro elemento en HTML, que es lo que estaba buscando (todas las respuestas de "enfoque inicial" no resuelven cómo reaccionar ante un evento cambiando el enfoque); desafortunadamente, necesitaba reaccionar a un mat-select selectionChangedevento que ocurre antes que otras cosas de la interfaz de usuario, por lo que enfocar el foco en ese punto no funcionó. En lugar de eso tenía que escribir un método setFocus(el:HTMLElement):void { setTimeout(()=>el.focus(),0); }y llamarlo desde el controlador de eventos: <mat-select (selectionChanged)="setFocus(myInput)">. No es tan agradable, pero simple y funciona bien. ¡gracias!
Guss
1

Solo usando plantilla angular

<input type="text" #searchText>

<span (click)="searchText.focus()">clear</span>
Sandipan Mitra
fuente
"Si está usando Material ..."
Andrew Koper hace
0

html del componente:

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

controlador de componente:

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

..y no te olvides de importar A11yModule desde @ angular / cdk / a11y

import { A11yModule } from '@angular/cdk/a11y'
Cichy
fuente
-1

Una forma más fácil también es hacer esto.

let elementReference = document.querySelector('<your css, #id selector>');
    if (elementReference instanceof HTMLElement) {
        elementReference.focus();
    }
patz
fuente
3
Realmente no desea consultar el dom directamente.
Richard.Davenport