Зосередьтеся на нещодавно доданому елементі введення


78

У мене є нова програма Angular 2 із переліком inputвікон. Коли користувач натискає клавішу повернення, я додаю нове inputполе відразу після того, яке вони редагують. Вірніше, я (асинхронно) додаю новий запис до масиву в моделі, що змушує Angular 2 автоматично генерувати нове inputполе найближчим часом.

Як я можу зробити так, щоб inputфокус автоматично змінювався на щойно доданий елемент?

EDIT 1:
альтернативи я отримую посилання на об’єкт моделі, який спричиняє створення DOM. З коду компонента, чи є спосіб пошуку елемента DOM, що представляє конкретний об’єкт моделі?

EDIT 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
Ви, setIntervalшвидше за все, просто повинні бути setTimeout.
Бенджамін Груенбаум,

Той, хто відвідував цю відповідь, подивись на це. github.com/spirosikmd/angular2-focus Досить простий у використанні. Це все легко, якщо ви використовуєте * ngFor
Saiyaff Farouk

Так просто вам потрібна зовнішня бібліотека ??
Крістоф Руссі,

Відповіді:


101

Якщо мені це дозволено, я візьму частину відповіді @Sasxa та зміню її, щоб зробити її більш схожою на те, що ви шукаєте.

Кілька змін

  • Я буду використовувати ngForso angular2 додає новий вхід, а не робити це сам. Основна мета - просто зробити angular2 для ітерації над ним.
  • Замість того, ViewChildщо я збираюся використовувати, ViewChildrenщо повертає QueryList, що має changesвластивість. Ця властивість є Observable і вона повертає елементи після їх зміни.

Оскільки в 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щоб зробити WebWorker безпечним. Оригінальна відповідь отримує доступ focus()безпосередньо до nativeElementякої не рекомендується.
  • Тепер я слухаю, keydown.enterщо спрощує подію зниження клавіші, мені не потрібно перевіряти whichзначення.

До суті. Компонент виглядає так (спрощений, повний код на plnkrs нижче)

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

1
Дуже елегантний. Я здивований, що ми можемо використовувати одне і те ж ім'я змінної локального шаблону / ідентифікатор - #input- для кількох елементів.
Mark Rajcok

1
Я хотів би перейменувати ngForлокальну змінну шаблон що - то ще, так що це не виглядає , як це пов'язано з іншим #input: *ngFor="#in of inputs".
Mark Rajcok

1
Замість того щоб додавати обробник подій до кожного input, я пропоную покласти його на divі дати подія пузиритися: <div (keydown)="add($event)"> <input #input *ngFor="#in of inputs">.
Mark Rajcok

Для оновлення 2018 року, замість використання ідентифікатора, я виявив, що це працює:this.renderer2.selectRootElement(this.elementRef.nativeElement).focus();
Ренді Чунг,

41

Погляньте на 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();
  }
}

все ще добре працює в Angular 6 і пропускаючи декларацію типу ViewChild "ElementRef" не є проблемою або для ліндера vscode
stackuser83,

26

З вбудованим кутовим кодом фокус після умовної фарби:

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

9

Ви можете реалізувати просту директиву введення тексту, щоб кожен раз, коли створюється новий ввід, він автоматично фокусувався. 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());
    }
}

Зверніть увагу на таке:

  1. (keyup.enter)Подія використовується для виявлення , коли <увійти> натиснута кнопка
  2. ngFor використовується для повторення вхідного елемента для кожного слова з масиву words
  3. last є логічним значенням, прив'язаним до локальної змінної, яка є істинною, коли вхід є останнім
  4. Подія keyup прив'язана до виразу last ? addNewWord() : 0. Це гарантує, що нове поле введення додається лише при натисканні клавіші <enter> з останнього вводу

Демо-версія Plnkr


Дуже приємно, спробувавши всі вищезазначені відповіді, ця спрацювала для мене! Anguler 5Дякую!!
Alfa Bravo

На мою думку, це найбільш тверде рішення
Kilian Perdomo Curbelo

3

Для того, щоб визначити пріоритет, який елемент отримає фокус при ініціалізації декількох директив в одному циклі, використовуйте:

Директива:

@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


1
Чи не краще замість цього використовувати табіндекс?
Андре Верланг,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.