2015-12-29 23 views
29

Phần 1 "#test" là undefined khi sử dụng * ngIf

Khi tham khảo một đầu vào có thể được ẩn/"tiêu diệt" (vì * ngIf được sử dụng và một số phần tử bị hủy), biến cục bộ được tạo bởi cú pháp hashtag # (#test trong ví dụ bên dưới) của angular2 không hoạt động, ngay cả khi phần tử tồn tại trong trang.mẫu Tại sao angular2 biến địa phương không thể sử dụng trong các mẫu khi sử dụng * ngIf

Mã này là:

@Component({ 
    selector: 'my-app', 
    template: `<h1>My First Angular 2 App</h1> 
    <button (click)="focusOther(test)">test</button> 
    <input #test *ngIf="boolValue" > 
    ` 
}) 
export class AppComponent { 

    private isVisible = false; 

    focusOther(testElement){ 
    this.isVisible = true; 
    alert(testElement); 
    testElement.focus(); 
    } 

} 

Các hiển thị cảnh báo "không xác định", bởi vì không có gì được truyền cho hàm.

Có giải pháp nào để hoạt động không? Mục tiêu của tôi là tập trung một yếu tố sẽ được tạo.

Giải pháp cho Mark Rajcok: làm một chỉ với một afterViewInit sử dụng elementRef và gọi .focus() trên phần tử.

Xem plunker này cho một phiên bản làm việc của phần 1: http://plnkr.co/edit/JmBBHMo1hXLe4jrbpVdv?p=preview

Phần 2 làm thế nào để tái tập trung yếu tố đó sau khi tạo ban đầu

