33

I need to be able to switch focus to an input element when some event occurs. How do I do that in Angular 2?

For example:

<input (keyUp)="processKeyUp($event)"/>
<input (focusme)="alert('i am focused')"/>

I want to focus the 2nd input box when a certain key is pressed in the first. I think I need to use a custom event (focusme in the snippet), but I don't know where or how to declare it, and whether to use a @Directive annotation for it, or include its definition in a component somehow. In short, I am stumped.

UPDATE

Forgot to mention, I know I can do that by using local variables in the html, but I want to be able to do it from the component, and I want to be able to do complex logic when firing the focusme event so that controls listening to it can determine if it is meant for them or not. Thanks!

isherwood
  • 58,414
  • 16
  • 114
  • 157
Aviad P.
  • 32,036
  • 14
  • 103
  • 124
  • You can't just extend the input element with a custom event, you need to create your own custom element that wraps an input control that can fire the focusme event, i.e. – Ajden Towfeek Jul 27 '15 at 13:43
  • Ok, how do I set focus to the input element inside my custom component once I get the event? Can you provide an example in an answer? – Aviad P. Jul 27 '15 at 15:47
  • 3
    See http://stackoverflow.com/a/34503163/215945 for a couple of different ways/implementations for setting focus on another element. – Mark Rajcok Dec 30 '15 at 22:07

4 Answers4

30

You can do this just passing the second input as a variable to the first one

For example

HTML

<!-- We pass focusable as a parameter to the processKeyUp function -->
<input (keyup)="processKeyUp($event, focusable)"/>

<!-- With #focusable we are creating a variable which references to the input -->
<input #focusable /> 

Later in your js/ts

@Component({
  selector: 'plunker-app'
})
@View({
  templateUrl: 'main.html'
})
class PlunkerApp {

  constructor() {
  }

  processKeyUp(e, el) {
    if(e.keyCode == 65) { // press A
      el.focus();
    }
  }
}

el is the raw element, so you can use pure javascript on it.

Here's a plnkr so you can see it working.

I hope it helps.

Rayjax
  • 7,494
  • 11
  • 56
  • 82
