Skip to content

Angular Integration

Jochen Kühner edited this page Jun 18, 2020 · 1 revision

Angular integration

Some notes on integrating Dock Spawn TS into an Angular Project

Project Configuration

NPM Setup

Add the module to your project:

npm i --save dock-spawn-ts

angular.json, package.json etc.

No updates, the classes are simply directly imported into any Angular components where you are are working with Dock Spawn TS.

style.css

The Dock Spawn TS CSS rules must be imported, this can easily by done by adding an @import to the style.css file:

@import "../node_modules/dock-spawn-ts/lib/css/dock-manager.css"; @import "../node_modules/dock-spawn-ts/lib/css/dock-manager-style.css";

Component Template Updates

Your angular application will have route such as / or /ide where you are are using Dock Spawn TS along with menu bars etc to present your main user interface. That component will need have its template updated to include <divs> for Dock Spawn TS to use, and that component will have to import and use Dock Spawn TS classes to actually setup those which are used to host the Dock Spawn TS windows.

The template for that main GUI component should include the that will be used to host the panels for Dock Spawn TS. For example, if your main GUI component is named MainPageComponent, you might have a file main.page.component.html template like:

...
<div id="dock_div" >  
  
  <div id="my_dock_manager" class="my-dock-manager" style="position: relative;">  
  </div>  
  
  <div id="solution_window"  
  class="solution-window window-panel"  
  hidden>  
    <div id="projecttree" style="overflow: hidden;"></div>  
  </div>
  ...

Your component template must include enough of the Dock Spawn TS style <div> elements to create panels as needed (similar to the example on the Dock Spawn TS homepage).

Component Updates

Continuing the above example, you would also have a main.page.component.ts file, with the Angular code for your main page, is must be updated to actually setup the <divs> to be Dock Span TS panels.

Importing

A minimum set of imports might be:

import { DockNode }          from 'dock-spawn-ts/lib/js/DockNode';  
import { DockManager }       from 'dock-spawn-ts/lib/js/DockManager';  
import { PanelContainer }    from 'dock-spawn-ts/lib/js/PanelContainer';  
import { DockConfig }        from 'dock-spawn-ts/lib/js/DockConfig';

If your component uses more advanced features, you may need more imports! To get started though, these are enough to create panels similar to the example on the Dock Spawn TS home page.

The HTML template for the component is rendered (DOM created) by the time your component's ngInit() or ngAfterViewInit() methods are called. You can easily setup Dock Spawn TS panels in either of these methods. Just ensure you are are doing an import and implements on the appropriate directive.

...
import { OnIniit } from '@angular/core';
...
import * as $      from 'jquery';  /* if using jQuery */
...
export class MainPageComponent implements ..., OnInit {
...

  ngOnInit(): void {

    ...
    
    /* ok time for some action! Use jQuery to fetch the hosting <divs> */
 
    const divDockContainer = $('#dock_div');  
    const divDockManager = $('#my_dock_manager');  
  
    /* configure options for Dock Spawn TS */
    
    const config: DockConfig = new DockConfig();  
    config.moveOnlyWithinDockConatiner = true;  
  
    /* 
     * create the Dock Spawn TS manager (and save reference for later) 
     *
     * Notice also that .get(0) is used to convert from a jQuery selector
     * result list to a DOM element, because Dock Spawn TS works with DOM
     * elements.
     *
     */
    
    this.dockManager = new DockManager(divDockManager.get(0), config);  
    this.dockManager.initialize();  
  
    /* 
     * Let the dock manager element fill in the entire component's space,
     * and keep on filling it even when it changes size.
     *   
     */  
  
    window.onresize = () => {  
      this.dockManager.resize(  
        divDockContainer.get(0).clientWidth,  
        divDockContainer.get(0).clientHeight  
      );  
    };      
    window.onresize(null);
    
    /*
     * to finish the setup, you will need to create panels and dock them,
     * just like the example on the Dock Spawn TS home page.  The steps 
     * for doing that will be very specific to your application.  As an 
     * example; maybe your application has a window manager and factory for 
     * generating windows and internally they have Dock Spawn TS panels. 
     * In that approach you would use your factory to build out your GUI.
     *  
     * Some examples are included below for working with dynamically created
     * panels and Angular components, it should be enough to get you going,
     * regardless of your overall architecture.
     *  
     */



    ... /* rest of your initialization */
  }
...

}

Creating Panels

The general steps for creating a panel; find the <div> being used to host it..create it, and dock it. For example:

