Adding Non-WebMap Angular Forms

The following section will guide you through this case scenario, in which you would need to add a non-WebMap Angular form to an existing migrated Angular project. This means that the developer can add new forms that don't use the WebMap model paradigm and launch them from the existing WebMap migrated app.

The initial assumption is that the developer already has access to a migrated WebMap Angular project, and the developer already implemented on the project a FrontEnd click handler in a menu item (just like the one described in the existing post on this site).

The first step is to create an Angular component named NoWebMapFormsContainerComponent. This component will be in charge of handling the open and close of the new forms that don't use the WebMap mechanism.

/***********************************************************************
 * Copyright (C) Mobilize.Net <info@mobilize.net> - All Rights Reserved
 *
 * This file is part of the Mobilize Frameworks, which is
 * proprietary and confidential.
 *
 * NOTICE:  All information contained herein is, and remains
 * the property of Mobilize.Net Corporation.
 * The intellectual and technical concepts contained herein are
 * proprietary to Mobilize.Net Corporation and may be covered
 * by U.S. Patents, and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained
 * from Mobilize.Net Corporation.
 *
 * This file is subject to the terms and conditions defined in
 * file 'LICENSE.txt', which is part of this source code package.
 ***********************************************************************/
 import {
  ChangeDetectorRef,
  Component,
  ComponentFactoryResolver,
  ComponentRef,
  OnDestroy,
  OnInit,
  ViewChild,
} from '@angular/core';
import { WebMapService, WMConstants } from '@mobilize/angularclient';
import { WMLogger } from '@mobilize/logging';
import { ContainerDirective } from '@mobilize/base-components';
import { TypeResolver } from '@mobilize/base-components';
import { ErrorCodes, ExceptionHandlerClass } from '@mobilize/webmap-core';

@Component({
  selector: 'wm-nowebmapformscontainer',
  styleUrls: ['./no-web-map-forms-container.component.css'],
  templateUrl: './no-web-map-forms-container.component.html'
})
@ExceptionHandlerClass(ErrorCodes.BaseComponents)
export class NoWebMapFormsContainerComponent implements OnInit, OnDestroy {
  constructor(
    private changeDetector: ChangeDetectorRef,
    private componentFactoryResolver: ComponentFactoryResolver,
    private webmapService: WebMapService
  ) {}

  @ViewChild(ContainerDirective, { static: true })
  componentContainer: ContainerDirective;

  private openedComponents = {};

  private events = [];

  /* istanbul ignore next */
  ngOnInit(): void {
    this.events.push(
      this.webmapService.core.getEvent().subscribe('showForm', (url) => {
        this.navigateToForm(url);
      })
    );
    this.events.push(
      this.webmapService.core.getEvent().subscribe('closeForm', (url) => {
        this.disposeComponent(url);
      })
    );
  }

  /**
   * Load a new component inside the formsContainerComponent
   * @param url the url object of the component.
   */
  /* istanbul ignore next */
  navigateToForm(id: any): void {
      const componentType = TypeResolver.getType(id);
      /* istanbul ignore else */
      if (!componentType) {
        WMLogger.instance()
          .error(`There is not a type registered with the name ${id} in the TypeResolver.
        Please check that the name is well written or the dataTransfer is the same in the backend`);
        return;
      }
      this.loadComponent(componentType, id);
  }

  /**
   * Creates a new component instance inside the formscontainer component
   * @param componentToLoad The new component to load
   * @param name The name of the component.
   */
  /* istanbul ignore next */
  loadComponent(componentToLoad: any, id: any): ComponentRef<any> {
    const componentFactory =
      this.componentFactoryResolver.resolveComponentFactory(componentToLoad);
    const viewContainerRef = this.componentContainer.viewContainerRef;
    const componentRef = viewContainerRef.createComponent(
      componentFactory
    ) as ComponentRef<any>;
    if (componentRef.instance.afterLoadComponent) {
      componentRef.instance.afterLoadComponent();
    }
    this.openedComponents[id] = componentRef;
    return componentRef;
  }

  /**
   *  Removes a component inside the formsContainerComponent
   * @param url the url object of the component.
   */
  /* istanbul ignore next */
  disposeComponent(id: any): void {
    if (id && this.openedComponents[id] && this.openedComponents[id].hostView) {
      const viewContainerRef = this.componentContainer.viewContainerRef;
      const vcrIndex = viewContainerRef.indexOf(
        this.openedComponents[id].hostView
      );
      try {
        viewContainerRef.remove(vcrIndex);
      } catch (e) {
        // Avoid triggering synchronization error while deleting the component.
        WMLogger.instance().debug(e);
      }
      delete this.openedComponents[id];
    } else {
      WMLogger.instance().debug(`The view ${id} to be removed was not found:
      ${
        this.openedComponents[id]
          ? 'HostView property undefined.'
          : ' The view is not present in the openedComponents collection.'
      }`);
    }
  }

