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 this article:

mm_300x300
Max Marschall is engineer consultant at Thinktecture with focus on Angular and 2D/3D visualization.

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.

Free
Newsletter

Current articles, screencasts and interviews by our experts

Don’t miss any content on Angular, .NET Core, Blazor, Azure, and Kubernetes and sign up for our free monthly dev newsletter.

EN Newsletter Anmeldung (#7)
Related Articles
AI
sg
One of the more pragmatic ways to get going on the current AI hype, and to get some value out of it, is by leveraging semantic search. This is, in itself, a relatively simple concept: You have a bunch of documents and want to find the correct one based on a given query. The semantic part now allows you to find the correct document based on the meaning of its contents, in contrast to simply finding words or parts of words in it like we usually do with lexical search. In our last projects, we gathered some experience with search bots, and with this article, I'd love to share our insights with you.
17.05.2024
Angular
sl_300x300
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
.NET
kp_300x300
.NET 8 brings Native AOT to ASP.NET Core, but many frameworks and libraries rely on unbound reflection internally and thus cannot support this scenario yet. This is true for ORMs, too: EF Core and Dapper will only bring full support for Native AOT in later releases. In this post, we will implement a database access layer with Sessions using the Humble Object pattern to get a similar developer experience. We will use Npgsql as a plain ADO.NET provider targeting PostgreSQL.
15.11.2023