What is Astro?
Astro is a high-performance framework designed to seamlessly merge the benefits of static and dynamic web development. It empowers developers to build modern websites using their preferred JavaScript frameworks while delivering lightning-fast loading times. With its unique “universal rendering” approach, Astro pre-renders pages into static HTML for optimal performance and security, while still enabling client-side interactivity. By leveraging partial hydration and other optimization techniques, Astro ensures efficient component loading and a superior user experience.
Recently, Astro has released version 3 with tons of improvements. I figure this is good timing to write some content for Astro and Angular since we’re going to use Astro to render our Angular components.
What is AnalogJS?
Analog is a fullstack meta-framework for building applications and websites with Angular. Additionally, Analog provides an Astro Integration that we can utilize to render Angular components in Astro.
What is Angular Three?
angular-three is a custom Angular Renderer to render THREE.js entities directly to the HTMLCanvasElement
instead of the DOM.
Create an Astro application
Getting started with Astro is super easy because they provide a CLI tool to create a new Astro application
Open up the terminal and enter the following command
Follow the prompts by create-astro
. If you’re wondering, here’s mine
npm create astro prompts
Add AnalogJS integration
To add AnalogJS integration to Astro, run the following command
Say Yes to both questions and our Astro application is ready to render some Angular components
Add tsconfig.app.json
Although we have added the Astro integration, we need to create a tsconfig.app.json
at the project’s root.
Start by creating tsconfig.app.json
Then fill it out with the following content
Add our first Angular component
Create a new file, Experience.ts
, in src/components
You can use any file name for the component.
Then fill it out with the following boilerplate
AnalogJS’s Astro integration requires components to be
standalone: true
To use this Experience
component, open up src/pages/index.astro
Import Experience
component
Then render the component using the name of the component <Experience />
Start our Astro application’s development server with the following command
Then go to http://localhost:4321
and we’ll see our Experience
component is rendered 🎊
Astro home page with Angular component rendered
At this point, we can technically start writing blog posts with Astro and Angular
Add Angular Three
Open up the terminal so we can install a few things
We might need to run
npm install
with--force
flag due to peer dependencies resolution
Let’s go back to Experience.ts
and add the following code
You can also break up
Scene
into a separate file
Since Three.js is a Canvas-base library (i.e: WebGL), we want
to enable client-side JavaScript for our Experience
component. Astro allows us to do so by using client:load
directive on
our component. So, let’s go back to src/pages/index.astro
and make the following change
Additionally, we also need to update astro.config.mjs
with the following change:
If you do not care about SSR, you can skip the config change and use
client:only
instead ofclient:load
. This makesExperience
component to only ever load on the client.
Make sure there is no error from our development server as well as in the browser’s console. Now, we are ready to render some 3D objects
Add our cube
Angular Three is a Custom Renderer. Hence, we need to teach it how to render tags that we put on the template, like how Angular knows
how to render <div>
into an HTMLDivElement
on the DOM.
To do so, we’ll use extend()
API from angular-three
to expand the internal catalogue of Angular Three
Here, we are teaching Angular Three how to render a THREE.Mesh
, a THREE.MeshBasicMaterial
, and a THREE.BoxGeometry
Notice how we use ngt-*
to render our 3D objects. extend()
turns what we pass in into a catalogue of HTML Custom Element tags prefixed with ngt-
followed by the name of the entity in kebab-case
. This is also the reason why we need CUSTOM_ELEMENTS_SCHEMA
.
Now, our Astro application renders a pink “box”
Astro home page a pink box
Increase the Canvas area
Our canvas is a little too small. This is because of two reasons:
- Canvas default height is
150px
per spec - Our
Experience
’s parent element doesn’t have aheight
set
NgtCanvas
component is designed to take on the parent’s dimensions. Hence, we can control our canvas’ sizes by wrapping <Experience />
with
a container with a specified height
The canvas will take up 600px
and our pink box will be more visible.
Astro home page a pink box on a large canvas
Animate the box
To animate our box, we can use (beforeRender)
output on <ngt-mesh>
(beforeRender)
allows our objects to participate in the animation loop that Angular Three creates to render the Scene Graph.
This function runs outside of Angular Zone (i.e: outside of Angular Change Detection mechanism) to maintain high FPS. It is good practice
to NOT update states in this function.
- The
state
object contains useful information about Angular Three internals likedelta
,clock
, the defaultTHREE.Scene
, the defaultTHREE.Camera
, and more. - The
object
is the instance of the 3D object that Angular Three renders. In this case, it is theTHREE.Mesh
instance fromngt-mesh
A rotating pink box
Let there be lights
At the moment, our “box” looks bland. In fact, we can’t even tell if it’s a box. The reason is we’re using MeshBasicMaterial
for our box and MeshBasicMaterial
does not reflect lights. Let’s switch to MeshStandardMaterial
, a material that can reflect lights.
Then we can update our template
But wait, our box is now pitch black.
A 'black' box
We haven’t added any lights yet. Imagine a dark room, there is no light that our box can reflect on. Let’s add some lights
Next, let’s update our template to render those lights
Voila! Now our box looks so much better, with dimensionality, like a real box.
A 'real' rotating pink box
Take control of the camera
Who hasn’t tried to “grab” the scene and rotate it around? Well, we can’t with what we currently have because
our camera is static. However, we will be able to control our camera with the help of OrbitControls
Let’s teach Angular Three how to render OrbitControls
Next, let’s adjust our Scene
component to render OrbitControls
- We import
injectNgtStore
andNgtArgs
fromangular-three
- We add
NgtArgs
toimports
array to make*args
directive available to our template - We call
injectNgtStore
to grab the internal Angular Three store object via DIcamera
is the default camera.this.store.select()
returns a SignaldomElement
is the DOM element from theWebGLRenderer
.this.store.select()
accepts multiple arguments to select nested objects.
- We render
ngt-orbit-controls
and passcamera()
along withdomElement()
to*args
- This is equivalent to
new OrbitControls(camera, domElement)
- This is equivalent to
Back to our running application and BOOM 💥, we can grab the scene, rotate it around, and zoom in/out
Controlling the camera with OrbitControls
Conclusion
Astro is truly a magnificient technology. It is light-weight, wicked fast, and easy to integrate with other frameworks. On the other hand, I cannot praise AnalogJS enough for what it enables for Angular developers. In this blog post, we learn how to use AnalogJS to bring Angular components to Astro land with an example of rendering a 3D box using Angular Three.
Give Astro, AnalogJS, and Angular each a star if you haven’t. I hope you have fun with all the technologies that we talk about in this blog post. See you in the next one.