I have a new Angular 2 app with a list of input
boxes. When the user hits the return key, I add a new input
box immediately after the one they\'re
If I'm allowed to do so, I will take part of @Sasxa answer and modify it to make it more like what you're looking for.
A few changes
ngFor
so angular2 adds the new input instead of doing it myself. The main purpose is just to make angular2 to iterate over it.ViewChild
I'm going to use ViewChildren
that returns a QueryList which has a changes property. This property is an Observable and it returns the elements after they've changed.Since in ES5, we have no decorators we have to use the queries
property to use ViewChildren
Component
Component({
selector: 'cmp',
template : `
<div>
// We use a variable so we can query the items with it
<input #input (keydown)="add($event)" *ngFor="#input of inputs">
</div>
`,
queries : {
vc : new ng.core.ViewChildren('input')
}
})
Focus on the last element.
ngAfterViewInit: function() {
this.vc.changes.subscribe(elements => {
elements.last.nativeElement.focus();
});
}
Like I said before, ViewChildren returns a QueryList which contains changes
property. When we subscribe to it everytime it changes it will return the list of elements. The list elements
contains a last
property (among others) that in this case returns the last element, we use nativeElement
on it and finally focus()
Add input elements This is for pure convenince, the inputs array has no real purpose more than redraw the ngFor
.
add: function(key) {
if(key.which == 13) {
// See plnkr for "this.inputs" usage
this.inputs.push(this.inputs.length+1);
}
}
We push a dummy item on the array so it redraws.
Example using ES5 : http://plnkr.co/edit/DvtkfiTjmACVhn5tHGex
Example using ES6/TS : http://plnkr.co/edit/93TgbzfPCTxwvgQru2d0?p=preview
Time has passed, things have been clarified and there are always best practices to learn/teach. I've simplified this answer by changing a few things
@ViewChildren
and subscribing to it I made a Directive that will be instatiated everytime a new input is createdRenderer
to make it WebWorker safe. The original answer accesses focus()
directly on the nativeElement
which is discouraged.keydown.enter
which simplifies the key down event, I don't have to check which
value.To the point. The component looks like (simplified, full code on the plnkrs below)
@Component({
template: `<input (keydown.enter)="add()" *ngFor="#input of inputs">`,
})
add() {
this.inputs.push(this.inputs.length+1);
}
And the directive
@Directive({
selector : 'input'
})
class MyInput {
constructor(public renderer: Renderer, public elementRef: ElementRef) {}
ngOnInit() {
this.renderer.invokeElementMethod(
this.elementRef.nativeElement, 'focus', []);
}
}
As you can see I'm calling invokeElementMethod
to trigger focus
on the element instead of accessing it directly.
This version is much cleaner and safer than the original one.
plnkrs updated to beta 12
Example using ES5 : http://plnkr.co/edit/EpoJvff8KtwXRnXZJ4Rr
Example using ES6/TS : http://plnkr.co/edit/em18uMUxD84Z3CK53RRe
invokeElementMethod
is deprecated. Use Renderer2 instead of Renderer.
Give your element an id, and you can use selectRootElement:
this.renderer2.selectRootElement('#myInput').focus();
Take a look at ViewChild, here's a example. This might be what you're looking for:
import {Component, ViewChild} from 'angular2/core'
@Component({
selector: 'my-app',
providers: [],
template: `
<div>
<input #name>
</div>
`,
directives: []
})
export class App {
@ViewChild('name') vc: ElementRef;
ngAfterViewInit() {
this.vc.nativeElement.focus();
}
}
In order to prioritize which element will get focus when initializing several directives in same cycle, use:
Directive:
@Directive({
selector: '[focusOnInit]'
})
export class FocusOnInitDirective implements OnInit, AfterViewInit {
@Input('focusOnInit') priority: number = 0;
static instances: FocusOnInitDirective[] = [];
constructor(public renderer: Renderer, public elementRef: ElementRef) {
}
ngOnInit(): void {
FocusOnInitDirective.instances.push(this)
}
ngAfterViewInit(): void {
setTimeout(() => {
FocusOnInitDirective.instances.splice(FocusOnInitDirective.instances.indexOf(this), 1);
});
if (FocusOnInitDirective.instances.every((i) => this.priority >= i.priority)) {
this.renderer.invokeElementMethod(
this.elementRef.nativeElement, 'focus', []);
}
}
}
Usage:
<input type="text" focusOnInit="9">
https://plnkr.co/edit/T9VDPIWrVSZ6MpXCdlXF
With inline angular code, focus after conditional paint:
<span *ngIf="editId==item.id">
<input #taskEditText type="text" [(ngModel)]="editTask" (keydown.enter)="save()" (keydown.esc)="saveCancel()"/>
<button (click)="save()">Save</button>
<button (click)="saveCancel()">Cancel</button>
{{taskEditText.focus()}}
</span>
You can implement a simple input text directive, so that whenever a new input is created, it will auto focus itself. The focus()
method is called inside of the ngAfterViewInit()
component lifecycle hook after the view is fully initialized.
@Directive({
selector: 'input[type=text]'
})
export class FocusInput implements AfterViewInit {
private firstTime: bool = true;
constructor(public elem: ElementRef) {
}
ngAfterViewInit() {
if (this.firstTime) {
this.elem.nativeElement.focus();
this.firstTime = false;
}
}
}
Use the FocusInput
directive in your component:
@Component({
selector: 'app',
directives: [FocusInput],
template: `<input type="text" (keyup.enter)="last ? addNewWord():0"
*ngFor="#word of words; #last = last" [value]="word.word"
#txt (input)="word.word = txt.value" />{{words |json}}`
})
export class AppComponent {
words: Word[] = [];
constructor() {
this.addNewWord();
}
addNewWord() {
this.words.push(new Word());
}
}
Note the following:
(keyup.enter)
event is used to detect when the <enter> key is pressedngFor
is used to repeat the input element for each word from the array of words
last
is a Boolean bound to a local variable which is true when the input is the last onelast ? addNewWord() : 0
. This ensures that a new input field is only added when the <enter> key is pressed from the last InputDemo Plnkr