Cesium.Viewer inside an Angular web app

Hi to all, I’m developing an Angular web app that uses Cesium on Front End.
I’ve a doubt about management of Cesium.Viewer instance inside FE.
I created an Angular directive and inside it (ngOnInit) I make the instance of the Cesium.Viewer
The Angular Cesium directive is used inside the Home component.
I noticed that every time I change page on FE and come back to Home the Cesium.Viewer is created again.
Is there some mechanism to avoid this?
My goal is to have a unique instance of Cesium.Viewer (such as Singleton) created once and “shared” among Angular components of the web app.
Thanks to all.

Hello @epok82 :wave:

You can keep the instance of the Cesium object inside an Angular service and use that service inside your directive. In this way, if the service is provided from the root injector, your Cesium object will be a singleton.

Hi Aristeidis, thanks a lot for your reply.
I followed your tip but second time I called the Angular service, the Cesium map is empty.
May be something wrong in my code; the Element sent to Viewer only the first time?
This is my code.
Thanks for your help.

Angular directive

@Directive({
  selector: '[appCesium]'
})
export class CesiumDirective implements OnInit {
  viewer: Viewer | null = null;
  constructor(private el: ElementRef, private cesiumService: CesiumService) {
  }
  ngAfterViewInit(): void {
    this.viewer = this.cesiumService.createCesiumViewer(this.el);
  }
}

Angular service

@Injectable({
    providedIn: 'root',
})
export class CesiumService {
    firstCall: boolean = true;
    cesiumViewer: Viewer | null = null;

    public createCesiumViewer(el: ElementRef): Viewer | null {
        if (true == this.firstCall) {
            this.firstCall = false;
            this.cesiumViewer = new Cesium.Viewer(el.nativeElement,
                {
                    timeline: false,
                    animation: false,
                    infoBox: false,
                    fullscreenButton: false,
                    baseLayerPicker: false,
                    geocoder: false,
                    homeButton: false,
                    navigationHelpButton: false,
                    navigationInstructionsInitiallyVisible: false,
                    scene3DOnly: true
                }
            );
        }
        return this.cesiumViewer;
    }
}

You are right! This is not going to work because when you leave the page the HTML element represented by the el.nativeElement property is removed from the DOM. Next time you visit the same page, el.nativeElement is a new HTML element added to the DOM. So, the viewer remains anchored in an HTML element that technically does not exist.

However, you can try to add the HTML element of the cesium viewer manually using the Renderer2 service from Angular:

cesium.directive.ts

import { Directive, ElementRef, AfterViewInit, Renderer2 } from '@angular/core';
import { Viewer } from 'cesium';
import { CesiumService } from './cesium.service';

@Directive({
  selector: '[appCesium]'
})
export class CesiumDirective implements AfterViewInit {

  viewer: Viewer | null = null;
  constructor(private el: ElementRef, private cesiumService: CesiumService, private renderer: Renderer2) {
  }
  ngAfterViewInit(): void {
    this.viewer = this.cesiumService.createCesiumViewer(this.el, this.renderer);
  }

}

cesium.service.ts

import { Injectable, ElementRef, Renderer2 } from '@angular/core';
import { Viewer } from 'cesium';

@Injectable({
  providedIn: 'root',
})
export class CesiumService {
  firstCall: boolean = true;
  cesiumViewer: Viewer | null = null;

  public createCesiumViewer(el: ElementRef, renderer: Renderer2): Viewer | null {
      if (true == this.firstCall) {
          this.firstCall = false;
          this.cesiumViewer = new Viewer(el.nativeElement,
              {
                  timeline: false,
                  animation: false,
                  infoBox: false,
                  fullscreenButton: false,
                  baseLayerPicker: false,
                  geocoder: false,
                  homeButton: false,
                  navigationHelpButton: false,
                  navigationInstructionsInitiallyVisible: false,
                  scene3DOnly: true
              }
          );
      } else {
        renderer.appendChild(el.nativeElement, this.cesiumViewer?.container?.firstChild);
      }
      return this.cesiumViewer;
  }
}

Thanks Aristeidis for your tip.
I’ve tried it but unfortunately it does not work.
After coming back to Cesium Viewer (second time it was created), only Cesium credit disclaimer is shown on the top left corner of the page and no map is shown.

Hey @epok82 :wave:

Can you provide a reproducable demo for your case to have a more detailed view? You can follow the steps in this post Troubleshooting Cesium and Angular apps to create one.