Почему angular2 шаблонные локальные переменные не используются в шаблонах при использовании * ngIf

Часть 1 "#test" - undefined при использовании * ngIf

При ссылке на ввод, который может быть скрыт/ "уничтожен" (поскольку используется * ngIf и некоторые из элементов уничтожены), локальная переменная, созданная синтаксисом hashtag # (#test в приведенном ниже примере) angular2 не работает, даже если элемент существует на странице.

Код был:

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

}

В предупреждении отображается "undefined", потому что ничего не передается этой функции.

Есть ли решение, чтобы заставить его работать? Моя цель - сфокусировать элемент, который будет создан.

Решение, данное Марк Райкок: создать директиву с afterViewInit, который использует elementRef и вызывает .focus() для элемента.

См. этот плункер для рабочей версии части 1: http://plnkr.co/edit/JmBBHMo1hXLe4jrbpVdv?p=preview

Часть 2, как переориентировать этот элемент после первоначального создания

Как только проблема "фокус после создания" исправлена, мне нужен способ перефокусировки() компонента, например, в "test.focus()" (где #test - это локальное имя переменной для ввода, но не может использоваться так, как я продемонстрировал ранее).

Несколько решений, заданных Марком Райкоком

Ответ 1

Что касается решения проблемы фокуса, вы можете создать директиву атрибута 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

Обновление 1: добавление решения для функции повторного фокуса:

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

Альтернативой использованию setTimeout() to reset свойства focus для false будет создание свойства event/output в FocusDirective и emit() событие, когда вызывается focus(). Затем AppComponent прослушивает это событие и reset свойство focus.

Обновление 2. Здесь альтернативный/лучший способ добавить функцию повторного фокуса с помощью ViewChild. Нам не нужно отслеживать состояние фокусировки таким образом, а также не нужно вводить свойство в директиве 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

Обновление 3. Здесь еще одна альтернатива, которая не требует директивы, которая все еще использует ViewChild, но мы обращаемся к ней через локальную переменную шаблона, а не директиву атрибута (спасибо @alexpods для отзыв):

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

Обновление 4. Я обновил код в Update 3, чтобы использовать NgZone, чтобы мы не вызывали алгоритм обнаружения изменений Angular, который запускается после завершения setTimeout(). (Подробнее об обнаружении изменений см. этот ответ).

Обновление 5. Я обновил код в вышеуказанном плункере, чтобы использовать Renderer, чтобы сделать его веб-рабочим безопасным. Доступ к focus() непосредственно на nativeElement не рекомендуется.

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

Я многое узнал из этого вопроса.