Eric Martinez
  • 31,277
  • 9
  • 92
  • 91
  • Thanks for the answer, but I need for the focus to pass to an input element in another component, so one component fires the event, another component acts on the event and sets the focus to an input element within it. – Aviad P. Jul 30 '15 at 16:53
  • You can read this thread meanwhile https://github.com/angular/angular/issues/3186 – Eric Martinez Jul 30 '15 at 17:48
  • 1
    @AviadP., if you want inter-component communication, and your components are not "related" (i.e., no ancestor or descendant pattern) you could put an EventEmitter inside a service, inject the service into both components, have one component trigger the event, and have the other component (the one you want to focus) subscribe to it. Here's [an example](http://stackoverflow.com/a/34402436/215945). – Mark Rajcok Dec 30 '15 at 21:36
  • For some reason this answer didn't work for me (using rc.3 in my code). I also needed to focus the element from within the component's code, and not just from an event triggered from the DOM, so [the accepted answer from here](http://stackoverflow.com/questions/34502768/why-angular2-template-local-variables-are-not-usable-in-templates-when-using-ng/34503163#34503163) was just what I needed. Pay attention to the updates. They have useful tips. – Lucio Mollinedo Jun 30 '16 at 04:06
  • Works marvelous for a validation in an input (reactive form) and cleaveJS – Tabares Mar 23 '17 at 22:19
19

For manipulation on DOM elements always try to use Directives. In this case you are able to write simple directive.

For accessing DOM from directive we can inject reference of our host DOM element by the ElementRef in directive constructor.

constructor(@Inject(ElementRef) private element: ElementRef) {}

For change detection of binded value we can use ngOnChanges livecycle method.

protected ngOnChanges() {}

All other stuff is simple.

Simple solution

// Simple 'focus' Directive
import {Directive, Input, ElementRef} from 'angular2/core';
@Directive({
    selector: '[focus]'
})
class FocusDirective {
    @Input()
    focus:boolean;
    constructor(@Inject(ElementRef) private element: ElementRef) {}
    protected ngOnChanges() {
        this.element.nativeElement.focus();
    }
}

// Usage
@Component({
    selector : 'app',
    template : `
        <input [focus]="inputFocused" type="text">
        <button (click)="moveFocus()">Move Focus</button>
    `,
    directives: [FocusDirective]
})
export class App {
    private inputFocused = false;
    moveFocus() {
        this.inputFocused = true;
        // we need this because nothing will 
        // happens on next method call, 
        // ngOnChanges in directive is only called if value is changed,
        // so we have to reset this value in async way,
        // this is ugly but works
        setTimeout(() => {this.inputFocused = false});
    }
}

Solution with EventEmitter

To solve the problem with setTimeout(() => {this.inputFocused = false}); We can bind our directive for events source - EventEmitter, or to Observable. Below is an example of EventEmitter usage.

// Directive
import {Directive, EventEmitter, Input, ElementRef} from 'angular2/core';

@Directive({
    selector: '[focus]'
})
class FocusDirective {
    private focusEmitterSubscription;   
    // Now we expect EventEmitter as binded value
    @Input('focus')
    set focus(focusEmitter: EventEmitter) {
        if(this.focusEmitterSubscription) {
            this.focusEmitterSubscription.unsubscribe();
        }
        this.focusEmitterSubscription = focusEmitter.subscribe(
            (()=> this.element.nativeElement.focus()).bind(this))
    }    
    constructor(@Inject(ElementRef) private element: ElementRef) {}
}

// Usage
@Component({
    selector : 'app',
    template : `
        <input [focus]="inputFocused" type="text">
        <button (click)="moveFocus()">Move Focus</button>
    `,
    directives: [FocusDirective]
})
class App {
    private inputFocused = new EventEmitter();
    moveFocus() {
        this.inputFocused.emit(null);    
    }
}

Both solutions solves your problem, but second has a little better performance and looks nicer.

ShellZero
  • 4,415
  • 12
  • 38
  • 56
majo
  • 381
  • 3
  • 8
  • 4
    This answer looks strikingly similar to [my recent answer](http://stackoverflow.com/a/34503163/215945) -- a little too similar, I would say. I suggest you take a look at my answer again. Update 2 and 3 provide additional implementations (which I like better). – Mark Rajcok Dec 30 '15 at 21:15
  • 2
    Sorry Mark I did not noticed your answer in comment. First solution is exactly the same as in your entry. I also like your solution with @ViewChild, but in some cases (eg. nesting unknown components) directive/Event emitter solution is more flexible. Depends on the use case I see the room for each solution. – majo Jan 02 '16 at 17:49
  • 1
    Worth noting that any Directive that subscribes to observables should be a good citizen and unsubscribe in the `ngOnDestroy` lifecycle hook. – David M. Aug 03 '16 at 14:05
10

Actually you don't need to write any TS code for that (I'm using one of the other answer's example):

<input (keyup.enter)="focusable.focus()"/>
<input #focusable /> 
ahu
  • 117
  • 1
  • 3
5

Set focus - Angular 2/5

<input type="text" [(ngModel)]="term2" #inputBox>

import { Component, ViewChild, ElementRef } from '@angular/core';

@Component({
  selector: 'app-general',
  templateUrl: './general.component.html',
  styleUrls: ['./general.component.scss']
})
export class GeneralComponent {

  @ViewChild("inputBox") _el: ElementRef;

  setFocus() {
    this._el.nativeElement.focus();
  }
}

For setting focus on load

ngAfterViewInit() {
    this._el.nativeElement.focus();
  }

You can update this logic accordingly for key press.

More Info

Prashobh
  • 9,216
  • 15
  • 61
  • 91