  /**
   * Executes on component destroy to unsubscribe the events
   */
  ngOnDestroy(): void {
    this.events.forEach((evnt) => {
      this.webmapService.core.getEvent().unSubscribe(evnt);
    });
  }
}

Notice how, inside of the ngOnInit method. the showForm and closeForm events are subscribed to the WebMap service. These subscriptions are the ones that handle the open/close of the non-WebMap forms.

The second step is to create a new Angular form to add (for this example, we will call the new angular component "heroes").

To accomplish this, first create the Hero interface:

export interface Hero {
    id: number;
    name: string;
}

Then, create a file called mock-heroes.ts that contains the Heroes list constant.

import { Hero } from './hero';

export const HEROES: Hero[] = [
  { id: 12, name: 'Dr. Nice' },
  { id: 13, name: 'Bombasto' },
  { id: 14, name: 'Celeritas' },
  { id: 15, name: 'Magneta' },
  { id: 16, name: 'RubberMan' },
  { id: 17, name: 'Dynama' },
  { id: 18, name: 'Dr. IQ' },
  { id: 19, name: 'Magma' },
  { id: 20, name: 'Tornado' }
];

The following elements must be added in heroes.components.ts :

  1. Add the WebMap dataTransfer decoration. The dataTransfer defines a unique identifier for the form. This will be used later by the nowebmapformscontainercomponent to know which form should be opened or closed.

  2. Inject the WebMapService to the form. This service will be used to trigger the closeForm event from the form.

import { Component } from '@angular/core';
import { Hero } from '../../hero';
import { HEROES } from '../../mock-heroes';
import { dataTransfer} from "@mobilize/base-components";
import { WebMapService} from "@mobilize/angularclient";

@Component({
  selector: 'heroes',
  templateUrl: './heroes.component.html',
  styleUrls: ['./heroes.component.css']
})
@dataTransfer(["HeroesComponent"])
export class HeroesComponent {
  constructor(private wmservice : WebMapService) {}

  heroes = HEROES;
  selectedHero?: Hero;

  onSelect(hero: Hero): void {
    this.selectedHero = hero;
  }

  close() {
    this.wmservice.core.getEvent().publish('closeForm', 'HeroesComponent');
  }
}

Once both components are created, the third step is to include them in the app.module.ts file. Besides, the selector of the nowebmapformscontainercomponent should be added to the app.component.html.

The non-WebMap component should be added in both declarations and bootstrap sections.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule, CUSTOM_ELEMENTS_SCHEMA, NgZone, ChangeDetectorRef} from '@angular/core';
import { AppComponent } from './app.component';
import { BaseComponentsModule } from '@mobilize/base-components';
import { WebMapKendoModule } from '@mobilize/winforms-components';
import { WebMapService, WebMapModule } from '@mobilize/angularclient';
import { HeroesComponent } from './components/heroes/heroes.component';
import { NoWebMapFormsContainerComponent } from './components/no-web-map-forms-container/no-web-map-forms-container.component';
import { WindowModule } from "@progress/kendo-angular-dialog";
@NgModule({
  declarations: [
    AppComponent,
    HeroesComponent,
    NoWebMapFormsContainerComponent,
  ],
  imports: [
    BrowserModule,
    BaseComponentsModule,
    WebMapKendoModule,
    WebMapModule,
    WindowModule,
  ],
  providers: [WebMapService  ],
  bootstrap: [AppComponent, HeroesComponent],
  schemas: [ CUSTOM_ELEMENTS_SCHEMA ]
})
export class AppModule { }

Using a FrontEnd handler just like the one described in the last post, the developer can open a Non-WebMap form in the following way:

WebMapService.currentService.core.getEvent().publish('showForm', 'HeroesComponent');

Please notice that opening the form just consists of publishing the showForm event and passing the data transfer of the form the developer wants to open.

Closing the form works in a similar way. For example, on the close method of the HeroesComponent you will find that the closeForm event is published passing the datatransfer of the HeroesComponent to close it.

To test this example, you must compile the angular project with all the changes described before and run the app. Then, you will have to click on the element with the frontend handler where the publish of the showForm event was added.

Last updated