diff --git a/src/sql/platform/dashboard/browser/interfaces.ts b/src/sql/platform/dashboard/browser/interfaces.ts index 791c584032..fc15ebed54 100644 --- a/src/sql/platform/dashboard/browser/interfaces.ts +++ b/src/sql/platform/dashboard/browser/interfaces.ts @@ -67,8 +67,9 @@ export interface IModelStore { * * @param componentId unique identifier of the component * @param action some action to perform + * @param isInitialization whether this is an initialization action that will run before other actions */ - eventuallyRunOnComponent(componentId: string, action: (component: IComponent) => T): Promise; + eventuallyRunOnComponent(componentId: string, action: (component: IComponent) => T, isInitialization?: boolean): Promise; /** * Register a callback that will validate components when given a component ID */ diff --git a/src/sql/workbench/browser/modelComponents/modelStore.ts b/src/sql/workbench/browser/modelComponents/modelStore.ts index fe7f5ef756..5469e29ca0 100644 --- a/src/sql/workbench/browser/modelComponents/modelStore.ts +++ b/src/sql/workbench/browser/modelComponents/modelStore.ts @@ -19,6 +19,7 @@ export class ModelStore implements IModelStore { private _descriptorMappings: { [x: string]: IComponentDescriptor } = {}; private _componentMappings: { [x: string]: IComponent } = {}; private _componentActions: { [x: string]: Deferred } = {}; + private _componentInitializationActions: { [x: string]: Deferred } = {}; private _validationCallbacks: ((componentId: string) => Thenable)[] = []; constructor() { } @@ -51,12 +52,12 @@ export class ModelStore implements IModelStore { return this._componentMappings[componentId]; } - eventuallyRunOnComponent(componentId: string, action: (component: IComponent) => T): Promise { + eventuallyRunOnComponent(componentId: string, action: (component: IComponent) => T, isInitialization: boolean = false): Promise { let component = this.getComponent(componentId); if (component) { return Promise.resolve(action(component)); } else { - return this.addPendingAction(componentId, action); + return this.addPendingAction(componentId, action, isInitialization); } } @@ -69,13 +70,18 @@ export class ModelStore implements IModelStore { return Promise.all(this._validationCallbacks.map(callback => callback(componentId))).then(validations => validations.every(validation => validation === true)); } - private addPendingAction(componentId: string, action: (component: IComponent) => T): Promise { + private addPendingAction(componentId: string, action: (component: IComponent) => T, isInitialization: boolean): Promise { // We create a promise and chain it onto a tracking promise whose resolve method // will only be called once the component is created - let deferredPromise = this._componentActions[componentId]; + + // If this is an initialization action we want to run it before the other actions that may have come in + // after initialization but before the component was finished being created or we hit race conditions with + // setting properties + const actionsStore = isInitialization ? this._componentInitializationActions : this._componentActions; + let deferredPromise = actionsStore[componentId]; if (!deferredPromise) { deferredPromise = new Deferred(); - this._componentActions[componentId] = deferredPromise; + actionsStore[componentId] = deferredPromise; } let promise = deferredPromise.promise.then((component) => { return action(component); @@ -84,9 +90,10 @@ export class ModelStore implements IModelStore { } private runPendingActions(componentId: string, component: IComponent) { - let promiseTracker = this._componentActions[componentId]; - if (promiseTracker) { - promiseTracker.resolve(component); - } + // If we have initialization actions to run start those first + const initializationActionsPromise = this._componentInitializationActions[componentId]; + initializationActionsPromise?.resolve(component); + const actionsPromise = this._componentActions[componentId]; + actionsPromise?.resolve(component); } } diff --git a/src/sql/workbench/browser/modelComponents/viewBase.ts b/src/sql/workbench/browser/modelComponents/viewBase.ts index 085e357348..01ba1b5274 100644 --- a/src/sql/workbench/browser/modelComponents/viewBase.ts +++ b/src/sql/workbench/browser/modelComponents/viewBase.ts @@ -58,12 +58,12 @@ export abstract class ViewBase extends AngularDisposable implements IModelView { throw new Error(nls.localize('componentTypeNotRegistered', "Could not find component for type {0}", ModelComponentTypes[component.type])); } let descriptor = this.modelStore.createComponentDescriptor(typeId, component.id); - this.setProperties(component.id, component.properties); - this.setLayout(component.id, component.layout); - this.registerEvent(component.id); + this.setProperties(component.id, component.properties, true); + this.setLayout(component.id, component.layout, true); + this.registerEvent(component.id, true); if (component.itemConfigs) { for (let item of component.itemConfigs) { - this.addToContainer(component.id, item); + this.addToContainer(component.id, item, undefined, true); } } @@ -82,12 +82,12 @@ export abstract class ViewBase extends AngularDisposable implements IModelView { this.queueAction(componentId, (component) => component.clearContainer()); } - addToContainer(containerId: string, itemConfig: IItemConfig, index?: number): void { + addToContainer(containerId: string, itemConfig: IItemConfig, index?: number, isInitialization?: boolean): void { // Do not return the promise as this should be non-blocking this.queueAction(containerId, (component) => { let childDescriptor = this.defineComponent(itemConfig.componentShape); component.addToContainer(childDescriptor, itemConfig.config, index); - }); + }, isInitialization); } removeFromContainer(containerId: string, itemConfig: IItemConfig): void { @@ -98,11 +98,11 @@ export abstract class ViewBase extends AngularDisposable implements IModelView { }); } - setLayout(componentId: string, layout: any): void { + setLayout(componentId: string, layout: any, isInitialization?: boolean): void { if (!layout) { return; } - this.queueAction(componentId, (component) => component.setLayout(layout)); + this.queueAction(componentId, (component) => component.setLayout(layout), isInitialization); } setItemLayout(containerId: string, itemConfig: IItemConfig): void { @@ -112,24 +112,24 @@ export abstract class ViewBase extends AngularDisposable implements IModelView { }); } - setProperties(componentId: string, properties: { [key: string]: any; }): void { + setProperties(componentId: string, properties: { [key: string]: any; }, isInitialization?: boolean): void { if (!properties) { return; } - this.queueAction(componentId, (component) => component.setProperties(properties)); + this.queueAction(componentId, (component) => component.setProperties(properties), isInitialization); } refreshDataProvider(componentId: string, item: any): void { this.queueAction(componentId, (component) => component.refreshDataProvider(item)); } - private queueAction(componentId: string, action: (component: IComponent) => T): void { - this.modelStore.eventuallyRunOnComponent(componentId, action).catch(err => { + private queueAction(componentId: string, action: (component: IComponent) => T, isInitialization?: boolean): void { + this.modelStore.eventuallyRunOnComponent(componentId, action, isInitialization).catch(err => { // TODO add error handling }); } - registerEvent(componentId: string) { + registerEvent(componentId: string, isInitialization?: boolean) { this.queueAction(componentId, (component) => { this._register(component.registerEventHandler(e => { let modelViewEvent: IModelViewEventArgs = assign({ @@ -138,7 +138,7 @@ export abstract class ViewBase extends AngularDisposable implements IModelView { }, e); this._onEventEmitter.fire(modelViewEvent); })); - }); + }, isInitialization); } public get onEvent(): Event {