All Blogs

My favorite CIFs from Angular Three

Custom Inject Function, or CIF, is my favorite way to compose functionalities in Angular nowadays thanks to the inject and Signal. Check out one of my previous blog posts on Abstract inject() the right way to learn more.

In this blog post, I’ll introduce some of my favorites CIFs that I implemented in angular-three, an Angular Renderer for THREE.js

1. injectBeforeRender()

THREE.js is an abstraction over WebGL API and interacts seamlessly with the Canvas API. To interact with Angular Three, Angular developers utilize the ngt-canvas component. Each ngt-canvas component starts a separate Animation Loop (i,e: requestAnimationFrame()).

When working on 3D projects, it is commonly, if not always, involving running animations on 3D objects.

1
function animate() {
2
// schedule the animate function in a animation loop
3
requestAnimationFrame(animate);
4
5
// change the properties of the 3D object bit by bit on every frame (before render step)
6
cube.rotation.x += 0.01;
7
8
// then render the scene
9
renderer.render(scene, camera);
10
}
11
12
// starts the render
13
animate();

In Angular Three, the naive way of opting into the Animation Loop is as follow:

1
export class Model {
2
#store = injectNgtStore();
3
#destroyRef = inject(DestroyRef);
4
5
constructor() {
6
const unsubscribe = this.#store.get("internal").subscribe(() => {
7
/* before render logic */
8
});
9
this.#destroyRef.onDestroy(unsubscribe);
10
}
11
}

This is quite verbose to have to inject two symbols to opt into the Animation Loop. Hence, angular-three exposes a CIF called injectBeforeRender which composes injectNgtStore and inject(DestroyRef).

1
export class Model {
2
#store = injectNgtStore();
3
#destroyRef = inject(DestroyRef);
4
5
constructor() {
6
const unsubscribe = this.#store.get("internal").subscribe(() => {
7
/* before render logic */
8
});
9
this.#destroyRef.onDestroy(unsubscribe);
10
injectBeforeRender(() => {
11
/* before render logic */
12
})
13
}
14
}

Code: https://github.com/angular-threejs/angular-three/blob/platform/libs/core/src/lib/before-render.ts

2. injectNgtsGLTFLoader()

In addition to running animations in 3D projects, we frequently load external assets like 3D models, textures etc… THREE.js deals with external assets via a set of Loader. In this section, we’ll take a look at GLTFLoader to load a .glb model

Let’s see how we can do it without injectNgtsGLTFLoader

1
export class Car {
2
#gltfLoader = new GLTFLoader();
3
// Optional: Provide a DRACOLoader instance to decode compressed mesh data
4
#dracoLoader = new DRACOLoader();
5
6
#model = signal<GLTF | null>(null);
7
scene = computed(() => this.#model()?.scene);
8
9
constructor() {
10
this.#dracoLoader.setDecoderPath("/examples/jsm/libs/draco/");
11
this.#gltfLoader.setDRACOLoader(this.#dracoLoader);
12
13
this.#gltfLoader.load("assets/car.glb", (gltf) => {
14
this.#model.set(gltf);
15
});
16
}
17
}

It is not bad but there are couple of things that are lacking:

Not to mention, we need to do the same thing for different models components in our 3D projects. Well, injectNgtsGLTFLoader solves all (maybe) the problems. Let’s take a look

1
export class Car {
2
#gltfLoader = new GLTFLoader();
3
// Optional: Provide a DRACOLoader instance to decode compressed mesh data
4
#dracoLoader = new DRACOLoader();
5
6
#model = signal<GLTF | null>(null);
7
#model = injectNgtsGLTFLoader(() => 'assets/car.glb');
8
scene = computed(() => this.#model()?.scene);
9
10
constructor() {
11
this.#dracoLoader.setDecoderPath("/examples/jsm/libs/draco/");
12
this.#gltfLoader.setDRACOLoader(this.#dracoLoader);
13
14
this.#gltfLoader.load("assets/car.glb", (gltf) => {
15
this.#model.set(gltf);
16
});
17
}
18
}

injectNgtsGLTFLoader does several things underneath:

In addition, the base injectNgtLoader() (which injectNgtsGLTFLoader utilizes) handles in-memory cache so the consumers don’t have to load the same asset twice.

Code: https://github.com/angular-threejs/angular-three/blob/platform/libs/soba/loaders/src/gltf-loader/gltf-loader.ts

3. injectBody()

injectBody() is a CIF that deals with connecting the 3D objects to the Physics World.

injectBody Demo injectBody Demo

Sorry for the low quality GIF, I had to tune it way down to display it in my blog.

As we can see from the GIF (I hope you could see it 😛), there are quite a few things that are happenning when we deal with Physics

Without injectBody(), the amount of code we need to tie 3D objects to the Cannon World would be tremendous and repetitive.

Code: https://github.com/angular-threejs/angular-three/blob/platform/libs/cannon/services/src/lib/body.ts

Conclusion

Above is my 3 favorite CIFs in angular-three. There are various other CIFs in angular-three but the concept they share is very similar:

Do you have a favorite CIF or do you have something that could be turned into a CIF? I would love to know! Thanks for reading.

Published on Fri Sep 22 2023


Angular