ตั้งโฟกัสที่องค์ประกอบ <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();
  }

ไม่ได้ตั้งค่าช่องค้นหาให้โฟกัส

ฉันจะทำเช่นนั้นได้อย่างไร?

คำตอบ:


121

ปรับเปลี่ยนวิธีการค้นหาการแสดงเช่นนี้

showSearch(){
  this.show = !this.show;  
  setTimeout(()=>{ // this will make the execution after the above boolean has changed
    this.searchElement.nativeElement.focus();
  },0);  
}

14
ทำไมเราต้องใช้ setTimeout? การเปลี่ยนแปลงบูลีนซิงโครนัสไม่ใช่หรือ
Andrei Rosu

5
ไม่ยุ่งกับ zonejs เหรอ?
lawphotog

1
@AndreiRosu โดยที่ฉันได้รับข้อผิดพลาดเนื่องจากไม่ได้แสดงการเปลี่ยนแปลง
Islam Q.

11
มันใช้งานได้ แต่ไม่มีคำอธิบายที่ชัดเจนมันเป็นเวทมนตร์ เวทมนตร์หยุดพักบ่อยๆ
John White

1
ฉันพยายามโฟกัสองค์ประกอบของฉันภายในแผงเมื่อแผงถูกเปิดดังนั้นการหมดเวลา 0 จึงไม่ได้ผลสำหรับฉัน แต่สิ่งนี้ทำได้:setTimeout(() => { this.searchElement.nativeElement.focus() }, 100)
tclark333

41

คุณควรใช้โฟกัสอัตโนมัติ html สำหรับสิ่งนี้:

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

หมายเหตุ: หากส่วนประกอบของคุณยังคงอยู่และนำกลับมาใช้ใหม่จะโฟกัสอัตโนมัติในครั้งแรกที่ต่อชิ้นส่วน สิ่งนี้สามารถเอาชนะได้โดยการมี global dom listener ที่ตรวจสอบแอตทริบิวต์ออโต้โฟกัสภายใน Dom fragment เมื่อมีการเชื่อมต่อจากนั้นนำไปใช้ซ้ำหรือโฟกัสผ่าน javascript


42
สิ่งนี้จะใช้ได้เพียงครั้งเดียวในการรีเฟรชทุกหน้าไม่ใช่หลายครั้ง
moritzg

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>

ขอบคุณนี่เป็นคำชักชวนเดียวที่ใช้ได้ผลสำหรับฉันในกรณีที่รอคำขอ http
Abdelhalim FELLAGUE CHEBRA

ฉันพบว่าวิธีนี้เป็นที่ต้องการมากกว่าคำตอบที่ยอมรับ จะสะอาดกว่าในกรณีของการใช้โค้ดซ้ำและ Angular-centric มากขึ้น
derekbaker783

9

ฉันจะชั่งน้ำหนักในเรื่องนี้ (Angular 7 Solution)

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
ดูเหมือนว่าจะเหมือนกับคำตอบที่ยอมรับ
Liam

8

สิ่งนี้ใช้งานได้ i 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();
  }
}

คำอธิบาย: ตกลงดังนั้นสิ่งนี้ได้ผลเนื่องจาก: Change detection เป็นเหตุผลเดียวกับที่ setTimout ทำงาน แต่เมื่อเรียกใช้ setTimeout ใน Angular มันจะข้าม Zone.js และเรียกใช้การตรวจสอบทั้งหมดอีกครั้งและทำงานได้เนื่องจากเมื่อ setTimeout เสร็จสมบูรณ์การเปลี่ยนแปลงทั้งหมดจะเสร็จสมบูรณ์ ด้วย Lifecycle Hook ที่ถูกต้อง (AfterContentChecked) ผลลัพธ์เดียวกันสามารถเข้าถึงได้ แต่มีข้อได้เปรียบที่จะไม่เรียกใช้รอบพิเศษ ฟังก์ชันจะเริ่มทำงานเมื่อมีการตรวจสอบและส่งผ่านการเปลี่ยนแปลงทั้งหมดและทำงานหลังจาก hooks AfterContentInit และ DoCheck ถ้าฉันผิดที่นี่โปรดแก้ไขฉัน

