Declarative THREE.js in Angular
Chau Tran
Sr Engineer @nrwl
Core team member @nestframework
Brand new father
Creator of @automapper/core
Agenda
- What is THREE.js? Quick demo using Angular
- How do we write it declaratively? Quick demo
- How does it work?
- Expand on demo (if time permits)
- Q/A
Disclaimer
This is not a "how to". This presentation is to show an introduction to and what the library is leveraging (from Angular) to make everything work. So I hope everybody learns something from today's presentation.
This is how vanilla THREE.js code looks like
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000)
const renderer = new THREE.WebGLRenderer()
renderer.setSize(width, height)
document.querySelector('#canvas-container').appendChild(renderer.domElement)
const mesh = new THREE.Mesh()
mesh.geometry = new THREE.BoxGeometry()
mesh.material = new THREE.MeshPhongMaterial()
scene.add(mesh)
function animate() {
requestAnimationFrame(animate)
renderer.render(scene, camera)
}
animate()
This is how vanilla THREE.js code looks like
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000)
const renderer = new THREE.WebGLRenderer()
renderer.setSize(width, height)
document.querySelector('#canvas-container').appendChild(renderer.domElement)
const geometry = new THREE.BoxGeometry()
const material = new THREE.MeshPhongMaterial()
const mesh = new THREE.Mesh(geometry, material)
scene.add(mesh)
function animate() {
requestAnimationFrame(animate)
renderer.render(scene, camera)
}
animate()
This is how vanilla THREE.js code looks like
J/K it was alright
How do we make it better for Angular developers?
How do we make it better for Angular developers?
with Angular 3
How do we make it better for Angular developers?
with Angular 3
How do we make it better for Angular developers?
with Angular 3 THREE (.js)
Here's Angular Three
@Component({
selector: 'app-root',
template: `
<ngt-canvas>
<ngt-mesh
o3d
(animateReady)="onAnimateReady($event.animateObject)"
>
<ngt-box-geometry></ngt-box-geometry>
<ngt-mesh-basic-material></ngt-mesh-basic-material>
</ngt-mesh>
</ngt-canvas>
`
})
export class AppComponent {
onAnimateReady(cube: THREE.Mesh) {
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
}
}
and of course...the imports
Here's Angular Three
import { ThreeCoreModule } from '@angular-three/core';
import { ThreeMeshModule } from '@angular-three/core/meshes';
import { ThreeBoxBufferGeometryModule } from '@angular-three/core/geometries';
import { ThreeMeshBasicMaterialModule } from '@angular-three/core/materials';
@NgModule({
imports: [
/* ... */,
ThreeCoreModule,
ThreeMeshModule,
ThreeBoxBufferGeometryModule,
ThreeMeshBasicMaterialModule
]
})
export class AppModule {}
@Component({
selector: 'app-root',
template: `
<ngt-canvas>
<ngt-mesh>
<ngt-box-geometry></ngt-box-geometry>
<ngt-mesh-basic-material></ngt-mesh-basic-material>
</ngt-mesh>
</ngt-canvas>
`
})
export class AppComponent {}
Here's Angular Three
<ngt-canvas>
<ngt-mesh>
<ngt-box-geometry></ngt-box-geometry>
<ngt-mesh-basic-material></ngt-mesh-basic-material>
</ngt-mesh>
</ngt-canvas>
Here's Angular Three
<ngt-canvas>
<ngt-mesh>
<ngt-box-geometry></ngt-box-geometry>
<ngt-mesh-basic-material></ngt-mesh-basic-material>
</ngt-mesh>
</ngt-canvas>
so how does it actually work?
Here's Angular Three
<ngt-canvas>
<ngt-mesh>
<ngt-box-geometry></ngt-box-geometry>
<ngt-mesh-basic-material></ngt-mesh-basic-material>
</ngt-mesh>
</ngt-canvas>
so how does it actually work?
- Content Projection
- Hierarchical Dependency Injection
Here's Angular Three
<ngt-canvas>
<ngt-mesh>
<ngt-box-geometry></ngt-box-geometry>
<ngt-mesh-basic-material></ngt-mesh-basic-material>
</ngt-mesh>
</ngt-canvas>
so how does it actually work?
- Content
ProjectionQuerying - Hierarchical Dependency Injection
Content Projection/Querying
<parent>
<child></child>
</parent>
Content Projection/Querying
<parent>
<child></child>
</parent>
<parent>
<!-- aka the content -->
<child></child>
</parent>
Content Projection/Querying
<parent>
<child></child>
</parent>
<parent>
<!-- aka the content -->
<child></child>
</parent>
@Component({
selector: 'parent',
template: `
<ng-content></ng-content>
`
})
export class ParentComponent {
@ContentChild(ChildComponent) childComp: ChildComponent;
}
Content Projection/Querying
<parent>
<child></child>
</parent>
<parent>
<!-- aka the content -->
<child></child>
</parent>
@Component({
selector: 'parent',
template: `
<ng-content></ng-content>
`
})
export class ParentComponent {
@ContentChild(ChildComponent) childComp: ChildComponent;
}
Content Projection/Querying
<parent>
<child></child>
</parent>
<parent>
<!-- aka the content -->
<child></child>
</parent>
@Component({
selector: 'parent',
template: `
<!-- nothing to see here -->
`
})
export class ParentComponent {
@ContentChild(ChildComponent) childComp: ChildComponent;
}
Content Projection/Querying
<ngt-mesh>
<ngt-box-geometry></ngt-box-geometry>
<ngt-mesh-basic-material></ngt-mesh-basic-material>
</ngt-mesh>
Content Projection/Querying
<ngt-mesh>
<ngt-box-geometry></ngt-box-geometry>
<ngt-mesh-basic-material></ngt-mesh-basic-material>
</ngt-mesh>
Content Projection/Querying
<ngt-mesh>
<ngt-box-geometry></ngt-box-geometry>
<ngt-mesh-basic-material></ngt-mesh-basic-material>
</ngt-mesh>
@Directive({
selector: 'ngt-mesh'
})
export class MeshDirective {
@ContentChild(ThreeGeometry) geometryDirective?: ThreeGeometry;
@ContentChild(ThreeMaterial) materialDirective?: ThreeMaterial;
}
Content Projection/Querying
<ngt-mesh>
<ngt-box-geometry></ngt-box-geometry>
<ngt-mesh-basic-material></ngt-mesh-basic-material>
</ngt-mesh>
@Directive({
selector: 'ngt-mesh'
})
export class MeshDirective {
@ContentChild(ThreeGeometry) geometryDirective?: ThreeGeometry;
@ContentChild(ThreeMaterial) materialDirective?: ThreeMaterial;
}
Content Projection/Querying
<ngt-mesh>
<ngt-box-geometry></ngt-box-geometry>
<ngt-mesh-basic-material></ngt-mesh-basic-material>
</ngt-mesh>
@Directive({
selector: 'ngt-mesh'
})
export class MeshDirective {
@ContentChild(ThreeGeometry) geometryDirective?: ThreeGeometry;
@ContentChild(ThreeMaterial) materialDirective?: ThreeMaterial;
private mesh?: THREE.Mesh;
ngOnInit() {
this.mesh = new THREE.Mesh(
this.geometryDirective.geometry,
this.materialDirective.material
);
}
}
Content Projection/Querying
<ngt-mesh>
<ngt-box-geometry></ngt-box-geometry>
<ngt-mesh-basic-material></ngt-mesh-basic-material>
</ngt-mesh>
@Directive({
selector: 'ngt-mesh'
})
export class MeshDirective {
@ContentChild(ThreeGeometry) geometryDirective?: ThreeGeometry;
@ContentChild(ThreeMaterial) materialDirective?: ThreeMaterial;
private mesh?: THREE.Mesh;
ngOnInit() {
this.mesh = new THREE.Mesh(
this.geometryDirective.geometry,
this.materialDirective.material
);
}
}
Content Projection/Querying
<ngt-mesh>
<ngt-box-geometry></ngt-box-geometry>
<ngt-mesh-basic-material></ngt-mesh-basic-material>
</ngt-mesh>
@Directive({
selector: 'ngt-mesh'
})
export class MeshDirective {
@ContentChild(ThreeGeometry) geometryDirective?: ThreeGeometry;
@ContentChild(ThreeMaterial) materialDirective?: ThreeMaterial;
private mesh?: THREE.Mesh;
ngOnInit() {
this.mesh = new THREE.Mesh(
this.geometryDirective.geometry,
this.materialDirective.material
);
}
}
Hierarchical Dependency Injection
@Component({
selector: 'ngt-canvas',
template: `<canvas #renderCanvas></canvas>`,
providers: [
CanvasStore,
AnimationStore,
EventsStore,
LoopService,
/* some other injectables */
]
})
export class CanvasComponent {}
Hierarchical Dependency Injection
@Component({
selector: 'ngt-canvas',
template: `<canvas #renderCanvas></canvas>`,
providers: [
CanvasStore,
AnimationStore,
EventsStore,
LoopService,
/* some other injectables */
]
})
export class CanvasComponent {}
Hierarchical Dependency Injection
// Remember?
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000)
const renderer = new THREE.WebGLRenderer()
renderer.setSize(width, height)
document.querySelector('#canvas-container').appendChild(renderer.domElement)
// setup Mesh
scene.add(mesh)
Hierarchical Dependency Injection
// Remember?
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000)
const renderer = new THREE.WebGLRenderer()
renderer.setSize(width, height)
document.querySelector('#canvas-container').appendChild(renderer.domElement)
// setup Mesh
scene.add(mesh)
CanvasComponent sets up:
- Renderer
- Camera
- Scene
Hierarchical Dependency Injection
scene.add(mesh)
Hierarchical Dependency Injection
scene.add(mesh)
<ngt-canvas>
<ngt-mesh>
<!-- stuffs -->
</ngt-mesh>
</ngt-canvas>
Hierarchical Dependency Injection
scene.add(mesh)
<ngt-canvas>
<ngt-mesh>
<!-- stuffs -->
</ngt-mesh>
</ngt-canvas>
@Component({
/* ... */
})
export class CanvasComponent {
@ContentChild(MeshDirective) meshDirective: MeshDirective;
}
Hierarchical Dependency Injection
scene.add(mesh)
<ngt-canvas>
<ngt-mesh>
<!-- stuffs -->
</ngt-mesh>
</ngt-canvas>
@Component({
/* ... */
})
export class CanvasComponent {
@ContentChild(MeshDirective) meshDirective: MeshDirective;
}
Hierarchical Dependency Injection
scene.add(mesh)
<ngt-canvas>
<ngt-mesh>
<!-- stuffs -->
</ngt-mesh>
</ngt-canvas>
@Component({
/* ... */,
providers: [
CanvasStore, // <-- this store initializes a THREE.Scene
/* .. */
]
})
export class CanvasComponent {
@ContentChild(MeshDirective) meshDirective: MeshDirective;
}
Hierarchical Dependency Injection
scene.add(mesh)
<ngt-canvas>
<ngt-mesh>
<!-- stuffs -->
</ngt-mesh>
</ngt-canvas>
@Component({
/* ... */,
providers: [
CanvasStore, // <-- this store initializes a THREE.Scene
/* .. */
]
})
export class CanvasComponent {
@ContentChild(MeshDirective) meshDirective: MeshDirective;
/**
* this.canvasStore.scene.add(this.meshDirective.mesh);
* /
}
Hierarchical Dependency Injection
scene.add(mesh)
<ngt-canvas>
<ngt-mesh>
<!-- stuffs -->
</ngt-mesh>
</ngt-canvas>
@Component({
/* ... */,
providers: [
CanvasStore, // <-- this store initializes a THREE.Scene
/* .. */
]
})
export class CanvasComponent {
@ContentChild(MeshDirective) meshDirective: MeshDirective;
/**
* this.canvasStore.scene.add(this.meshDirective.mesh);
* /
}
Hierarchical Dependency Injection
scene.add(mesh)
scene.add(literally_any_THREE_object)
<ngt-canvas>
<ngt-mesh>
<!-- stuffs -->
</ngt-mesh>
<ngt-anything></ngt-anything>
<ngt-anything-2></ngt-anything-2>
</ngt-canvas>
@Component({
/* ... */,
providers: [
CanvasStore,
/* .. */
]
})
export class CanvasComponent {
// WHAT ELSE DO WE NEED TO QUERY FOR???? NOBODY KNOWS
@ContentChild(MeshDirective) meshDirective: MeshDirective;
}
Hierarchical Dependency Injection
<parent>
<child></child>
</parent>
Hierarchical Dependency Injection
<parent>
<child></child>
</parent>
@Component({
selector: 'child',
template: ``
})
export class ChildComponent {
}
Hierarchical Dependency Injection
<parent>
<child></child>
</parent>
@Component({
selector: 'child',
template: ``
})
export class ChildComponent {
constructor(private readonly parentComponent: ParentComponent) {}
}
Hierarchical Dependency Injection
<parent>
<child></child>
</parent>
@Component({
selector: 'child',
template: ``
})
export class ChildComponent {
constructor(private readonly parentComponent: ParentComponent) {}
}
// Remember:
@Component({
providers: [/* ... */]
})
export class ParentComponent
Hierarchical Dependency Injection
<parent>
<child></child>
</parent>
@Component({
selector: 'child',
template: ``
})
export class ChildComponent {
constructor(
private readonly parentComponent: ParentComponent,
/* any provider on the parent injector is available here */
) {}
}
Hierarchical Dependency Injection
<parent>
<child></child>
</parent>
@Component({
selector: 'child',
template: ``
})
export class ChildComponent {
constructor(
private readonly parentComponent: ParentComponent,
/* any provider on the parent injector is available here */
) {}
}
Hierarchical Dependency Injection
<ngt-canvas>
<ngt-mesh>
<!-- stuffs -->
</ngt-mesh>
</ngt-canvas>
Hierarchical Dependency Injection
<ngt-canvas>
<ngt-mesh>
<!-- stuffs -->
</ngt-mesh>
</ngt-canvas>
@Directive({
selector: 'ngt-mesh'
})
export class MeshDirective {
ngOnInit() {
this.mesh = new THREE.Mesh(/* ... */);
}
}
Hierarchical Dependency Injection
<ngt-canvas>
<ngt-mesh>
<!-- stuffs -->
</ngt-mesh>
</ngt-canvas>
@Directive({
selector: 'ngt-mesh'
})
export class MeshDirective {
constructor(private readonly canvasStore: CanvasStore) {}
ngOnInit() {
this.mesh = new THREE.Mesh(/* ... */);
this.canvasStore.scene.add(this.mesh);
}
}
Hierarchical Dependency Injection
<ngt-canvas>
<ngt-mesh>
<!-- stuffs -->
</ngt-mesh>
</ngt-canvas>
@Directive({
selector: 'ngt-mesh'
})
export class MeshDirective {
constructor(private readonly canvasStore: CanvasStore) {}
ngOnInit() {
this.mesh = new THREE.Mesh(/* ... */);
this.canvasStore.scene.add(this.mesh);
}
}
Nx Workspace Generators
Nx Workspace Generators
@ContentChild(ThreeGeometry) geometryDirective?: ThreeGeometry;
@ContentChild(ThreeMaterial) materialDirective?: ThreeMaterial;
Nx Workspace Generators
@ContentChild(ThreeGeometry) geometryDirective?: ThreeGeometry;
@ContentChild(ThreeMaterial) materialDirective?: ThreeMaterial;
Nx Workspace Generators
@ContentChild(ThreeGeometry) geometryDirective?: ThreeGeometry;
@ContentChild(ThreeMaterial) materialDirective?: ThreeMaterial;
Nx Workspace Generators
@ContentChild(ThreeGeometry) geometryDirective?: ThreeGeometry;
@ContentChild(ThreeMaterial) materialDirective?: ThreeMaterial;
Nx Workspace Generators
@Directive({
selector: 'ngt-box-geometry',
exportAs: 'ngtBoxGeometry',
providers: [{provide: ThreeGeometry, useExisting: BoxGeometryDirective}]
})
export class BoxGeometryDirective extends ThreeGeometry<THREE.BoxGeometry> {
@Input() set args(v: ConstructorParameters<typeof THREE.BoxGeometry>) {
/* ... */
}
ngOnInit() {
this.init();
}
protected init() {
this.geometry = new THREE.BoxGeometry(/* ... */);
}
}
Nx Workspace Generators
Nx Workspace Generators
Generators Demo
Summary
- Content Querying and Hierarchical Dependency Injection come out of the box
Summary
- Content Querying and Hierarchical Dependency Injection come out of the box
- Regardless of whether you use ng-content or not, Angular already structures your Parent-Content relationship.
Summary
- Content Querying and Hierarchical Dependency Injection come out of the box
- Regardless of whether you use ng-content or not, Angular already structures your Parent-Content relationship.
- A content child can inherit the Injector of its parent thanks to Hierarchical Dependency Injection.
- A content child can inherit the Injector of its parent thanks to Hierarchical Dependency Injection.
Summary
- Content Querying and Hierarchical Dependency Injection come out of the box
- Regardless of whether you use ng-content or not, Angular already structures your Parent-Content relationship.
- A content child can inherit the Injector of its parent thanks to Hierarchical Dependency Injection.
- A content child can inherit the Injector of its parent thanks to Hierarchical Dependency Injection.
- (Nx only) Use Workspace Generators for boring repetitive tasks
DEMO
Q/A
Angular Three
By Chau Tran
Angular Three
- 871