Why template local variables are not usable in templates when using *ngIf?

前端 未结 1 1947
轻奢々
轻奢々 2020-12-04 10:42

Part 1: \"#test\" is undefined when using *ngIf

When referencing an input that can be hidden/\"destroyed\" (because the *ngIf is used and some of the

1条回答
  •  抹茶落季
    2020-12-04 11:15

    As for a solution to the focus problem, you could create an attribute directive, 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: `

    My First Angular 2 App

    ` }) export class AppComponent { constructor() { console.clear(); } private isVisible = false; toggle() { this.isVisible = !this.isVisible; } }

    Plunker

    Update 1: Adding the solution for the re-focus feature:

    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: `

    My First Angular 2 App

    `, 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

    An alternative to using setTimeout() to reset the focus property to false would be to create an event/output property on the FocusDirective, and emit() an event when focus() is called. The AppComponent would then listen for that event and reset the focus property.

    Update 2: Here's an alternative/better way to add the re-focus feature, using ViewChild. We don't need to track the focus state this way, nor do we need an input property on the FocusMe directive.

    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: `

    My First Angular 2 App

    `, 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: Here's yet another alternative that does not require a directive, which still uses ViewChild, but we access the child via a local template variable rather than an attribute directive (thanks @alexpods for the tip):

    import {Component, ViewChild, NgZone} from 'angular2/core';
    
    @Component({
        selector: 'my-app',
        template: `

    Focus test

    `, }) 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 . // 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 updated the code in Update 3 to use NgZone so that we don't cause Angular's change detection algorithm to run after the setTimeout() finishes. (For more on change detection, see this answer).

    Update 5: I updated the code in the above plunker to use Renderer to make it web worker safe. Accessing focus() directly on nativeElement is discouraged.

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

    I learned a lot from this question.

    0 讨论(0)
提交回复
热议问题