Фокус на вновь добавленном элементе ввода
У меня есть новое приложение Angular 2 со списком полей input
. Когда пользователь нажимает клавишу возврата, я добавляю новое поле input
сразу после того, которое они редактируют в данный момент. Вернее, я (асинхронно) добавляю новую запись в массив в модели, что заставляет Angular 2 автоматически генерировать новое поле input
в ближайшем будущем.
Как сделать так, чтобы фокус input
автоматически изменялся на добавленный элемент?
РЕДАКТИРОВАТЬ 1:
В качестве альтернативы я получаю ссылку на объект модели, который вызывает генерацию DOM. Из кода компонента есть ли способ поиска элемента DOM, представляющего конкретный объект модели?
РЕДАКТИРОВАТЬ 2:
Вот мой код, чтобы просто сделать эту работу. Надеюсь, это достаточно оскорбительно для некоторых разработчиков Angular 2, чтобы поощрить ответ :-)
app.WordComponent = ng.core
.Component({
selector: 'word-editor',
template:'<input type="text" [value]="word.word" (input)="word.update($event.target.value)" (keydown)="keydown($event)"/>',
styles:[
''
],
properties:[
'list:list',
'word:word'
]
})
.Class({
constructor:[
function() {
}
],
keydown:function(e) {
if(e.which == 13) {
var ul = e.target.parentNode.parentNode.parentNode;
var childCount = ul.childNodes.length;
this.list.addWord("").then(function(word) {
var interval = setInterval(function() {
if(childCount < ul.childNodes.length) {
ul.lastChild.querySelector('input').focus();
clearInterval(interval);
}
}, 1);
});
}
}
});
Ответы
Ответ 1
Если мне будет позволено это сделать, я приму участие в ответе @Sasxa и внесу в него изменения, чтобы сделать его более похожим на то, что вы ищете.
Несколько изменений
- Я буду использовать
ngFor
поэтому angular2 добавляет новый ввод вместо того, чтобы делать это самому. Основная цель - сделать так, чтобы angular2 итерировал по нему. - Вместо
ViewChild
я собираюсь использовать ViewChildren
который возвращает QueryList, у которого есть свойство changes
. Это свойство является Наблюдаемым и возвращает элементы после их изменения.
Поскольку в ES5 у нас нет декораторов, мы должны использовать свойство queries
чтобы использовать ViewChildren
Составная часть
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')
}
})
Сосредоточиться на последнем элементе.
ngAfterViewInit: function() {
this.vc.changes.subscribe(elements => {
elements.last.nativeElement.focus();
});
}
Как я уже говорил ранее, ViewChildren возвращает QueryList, который содержит свойство changes
. Когда мы подписываемся на него каждый раз, когда он меняется, он возвращает список элементов. elements
списка содержат last
свойство (среди прочего), которое в этом случае возвращает последний элемент, мы используем его nativeElement
и, наконец, focus()
Добавление элементов ввода Это просто для удобства, массив входов не имеет реальной цели, кроме перерисовки ngFor
.
add: function(key) {
if(key.which == 13) {
// See plnkr for "this.inputs" usage
this.inputs.push(this.inputs.length+1);
}
}
Мы помещаем фиктивный элемент в массив, чтобы он перерисовывался.
Пример использования ES5: http://plnkr.co/edit/DvtkfiTjmACVhn5tHGex
Пример использования ES6/TS: http://plnkr.co/edit/93TgbzfPCTxwvgQru2d0?p=preview
Обновление 29/03/2016
Время прошло, все прояснилось, и всегда есть лучшие практики для изучения/преподавания. Я упростил этот ответ, изменив несколько вещей
- Вместо использования
@ViewChildren
и подписки на него я создал директиву, которая будет утверждаться каждый раз при создании нового ввода - Я использую
Renderer
чтобы сделать его безопасным. Оригинальный ответ обращается к focus()
непосредственно к nativeElement
который не рекомендуется. - Теперь я слушаю
keydown.enter
который упрощает событие нажатия клавиш, мне не нужно проверять, which
значение.
К точке. Компонент выглядит так (упрощенно, полный код приведен ниже)
@Component({
template: '<input (keydown.enter)="add()" *ngFor="#input of inputs">',
})
add() {
this.inputs.push(this.inputs.length+1);
}
И директива
@Directive({
selector : 'input'
})
class MyInput {
constructor(public renderer: Renderer, public elementRef: ElementRef) {}
ngOnInit() {
this.renderer.invokeElementMethod(
this.elementRef.nativeElement, 'focus', []);
}
}
Как вы можете видеть, я invokeElementMethod
чтобы вызвать focus
на элементе вместо прямого доступа к нему.
Эта версия намного чище и безопаснее, чем оригинальная.
plnkrs обновлен до бета 12
Пример использования ES5: http://plnkr.co/edit/EpoJvff8KtwXRnXZJ4Rr
Пример использования ES6/TS: http://plnkr.co/edit/em18uMUxD84Z3CK53RRe
Обновление 2018
invokeElementMethod
устарел. Используйте Renderer2 вместо Renderer.
Дайте вашему элементу идентификатор, и вы можете использовать selectRootElement:
this.renderer2.selectRootElement('#myInput').focus();
Ответ 2
Взгляните на ViewChild, вот пример. Это может быть то, что вы ищете:
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();
}
}
Ответ 3
С встроенным angular кодом, фокус после условной краски:
<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>
Ответ 4
Вы можете реализовать простую текстовую директиву ввода, чтобы при создании нового ввода он автоматически фокусировался. Метод focus()
вызывается внутри ngAfterViewInit()
жизненного цикла компонента ngAfterViewInit()
после полной инициализации представления.
@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;
}
}
}
Используйте директиву FocusInput
в вашем компоненте:
@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());
}
}
Обратите внимание на следующее:
-
(keyup.enter)
используется для обнаружения нажатия клавиши <enter> -
ngFor
используется для повторения элемента ввода для каждого слова из массива words
-
last
является логическим значением, связанным с локальной переменной, которая имеет значение true, когда вход является последним - Событие keyup связано с
last? addNewWord(): 0
выражением last? addNewWord(): 0
last? addNewWord(): 0
. Это гарантирует, что новое поле ввода будет добавлено только при нажатии клавиши <enter> с последнего ввода
Демо Плнкр
Ответ 5
Чтобы определить приоритет, какой элемент получит фокус при инициализации нескольких директив в одном цикле, используйте:
Директива
@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', []);
}
}
}
Применение:
<input type="text" focusOnInit="9">
https://plnkr.co/edit/T9VDPIWrVSZ6MpXCdlXF