Сосредоточьтесь на недавно добавленном элементе ввода
У меня есть новое приложение Angular 2 со списком полей ввода. Когда пользователь нажимает клавишу возврата, я добавляю новое поле ввода сразу после того, которое он в данный момент редактирует. Вернее, я (асинхронно) добавляю новую запись в массив в модели, что заставляет Angular 2 автоматически генерировать новое поле ввода в ближайшем будущем.
Как сделать так, чтобы фокус ввода автоматически изменялся на добавленный элемент?
[edit] Кроме того, я получаю ссылку на объект модели, который вызывает генерацию DOM. Из кода компонента есть ли способ поиска элемента DOM, представляющего конкретный объект модели?
[править] Вот мой код, чтобы просто сделать эту работу. Надеюсь, что это достаточно оскорбительно для некоторых разработчиков 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);
});
}
}
});
5 ответов
Если мне будет позволено это сделать, я приму участие в ответе @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();
Взгляните на 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();
}
}
С линейным угловым кодом, сфокусируйтесь после условной краски:
<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>
Вы можете реализовать простую текстовую директиву ввода, чтобы при создании нового ввода он автоматически фокусировался. focus()
метод вызывается внутри 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)
событие используется для обнаружения нажатия клавишиngFor
используется для повторения элемента ввода для каждого слова из массиваwords
last
является логическим значением, связанным с локальной переменной, которая имеет значение true, когда вход является последним- Событие keyup связано с выражением
last ? addNewWord() : 0
, Это гарантирует, что новое поле ввода будет добавлено только при нажатии клавишис последнего ввода
Чтобы определить приоритет того, какой элемент получит фокус при инициализации нескольких директив в одном и том же цикле, используйте:
Директива:
@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">