/* 
 * which kind of panel is being created?  Here the enclosing class,
 *  has a field named 'tabbed', which flags which kind of panel it is.
 * 
 */
 
 const pType: PanelType = 
   this.tabbed ? PanelType.document : PanelType.panel;  

/*
 * create the panel and save its reference for later use.  Also, the
 * enclosing class already has a reference to the document manager (the 
 * field named '_manager'), and already has a panel title (the field 
 * named 'title'). 
 * 
 * For locating the <div> that will host the panel, jQuery is used to 
 * find it (the variable 'id' has the #<id> value), also note that 
 * .get(0) is used to convert from a jQuery selector result list...to 
 * an actual DOM element, because Dock Spawn TS works with DOM 
 * elements.
 *
 */

this._panel = 
  new PanelContainer(
    $(id).get(0), 
    this._manager, 
    this.title, 
    pType);
/*
 * now that we have a panel...we can dock it :)  In this example, 
 * the enclosing class already has as reference to the document
 * manager and we can usue that to get the node we are docking the 
 * panel in.  In this example we are givin the panel a fifth of the
 * IDE space on the left side.
 * 
 */
 
 let documentNode = this.dockManager.context.model.documentManagerNode;
 this.dockManager.dockLeft(documentNode, this.panel, 0.20);

Once you have created and docked your panel, Dock Spawn TS will move your content <div> to being a child inside the DOM parents that make up a panel. For example, from above, one of the panels was the #solution_window div. If $(id) above had been #solution_window, then after the creating and docking above, the DOM will have a new structure:

<div id="doc_div">
   ...
  <div class="panel-base" ...> /* outer wrapper for your panel */
    <div class="panel-titlebar ...> /* the header or tab */
    <div class="panel-content" ...>
      <div id="solution_window" ...>  /* your content hosting div */

Notice that your panel has been moved to be a child of the DOM elements that are all managed by Dock Spawn TS, to allow for resizing of panels, drag and drop etc.

