14

This should be simple but I can't find a solution, the closest answer i can find on SO is this: How do I programmatically set focus to dynamically created FormControl in Angular2

However this is way more convoluted that my requirements. Mine are simple. I have an input in the template like so:

<div *ngIf="someValue">    
    <input class="form-control" #swiper formControlName="swipe" placeholder="Swipe">
</div>

(Note, it's in an ngx-boostrap form-control group but I'm pretty sure this should not matter)

And I have a button that when clicked calls the following function:

onClick() {
    this.renderer.invokeElementMethod(this.swiper.nativeElement, 'focus')
}

The goal is that when the button is clicked, my input element gets focus. here is the rest of my component (the relevant parts):

import { Component, ElementRef, ViewChildren, Renderer } from '@angular/core';
import { MyService } from wherever/my/service/is

export class MyClass {
    @ViewChildren('swiper') input: ElementRef;
    someValue: any = false;

    constructor(private renderer: Renderer, service: MyService) {
       this.service.getSomeData().subscribe(data => {
          this.someValue = true;
       }
    }

    onClick() {
        this.renderer.invokeElementMethod(this.swiper.nativeElement, 'focus');
    }
}

The error I get is:

ERROR TypeError: Cannot read property 'focus' of undefined
at RendererAdapter.invokeElementMethod (core.js:12032)
etc...

Any ideas?

(Angular 5 btw)

Note, I edited the code to more accurately reflect what is actually going on, I think there may be some synchronization issue.

Dallas Caley
  • 5,367
  • 6
  • 40
  • 69

6 Answers6

18

You are using ViewChildren which returns a QueryList You need to use ViewChild and your code should work.

export class MyClass {
    @ViewChild('swiper') swiper: ElementRef;

    constructor(private renderer: Renderer) {}

    onClick() {
        this.swiper.nativeElement.focus();
    }
}
Teddy Sterne
  • 13,774
  • 2
  • 46
  • 51
  • 1
    so I changed it to ViewChild and it still doesn't work. Also put in a console log right at the beginning of my onClick function to see what the value of 'this.swiper' was and saw that it was undefined so i think my ViewChild is not set up correctly. not sure – Dallas Caley Apr 19 '18 at 18:26
  • note, there is more to this template but i didn't think it was relevant. now i do. One of the issues is possibly that i have a conditional *ngIf on one of the parent elements so the whole thing isn't displayed till i get some data from the back end. could this be the issue? – Dallas Caley Apr 19 '18 at 18:27
  • Yes that is most likely the reason that it is undefined. I also noticed that you named the swiper `input` but reference `swiper` in the `OnClick` method. Update that and see if it works. – Teddy Sterne Apr 19 '18 at 18:29
  • I figured it out. you gave me half of what i needed. i'll post an answer to explain... – Dallas Caley Apr 19 '18 at 18:38
  • 2
    i got error @ViewChild() error: Expected 2 arguments, but got 1. solved by adding {static:false} ie: `@ViewChild('swiper', {static: false}) swiper: ElementRef;` – suhailvs Jan 19 '21 at 10:52
9

This is how I solved it:

Template:

<input class="form-control" #swiper formControlName="swipe" placeholder="Swipe">

Component:

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

export class MyClass {
    @ViewChild('swiper') swiper: ElementRef;

    constructor() {}

    onClick() {
        this.swiper.nativeElement.focus();
    }
}

The key being two things: first using the ViewChild not ViewChildren (thanks @Teddy Stern for that one) and second there is no need to use the Renderer at all, just directly use the nativeElement's focus function.

Dallas Caley
  • 5,367
  • 6
  • 40
  • 69
3

if you have a input and a button

<input #myinput>
<button (click)="myinput.focus()">click</button>
Eliseo
  • 50,109
  • 4
  • 29
  • 67
2

If anyone is interested, to focus on an element which is generated dynamically, #name is not possible to keep in the field as attribute. I found a work around using native JS.

<textarea [(ngModel)]="update.newComment"
          [name]="'update-' + index" 
          placeholder="comment"></textarea>

Now, a trigger to focus on above textarea could be like

<span (click)="focusOn(index);">Comment</span>

Click event will be handled in ts as

focusOn(fieldName) {
    const commmentBoxes = document.querySelectorAll(`textarea[ng-reflect-name="update-${fieldName}"]`);
    for( let i in commmentBoxes) {
        const element: any = commmentBoxes[i];
        const attributs = element.attributes;
        for(let j in attributs) {
            const attribute = attributs[j];
            if (attribute.nodeName === 'ng-reflect-name' && attribute.nodeValue === `update-${fieldName}`) {
                element.focus();
            }

        }

    }

}
Praveen Pandey
  • 1,675
  • 3
  • 13
  • 17
1

Adding a cdkFocusInitial directive works for me:

<input
    matInput
    cdkFocusInitial />
BuZZ-dEE
  • 6,075
  • 12
  • 66
  • 96
sai amar
  • 1,758
  • 1
  • 10
  • 9
  • I spent about 3 hours scouring stackoverflow, google, blogs, etc., tried at least 10 approaches, and this was the only answer that worked. Thank you!! – TimH Sep 09 '20 at 15:10
-2

And sometimes you can use attribute autofocus in html...

ge1678
  • 65
  • 3