อีกหนึ่งวงจรชีวิตและการตรวจจับการเปลี่ยนแปลงบนhttps://angular.io/guide/lifecycle-hooks

อัปเดต : ฉันพบวิธีที่ดีกว่านี้หากใช้ Angular Material CDK ซึ่งเป็นแพ็คเกจ a11y ก่อนนำเข้าA11yModuleในโมดูลประกาศส่วนประกอบที่คุณมีการป้อนข้อมูลในสนามแล้วใช้. cdkTrapFocusและcdkTrapFocusAutoCaptureสั่งและการใช้งานเช่นนี้ใน html และ tabIndex ชุดในการป้อนข้อมูล:

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

เรามีปัญหาบางอย่างกับดรอปดาวน์ของเราเกี่ยวกับการวางตำแหน่งและการตอบสนองและเริ่มใช้ OverlayModule จาก cdk แทนและวิธีนี้โดยใช้ A11yModule ก็ทำงานได้อย่างไม่มีที่ติ


สวัสดี ... ฉันไม่ได้ลองcdkTrapFocusAutoCaptureแอตทริบิวต์ แต่ฉันเปลี่ยนตัวอย่างแรกของคุณเป็นรหัสของฉัน ด้วยเหตุผลที่ไม่รู้จัก (สำหรับฉัน) มันไม่ทำงานกับ AfterContentChecked lifecycle hook แต่เฉพาะกับ OnInit ใน particolar ที่มี AfterContentChecked ฉันไม่สามารถเปลี่ยนโฟกัสและย้าย (ด้วยเมาส์หรือคีบอร์ด) ไปยังอินพุตอื่นโดยไม่มีคำสั่งนั้นในรูปแบบเดียวกัน
จับเวลา

@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
ฉันลองสิ่งนี้ด้วยเชิงมุม 7 มันไม่ได้ผลการใช้การหมดเวลาทำงานได้ดี
rekiem87

สำหรับฉันในเชิงมุม 8 ก็ไม่ทำงานแย่แล้วเราต้องกลับไปที่ setTimeout
Andrei Chivu

5

ใน Angular ภายใน HTML เองคุณสามารถตั้งค่าโฟกัสที่จะป้อนข้อมูลเมื่อคลิกปุ่ม

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

<input #myInput></input>

คำตอบนี้แสดงวิธีง่ายๆในการเลือกองค์ประกอบอื่นใน HTML โดยใช้โปรแกรมซึ่งเป็นสิ่งที่ฉันกำลังมองหา (คำตอบ "โฟกัสเริ่มต้น" ทั้งหมดไม่ได้ช่วยแก้วิธีตอบสนองต่อเหตุการณ์โดยการเปลี่ยนโฟกัส) - น่าเสียดายที่ฉันต้องตอบสนอง กับmat-select selectionChangedเหตุการณ์ที่เกิดขึ้นก่อน UI อื่น ๆ ดังนั้นการดึงโฟกัสที่จุดนั้นจึงไม่ได้ผล แต่ฉันได้เขียนวิธีการและเรียกว่าจากการจัดการเหตุการณ์:setFocus(el:HTMLElement):void { setTimeout(()=>el.focus(),0); } <mat-select (selectionChanged)="setFocus(myInput)">ไม่ดีเท่า แต่เรียบง่ายและใช้งานได้ดี ขอบคุณ!
Guss



0

html ของส่วนประกอบ:

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

ตัวควบคุมส่วนประกอบ:

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

.. และอย่าลืมนำเข้าA11yModuleจาก@ angular / cdk / a11y

import { A11yModule } from '@angular/cdk/a11y'

-1

วิธีที่ง่ายกว่าคือการทำเช่นนี้

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

3
คุณไม่ต้องการสอบถาม Dom โดยตรง
Richard.Davenport
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.