Tom O'Brien
Tom O'Brien

Reputation: 1831

Angular Directive not setting focus properly on recently disabled input

I have an input field that I want to disable when a user presses the 'enter' key. It will then try and fetch a user details. When it has fetched the details or there has been an error, I want to enable the input field again and set the focus on that field.

I am using this directive to set the focus on the input field:

import { Directive, OnChanges, ElementRef, Input } from '@angular/core';

@Directive({
  selector: '[myFocus]'
})
export class FocusDirective implements OnChanges {

  @Input('myFocus') isFocused: boolean;
  constructor(private hostElement: ElementRef) { }

  ngOnChanges() {
    if(this.isFocused) {
        this.hostElement.nativeElement.focus();
    }
  }
}

It is implemented in the html like so:

<input type="text" class="form-control" [(ngModel)]="text"
    [myFocus]="isFocused" [disabled]="disableInput" 
    (keydown)="podcastsKeyDown($event)">

The podcastKeyDown() function looks like this:

podcastsKeyDown(event) {
    if(event.keyCode == 13) {
        this.disableInput = true;
        this.getUser(this.text).subscribe(account => {
            this.disableInput = false;
            this.isFocused = true;
        });
    }
}

When the user account has successfully returned i set the input field to be enabled, which works correctly. However the focus is not returned to this input. I have tried placing a timeout around re-setting the focus just in case there is some timing issue, however this did not work either. The code looked like this:

setTimeout(() => {
    this.isPodcastNamesFocused = true;
}, 100);

Any ideas what is happening here and how to fix it?

Thanks in Advance.

EDIT 1: To be precise - it actually works the first time when using the timeout, but for subsequent user entries it does not...

Upvotes: 1

Views: 1876

Answers (3)

Rahul
Rahul

Reputation: 2128

ngOnChanges will work only if there is a pure change means if you have declared this.isFocused as true and you are trying to set it as true again ngOnChanges won't trigger

Try something like this

@Input('myFocus')
  set isFocused(value: boolean) {
    if (value) {
      this.hostElement.nativeElement.focus();
    }
  };

or change your directive constructor like this constructor(@Host() @Self() private hostElement: ElementRef) { }

Just got an idea - hope it works

Try to pass the reference variable as an @Input() and read it

<input type="text" class="form-control" [(ngModel)]="text" #inputType [element]="inputType"
    [myFocus]="isFocused" [disabled]="disableInput" 
    (keydown)="podcastsKeyDown($event)">

@Input('element') elementType : ElementRef;

Try to focus the elementType now

Hope it works - Happy coding !!

Upvotes: 0

Tom O&#39;Brien
Tom O&#39;Brien

Reputation: 1831

It seems this was fixed by using the setTimeout() function AND remembering to set the focus to false while the user was being fetched. This must have been causing the focus directive to get confused.

podcastsKeyDown(event) {
    if(event.keyCode == 13) {

        //This is the important line that was missing
        this.isFocused = false;

        this.disableInput = true;
        this.getUser(this.text).subscribe(account => {
            this.disableInput = false;
            setTimeout(() => {
                this.isFocused = true;
            });
        });
    }
}

Upvotes: 0

I suggest the following solution. I tested this code locally. And it works every time for me. Most of the code here is for demonstration.

Directive:

import {Directive, ElementRef, Input} from '@angular/core';

@Directive({
  selector: '[myFocus]'
})
export class MyFocusDirective {
  // as for me I prefer using setters for properties that have non-trivial logic when being set. They are simple, predictable and robust.
  @Input('myFocus')
  set isFocused(val: boolean) {
    this._isFocused = val;
    if (this._isFocused) {
      this.hostElement.nativeElement.focus();
    }
  }
  get isFocused(): boolean {
    return this._isFocused;
  }

  _isFocused: boolean;
  constructor(private hostElement: ElementRef) { }
}

Component:

import { Component } from '@angular/core';
import {Observable, of, timer} from 'rxjs';
import {map, take} from 'rxjs/operators';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  text = 'app';
  isFocused: boolean = true;
  isDisabled: boolean = false;
  userData: {name: string, age: number} = null;

  podcastsKeyDown(event) {
    if (event.keyCode === 13) {
      this.isFocused = false;
      this.isDisabled = true;
      // take(1) not to have subscriptions hanging around
      this.getUser(this.text).pipe(take(1)).subscribe(account => {
        this.isDisabled = false;
        this.userData = account;
        // timeout first makes DOM to render setting isDisabled to false
        // when field is enabled we can make it focused
        setTimeout(() => {
          this.isFocused = true;
        }); 
      });
    }
  }

  // some mock data
  private getUser(name: string): Observable<{name: string, age: number}> {
    return timer(2000).pipe(map(() => {
      return {name, age: Math.round(Math.random() * 30 + 10)};
    }));
  }
}
<input type="text" class="form-control" [(ngModel)]="text"
       [myFocus]="isFocused" [disabled]="isDisabled"
       (keydown)="podcastsKeyDown($event)">
<p>{{account | json}}</p>

Upvotes: 1

Related Questions