If your hosting div (i.e. #solution_window) already had its content defined...then you are done! :)

Creating Panels with Dynamic Content

Once you have created and docked your panel, you may still have to define the content of the window. In some applications you can't define the content ahead of time; perhaps its loaded from a database or is configured by user preferences etc.

Creating your content dynamically is not so much more difficult, but you may need to be careful about timing; inserting new DOM elements doesn't necessarily mean they are ready right away. Your framework or your browser may delay when the DOM is actually ready.

To handle the potential timing issues, you can add dynamic content with these steps:

  • First find the "hosting" <div> , where you will insert your content, continuing our example, we can use #solution_window to find the <div> to insert into.
  • Add your content, with jQuery this might be using the .html(...) method.
  • Pick some part of the content you added, doesn't matter which part, give it an #<id>, and then pool for it to be present and ready to use.
  • Once your existence checking loop is done...you can safely go ahead and add click handlers or do other operations...such as creating Angular components in your new panel.

Some example code:

/* use jQuery to dynamically add a button */

$('#solution_window').
  html(`<div id='button_wrap'><button>Click Me</button></div>`
  
/* now wait for it to be ready, before we try to operate on it... */

const checkExist = setInterval(() => {  
  
  const dom: HTMLElement = 
    document.getElementById(`#button_wrap`) as HTMLElement;  
  
  if (dom) {  

    /* we're done polling, its ready */
      
    clearInterval(checkExist);  
 
    $(`#button_wrap button`).on('click', () => {
    
      alert(`Clicked!!`);
    });
  }
    /* wait 100ms before we check again */
    
}, 100);

For setting up your panel, other steps you might want to take:

  • Update the title, and/or the icon
  • Use CSS or JavaScript to set the sizing of your content inside the panel; scrolling or no scrolling etc.
  • Notify the application or other panels that this panel is now available
  • Load from previous session
  • Configure via user preferences

If you are working in Angular there is still another step you'll need to take...dynamic creation of an angular component. An example of this is given below.

Dynamic Content with Angular Components

In the examples above, a panel was created, and docked, and dynamic content was even added and used. But still, this is not enough to work with arbitrary Angular components. With a little more code you can easily host any existing Angular component inside the panel you created.

You will not have to modify the existing angular component, but you will need to add some bridging code that will handle the steps of creating the component and also moving it to be a child of the DOM inside your panel. This last step of re-parenting makes sure the geometry of your component matches the panel...and when the dragging and dropping starts, the component goes wherever your panel goes :)

Lets say you have an existing angular component MyComponent, in a file my.component.ts (perhaps it also has a template file and other resources as well). You can't insert it directly into the Dock Spawn TS based panel...because your panel is not Angular, specifically its not an Angular View; in Angular everything is a hierarchy of views. So, when you used Dock Spawn TS...you went outside of the hierarchy, and now there is no where to insert (as a child) MyComponent.

This isn't actually a problem; DOM is DOM....you can happily have Angular based DOM and other DOM on the same web page. To insert MyComponent, the general steps are:

  • Use an Angular component factory to create the component, beneath the root of the view hierarchy. This is the wrong place in the DOM...but we'll fix that...
  • Move the component (re-parent) so that its sitting inside your panel's DOM (as a child)

Once those steps are complete...your Angular component will act as a normal Angular component...but it will be visible inside a Dock Spawn TS panel (tabbed or regular).

There is a little plumbing to do as well; any component (and child components) that you are creating dynamically, must be declared as dynamic in your module settings, so in app.module.ts:

import { MyComponent }     from 'my.component';```
...

@NgModule({
declarations: [  
  AppComponent,
  ...
  MyComponent,
  ...
],
entryComponents: [  
  MyComponent,
],
...

Adding your component to entryComponents is the key step; it tells Angular to create a component factory for your component when your application is built. This is what allows you to create your component on the fly from anywhere in your application.

Next you need to add the bridging code that will actually create the component on the fly.

To continue the example above, inside the #solution_window panel, using the dynamic content creation method above, we will add a simple <div> to use as the anchor for the Angular component:

/* create a simple <div> to use as our anchor point */

$('#solution_window').
  html(`<div id='component_anchor'></div>`
  
/* now wait for it to be ready, before we try to create the component... */

const checkExist = setInterval(() => {  
  
  const dom: HTMLElement = 
    document.getElementById(`#component_anchor`) as HTMLElement;  
  
  if (dom) {  

    /* we're done polling, its ready */
      
    clearInterval(checkExist);  
 
    /* 
     * before we can do anything, we need a reference to the root 
     * view of page; or at least some Angular componenent.  Here, 
     * the enclosing class already has a field (named 'view') that
     * has a reference to the view.  That refernece was obtained 
     * from a component that wraps the whole page and is the root
     * componenent for the URL '/' or '/ide'...basically is the main
     * outer GUI component.  
     * 
     * On that one...we used constructor injection:
     *    
     *   constructor(private view: ViewContainerRef) {...}
     * 
     * to get a ViewContainerRef...that ref can now be used 
     * here as a surrogate for creating our dynamic component.
     * 
     * If you need to query the componenent you are creating 
     * from some parent component (say with @ViewChild)...use 
     * that component as the surrogate view.
     */
     
    const view: ViewContainerRef = this.view;  

    /* 
     * similarly, we picked some existing angular component to 
     * use constructor injection on to get a reference to the 
     * component factory in Angular.
     *  
     * If your application is such that you can't reach this 
     * code from an Angular component, just use a service as 
     * the bridge; pick a component to use for injecting your 
     * view and factory refs...pass them to a service, and then
     * access them from the service in this code.  Services can
     * be injected into any ordinary Type Script class as well 
     * as Angular component classes, so they are a natural bridge.
     *  
     */
     
    const factory: ComponentFactoryResolver = this.componentResolver;  
  
    /* ok, time to actually create the component ... */
     
    const creator      = factory.resolveComponentFactory(MyComponent);  
    const componentRef = view.createComponent(creator);  
  
    /* 
     * notice we used the component factory to create it, and it 
     * was created  in the surrogate view.  So its not going to be 
     * in the right location in the DOM.  But, it is there. :)
     * 
     * Finally capture a reference to the Angular component, but 
     * properly typed, so twe can naturally call its method and use
     * its public fields. 
     */ 

    this.myComponent  = componentRef.instance as StringViewerComponent;  
  
    /* 
     * final step, relocate it, so that its a child of our Dock Spawn
     * TS panel content...
     *  
     */
     
    const domElem = 
      (componentRef.hostView as EmbeddedViewRef<any>).rootNodes[0]
        as HTMLElement;  
  
    dom.appendChild(domElem);

    /* 
     * thats it! The Angular component is now visible and running 
     * insdie the Dock Spawn TS panel.
     *  
     */

  }
    
  /* wait 100ms before we check again */
    
}, 100);