Renderer
The custom renderer powers the entire Angular Three and is provided via a dedicated platform entry point.
angular-three/domfor DOM/Browser platform
import { provideNgtRenderer } from 'angular-three/dom';
bootstrapApplication(AppComponent, { providers: [provideNgtRenderer()]});Catalogue
Section titled âCatalogueâAngular Three maintains a single catalogue of THREE.js objects to render. By default, the catalogue is empty.
In order to populate the catalogue, you can call the extend() function and pass in a Record of THREE.js objects. Angular Three then maps the catalogue to Custom Element tags with the following naming convention:
import { extend } from 'angular-three';import { Mesh, MeshBasicMaterial, LOD, Object3D } from 'three';
extend({ Mesh, // makes ngt-mesh available MeshBasicMaterial, // makes ngt-mesh-basic-material available LOD, // makes ngt-lOD available Object3D, // makes ngt-object3D MyMesh: Mesh, // makes ngt-my-mesh available});Overrides
Section titled âOverridesâextend() can be called multiple times and the later invocation will merge the Record with the existing catalogue. This is useful for when you want to provide a different implementation for a specific Custom Element tag like switching between WebGPURenderer and WebGLRenderer
import { extend } from 'angular-three';import { DirectionalLight } from 'three';import { DirectionalLight as WebGPUDirectionalLight } from 'three/webgpu';
extend({ DirectionalLight }); // make ngt-directional-light availableextend({ DirectionalLight: WebGPUDirectionalLight }); // after this, ngt-directional-light will now instantiate the WebGPU DirectionalLight insteadCUSTOM_ELEMENTS_SCHEMA
Section titled âCUSTOM_ELEMENTS_SCHEMAâSince Angular Three works with Custom Element tags, you need to have schemas: [CUSTOM_ELEMENTS_SCHEMA] in your componentâs metadata.
Property Bindings
Section titled âProperty BindingsâYou can bind values to any ngt-* elements using Property Binding syntax.
<ngt-mesh [position]="[1, 2, 3]"> <ngt-box-geometry /> <ngt-mesh-basic-material color="red" /></ngt-mesh>Shortcuts
Section titled âShortcutsâSome THREE.js objects have different methods to update their values due to performance and how WebGL works. For example, THREE.Vector3 has a set method that accepts a tuple of [x, y, z] instead. On the other hand, if we pass in a THREE.Vector3 instance, the current THREE.Vector3 will call copy and pass in the new instance instead.
This characteristic is baked into the custom renderer so it is more intuitive to pass values to these properties.
<ngt-mesh
[position]="[0, 1, 0]"
[scale]="1.5"
[rotation]="myRotation"/>ColorRepresentation
Section titled âColorRepresentationâAny elements that accept a color property can accept a ColorRepresentation type
<!-- different ways to pass in color -->
<ngt-mesh-basic-material color="hotpink" />
<ngt-mesh-basic-material color="#ff00ff" />
<ngt-mesh-basic-material color="rgb(255, 0, 255)" />
<ngt-mesh-basic-material [color]="myColor" />
<ngt-mesh-basic-material [color]="myHexadecimalColor" />Coercion
Section titled âCoercionâAngular Three custom renderer automatically coerces primitive values.
<ngt-mesh castShadow receiveShadow/>Pierced Properties
Section titled âPierced PropertiesâAngular Three supports pierced properties via dot-notation. Imagine you want to turn a plane (THREE.Mesh with THREE.PlaneGeometry) on its side to make the plane into a floor, instead of passing [rotation]="[-Math.PI / 2, 0, 0]" to the <ngt-mesh>, you can pierce the rotation property with [rotation.x]="-Math.PI / 2".
<ngt-mesh [position.y]="-0.5" [rotation.x]="-Math.PI / 2"> <ngt-plane-geometry /></ngt-mesh>Pierced properties trade-offs
Section titled âPierced properties trade-offsâThere are trade-offs worth noting for piercing properties.
The pro is it works extremely well for component-based frameworks like Angular where underlying operations use some sort of change detection strategy to determine whether to update the template or not. Since pierced properties allow us to work with more primitive values on the template, it is easier for the change detection mechanism to work with.
However, when it comes to piercing properties of a non-builtin object like an external THREE.Material that you bind to a <ngt-mesh> via [material], timing of property bindings might get weird and things might not work the way you expect it to. For cases like this, it is recommended to use ngt-value instead.
Considering the following example
<ngt-mesh [material]="gltf.materials.material" material.color="green" /><!-- vs --><ngt-mesh [material]="gltf.materials.material" [material.color]="'green'">These 2 snippets behave differently in terms of timing. The first snippet will update the original material first then bind a new material later; while the second snippet will bind correctly but the order matters. It gets trickier when it comes to more complex objects like textures.
Hence, pierce properties are only recommended for built-in properties of a THREE.js object that are less likely to change like position, rotation, scale etcâŠ
Angular Three Properties
Section titled âAngular Three PropertiesâBeside the properties of the native THREE.js objects, there are some special properties that are specific to Angular Three.
parameters
Section titled âparametersâAll custom elements accept a parameters property that accepts an object of properties to pass to the underlying object.
<ngt-mesh-basic-material [parameters]="{ color: 'hotpink', side: BackSide, transparent: true }"/>Shortcuts are still applied with parameters
<ngt-mesh [parameters]="{ position: [1, 2, 3], scale: 1.5, rotation: myRotation }"/>This property is used to specify a property on the parent that this element should be attached to. Attaching takes into account the life-cycle of the elements and will automatically detach when the elements are destroyed.
Static Attach
Section titled âStatic AttachâIf you want to bind a static value to attach, use Attribute Binding to bind a static string to it
<ngt-mesh> <ngt-box-geometry attach="geometry" /> <ngt-mesh-basic-material attach="material" /></ngt-mesh>THREE.js equivalent
const mesh = new THREE.Mesh();const material = new THREE.MeshBasicMaterial();const geometry = new THREE.BoxGeometry();
mesh.material = material;mesh.geometry = geometry;Nested Path Attach
Section titled âNested Path AttachâYou can attach to a deeply nested property on the parent by using dot-separated path
<ngt-spot-light castShadow> <ngt-vector2 attach="shadow.mapSize" /></ngt-spot-light>THREE.js equivalent
const spotLight = new THREE.SpotLight();spotLight.castShadow = true;
const vector2 = new THREE.Vector2();// shortcut is still applied automaticallyspotLight.shadow.mapSize.copy(vector2);Dynamic Attach
Section titled âDynamic AttachâYou can pass a dynamic value by using Property Binding syntax [attach]. When this is the case, attach accepts Array<string | number> in addition to just string. Common use-case for this is to attach different materials to different faces of a geometry.
@Component({ template: ` <ngt-mesh> <ngt-box-geometry /> @for (color of colors; track $index) { <ngt-mesh-basic-material [attach]="['material', $index]" [color]="color" /> } </ngt-mesh> `})export class MyCube { protected colors = ['red', 'green', 'blue', 'yellow', 'orange', 'purple']; // cube has 6 faces}THREE.js equivalent
const mesh = new THREE.Mesh();const geometry = new THREE.BoxGeometry();
mesh.geometry = geometry;mesh.material = [];
const colors = ['red', 'green', 'blue', 'yellow', 'orange', 'purple'];
for (let i = 0; i < colors.length; i++) { const material = new THREE.MeshBasicMaterial(); material.color.set(colors[i]); mesh.material[i] = material;}NgtAttachFunction
Section titled âNgtAttachFunctionâLast but not least, you can pass a NgtAttachFunction to [attach] property. When this is the case, you are responsible for attaching and detaching the element yourself.
NgtAttachFunction can be created using an identity function createAttachFunction to help with typings.
import { createAttachFunction } from 'angular-three';
@Component({ template: ` <ngt-mesh> <ngt-mesh-basic-material [attach]="attachFn" /> </ngt-mesh> `})export class SceneGraph { protected attachFn = createAttachFunction<THREE.MeshBasicMaterial, THREE.Mesh>(({ parent, child }) => { const oldMaterial = parent.material; parent.material = child;
// return a clean-up function that will be called when `ngt-mesh-basic-material` is destroyed return () => { parent.material = oldMaterial; } })}Event Bindings
Section titled âEvent BindingsâAs youâve learned in Handling Events, you can use Event Binding syntax to listen to various events on the elements. This section will go over some THREE.js native events and some specific Angular Three events that all objects share in addition to Raycast Events.
THREE.js native events
Section titled âTHREE.js native eventsâMost THREE.js objects extends EventDispatcher and can emit arbitrary events. There are some well-known events that Angular Three renderer supports via the Event Binding syntax.
added: when the object is added to the parentremoved: when the object is removed from the parentchildadded: when a child is added to the objectchildremoved: when a child is removed from the objectchange: when the objectâs properties change. This is mostly applied to controls likeOrbitControlsdisposed: when the object is disposed. The original event name isdisposebut Angular Three usesdisposedto not have conflict with[dispose]property
<ngt-group (childadded)="onChildAdded()"> <ngt-mesh (added)="onAdded()"></ngt-mesh></ngt-group>THREE.js equivalent
const group = new THREE.Group();group.addEventListener('childadded', onChildAdded);
const mesh = new THREE.Mesh();mesh.addEventListener('added', onAdded);
group.add(mesh);Angular Three events
Section titled âAngular Three eventsâcreated
Section titled âcreatedâThis event is emitted when the element is created but before attached.
@Component({ template: ` <ngt-mesh [position]="[1, 1, 1]" (created)="onCreated($event)" ></ngt-mesh> `})export class SceneGraph { onCreated(event: THREE.Mesh) { }}attached
Section titled âattachedâThis event is emitted when the element is attached or added to the parent. This is subtly different than the THREE.js added event because it also emits when a non-Object3D object (i.e: Material) is attached to the parent.
@Component({ template: ` <ngt-mesh> <ngt-mesh-basic-material (attached)="onAttached($event)" /> </ngt-mesh> `})export class SceneGraph { onAttached(event: NgtAfterAttach<THREE.MeshBasicMaterial, THREE.Mesh>) { const { parent, node } = event; // ^? Mesh ^? MeshBasicMaterial }}updated
Section titled âupdatedâThis event is emitted when the element is updated, mostly via Property Binding
@Component({ template: ` <ngt-mesh [position]="[1, 1, 1]" (updated)="onUpdated($event)" ></ngt-mesh> `})export class SceneGraph { onUpdated(event: THREE.Mesh) { }}