Still continuing my Angular2 (beta 1) newcomer journey, I'm trying to understand how to properly implement 2-way binding without having my app stuck in an endless loop.
Please find the sample repro at this Plunker: http://plnkr.co/edit/83NeiUCEpPvYvUXIkl0g . Just run and click the button.
My scenario:
- a directive wraps the Ace code editor and exposes a
textproperty and atextChangedevent. - an (XML) editor component uses this directive. It should be able to respond to changes in the underlying editor to update its XML code, and to set a new text in the underlying editor from its XML code.
My problem is that whenever the editor component programmatically sets its xml property, this triggers the change in the underlying Ace editor, which in turn triggers the text-changed event, which in turn is handled by the editor component's callback, and so forth. This generates an endless loop and you can see the editor's text flicker. What I'm doing wrong here?
Here is the code for the directive:
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);
});
}
}
And here is the editor component:
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>";
}
}