Integration Basics: Integrating BabylonJS 3D Engine Into An Angular Business Application – Part 1

In this two parts article, I will demonstrate how easy it can be to integrate a 3D engine like BabylonJS into a business application built with a SPA framework like Angular. You will see how those two frameworks can interact with each other and where possible performance pitfalls may occur.

In diesem Artikel:

mm_300x300
Max Schulte ist Consultant bei Thinktecture mit dem Schwerpunkt auf Angular und 2D/3D Visualisierung.

What to expect in part 1

This part is about the integration basics of BabylonJS and Angular. I will show you how you can use your existing knowledge of Angular and combine it with a graphics engine to create a compelling and modern user experience. You can find the performance-related aspects in part 2.

Integration Demo, BabylonJS and Angular working together

Take a look at the demo below, this is what we will discuss here. More specific, I will demonstrate how to implement the first tab (unoptimized), the other versions are discussed in part 2.

A Primer on BabylonJS

BabylonJS is a 2D/3D engine for the web and is based on the WebGL standard. It is possible to create complex visualizations, configurator tools, or games that run blazingly fast and are easy to use.

In contrast to other engines like Unity or Unreal, content created with BabylonJS is written in JavaScript or TypeScript. This means that it runs natively in the browser and therefore we can enter, manipulate, and extend the graphic content generation at any time. This is key for an easy integration between any graphics engine and SPA frameworks.

Where to start

Angular is a well-known application framework and you surely know how to use it. However, you might not yet know the intrinsics and basics of BabylonJS. BabylonJS provides us with a number of concepts and API interfaces that we need to integrate with our Angular code. The most important objects in the API are Engine and Scene. A closer look will show that they are similar to Angular services and components, concept-wise.

Before we start, we have to make a few assumptions:

  1. The Engine resembles an Angular service
  2. The Engine renders a Scene
  3. Scene corresponds to an Angular component
  4. Scene holds information about its state

An engine works by rapidly rendering the scene image by image, this is called the renderLoop.

In the next paragraphs, I will demonstrate you my approach to integrate Angular and BabylonJS.

Providing the Engine as an Angular Service

As mentioned, the Engine can be compared to a service. What we have to do is to manage the state and provide it to the engine.

We embed the Engine by using an Angular service as our singleton and proxy to the Engine.

				
					// store the Engine
engine: Engine;


// the Engine will render into the Canvas element
start(canvas: ElementRef<HTMLCanvasElement>, scene: Scene) {
  this.engine = new Engine(this.canvas, true);

  // ... you can add content to the Scene

  // ignore the change events from the Engine in the Angular ngZone
  this.ngZone.runOutsideAngular( () => {
    // start the render loop and therefore start the Engine
    this.engine.runRenderLoop(() => scene.render())
  });
}
				
			

Create the “engine” with some content and start the render loop.

The service holds the Engine state, but we also need a way to manipulate the Scene. Each Scene could correspond to an Angular component.

The Scene Component

Using an Angular component, we integrate the Scene perfectly in our existing architecture.

The component template must contain a Canvas and a reference where the graphics will be rendered:

				
					<div class="wrapper">
    <canvas #rCanvas id="rCanvas"></canvas>
</div>
				
			

That reference will be selected by our component and is provided to the service.

				
					// ...
@ViewChild('rCanvas', {static: true})
canvasRef: ElementRef<HTMLCanvasElement>;

private scene: Scene;

constructor(private readonly engineService: EngineService) {}

// ...
ngOnInit(): void {
  // .. manipulate the scene
}

ngAfterViewInit(): void {
    // start the Engine
    // be aware that we have to setup the Scene before
    this.engineService.start();
}
				
			

The scene component calling the created Angular EngineService to start the graphics engine

Notice: To be sure that the Canvas element is available in the DOM, the Engine service is started after the view has been initialized.

But even if we are now able to display things, there are no interactions possible, yet. How can these two separate systems interact?

Interaction between Angular and BabylonJS

Now that everything is set up, we can begin to implement interactions between our graphics framework and the business application framework.

As discussed, the Engine renders the Scene in a loop, and therefore, we can change the Scene, and the Engine will update the rendered image accordingly.

Creating interaction with the scene is as easy as subscribing to any event in Angular, in our case listening to changes of our configuration object. The renderLoop will do the rest by updating the rendered scene on the next iteration.

				
					this.preferences.config.subscribe(config => {
    // update config
    this.someConfig = config;
    // change / update asteroids
    this.updateElements();
});
				
			

