diff --git a/e2e/src/advanced/pluginApi.e2e-spec.js b/e2e/src/advanced/pluginApi.e2e-spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..1a4d5f2ba89f7c83208d5db5a9a4df92a7ab91d0
--- /dev/null
+++ b/e2e/src/advanced/pluginApi.e2e-spec.js
@@ -0,0 +1,68 @@
+const { AtlasPage } = require('../util')
+const template = 'ICBM 2009c Nonlinear Asymmetric'
+
+const pluginName = `fzj.xg.testWidget`
+const pluginDisplayName = `Test Widget Title`
+
+const prepareWidget = ({ template = 'hello world', script = `console.log('hello world')` } = {}) => {
+  
+  return `
+const jsSrc = \`${script.replace(/\`/, '\\`')}\`
+const blob = new Blob([jsSrc], { type: 'text/javascript' })
+window.interactiveViewer.uiHandle.launchNewWidget({
+  name: '${pluginName}',
+  displayName: '${pluginDisplayName}',
+  template: \`${template.replace(/\`/, '\\`')}\`,
+  scriptURL: URL.createObjectURL(blob)
+})`
+}
+
+describe('> plugin api', () => {
+  let iavPage
+
+  beforeEach(async () => {
+    iavPage = new AtlasPage()
+    await iavPage.init()
+    await iavPage.goto()
+    await iavPage.selectTitleCard(template)
+    await iavPage.wait(500)
+    await iavPage.waitUntilAllChunksLoaded()
+  })
+
+  describe('> interactiveViewer', () => {
+    describe('> uiHandle', () => {
+      describe('> launchNewWidget', () => {
+        it('should launch new widget', async () => {
+
+          const prevTitle = await iavPage.execScript(() => window.document.title)
+          await iavPage.execScript(prepareWidget({ script: `window.document.title = 'hello world ' + window.document.title` }))
+
+          await iavPage.wait(500)
+
+          const isDisplayed = await iavPage.widgetPanelIsDispalyed(`Test Widget Title`)
+          expect(isDisplayed).toEqual(true)
+
+          const newTitle = await iavPage.execScript(() => window.document.title)
+          expect(newTitle).toEqual(`hello world ${prevTitle}`)
+        })
+      })
+    })
+  })
+
+  describe('> pluginControl', () => {
+    describe('> onShutdown', () => {
+      it('> works', async () => {
+        const newTitle = `testing pluginControl onShutdown`
+        const script = `window.interactiveViewer.pluginControl['${pluginName}'].onShutdown(() => window.document.title = '${newTitle}')`
+        await iavPage.execScript(prepareWidget({ script }))
+        await iavPage.wait(500)
+        const oldTitle = await iavPage.execScript(() => window.document.title)
+        await iavPage.closeWidgetByname(pluginDisplayName)
+        await iavPage.wait(500)
+        const actualNewTitle = await iavPage.execScript(() => window.document.title)
+        expect(oldTitle).not.toEqual(actualNewTitle)
+        expect(actualNewTitle).toEqual(newTitle)
+      })
+    })
+  })
+})
diff --git a/e2e/src/util.js b/e2e/src/util.js
index e8f4ece07b8c205a2d1ef967e8d2ab22aa076d40..00f524b8efafe85c114276d31988a51e6dad168c 100644
--- a/e2e/src/util.js
+++ b/e2e/src/util.js
@@ -210,6 +210,11 @@ class WdBase{
       )
       .perform()
   }
+
+  async execScript(fn, ...arg){
+    const result = await this._driver.executeScript(fn)
+    return result
+  }
 }
 
 class WdLayoutPage extends WdBase{
@@ -423,6 +428,59 @@ class WdLayoutPage extends WdBase{
     await this._getFavDatasetIcon().click()
     await this.wait(500)
   }
+
+  _getPinnedDatasetPanel(){
+    return this._driver
+      .findElement(
+        By.css('[aria-label="Pinned datasets panel"]')
+      )
+  }
+
+  async getPinnedDatasetsFromOpenedPanel(){
+    const list = await this._getPinnedDatasetPanel()
+      .findElements(
+        By.tagName('mat-list-item')
+      )
+
+    const returnArr = []
+    for (const el of list) {
+      const text = await _getTextFromWebElement(el)
+      returnArr.push(text)
+    }
+    return returnArr
+  }
+
+  async unpinNthDatasetFromOpenedPanel(index){
+    const list = await this._getPinnedDatasetPanel()
+      .findElements(
+        By.tagName('mat-list-item')
+      )
+
+    if (!list[index]) throw new Error(`index out of bound: ${index} in list with size ${list.length}`)
+    await list[index]
+      .findElement( By.css('[aria-label="Toggle pinning this dataset"]') )
+      .click()
+  }
+
+  _getWidgetPanel(title){
+    return this._driver.findElement( By.css(`[aria-label="Widget for ${title}"]`) )
+  }
+
+  async widgetPanelIsDispalyed(title){
+    try {
+      const isDisplayed = await this._getWidgetPanel(title).isDisplayed()
+      return isDisplayed
+    } catch (e) {
+      console.warn(`widgetPanelIsDisplayed error`, e)
+      return false
+    }
+  }
+
+  async closeWidgetByname(title){
+    await this._getWidgetPanel(title)
+      .findElement( By.css(`[aria-label="close"]`) )
+      .click()
+  }
 }
 
 class WdIavPage extends WdLayoutPage{
diff --git a/src/atlasViewer/atlasViewer.apiService.service.ts b/src/atlasViewer/atlasViewer.apiService.service.ts
index 6ee20f5ab6abdde3c7304fcda9486523992b6ce2..9be4720ba7d95a1f743d5fdc913c8bd33dffcc0c 100644
--- a/src/atlasViewer/atlasViewer.apiService.service.ts
+++ b/src/atlasViewer/atlasViewer.apiService.service.ts
@@ -1,9 +1,8 @@
 import {Injectable, NgZone} from "@angular/core";
 import { select, Store } from "@ngrx/store";
-import { Observable, Subscribable } from "rxjs";
+import { Observable } from "rxjs";
 import { distinctUntilChanged, map, filter, startWith } from "rxjs/operators";
 import { DialogService } from "src/services/dialogService.service";
-import { LoggingService } from "src/services/logging.service";
 import {
   DISABLE_PLUGIN_REGION_SELECTION,
   getLabelIndexMap,
@@ -13,8 +12,8 @@ import {
 } from "src/services/stateStore.service";
 import { ModalHandler } from "../util/pluginHandlerClasses/modalHandler";
 import { ToastHandler } from "../util/pluginHandlerClasses/toastHandler";
-import { IPluginManifest } from "./atlasViewer.pluginService.service";
-import {ENABLE_PLUGIN_REGION_SELECTION} from "src/services/state/uiState.store";
+import { IPluginManifest, PluginServices } from "./atlasViewer.pluginService.service";
+import { ENABLE_PLUGIN_REGION_SELECTION } from "src/services/state/uiState.store";
 
 declare let window
 
@@ -36,8 +35,8 @@ export class AtlasViewerAPIServices {
   constructor(
     private store: Store<IavRootStoreInterface>,
     private dialogService: DialogService,
-    private log: LoggingService,
     private zone: NgZone,
+    private pluginService: PluginServices,
   ) {
 
     this.loadedTemplates$ = this.store.pipe(
@@ -125,9 +124,14 @@ export class AtlasViewerAPIServices {
         /**
          * to be overwritten by atlas
          */
-        launchNewWidget: (_manifest) => {
-          return Promise.reject('Needs to be overwritted')
-        },
+        launchNewWidget: (manifest) => this.pluginService.launchNewWidget(manifest)
+          .then(() => {
+            // trigger change detection in Angular
+            // otherwise, model won't be updated until user input
+
+            /* eslint-disable-next-line @typescript-eslint/no-empty-function */
+            this.zone.run(() => {  })
+          }),
 
         getUserInput: config => this.dialogService.getUserInput(config) ,
         getUserConfirmation: config => this.dialogService.getUserConfirm(config),
@@ -156,13 +160,14 @@ export class AtlasViewerAPIServices {
         }
 
       },
-      pluginControl : {
-        loadExternalLibraries : () => Promise.reject('load External Library method not over written')
-        ,
-        unloadExternalLibraries : () => {
-          this.log.warn('unloadExternalLibrary method not overwritten by atlasviewer')
-        },
-      },
+      pluginControl: new Proxy({}, {
+        get: (_, prop) => {
+          if (prop === 'loadExternalLibraries') return this.pluginService.loadExternalLibraries
+          if (prop === 'unloadExternalLibraries') return this.pluginService.unloadExternalLibraries
+          if (typeof prop === 'string') return this.pluginService.pluginHandlersMap.get(prop)
+          return undefined
+        }
+      }) as any,
     }
     window.interactiveViewer = this.interactiveViewer
     this.init()
diff --git a/src/atlasViewer/atlasViewer.pluginService.service.ts b/src/atlasViewer/atlasViewer.pluginService.service.ts
index 3c02c791f16e49ca05e918594a2f77249d20ed87..b8d5903db5bdd5c4ad353090560bc8c4101e446b 100644
--- a/src/atlasViewer/atlasViewer.pluginService.service.ts
+++ b/src/atlasViewer/atlasViewer.pluginService.service.ts
@@ -1,8 +1,7 @@
 import { HttpClient } from '@angular/common/http'
-import { ComponentFactory, ComponentFactoryResolver, Injectable, NgZone, ViewContainerRef } from "@angular/core";
+import { ComponentFactory, ComponentFactoryResolver, Injectable, ViewContainerRef } from "@angular/core";
 import { PLUGINSTORE_ACTION_TYPES } from "src/services/state/pluginState.store";
 import { IavRootStoreInterface, isDefined } from 'src/services/stateStore.service'
-import { AtlasViewerAPIServices } from "./atlasViewer.apiService.service";
 import { PluginUnit } from "./pluginUnit/pluginUnit.component";
 import { WidgetServices } from "./widgetUnit/widgetService.service";
 
@@ -21,6 +20,11 @@ import { WidgetUnit } from "./widgetUnit/widgetUnit.component";
 
 export class PluginServices {
 
+  public pluginHandlersMap: Map<string, PluginHandler> = new Map()
+
+  public loadExternalLibraries: (libraries: string[]) => Promise<any> = () => Promise.reject(`fail to overwritten`)
+  public unloadExternalLibraries: (libraries: string[]) => void = () => { throw new Error(`failed to be overwritten`) }
+
   public fetchedPluginManifests: IPluginManifest[] = []
   public pluginViewContainerRef: ViewContainerRef
   public appendSrc: (script: HTMLElement) => void
@@ -34,13 +38,11 @@ export class PluginServices {
   public fetch: (url: string, httpOption?: any) => Promise<any> = (url, httpOption = {}) => this.http.get(url, httpOption).toPromise()
 
   constructor(
-    private apiService: AtlasViewerAPIServices,
     private constantService: AtlasViewerConstantsServices,
     private widgetService: WidgetServices,
     private cfr: ComponentFactoryResolver,
     private store: Store<IavRootStoreInterface>,
     private http: HttpClient,
-    zone: NgZone,
     private log: LoggingService,
   ) {
 
@@ -52,18 +54,6 @@ export class PluginServices {
     )
 
     this.pluginUnitFactory = this.cfr.resolveComponentFactory( PluginUnit )
-    this.apiService.interactiveViewer.uiHandle.launchNewWidget = (arg) => {
-
-      return this.launchNewWidget(arg)
-        .then(arg2 => {
-          // trigger change detection in Angular
-          // otherwise, model won't be updated until user input
-
-          /* eslint-disable-next-line @typescript-eslint/no-empty-function */
-          zone.run(() => {  })
-          return arg2
-        })
-    }
 
     /**
      * TODO convert to rxjs streams, instead of Promise.all
@@ -216,7 +206,7 @@ export class PluginServices {
         */
 
         const handler = new PluginHandler()
-        this.apiService.interactiveViewer.pluginControl[plugin.name] = handler
+        this.pluginHandlersMap.set(plugin.name, handler)
 
         /**
          * define the handler properties prior to appending plugin script
@@ -289,7 +279,7 @@ export class PluginServices {
 
         handler.onShutdown(() => {
           unsubscribeOnPluginDestroy.forEach(s => s.unsubscribe())
-          delete this.apiService.interactiveViewer.pluginControl[plugin.name]
+          this.pluginHandlersMap.delete(plugin.name)
           this.mapPluginNameToWidgetUnit.delete(plugin.name)
         })
 
diff --git a/src/atlasViewer/widgetUnit/widgetService.service.ts b/src/atlasViewer/widgetUnit/widgetService.service.ts
index b784a431b7753af0ce54ddfc0bc30bf9a1844136..6a9ff82d54ebc76424bed0534190e62137d39d6c 100644
--- a/src/atlasViewer/widgetUnit/widgetService.service.ts
+++ b/src/atlasViewer/widgetUnit/widgetService.service.ts
@@ -92,8 +92,13 @@ export class WidgetServices implements OnDestroy {
     if (component.constructor === Error) {
       throw component
     } else {
-      const _component = (component as ComponentRef<WidgetUnit>);
+      const _component = (component as ComponentRef<WidgetUnit>)
+
+      // guestComponentRef
+      // insert view
       _component.instance.container.insert( guestComponentRef.hostView )
+      // on host destroy, destroy guest
+      _component.onDestroy(() => guestComponentRef.destroy())
 
       /* programmatic DI */
       _component.instance.widgetServices = this
diff --git a/src/atlasViewer/widgetUnit/widgetUnit.template.html b/src/atlasViewer/widgetUnit/widgetUnit.template.html
index de35f7f2a2ee8c0c57dcb50db74ce0521dfdce62..73a764fbc3d39199064a127e2b5b29c3ad73f906 100644
--- a/src/atlasViewer/widgetUnit/widgetUnit.template.html
+++ b/src/atlasViewer/widgetUnit/widgetUnit.template.html
@@ -1,4 +1,5 @@
 <panel-component
+  [attr.aria-label]="'Widget for ' + title"
   widgetUnitPanel
   [ngClass]="{'blinkOn': blinkOn}"
   [bodyCollapsable] = "state === 'docked'"
@@ -37,6 +38,7 @@
       </ng-container>
 
       <i *ngIf="exitable"
+        aria-label="close"
         (click)="exit($event)" 
         class="fas fa-times" 
         [hoverable] ="hoverableConfig"></i>
diff --git a/src/ui/databrowserModule/databrowser.useEffect.ts b/src/ui/databrowserModule/databrowser.useEffect.ts
index b30394864e479998842c00434ed2157320ec9f44..00e9e3a347a1ce9398659e3f7bda214f9c873586 100644
--- a/src/ui/databrowserModule/databrowser.useEffect.ts
+++ b/src/ui/databrowserModule/databrowser.useEffect.ts
@@ -75,7 +75,7 @@ export class DataBrowserUseEffect implements OnDestroy {
       ).subscribe(({ datasetId, filename }) => {
         
         // TODO replace with common/util/getIdFromFullId
-        
+        // TODO replace with widgetService.open
         const re = getKgSchemaIdFromFullId(datasetId)
         this.dialog.open(
           PreviewComponentWrapper,
diff --git a/src/util/directives/pluginFactory.directive.ts b/src/util/directives/pluginFactory.directive.ts
index eb40319fb85ad6028ddfa681540ff07b4813efef..5ca5401d638cd8f9a46f8eb40fab485c9cc3ef4b 100644
--- a/src/util/directives/pluginFactory.directive.ts
+++ b/src/util/directives/pluginFactory.directive.ts
@@ -1,5 +1,4 @@
 import { Directive, Renderer2, ViewContainerRef } from "@angular/core";
-import { AtlasViewerAPIServices } from "src/atlasViewer/atlasViewer.apiService.service";
 import { SUPPORT_LIBRARY_MAP } from "src/atlasViewer/atlasViewer.constantService.service";
 import { PluginServices } from "src/atlasViewer/atlasViewer.pluginService.service";
 import { LoggingService } from "src/services/logging.service";
@@ -12,68 +11,70 @@ export class PluginFactoryDirective {
   constructor(
     pluginService: PluginServices,
     viewContainerRef: ViewContainerRef,
-    rd2: Renderer2,
-    apiService: AtlasViewerAPIServices,
+    private rd2: Renderer2,
     private log: LoggingService,
   ) {
+    pluginService.loadExternalLibraries = this.loadExternalLibraries.bind(this)
+    pluginService.unloadExternalLibraries = this.unloadExternalLibraries.bind(this)
     pluginService.pluginViewContainerRef = viewContainerRef
     pluginService.appendSrc = (src: HTMLElement) => rd2.appendChild(document.head, src)
     pluginService.removeSrc = (src: HTMLElement) => rd2.removeChild(document.head, src)
+  }
+
+  private loadedLibraries: Map<string, {counter: number, src: HTMLElement|null}> = new Map()
+  
+  loadExternalLibraries(libraries: string[]) {
+    const srcHTMLElement = libraries.map(libraryName => ({
+      name: libraryName,
+      srcEl: SUPPORT_LIBRARY_MAP.get(libraryName),
+    }))
 
-    apiService.interactiveViewer.pluginControl.loadExternalLibraries = (libraries: string[]) => new Promise((resolve, reject) => {
-      const srcHTMLElement = libraries.map(libraryName => ({
-        name: libraryName,
-        srcEl: SUPPORT_LIBRARY_MAP.get(libraryName),
-      }))
+    const rejected = srcHTMLElement.filter(scriptObj => scriptObj.srcEl === null)
+    if (rejected.length > 0) {
+      return Promise.reject(`Some library names cannot be recognised. No libraries were loaded: ${rejected.map(srcObj => srcObj.name).join(', ')}`)
+    }
 
-      const rejected = srcHTMLElement.filter(scriptObj => scriptObj.srcEl === null)
-      if (rejected.length > 0) {
-        return reject(`Some library names cannot be recognised. No libraries were loaded: ${rejected.map(srcObj => srcObj.name).join(', ')}`)
+    return Promise.all(srcHTMLElement.map(scriptObj => new Promise((rs, rj) => {
+      /**
+       * if browser already support customElements, do not append polyfill
+       */
+      if ('customElements' in window && scriptObj.name === 'webcomponentsLite') {
+        return rs()
       }
+      const existingEntry = this.loadedLibraries.get(scriptObj.name)
+      if (existingEntry) {
+        this.loadedLibraries.set(scriptObj.name, { counter: existingEntry.counter + 1, src: existingEntry.src })
+        rs()
+      } else {
+        const srcEl = scriptObj.srcEl
+        srcEl.onload = () => rs()
+        srcEl.onerror = (e: any) => rj(e)
+        this.rd2.appendChild(document.head, srcEl)
+        this.loadedLibraries.set(scriptObj.name, { counter: 1, src: srcEl })
+      }
+    })))
+  }
 
-      Promise.all(srcHTMLElement.map(scriptObj => new Promise((rs, rj) => {
-        /**
-         * if browser already support customElements, do not append polyfill
-         */
-        if ('customElements' in window && scriptObj.name === 'webcomponentsLite') {
-          return rs()
+  unloadExternalLibraries(libraries: string[]) {
+    libraries
+      .filter((stringname) => SUPPORT_LIBRARY_MAP.get(stringname) !== null)
+      .forEach(libname => {
+        const ledger = this.loadedLibraries.get(libname)
+        if (!ledger) {
+          this.log.warn('unload external libraries error. cannot find ledger entry...', libname, this.loadedLibraries)
+          return
         }
-        const existingEntry = apiService.loadedLibraries.get(scriptObj.name)
-        if (existingEntry) {
-          apiService.loadedLibraries.set(scriptObj.name, { counter: existingEntry.counter + 1, src: existingEntry.src })
-          rs()
-        } else {
-          const srcEl = scriptObj.srcEl
-          srcEl.onload = () => rs()
-          srcEl.onerror = (e: any) => rj(e)
-          rd2.appendChild(document.head, srcEl)
-          apiService.loadedLibraries.set(scriptObj.name, { counter: 1, src: srcEl })
+        if (ledger.src === null) {
+          this.log.log('webcomponents is native supported. no library needs to be unloaded')
+          return
         }
-      })))
-        .then(() => resolve())
-        .catch(e => (this.log.warn(e), reject(e)))
-    })
-
-    apiService.interactiveViewer.pluginControl.unloadExternalLibraries = (libraries: string[]) =>
-      libraries
-        .filter((stringname) => SUPPORT_LIBRARY_MAP.get(stringname) !== null)
-        .forEach(libname => {
-          const ledger = apiService.loadedLibraries.get(libname)
-          if (!ledger) {
-            this.log.warn('unload external libraries error. cannot find ledger entry...', libname, apiService.loadedLibraries)
-            return
-          }
-          if (ledger.src === null) {
-            this.log.log('webcomponents is native supported. no library needs to be unloaded')
-            return
-          }
 
-          if (ledger.counter - 1 == 0) {
-            rd2.removeChild(document.head, ledger.src)
-            apiService.loadedLibraries.delete(libname)
-          } else {
-            apiService.loadedLibraries.set(libname, { counter: ledger.counter - 1, src: ledger.src })
-          }
-        })
+        if (ledger.counter - 1 == 0) {
+          this.rd2.removeChild(document.head, ledger.src)
+          this.loadedLibraries.delete(libname)
+        } else {
+          this.loadedLibraries.set(libname, { counter: ledger.counter - 1, src: ledger.src })
+        }
+      })
   }
 }