Konzentrieren Sie sich auf das neu hinzugefügte Eingabeelement

78

Ich habe eine neue Angular 2-App mit einer Liste von inputBoxen. Wenn der Benutzer die Eingabetaste drückt, füge ich inputunmittelbar nach dem Feld, das er gerade bearbeitet , ein neues Feld hinzu. Oder besser gesagt, ich füge (asynchron) dem Array im Modell einen neuen Eintrag hinzu, wodurch Angular 2 inputin naher Zukunft automatisch eine neue Box generiert .

Wie kann ich dafür sorgen, dass sich der inputFokus automatisch auf das neu hinzugefügte Element ändert?

EDIT 1:
Alternativ erhalte ich einen Verweis auf das Modellobjekt, durch das das DOM generiert wird. Gibt es eine Möglichkeit, im Komponentencode nach einem DOM-Element zu suchen, das ein bestimmtes Modellobjekt darstellt?

EDIT 2:
Hier ist mein Code, damit dies funktioniert. Hoffentlich ist dies für einige Angular 2-Entwickler anstößig genug, um eine Antwort zu ermutigen :-)

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);
                });
            }
        }
    });
Ben Dilts
quelle
1
Du setIntervalsolltest höchstwahrscheinlich nur ein sein setTimeout.
Benjamin Gruenbaum
Wer diese Antwort besucht, schaut sich das an. github.com/spirosikmd/angular2-focus Sehr einfach zu bedienen. Dies macht alles einfach, wenn Sie * ngFor
Saiyaff Farouk
So einfach brauchst du eine externe Bibliothek?
Christophe Roussy

Antworten:

101

Wenn ich dazu berechtigt bin, nehme ich an der Antwort von @Sasxa teil und ändere sie so, dass sie dem entspricht, wonach Sie suchen.

Ein paar Änderungen

  • Ich werde ein ngForso eckiges2 verwenden, das die neue Eingabe hinzufügt, anstatt es selbst zu tun. Der Hauptzweck ist nur, angle2 so zu machen, dass es darüber iteriert.
  • Anstatt dass ViewChildich das verwende ViewChildren, wird eine QueryList zurückgegeben, die eine changesEigenschaft hat. Diese Eigenschaft ist eine Observable und gibt die Elemente zurück, nachdem sie geändert wurden.

Da wir in ES5 keine Dekorateure haben, müssen wir die queriesEigenschaft verwenden, um sie zu verwendenViewChildren

Komponente

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')
    }
})

Konzentrieren Sie sich auf das letzte Element.

ngAfterViewInit: function() {

    this.vc.changes.subscribe(elements => {
        elements.last.nativeElement.focus();
    });

}

Wie ich bereits sagte, gibt ViewChildren eine QueryList zurück, die eine Eigenschaft enthält changes. Wenn wir es bei jeder Änderung abonnieren, wird die Liste der Elemente zurückgegeben. Die Liste elementsenthält last(unter anderem) eine Eigenschaft, die in diesem Fall das letzte Element zurückgibt, das wir nativeElementdarauf und schließlich verwendenfocus()

Eingabeelemente hinzufügen Dies ist eine reine Annehmlichkeit. Das Eingabearray hat keinen wirklichen Zweck mehr als das Neuzeichnen des ngFor.

add: function(key) {
    if(key.which == 13) {
        // See plnkr for "this.inputs" usage
        this.inputs.push(this.inputs.length+1);
    }
}

Wir schieben ein Dummy- Element auf das Array, damit es neu gezeichnet wird.

Beispiel mit ES5: http://plnkr.co/edit/DvtkfiTjmACVhn5tHGex

Beispiel mit ES6 / TS: http://plnkr.co/edit/93TgbzfPCTxwvgQru2d0?p=preview

Update 29/03/2016

Die Zeit ist vergangen, die Dinge wurden geklärt und es gibt immer Best Practices zum Lernen / Lehren. Ich habe diese Antwort vereinfacht, indem ich einige Dinge geändert habe

  • Anstatt es zu verwenden @ViewChildrenund zu abonnieren, habe ich eine Richtlinie erstellt, die jedes Mal ausgelöst wird, wenn eine neue Eingabe erstellt wird
  • Ich verwende es Renderer, um WebWorker sicher zu machen. Die ursprüngliche Antwort greift focus()direkt auf die zu, von nativeElementder abgeraten wird.
  • Jetzt höre ich zu, keydown.enterwas das Key-Down-Ereignis vereinfacht, ich muss den whichWert nicht überprüfen .

Auf den Punkt. Die Komponente sieht aus wie (vereinfachter, vollständiger Code auf den folgenden Plnkrs)

