【问题标题】:Angular2: how to properly implement 2-way databinding without incurring in endless loopAngular2:如何正确实现 2 路数据绑定而不导致无限循环
【发布时间】:2016-01-14 15:37:10
【问题描述】:

我仍在继续我的 Angular2(beta 1)新手之旅,我正在尝试了解如何正确实现 2 向绑定,而不会让我的应用陷入无限循环。

请在此 Plunker 上找到示例复制品:http://plnkr.co/edit/83NeiUCEpPvYvUXIkl0g。只需运行并单击按钮即可。

我的场景:

  • 一个指令包装了 Ace 代码编辑器并公开了一个 text 属性和一个 textChanged 事件。
  • (XML)编辑器组件使用此指令。它应该能够响应底层编辑器中的更改以更新其 XML 代码,并根据其 XML 代码在底层编辑器中设置新文本。

我的问题是,每当编辑器组件以编程方式设置其xml 属性时,都会触发底层 Ace 编辑器中的更改,进而触发 text-changed 事件,该事件又由编辑器组件的回调处理,等等。这会生成一个无限循环,您可以看到编辑器的文本闪烁。我在这里做错了什么?

这是指令的代码:

import {Component,Directive,EventEmitter,ElementRef} from 'angular2/core';
declare var ace: any;

@Directive({
    selector: "ace-editor",
    inputs: [
        "text"
    ],
    outputs: [
        "textChanged"
    ]
})
export class AceDirective { 
    private editor : any;
    public textChanged: EventEmitter<string>;

    set text(s: string) {
        if (s === undefined) return;
        let sOld = this.editor.getValue();
        if (sOld === s) return;        
        this.editor.setValue(s);
        this.editor.clearSelection();
        this.editor.focus();
    }

    get text() {
        return this.editor.getValue();
    }

    constructor(elementRef: ElementRef) {
        var dir = this;
        this.textChanged = new EventEmitter<string>();

        let el = elementRef.nativeElement;
        this.editor = ace.edit(el);
        let session = this.editor.getSession();
        session.setMode("ace/mode/xml");
        session.setUseWrapMode(true);

        this.editor.on("change", (e) => {
            let s = dir.editor.getValue();
            dir.textChanged.next(s);
        });
    }
}

这里是编辑器组件:

import {Component,EventEmitter} from "angular2/core";
import {AceDirective} from "./ace.directive";

@Component({
    selector: "my-editor",
    directives: [AceDirective],
    template: `<div style="border:1px solid red">
    <ace-editor id="editor" [text]="xml" (textChanged)="onXmlChanged($event)"></ace-editor>
    </div>
    <div><button (click)="changeXml()">set xml</button></div>`,
    inputs: [
        "xml"
    ]
})
export class EditorComponent { 
    private _xml: string;

    // using a property here so that I can set a breakpoint
    public set xml(s: string) {
        this._xml = s; 
    }
    public get xml() : string {
        return this._xml;
    }

    constructor() {
        this._xml = "";
    }

    public onXmlChanged(xml: string) {
        this._xml = xml;
    }

    // an action which somehow changes the XML content
    public changeXml() {
        this._xml = "<x>abc</x>";
    }
}

【问题讨论】:

    标签: typescript angular


    【解决方案1】:

    只需执行 [(ngModel)]="property" 语法即可。尽管实际上它看起来像旧的双向绑定,但实际上它正在将其分解为两种不同的单向绑定,一种用于输入,一种用于输出,Angular 2 处理更改的方式和他在引擎盖下的脏检查循环是什么已更改,并且不允许您进行无限循环。

    【讨论】:

    • 谢谢,我刚试过,但它似乎不起作用,除非我的更改当然是错误的 ()。单击设置按钮时,没有任何反应。相反,在加载页面时,我得到一个“例外:[EditorComponent@1:28 中的 xml] 中的 '' 没有值访问器”。我相应地更新了我的 Plunker,你可以检查一下。
    • 我必须补充一点,至于不清楚的错误消息,我发现了这个提示:github.com/angular/angular/issues/4733,即我必须显式导入 NgModel 指令(而 NgFor 等其他指令已经被封装)。我更新了 Plunker,但它的行为仍然相同。有什么想法吗?
    • 使用文本,而不是 ngModel:&lt;ace-editor id="editor" [(text)]="xml"&gt;&lt;/ace-editor&gt;
    • 谢谢马克,在网页中阅读“ngModel”数小时后我还没有看到... :) 现在修复了 Plunker
    • @Naftis,我可能应该在我的评论中说更多...您可以对任何属性使用[(x)] 语法,但您的组件需要一个x 输入属性和一个xChange输出属性(EventEmitter)。我不明白为什么[(text)] 在您的场景中有效,但拆分却不行:[text]="xml" (textChanged)="onXmlChanged($event)"。我以为他们是等价的。当使用[(x)] 语法时,也许Angular 在一个更改检测周期中进行属性绑定和事件绑定??
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-19
    • 2015-11-14
    • 1970-01-01
    相关资源
    最近更新 更多