专注于新添加的输入元素


78

我有一个带有input框列表的新Angular 2应用程序。当用户按下回车键时,我会input在他们当前正在编辑的框之后立即添加一个新框。或者,我(异步)向模型中的数组添加一个新条目,这导致Angular 2input在不久的将来自动生成一个新盒子。

如何使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
setInterval最有可能只是一个setTimeout
本杰明·格伦鲍姆

谁访问这个答案,看看这个。github.com/spirosikmd/angular2-focus非常易于使用。如果您使用* ngFor
Saiyaff Farouk

如此简单,您需要一个外部库?
Christophe Roussy '18

Answers:


101

如果可以的话,我将参加@Sasxa答案并对其进行修改,使其更符合您的需求。

一些变化

  • 我将使用ngForangular2添加新输入,而不是自己进行添加。主要目的只是使angular2对其进行迭代。
  • 而不是ViewChild我将使用ViewChildren它返回具有属性的QueryListchanges。此属性是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返回一个包含changes属性的QueryList 。当我们每次对其进行订阅时,它都会返回元素列表。该列表elements包含一个last属性(在其他情况下),在这种情况下,该属性返回最后一个元素,我们将nativeElement其用于最后focus()

添加输入元素这纯粹是为了方便起见,inputs数组仅具有重绘的作用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更新为beta 12

使用ES5的示例:http : //plnkr.co/edit/EpoJvff8KtwXRnXZJ4Rr

使用ES6 / TS的示例:http : //plnkr.co/edit/em18uMUxD84Z3CK53RRe

更新2018

invokeElementMethod不推荐使用。使用Renderer2代替Renderer。

给您的元素一个ID,然后可以使用selectRootElement

this.renderer2.selectRootElement('#myInput').focus();

1
十分优雅。我很惊讶我们可以#input在多个元素上使用相同的本地模板变量名称/标识符。
Mark Rajcok '16

1
我会将ngFor本地模板变量重命名为其他名称,以使其看起来不与其他变量有关#input*ngFor="#in of inputs"
Mark Rajcok '16

1
而不是添加事件处理程序的每一个的input,我建议把它放在div和让事件冒泡:<div (keydown)="add($event)"> <input #input *ngFor="#in of inputs">
Mark Rajcok '16

对于2018年更新,我发现它可以使用,而不是使用ID:this.renderer2.selectRootElement(this.elementRef.nativeElement).focus();
Randy Chung,

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中还是不错的,并且省略了“ ChildRef” ViewChild类型声明不是问题,也不是vscode linter
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)事件用于检测何时按下<enter>键
  2. ngFor 用于重复输入数组中每个单词的输入元素 words
  3. last 是绑定到局部变量的布尔值,当输入为最后一个变量时为true
  4. keyup事件绑定到表达式last ? addNewWord() : 0。这样可以确保仅在最后一次输入中按下<enter>键时才添加新的输入字段

演示版


非常好,尝试了以上所有答案后,这个对我有用!Anguler 5谢谢!!
阿尔法·布拉沃

我认为这是最可靠的解决方案
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
改用tabindex会更好吗?
安德烈Werlang
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.