Khi vấn đề này "tập trung sau khi tạo" là cố định, tôi cần một cách để tái tập trung() một thành phần, như trong "test.focus()" (trong đó #test là tên biến cục bộ cho đầu vào, nhưng không thể được sử dụng như vậy như tôi đã trình bày trước đây).

Nhiều giải pháp được đưa Mark Rajcok

+0

Xem thêm https://github.com/angular/angular/issues/6179 –

+0

Tôi đã điền đầy đủ vấn đề về repo góc vì đó là lỗi nếu chúng tôi xem xét tài liệu thực tế nêu rõ "Chúng tôi có thể tham chiếu biến mẫu cục bộ trên cùng một yếu tố, trên một phần tử anh chị em, hoặc trên bất kỳ phần tử con nào của nó. –

Trả lời

45

Đối với một giải pháp cho vấn đề trọng tâm, bạn có thể tạo ra một chỉ thị thuộc tính, focusMe:

import {Component, Directive, ElementRef} from 'angular2/core'; 
@Directive({ 
    selector: '[focusMe]' 
}) 
export class FocusDirective { 
    constructor(private el: ElementRef) {} 
    ngAfterViewInit() { 
    this.el.nativeElement.focus(); 
    } 
} 
@Component({ 
    selector: 'my-app', 
    directives: [FocusDirective], 
    template: `<h1>My First Angular 2 App</h1> 
     <button (click)="toggle()">toggle</button> 
     <input focusMe *ngIf="isVisible"> 
    ` 
}) 
export class AppComponent { 
    constructor() { console.clear(); } 
    private isVisible = false; 
    toggle() { 
    this.isVisible = !this.isVisible; 
    } 
} 

Plunker

Cập nhật 1: Thêm giải pháp cho tính năng tái tập trung:

import {Component, Directive, ElementRef, Input} from 'angular2/core'; 

@Directive({ 
    selector: '[focusMe]' 
}) 
export class FocusMe { 
    @Input('focusMe') hasFocus: boolean; 
    constructor(private elementRef: ElementRef) {} 
    ngAfterViewInit() { 
     this.elementRef.nativeElement.focus(); 
    } 
    ngOnChanges(changes) { 
     //console.log(changes); 
     if(changes.hasFocus && changes.hasFocus.currentValue === true) { 
     this.elementRef.nativeElement.focus(); 
     } 
    } 
} 
@Component({ 
    selector: 'my-app', 
    template: `<h1>My First Angular 2 App</h1> 
    <button (click)="showInput()">Make it visible</button> 
    <input *ngIf="inputIsVisible" [focusMe]="inputHasFocus"> 
    <button (click)="focusInput()" *ngIf="inputIsVisible">Focus it</button> 
    `, 
    directives:[FocusMe] 
}) 
export class AppComponent { 
    private inputIsVisible = false; 
    private inputHasFocus = false; 
    constructor() { console.clear(); } 
    showInput() { 
    this.inputIsVisible = true; 
    } 
    focusInput() { 
    this.inputHasFocus = true; 
    setTimeout(() => this.inputHasFocus = false, 50); 
    } 
} 

Plunker

Một thay thế cho việc sử dụng setTimeout() để thiết lập lại tài sản tập trung để false sẽ tạo ra một sự kiện/tài sản đầu ra trên FocusDirective và emit() sự kiện khi gọi focus(). Sau đó AppComponent sẽ lắng nghe sự kiện đó và thiết lập lại thuộc tính focus.

Cập nhật 2: Dưới đây là một cách thay thế/tốt hơn để thêm tính năng lấy nét lại, sử dụng ViewChild. Chúng ta không cần theo dõi trạng thái lấy nét theo cách này, cũng như chúng ta không cần một thuộc tính đầu vào trên chỉ thị FocusMe.

import {Component, Directive, ElementRef, Input, ViewChild} from 'angular2/core'; 

@Directive({ 
    selector: '[focusMe]' 
}) 
export class FocusMe { 
    constructor(private elementRef: ElementRef) {} 
    ngAfterViewInit() { 
     // set focus when element first appears 
     this.setFocus(); 
    } 
    setFocus() { 
     this.elementRef.nativeElement.focus(); 
    } 
} 
@Component({ 
    selector: 'my-app', 
    template: `<h1>My First Angular 2 App</h1> 
    <button (click)="showInput()">Make it visible</button> 
    <input *ngIf="inputIsVisible" focusMe> 
    <button (click)="focusInput()" *ngIf="inputIsVisible">Focus it</button> 
    `, 
    directives:[FocusMe] 
}) 
export class AppComponent { 
    @ViewChild(FocusMe) child; 
    private inputIsVisible = false; 
    constructor() { console.clear(); } 
    showInput() { 
    this.inputIsVisible = true; 
    } 
    focusInput() { 
    this.child.setFocus(); 
    } 
} 

Plunker

Cập nhật 3: Dưới đây là thêm một thay thế mà không đòi hỏi một chỉ thị, mà vẫn sử dụng ViewChild, nhưng chúng tôi truy cập vào đứa trẻ qua mẫu biến cục bộ chứ không phải là một chỉ thị thuộc tính (nhờ @alexpods cho the tip):

import {Component, ViewChild, NgZone} from 'angular2/core'; 

@Component({ 
    selector: 'my-app', 
    template: `<h1>Focus test</h1> 
    <button (click)="showInput()">Make it visible</button> 
    <input #input1 *ngIf="input1IsVisible"> 
    <button (click)="focusInput1()" *ngIf="input1IsVisible">Focus it</button> 
    `, 
}) 
export class AppComponent { 
    @ViewChild('input1') input1ElementRef; 
    private input1IsVisible = false; 
    constructor(private _ngZone: NgZone) { console.clear(); } 
    showInput() { 
    this.input1IsVisible = true; 
    // Give ngIf a chance to render the <input>. 
    // Then set the focus, but do this outside the Angualar zone to be efficient. 
    // There is no need to run change detection after setTimeout() runs, 
    // since we're only focusing an element. 
    this._ngZone.runOutsideAngular(() => { 
     setTimeout(() => this.focusInput1(), 0); 
    }); 
    } 
    setFocus(elementRef) { 
    elementRef.nativeElement.focus(); 
    } 
    ngDoCheck() { 
    // if you remove the ngZone stuff above, you'll see 
    // this log 3 times instead of 1 when you click the 
    // "Make it visible" button. 
    console.log('doCheck'); 
    } 
    focusInput1() { 
    this.setFocus(this.input1ElementRef); 
    } 
} 

Plunker

Cập nhật 4: Tôi cập nhật mã trong Cập nhật 3 sử dụng NgZone để chúng ta không gây ra thuật toán phát hiện sự thay đổi góc để chạy sau khi setTimeout() kết thúc. (Để biết thêm về phát hiện thay đổi, hãy xem this answer).

Cập nhật 5: Tôi đã cập nhật mã trong trình chỉnh sửa ở trên để sử dụng Trình kết xuất để đảm bảo an toàn cho công nhân web. Truy cập focus() trực tiếp trên nativeElement không được khuyến khích.

focusInput1() { 
    this._renderer.invokeElementMethod(
    this.input1ElementRef.nativeElement, 'focus', []); 
} 

Tôi đã học được rất nhiều từ câu hỏi này.

+0

Tôi có một vấn đề khác xuất hiện sau đó, làm thế nào tôi có thể lấy lại tiêu điểm cho đầu vào đó, sau khi khởi tạo, trong một hàm của mã của AppComponent sẽ được kích hoạt? Tôi sẽ cập nhật plunker một lần có sẵn. Không có giải pháp nào khác ngoài việc tạo sự kiện tùy chỉnh bên trong chỉ thị và sau đó liên kết nó với thành phần để khi sự kiện đó được kích hoạt, tiêu điểm sẽ quay lại đầu vào? –

+1

@MykaEyl, xem câu trả lời cập nhật của tôi. –

+1

Tuyệt vời! Tôi thích @ViewChild ('input1') sử dụng, bởi vì tôi cảm thấy rằng nó sẽ thực sự là xấu nếu tôi đã phải sử dụng một chỉ thị tùy chỉnh chỉ để lựa chọn. Cảm ơn bạn! –

Các vấn đề liên quan