Defina o foco no elemento <input>

108

Estou trabalhando em um aplicativo front-end com Angular 5 e preciso ocultar uma caixa de pesquisa, mas, ao clicar em um botão, a caixa de pesquisa deve ser exibida e focada.

Eu tentei algumas maneiras encontradas no StackOverflow com diretiva ou algo assim, mas não consegui.

Aqui está o código de exemplo:

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

A caixa de pesquisa não está definida para o foco.

Como eu posso fazer isso?

Prumo
fonte

Respostas:

128

Modifique o método de pesquisa de exibição 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
fonte
15
Por que precisamos usar setTimeout? A mudança do booleano não é síncrona?
Andrei Rosu
6
isso não mexe com o zonejs?
lawphotog
1
@AndreiRosu, sem ele eu estava recebendo um erro porque as alterações não foram processadas
Islam Q.
13
Isso funciona, mas sem uma explicação clara é mágico. A magia quebra frequentemente.
John White,
1
Eu estava tentando focar meu elemento dentro de um painel quando o painel foi aberto, então o tempo limite 0 não funcionou para mim, mas funcionou:setTimeout(() => { this.searchElement.nativeElement.focus() }, 100)
tclark333
41

Você deve usar o foco automático html para isso:

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

Nota: se o seu componente for persistido e reutilizado, ele só fará o foco automático na primeira vez que o fragmento for anexado. Isso pode ser superado por ter um ouvinte global de dom que verifica o atributo autofocus dentro de um fragmento de dom quando ele é anexado e, em seguida, reaplica-o ou focus via javascript.

N-ate
fonte
46
Isso funcionará apenas uma vez a cada atualização de página, não várias vezes.
moritzg
23

Esta diretiva irá instantaneamente focar e selecionar qualquer texto no elemento assim que ele for exibido. Isso pode exigir um setTimeout para alguns casos, ele não foi muito testado.

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

E no HTML:

 <mat-form-field>
     <input matInput type="text" appPrefixFocusAndSelect [value]="'etc'">
 </mat-form-field>
ggranum
fonte
obrigado, esta é a única solução que funcionou para mim no caso de aguardar um pedido de http
Abdelhalim FELLAGUE CHEBRA
1
Acho esta solução mais preferível do que a resposta aceita. É mais limpo no caso de reutilização de código e mais centrado em Angular.
derekbaker783
Também não se esqueça de adicionar a diretiva nas declarações do módulo.
Elijah Dollin
11

Vou ponderar sobre isso (solução 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
fonte
1
Esta parece ser praticamente idêntica à resposta aceita
Liam
9

Isso está funcionando no Angular 8 sem 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();
  }
}

Explicação: Ok, então isso funciona por causa de: Detecção de alterações. É a mesma razão que setTimout funciona, mas ao executar um setTimeout em Angular, ele ignorará Zone.js e executará todas as verificações novamente, e funciona porque quando setTimeout for concluído, todas as alterações serão concluídas. Com o gancho de ciclo de vida correto (AfterContentChecked), o mesmo resultado pode ser alcançado, mas com a vantagem de que o ciclo extra não será executado. A função será acionada quando todas as alterações forem verificadas e aprovadas e será executada após os ganchos AfterContentInit e DoCheck. Se eu estiver errado aqui, por favor me corrija.

Mais um ciclo de vida e detecção de mudança em https://angular.io/guide/lifecycle-hooks

ATUALIZAÇÃO : descobri uma maneira ainda melhor de fazer isso usando Angular Material CDK, o pacote a11y. Primeiro importe A11yModule no módulo declarando o componente em que você tem o campo de entrada. Em seguida, use as diretivas cdkTrapFocus e cdkTrapFocusAutoCapture e use assim em html e defina tabIndex na entrada:

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

Tivemos alguns problemas com nossos menus suspensos em relação ao posicionamento e capacidade de resposta e começamos a usar o OverlayModule do cdk, e este método usando A11yModule funciona perfeitamente.

