¿Por qué las variables locales de la plantilla angular2 no se pueden utilizar en las plantillas cuando se utiliza *ngIf?


La parte 1 "# test " no está definida cuando se usa * ngIf

Cuando se hace referencia a una entrada que se puede ocultar/"destruir" (porque se usa el *ngIf y se destruyen algunos de los elementos), la variable local creada por la sintaxis del hashtag # (#test en el ejemplo a continuación) de angular2 no funciona, incluso cuando el elemento existe en la página.

El código era:

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

}

La alerta muestra "indefinido", porque no se pasa nada a esa función.

Hay una solución para hacer ¿funciona? Mi objetivo es enfocar un elemento que se va a crear.

Solución dada por Mark Rajcok: cree una directiva con un AfterViewInit que use ElementRef y calls .focus() en el elemento.

Vea este émbolo para una versión de trabajo de la parte 1: http://plnkr.co/edit/JmBBHMo1hXLe4jrbpVdv?p=preview

Parte 2 cómo volver a enfocar ese elemento después de la creación inicial

Una vez solucionado este problema de "enfoque después de la creación", necesito una forma de re-focus () un componente, como en "test.focus () " (donde #test es el nombre de la variable local para la entrada, pero no se puede usar así como demostré antes).

Múltiples soluciones dadas por Mark Rajcok

 31
Author: Lynx 242, 2015-12-29

1 answers

En cuanto a una solución al problema de focus, puede crear una directiva de atributos, 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

Actualización 1 : Adición de la solución para la función de reenfoque:

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

Una alternativa a usar setTimeout() para restablecer la propiedad focus a false sería crear una propiedad event/output en la FocusDirective, y emit() un evento cuando se llame a focus(). El AppComponent lo haría a continuación, escuche ese evento y restablezca la propiedad focus.

Actualización 2: Aquí hay una forma alternativa/mejor de agregar la función de reenfoque, usando ViewChild. No necesitamos rastrear el estado de enfoque de esta manera, ni necesitamos una propiedad de entrada en la directiva 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

Actualización 3: Aquí hay otra alternativa que no requiere una directiva, que todavía usa ViewChild, pero accedemos al hijo a través de un local variable de plantilla en lugar de una directiva de atributos (gracias @ alexpods por la sugerencia):

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

Actualización 4 : Actualizé el código en la actualización 3 para usar NgZone para que no causemos que el algoritmo de detección de cambios de Angular se ejecute después de que finalice setTimeout(). (Para más información sobre la detección de cambios, vea esta respuesta ).

Actualización 5: Actualizé el código en el plunker anterior para usar Renderer para convertirlo en web worker seguro. Se desaconseja acceder a focus() directamente en nativeElement.

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

Aprendí mucho de esta pregunta.

 46
Author: Mark Rajcok,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:55:07