typescript - Angular2: hur man korrekt implementerar tvåvägsdatabindning utan att hamna i en oändlig loop


Fortsätter fortfarande min Angular2 (beta 1) nybörjarresa, jag försöker förstå hur man korrekt implementerar tvåvägsbindning utan att min app fastnar i en oändlig loop.

Vänligen hitta exempelreproduktionen på denna Plunker: http://plnkr.co/edit/83NeiUCEpPvYvUXIkl0g. Kör bara och klicka på knappen.

Mitt scenario:

  • en direktiv omsluter Ace-kodredigeraren och exponerar en text-egenskap och en textChanged-händelse.
  • En (XML) redigeringskomponent använder denna direktiv. Den bör kunna reagera på ändringar i den underliggande redigeraren för att uppdatera sin XML-kod, och för att sätta en ny text i den underliggande redigeraren från sin XML-kod.

Mitt problem är att när redigeringskomponenten programmatisk sätter sin xml-egenskap, så utlöser detta ändringen i den underliggande Ace-redigeraren, vilket i sin tur utlöser text-changed-händelsen, som i sin tur hanteras av redigeringskomponentens återanropsfunktion, och så vidare. Detta skapar en oändlig loop och du kan se redigerarens text blinka. Vad gör jag fel här?

Här är koden för direktivet:

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

Och här är redigeringskomponenten:

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

Svar #1

Gör bara [(ngModel)]="property"-syntaksen. Även om det ser ut att bete sig som den gamla tvåvägsbindningen, bryts det i verkligheten upp i två olika envägsbindningar, en för input och en för output, sättet Angular 2 hanterar förändringar och sina dirty check-cykler under huven är det som har förändrats och som inte tillåter dig att göra en ändlös loop.