typescript - Angular2: cum să implementezi corect legarea bidirecțională a datelor fără a provoca un ciclu infinit


Continuând încă călătoria mea de începător în Angular2 (beta 1), încerc să înțeleg cum să implementez corect legătura bidirecțională fără ca aplicația mea să rămână blocată într-un ciclu infinit.

Vă rugăm să găsiți exemplul de reproducere la acest Plunker: http://plnkr.co/edit/83NeiUCEpPvYvUXIkl0g . Doar rulați și faceți clic pe buton.

Scenariul meu:

  • o directivă învelește editorul de cod Ace și expune o proprietate text și un eveniment textChanged.
  • un component de editor (XML) folosește această directivă. Ar trebui să fie capabil să răspundă la modificările din editorul de bază pentru a-și actualiza codul XML și să seteze un text nou în editorul de bază din codul său XML.

Problema mea este că ori de câte ori componenta editor setează programatic proprietatea sa xml, acest lucru declanșează schimbarea în editorul Ace de bază, ceea ce, la rândul său, declanșează evenimentul text-changed, care, la rândul său, este gestionat de callback-ul componentei editor, și așa mai departe. Aceasta generează un ciclu nesfârșit și poți vedea textul editorului clipind. Ce fac greșit aici?

Iată codul pentru directivă:

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

Și aici este componenta editorului:

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

Răspuns #1

Doar folosește sintaxa [(ngModel)]="property". Chiar dacă pare să se comporte ca vechea legare bidirecțională, în realitate ea se desparte în două legări unidirecționale diferite, una pentru input și una pentru output, modul în care Angular 2 gestionează schimbările și ciclurile sale de verificare a modificărilor 'dirty check' sub capotă este ceea ce s-a schimbat și nu îți va permite să faci un ciclu infinit.