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.
In Angular Three, the naive way of opting into the Animation Loop is as follow:
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)
.
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
It is not bad but there are couple of things that are lacking:
- What if
assets/car.glb
was dynamic (viaInput
)? gltf
contains more information than just thescene
. Likeanimations
that the GLTF model has. How can we run those animations?
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
injectNgtsGLTFLoader
does several things underneath:
-
Create a
Signal
to store the 3D model data -
Set up an
effect
to re-fetch if the input changes- Support Array input, Directory input, or single input
-
Allow consumers to specify whether they want to use
DRACOLoader
and/orMeshoptDecoder
-
Support for preloading external assets OUTSIDE of Angular building blocks
In addition, the base injectNgtLoader()
(which injectNgtsGLTFLoader
utilizes) handles in-memory cache so the consumers
don’t have to load the same asset twice.
3. injectBody()
injectBody()
is a CIF that deals with connecting the 3D objects to the Physics World.
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
- The physics engine itself (i.e:
cannonjs
) - Attaching the 3D objects (the cubes and planes) to the Body in the Cannon World
- Reacting to inputs like
debug
andgravity
etc. - Expose APIs to update the properties of the Body in the Cannon World (via Web Worker)
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:
- Compose other CIFs
- Make use of APIs that rely on the Injection Context (i.e:
inject
,effect
) - Are friendly with Signals. They accept Signals and return Signals
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.