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?

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

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.


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>

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

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.


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>

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

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>

"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'

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


-1

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

let elementReference = document.querySelector('<your css, #id selector>');
    if (elementReference instanceof HTMLElement) {
        elementReference.focus();
    }

3
Você realmente não deseja consultar o dom diretamente.
Richard.Davenport
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.