The scene component, listening to configuration changes

The other way round from BabylonJS to Angular is also straightforward: We add an event listener to e.g. the Scene we want to interact with and do something with the event we receive.

				
					const jupyter = this.engineService.createObject('jupyter');
jupyter.actionManager = new ActionManager(this.engineService.scene);
jupyter.actionManager.registerAction(new ExecuteCodeAction(ActionManager.OnPickUpTrigger,
      () => this.onJupyterClick.next());
				
			

Creating a Scene object and adding an action to it

The ActionManagerExecuteCodeAction, and OnPickUpTrigger objects are BabylonJS’s way to register an onClicked listener that executes a function. In this case, we will pass on the request and increase a click counter.

As you can see, integrating BabylonJS into Angular is straightforward, interaction is as easy as calling a function.

Conclusion

At this point, we have integrated Angular and BabylonJS. However it is not performant yet. Take a look at the Stackblitz demo and select unoptimized to see the effect of our doing so far.

https://stackblitz.com/edit/angular-babylonjs-performance-demo?file=src/app/app.component.ts

Wrap Up

That’s it! We successfully integrated the 2D/3D graphics engine BabylonJS in our business application written with Angular and Typescript.

In part 2, you will see where performance pitfalls are, how to find and avoid them. Subscribe to our monthly newsletter to not miss the next article.

Kostenloser
Newsletter

Aktuelle Artikel, Screencasts, Webinare und Interviews unserer Experten für Sie

Verpassen Sie keine Inhalte zu Angular, .NET Core, Blazor, Azure und Kubernetes und melden Sie sich zu unserem kostenlosen monatlichen Dev-Newsletter an.

Newsletter Anmeldung
Diese Artikel könnten Sie interessieren
Angular
sl_300x300

View Transition API Integration in Angular—a brave new world (Part 1)

If you previously wanted to integrate view transitions into your Angular application, this was only possible in a very cumbersome way that needed a lot of detailed knowledge about Angular internals. Now, Angular 17 introduced a feature to integrate the View Transition API with the router. In this two-part series, we will look at how to leverage the feature for route transitions and how we could use it for single-page animations.
15.04.2024
Low-angle photography of metal structure
AI
cl-neu

AI-Funktionen zu Angular-Apps hinzufügen: lokal und offlinefähig

Künstliche Intelligenz (KI) ist spätestens seit der Veröffentlichung von ChatGPT in aller Munde. Wit WebLLM können Sie einen KI-Chatbot in Ihre eigenen Angular-Anwendungen integrieren. Wie das funktioniert und welche Vor- und Nachteile WebLLM hat, lesen Sie hier.
26.02.2024
Angular
sl_300x300

Konfiguration von Lazy Loaded Angular Modulen

Die Konfigurierbarkeit unserer Angular-Module ist für den Aufbau einer wiederverwendbaren Architektur unerlässlich. Aber in der jüngsten Vergangenheit hat uns Angular seine neue modullose Zukunft präsentiert. Wie sieht das Ganze jetzt aus? Wie konfigurieren wir jetzt unsere Lazy-Komponenten? Lasst uns gemeinsam einen Blick darauf werfen.
03.08.2023
Angular
YB_300x300

Using EntityAdapter with ComponentStore: @ngrx/entity Series – Part 3

As someone who enjoys the ComponentStore on an average level, I have written simple reactive CRUD logic several times. While storing a vast number of entities in the component state might not be a frequent use case, I will briefly illustrate the usage of the EntityAdapter with the @ngrx/component-store.
14.02.2023
Angular
YB_300x300

Multiple Entity Collections in the Same Feature State: @ngrx/entity-Series – Part 2

After introducing the @ngrx/entity package, I am often asked how to manage multiple entity types in the same feature state. While I hope that the previous part of this article series has made this more apparent, I will further focus on this question in the following.
07.02.2023
Angular
YB_300x300

Managing Your Collections With the EntityAdapter: @ngrx/entity-Series – Part 1

This three-part series of blogposts is targeted at developers who have already gained experience with NgRx but still manage their collections themselves. In the first part I introduce the Entity Adapter, in the second part I show you how to connect it to NgRx and in the third part how to do it with the Component Store as well.
31.01.2023