@Component({
  template: `<input (keydown.enter)="add()" *ngFor="#input of inputs">`,
})

add() {
    this.inputs.push(this.inputs.length+1);
}

Und die Richtlinie

@Directive({
  selector : 'input'
})
class MyInput {
  constructor(public renderer: Renderer, public elementRef: ElementRef) {}

  ngOnInit() {
    this.renderer.invokeElementMethod(
      this.elementRef.nativeElement, 'focus', []);
  }
}

Wie Sie sehen, rufe ich invokeElementMethodauf, um focusdas Element auszulösen , anstatt direkt darauf zuzugreifen.

Diese Version ist viel sauberer und sicherer als die ursprüngliche.

plnkrs auf Beta 12 aktualisiert

Beispiel mit ES5: http://plnkr.co/edit/EpoJvff8KtwXRnXZJ4Rr

Beispiel mit ES6 / TS: http://plnkr.co/edit/em18uMUxD84Z3CK53RRe

Update 2018

invokeElementMethodist veraltet. Verwenden Sie Renderer2 anstelle von Renderer.

Geben Sie Ihrem Element eine ID, und Sie können selectRootElement verwenden :

this.renderer2.selectRootElement('#myInput').focus();
Eric Martinez
quelle
1
Sehr elegant. Ich bin überrascht, dass wir denselben lokalen Namen / Bezeichner der lokalen Vorlage #inputfür mehrere Elemente verwenden können.
Mark Rajcok
1
Ich würde die ngForlokale Vorlagenvariable in etwas anderes umbenennen , damit sie nicht so aussieht, als ob sie mit der anderen verwandt wäre #input: *ngFor="#in of inputs".
Mark Rajcok
1
Anstatt jedem einen Event-Handler hinzuzufügen input, schlage ich vor, ihn auf den zu setzen divund das Event in die Luft sprudeln zu lassen : <div (keydown)="add($event)"> <input #input *ngFor="#in of inputs">.
Mark Rajcok
Für das Update 2018 habe ich festgestellt, dass dies funktioniert, anstatt eine ID zu verwenden:this.renderer2.selectRootElement(this.elementRef.nativeElement).focus();
Randy Chung
41

Schauen Sie sich ViewChild an , hier ein Beispiel . Dies könnte das sein, wonach Sie suchen:

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();
  }
}
Sasxa
quelle
immer noch gut in Angular 6 und das Weglassen der ViewChild-Typdeklaration "ElementRef" kein Problem oder für den vscode linter
stackuser83
26

Mit Inline-Winkelcode nach bedingter Farbe fokussieren:

  <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>
surfealokesea
quelle
9

Sie können eine einfache Eingabetextanweisung implementieren, sodass sich jede neue Eingabe automatisch fokussiert, wenn sie erstellt wird. Die focus()Methode wird innerhalb des ngAfterViewInit()Komponentenlebenszyklus-Hooks aufgerufen, nachdem die Ansicht vollständig initialisiert wurde.

@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;
      }
    }
}

Verwenden Sie die FocusInputDirektive in Ihrer Komponente:

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

Beachten Sie das Folgende:

  1. Das (keyup.enter)Ereignis wird verwendet, um zu erkennen, wann die <Eingabetaste> gedrückt wird
  2. ngFor wird verwendet, um das Eingabeelement für jedes Wort aus dem Array von zu wiederholen words
  3. last ist ein Boolescher Wert, der an eine lokale Variable gebunden ist. Dies ist der Fall, wenn die Eingabe die letzte ist
  4. Das Keyup-Ereignis ist an den Ausdruck gebunden last ? addNewWord() : 0. Dadurch wird sichergestellt, dass ein neues Eingabefeld nur hinzugefügt wird, wenn die <Eingabetaste> von der letzten Eingabe gedrückt wird

Demo Plnkr

Pixelbits
quelle
Sehr schön, nachdem ich alle oben genannten Antworten ausprobiert habe, hat diese für mich funktioniert! Anguler 5Vielen Dank!!
Alfa Bravo
Dies ist meiner Meinung nach die solide Lösung
Kilian Perdomo Curbelo
3

Verwenden Sie Folgendes, um zu priorisieren, welches Element beim Initialisieren mehrerer Anweisungen im selben Zyklus den Fokus erhält:

Richtlinie:

@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', []);
    }
  }
}

Verwendung:

<input type="text" focusOnInit="9">

https://plnkr.co/edit/T9VDPIWrVSZ6MpXCdlXF

Gilhanan
quelle
1
Wäre es nicht besser, stattdessen Tabindex zu verwenden?
André Werlang