diff --git a/.gitignore b/.gitignore index 037afdf..20fb034 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ # dependencies /node_modules /src/app +/src # profiling files chrome-profiler-events.json diff --git a/projects/gojs-angular/package.json b/projects/gojs-angular/package.json index 6b170f5..fd723aa 100644 --- a/projects/gojs-angular/package.json +++ b/projects/gojs-angular/package.json @@ -1,6 +1,6 @@ { "name": "gojs-angular", - "version": "1.0.17", + "version": "1.0.20", "peerDependencies": { "@angular/common": ">=11.0.0", "@angular/core": ">=11.0.0" diff --git a/projects/gojs-angular/src/lib/diagram.component.ts b/projects/gojs-angular/src/lib/diagram.component.ts index 3f87c43..e70616c 100644 --- a/projects/gojs-angular/src/lib/diagram.component.ts +++ b/projects/gojs-angular/src/lib/diagram.component.ts @@ -39,9 +39,11 @@ export class DiagramComponent { // differs for array inputs (node / link data arrays) private _ndaDiffer: KeyValueDiffer; private _ldaDiffer: KeyValueDiffer; - private _mdaDiffer: KeyValueDiffer; + /** An internal flag used to tell ngOnChanges to treat the next sync operation as a Diagram initialization */ + private wasCleared: boolean = false; + constructor(private _kvdiffers: KeyValueDiffers, public zone: NgZone) { // differs used to check if there have been changed to the array @Inputs // without them, changes to the input arrays won't register in ngOnChanges, @@ -83,14 +85,13 @@ export class DiagramComponent { this.diagram.delayInitialization(() => { const model = this.diagram.model; model.commit((m: go.Model) => { + if (this.modelData) { + m.assignAllDataProperties(m.modelData, this.modelData); + } m.mergeNodeDataArray(m.cloneDeep(this.nodeDataArray)); if (this.linkDataArray && m instanceof go.GraphLinksModel) { m.mergeLinkDataArray(m.cloneDeep(this.linkDataArray)); } - if (this.modelData) { - m.assignAllDataProperties(m.modelData, this.modelData); - } - this.diagram.layoutDiagram(true); }, null); }); @@ -106,16 +107,15 @@ export class DiagramComponent { }; this.diagram.addModelChangedListener(this.modelChangedListener); - } // end ngAfterViewInit /** - * Merges changes from app data into GoJS model data, + * Merges changes from app data into GoJS model data, * making sure only actual changes (and not falsely flagged no-ops on array / obj data props) are logged * @param component an instance of DiagramComponent or PaletteComponent * @param kvchanges The kvchanges object produced by either a node or link Angular differ object * @param str "n" for node data changes, "l" for link data changes - * */ + * */ public static mergeChanges(component, kvchanges, str): boolean { // helper function @@ -193,7 +193,7 @@ export class DiagramComponent { // handle changed data for nodes / links kvchanges.forEachChangedItem((r: KeyValueChangeRecord) => { - + // ensure "changes" to array / object / enumerable data properties are legit const sameVals = compareObjs(r.currentValue, r.previousValue); @@ -232,11 +232,11 @@ export class DiagramComponent { } } } - + }); } - - } + + } // end mergeChanges function /** * Always be checking if array Input data has changed (node and link data arrays) @@ -248,7 +248,7 @@ export class DiagramComponent { // these need to be run each check, even if no merging happens // otherwise, they will detect all diffs that happened since last time skipsDiagram was false, - // such as remove ops that happened in GoJS when skipsDiagram = true, + // such as remove ops that happened in GoJS when skipsDiagram = true, // and then realllllly bad stuff happens (deleting random nodes, updating the wrong Parts) // Angular differs are a lot of fun var nodeDiffs = this._ndaDiffer.diff(this.nodeDataArray); @@ -260,23 +260,51 @@ export class DiagramComponent { if (this.skipsDiagramUpdate) return; - // don't need model change listener while performing known data updates - if (this.modelChangedListener !== null) this.diagram.model.removeChangedListener(this.modelChangedListener); + if (this.wasCleared) { + this.diagram.delayInitialization(() => { + this.mergeAppDataWithModel(this, true); + this.wasCleared = false; + }); + } else { + this.mergeAppDataWithModel(this, false); + } + + } // end ngDoCheck - this.diagram.model.startTransaction('update data'); - // update modelData first, in case bindings on nodes / links depend on model data - this.diagram.model.assignAllDataProperties(this.diagram.model.modelData, this.modelData); - // merge node / link data - DiagramComponent.mergeChanges(this, nodeDiffs, "n"); - DiagramComponent.mergeChanges(this, linkDiffs, "l"); - this.diagram.model.commitTransaction('update data'); + private mergeAppDataWithModel(component: DiagramComponent, isInit?: boolean) { + + // don't need model change listener while performing known data updates + if (component.modelChangedListener !== null) this.diagram.model.removeChangedListener(this.modelChangedListener); + + component.diagram.model.commit((m: go.Model) => { + // update modelData first, in case bindings on nodes / links depend on model data + component.diagram.model.assignAllDataProperties(this.diagram.model.modelData, this.modelData); + // merge node / link data + this.diagram.model.mergeNodeDataArray(this.nodeDataArray); + if (component.linkDataArray && component.diagram.model instanceof go.GraphLinksModel) { + component.diagram.model.mergeLinkDataArray(this.linkDataArray); + } + }, isInit ? null : 'merge data'); // reset the model change listener - if (this.modelChangedListener !== null) this.diagram.model.addChangedListener(this.modelChangedListener); + if (component.modelChangedListener !== null) component.diagram.model.addChangedListener(this.modelChangedListener); + } // end mergeAppDataWithModel function - } // end ngDoCheck + /** + * Clears the diagram of all nodes, links, and model data. + * Also clears the UndoManager history and clipboard. + * The next state update will be treated as diagram initialization. + */ + public clear(): void { + const diagram = this.diagram; + if (diagram !== null) { + diagram.clear(); + this.wasCleared = true; + } + } // end clear function public ngOnDestroy() { this.diagram.div = null; // removes event listeners - } -} + } // end ngOnDestroy function + +} // end DiagramComponent class diff --git a/projects/gojs-angular/src/lib/palette.component.ts b/projects/gojs-angular/src/lib/palette.component.ts index 8b634be..1ff0c94 100644 --- a/projects/gojs-angular/src/lib/palette.component.ts +++ b/projects/gojs-angular/src/lib/palette.component.ts @@ -87,14 +87,13 @@ export class PaletteComponent { this.palette.delayInitialization(() => { const model = this.palette.model; model.commit((m: go.Model) => { + if (this.modelData) { + m.assignAllDataProperties(m.modelData, this.modelData); + } m.mergeNodeDataArray(m.cloneDeep(this.nodeDataArray)); if (this.linkDataArray && m instanceof go.GraphLinksModel) { m.mergeLinkDataArray(m.cloneDeep(this.linkDataArray)); } - if (this.modelData) { - m.assignAllDataProperties(m.modelData, this.modelData); - } - this.palette.layoutDiagram(true); }, null); }); @@ -122,7 +121,7 @@ export class PaletteComponent { // these need to be run each check, even if no merging happens // otherwise, they will detect all diffs that happened since last time skipsPaletteUpdate was false, - // such as remove ops that happened in GoJS when skipsPaletteUpdate = true, + // such as remove ops that happened in GoJS when skipsPaletteUpdate = true, // and then realllllly bad stuff happens (deleting random nodes, updating the wrong Parts) // Angular differs are a lot of fun var nodeDiffs = this._ndaDiffer.diff(this.nodeDataArray); @@ -150,6 +149,6 @@ export class PaletteComponent { public ngOnDestroy() { this.palette.div = null; // removes event listeners - } + } // end ngOnDestroy }