2015-12-29 9 views
29

Part 1 „# # # test” nie jest zdefiniowany przy użyciu * ngIf

Po przedstawieniu wejście, które mogą być ukryte/„zniszczone” (bo * ngIf jest używany i niektóre elementy są niszczone), zmienna lokalna utworzona przez składnię hashtag # (#test w poniższym przykładzie) z angular2 nie działa, nawet jeśli element istnieje na stronie.Dlaczego angular2 szablon zmienne lokalne nie są używane w szablonach przy użyciu * ngIf

Kod było:

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

} 

wyświetla alert "nieokreślony", bo nic nie jest przekazywane do tej funkcji.

Czy istnieje rozwiązanie, które sprawi, że będzie działać? Moim celem jest skupienie się na elemencie, który zostanie utworzony.

Rozwiązanie podane przez Marka Rajcok: uczynić dyrektywę z afterViewInit który używa elementRef i nazywa .focus() na elemencie.

Zobacz ten plunker dla wersji roboczej Część 1: http://plnkr.co/edit/JmBBHMo1hXLe4jrbpVdv?p=preview

część 2, jak ponownie skupić tego elementu po początkowym tworzenia

Gdy ten problem „Focus po stworzeniu” jest stała, I Potrzebuję sposobu na ponowne skupienie się() komponentu, jak w "test.focus()" (gdzie #test jest lokalną nazwą zmiennej dla danych wejściowych, ale nie można tego użyć tak jak wcześniej to pokazano).

Wiele rozwiązań podane przez Marka Rajcok

+0

Zobacz także https://github.com/angular/angular/issues/6179 –

+0

Wypełniłem ten problem na repozytorium kątowym, ponieważ jest to błąd, jeśli weźmiemy pod uwagę faktyczną dokumentację stwierdzającą: "Możemy odwoływać się do lokalnej zmiennej szablonu ten sam element, na elemencie rodzeństwa lub na jego elementach potomnych. ". –

Odpowiedz

45

chodzi o rozwiązanie problemu ostrości można utworzyć dyrektywę atrybutu 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

Update 1: Dodanie rozwiązanie dla funkcji re-focus:

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

Alternatywą korzystania setTimeout() zresetować właściwość ostrości false byłoby utworzyć właściwość zdarzenia/wyjścia na FocusDirective i emit() zdarzenie, gdy jest wywoływana focus(). AppComponent będzie nasłuchiwać tego zdarzenia i zresetować właściwość focus.

Aktualizacja 2: Oto alternatywny/lepszy sposób dodania funkcji ponownego ogniskowania za pomocą narzędzia ViewChild. Nie musimy śledzić stanu skupienia w ten sposób, ani nie potrzebujemy właściwości wejściowej w dyrektywie 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

Update 3: Tu jest jeszcze inna alternatywa, która nie wymaga dyrektywa, która nadal wykorzystuje ViewChild, ale mamy dostęp do dziecka poprzez lokalną zmienną szablonu, a nie dyrektywy atrybutu (dzięki @alexpods dla 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

Update 4: I uaktualniony kod Update 3 do korzystania NgZone tak, że nie powodują algorytm wykrywania zmian kątowych, aby uruchomić po setTimeout() wykończeniach. (Aby uzyskać więcej informacji na temat wykrywania zmian, zobacz this answer).

Aktualizacja 5: Zaktualizowałem kod w powyższym plunkerze, aby użyć Renderera, aby uczynić go bezpiecznym w Internecie. Uzyskanie dostępu do focus() bezpośrednio pod numerem nativeElement jest odradzane.

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

Wiele się nauczyłem od tego pytania.

+0

Mam inny problem, który pojawia się po tym, jak mogę przywrócić to skupienie, po inicjalizacji, w funkcji kodu AppComponent, który zostałby wyzwolony? Zaktualizuję plunkera, gdy tylko będzie dostępny. Czy nie ma innego rozwiązania niż tworzenie niestandardowego zdarzenia wewnątrz dyrektywy, a następnie powiązanie go z komponentem, więc gdy to zdarzenie zostanie wyzwolone, fokus wróci na dane wejściowe? –

+1

@MykaEyl, zobacz moją zaktualizowaną odpowiedź. –

+1

Świetnie! Podoba mi się użycie @ViewChild ('input1'), ponieważ czułem, że byłoby naprawdę źle, gdybym musiał użyć dyrektywy niestandardowej tylko do wyboru. Dziękuję Ci! –

Powiązane problemy