SNDVLL
fonte
Oi ... Eu não tento cdkTrapFocusAutoCaptureattribute, mas mudei seu primeiro exemplo em meu código. Por razões desconhecidas (para mim), ele não funcionou com o gancho de ciclo de vida AfterContentChecked, mas apenas com OnInit. Em particular com AfterContentChecked não posso mudar o foco e mover (com o mouse ou teclado) para outra entrada sem aquela diretiva no mesmo formulário.
timhecker
@timhecker sim, enfrentamos um problema semelhante ao migrar nossos componentes para a sobreposição cdk (funcionou ao usar apenas css em vez de uma sobreposição para posicionamento). Ele se concentrava indefinidamente na entrada quando um componente usando a diretiva estava visível. A única solução que encontrei foi usar as diretivas cdk mencionadas na atualização.
SNDVLL de
7

No Angular, dentro do próprio HTML, você pode definir o foco para a entrada com o clique de um botão.

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

<input #myInput></input>
Shaheer Shukur
fonte
Esta resposta mostra uma maneira simples de selecionar programaticamente outro elemento em HTML, que é o que eu estava procurando (todas as respostas de "foco inicial" não resolvem como reagir a um evento mudando o foco) - infelizmente, eu precisava reagir a um mat-select selectionChangedevento que acontece antes de outras coisas da interface do usuário, portanto, puxar o foco naquele ponto não funcionou. Em vez disso eu tive que escrever um método setFocus(el:HTMLElement):void { setTimeout(()=>el.focus(),0); }e chamá-lo a partir do manipulador de eventos: <mat-select (selectionChanged)="setFocus(myInput)">. Não é tão bom, mas simples e funciona bem. THX!
Guss de
Não funcionou para mim. Recebo ERROR TypeError: Não é possível ler a propriedade 'focus' de undefined
Reza Taba
@RezaTaba, você chamou a função 'myInput.focus ()' ou apenas escreveu 'myInput.focus'? Além disso, certifique-se de que seu elemento de entrada esteja dentro de uma div renderizada (* ngSe false pode causar erro, por exemplo)
shaheer shukur
6

Para fazer a execução após o booleano ser alterado e evitar o uso de tempo limite, você pode fazer:

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

constructor(private cd: ChangeDetectorRef) {}

showSearch(){
  this.show = !this.show;  
  this.cd.detectChanges();
  this.searchElement.nativeElement.focus();
}
Marcin Restel
fonte
5
Eu tentei fazer isso com o angular 7, não funcionou, usar o tempo limite funcionou bem
rekiem87
para mim também no angular 8 não está funcionando, para o mal temos que voltar para setTimeout
Andrei Chivu
1

Apenas usando modelo angular

<input type="text" #searchText>

<span (click)="searchText.focus()">clear</span>
Sandipan Mitra
fonte
"Se você estiver usando Material ..."
Andrew Koper
ERROR TypeError: Não é possível ler a propriedade 'focus' de undefined
Reza Taba
1

html do componente:

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

controlador de componente:

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

..e não se esqueça de importar A11yModule de @ angular / cdk / a11y

import { A11yModule } from '@angular/cdk/a11y'
Cichy
fonte
Melhor solução. Obrigado. Outras soluções me deram erro.
Reza Taba
1

Estou tendo o mesmo cenário, funcionou para mim, mas não estou tendo o recurso "ocultar / mostrar" que você tem. Então, talvez você possa primeiro verificar se obtém o foco quando tem o campo sempre visível e, em seguida, tentar resolver por que não funciona quando você altera a visibilidade (provavelmente é por isso que você precisa aplicar um sono ou uma promessa)

Para definir o foco, esta é a única mudança que você precisa fazer:

sua entrada de tapete Html deve ser:

<input #yourControlName matInput>

em sua classe TS, referência como esta na seção de variáveis ​​(

export class blabla...
    @ViewChild("yourControlName") yourControl : ElementRef;

Seu botão está bom, chamando:

  showSearch(){
       ///blabla... then finally:
       this.yourControl.nativeElement.focus();
}

e é isso. Você pode verificar esta solução neste post que encontrei, então, obrigado a -> https://codeburst.io/focusing-on-form-elements-the-angular-way-e9a78725c04f

Yogurtu
fonte
-1

A maneira mais fácil também é fazer isso.

let elementReference = document.querySelector('<your css, #id selector>');
    if (elementReference instanceof HTMLElement) {
        elementReference.focus();
    }
patz
fonte
3
Você realmente não deseja consultar o dom diretamente.
Richard.Davenport