16

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.

0

6 Answers 6

21

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();
    }
}
Sign up to request clarification or add additional context in comments.

5 Comments

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
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?
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.
I figured it out. you gave me half of what i needed. i'll post an answer to explain...
i got error @ViewChild() error: Expected 2 arguments, but got 1. solved by adding {static:false} ie: @ViewChild('swiper', {static: false}) swiper: ElementRef;
10

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.

Comments

5

Adding a cdkFocusInitial directive works for me:

<input
    matInput
    cdkFocusInitial />

1 Comment

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!!
3

if you have a input and a button

<input #myinput>
<button (click)="myinput.focus()">click</button>

2 Comments

Also correct, and simpler. In my actual scenario i also needed to do other things in the function that was called besides just focusing on the input.
Clean and simple :)
3

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

        }

    }

}

Comments

-2

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

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.