How can I autofocus input element? Similar to this question, but not with AngularDart. Something like this:
You could assign the input element a template reference variable #myInput
:
<input type="text" [(ngModel)]="title" #myInput />
Let your component implement AfterViewInit
, grab the reference of the input element with the ViewChild
annotation and focus your element in the ngAfterViewInit
hook:
export class MyComponent implements AfterViewInit {
@ViewChild("myInput") private _inputElement: ElementRef;
[...]
ngAfterViewInit(): void {
this._inputElement.nativeElement.focus();
}
}
<input type="text" [(ngModel)]="title" #myInput />
{{ myInput.focus() }}
just add {{ myInput.focus() }} right after input inside template
My solution :
<input type="text" id="searchInput">
// put focus on element with id 'searchInput', try every 100ms and retry 30 times
this.focus('searchInput',30,100);
focus( idElement:string, maxNbRetries:number, intervalMs:number){
let nbRetries = 0;
let elt = null;
const stop$ = new Subject<boolean>();
const source = interval(intervalMs);
const source$ = source.pipe(
tap(item=>{
elt = document.getElementById(idElement);
nbRetries++;
if(nbRetries>maxNbRetries){
stop$.next(true);
console.log(`unable to put the focus on the element !`)
}
}),
filter(item=>elt !=null),
map(item=>{
elt.focus();
stop$.next(true);
}),
takeUntil(stop$)
).subscribe();
}
Focus does not work well with angular lifecycle. To force it on a field, i run an observable that emits every 'intervalMs'. If the element has been rendered, i can find it by its id. After that, i can set the focus. If nbRetries > maxNbRetries or element id is found, i stop the observable with the takeUntil operator.
This is my current code:
import { Directive, ElementRef, Input } from "@angular/core";
@Directive({
selector: "[autofocus]"
})
export class AutofocusDirective
{
private focus = true;
constructor(private el: ElementRef)
{
}
ngOnInit()
{
if (this.focus)
{
//Otherwise Angular throws error: Expression has changed after it was checked.
window.setTimeout(() =>
{
this.el.nativeElement.focus(); //For SSR (server side rendering) this is not safe. Use: https://github.com/angular/angular/issues/15008#issuecomment-285141070)
});
}
}
@Input() set autofocus(condition: boolean)
{
this.focus = condition !== false;
}
}
use case:
[autofocus] //will focus
[autofocus]="true" //will focus
[autofocus]="false" //will not focus
Outdated code (old answer, just in case):
I ended up with this code:
import {Directive, ElementRef, Renderer} from '@angular/core';
@Directive({
selector: '[autofocus]'
})
export class Autofocus
{
constructor(private el: ElementRef, private renderer: Renderer)
{
}
ngOnInit()
{
}
ngAfterViewInit()
{
this.renderer.invokeElementMethod(this.el.nativeElement, 'focus', []);
}
}
If I put code in ngOnViewInit
it does not work. Code also use best practices, since calling focus on element directly is not recommended.
Edited (conditional autofocus):
A few days ago I needed conditional auto focus, because I hide first autofocus element and I want to focus another, but only when first is not visible, and I ended with this code:
import { Directive, ElementRef, Renderer, Input } from '@angular/core';
@Directive({
selector: '[autofocus]'
})
export class AutofocusDirective
{
private _autofocus;
constructor(private el: ElementRef, private renderer: Renderer)
{
}
ngOnInit()
{
}
ngAfterViewInit()
{
if (this._autofocus || typeof this._autofocus === "undefined")
this.renderer.invokeElementMethod(this.el.nativeElement, 'focus', []);
}
@Input() set autofocus(condition: boolean)
{
this._autofocus = condition != false;
}
}
Edited2:
Renderer.invokeElementMethod is deprecated and new Renderer2 does not support it.
So we are back to native focus (which doesn't work outside DOM - SSR for example!).
import { Directive, ElementRef, Input } from '@angular/core';
@Directive({
selector: '[autofocus]'
})
export class AutofocusDirective
{
private _autofocus;
constructor(private el: ElementRef)
{
}
ngOnInit()
{
if (this._autofocus || typeof this._autofocus === "undefined")
this.el.nativeElement.focus(); //For SSR (server side rendering) this is not safe. Use: https://github.com/angular/angular/issues/15008#issuecomment-285141070)
}
@Input() set autofocus(condition: boolean)
{
this._autofocus = condition != false;
}
}
use case:
[autofocus] //will focus
[autofocus]="true" //will focus
[autofocus]="false" //will not focus
If you don't need true/false functionality, but want autofocus set always, there's a shorter implementation to Makla's solution:
autofocus.directive.ts:
import { Directive, ElementRef, Input, OnInit } from '@angular/core';
@Directive({
selector: '[autofocus]'
})
export class AutofocusDirective implements AfterViewInit {
constructor(private el: ElementRef) {
}
ngAfterViewInit() {
// Otherwise Angular throws error: Expression has changed after it was checked.
window.setTimeout(() => {
this.el.nativeElement.focus();
});
}
}
use case:
<input autofocus> //will focus
Using AfterViewInit instead of OnInit makes the cursor placed after the content inside input field, should it get populated.
Remember to declare and export autofocus directive in your module!
autofocus
is a native html feature that should work at least for page initialization. However it fails to work with many angular scenarios, especially with *ngIf
.
You can make a really simple custom directive to get desired behaviour.
import { Directive, OnInit, ElementRef } from '@angular/core';
@Directive({
selector: '[myAutofocus]'
})
export class AutofocusDirective implements OnInit {
constructor(private elementRef: ElementRef) { };
ngOnInit(): void {
this.elementRef.nativeElement.focus();
}
}
The above directive works for my use cases.
How to use
<input *ngIf="someCondition" myAutofocus />
EDIT: There seems to be usecases where it is to early to call focus in the OnInit
lifecycle method. If that is the case, change to OnAfterViewInit
instead.