DAVID EAST: As a server-side friend re--
[BLEEP]
Dang it!
SPEAKER 1: Server-side friender.
DAVID EAST: Server-side friender.
[BLEEP]
It never just goes away.
[MUSIC PLAYING]
Hey, everybody.
Welcome to the Angular Universal episode
in the server-side rendering with JavaScript framework
series.
And in this episode, we're going to get up
and running quickly with Angular Universal.
We're going to be using the Angular
CLI to generate a universal bundle, that
is a core piece to getting a server-side rendered app.
Then, we're going to be using Angular Universal itself
to take this universal bundle and generate HTML and CSS.
And then lastly, we're going to use Express to handle the HTTP
requests to send back the dynamically created HTML
and CSS from Angular Universal.
And in this video, we're going to be making an app called
True Facts, which is really just a simple list of facts that's
generated from an API call.
Now, it's not really that complicated.
But it's a great example of how server-side rendering can
help improve your page load.
So let's dive down into the laptop and get started.
[BEEP]
So I'm here in the command line, and I'm
going to use the Angular CLI to create a new project.
So I'm going to use, ng New, ng true facts.
And this will go and install everything.
And then I'll open it up in my editor.
So open up the Source Folder, an I will open up my app module,
and import the HTTP client module.
So import from Angular, slash common,
slash HTTP, and HTTP client module,
and specify that in my ng module imports.
So we'll go into the component now,
where I can actually go and make a request with the HTTP client
service.
So I'll import the HTTP client service
from the same place, Angular, common, HTTP,
and grab the actual service, and we'll inject that
into the constructor.
And from here, I can say, this, dot HTTP, dot get.
And I'll use an endpoint that points to the real time
database.
And now, I want to create an observable of the facts
that I'm retrieving, which means I'll
have to import it from RXJS.
So now, I can say, this dot facts equals the facts
that we were getting back.
So at this point, I need to go and render this.
So I'll delete all the boiler plates,
and I'll create a UL with an LI, use star ng 4,
say, let fact of facts, pipe it out to async,
since this is a synchronous.
And then, inside of here, I can use fact dot text.
And I'll close out the LI.
So now, I want to go and add my CSS.
So I'm going to go to styles dot CSS, which
is all the global cells, and just
paste in all of this basic CSS.
So now that I have my CSS, I'm going
to initialize Firebase Hosting to deploy out this website.
So you'll make sure to install the Firebase tools.
And then once you have installed,
use Firebase init hosting.
I'm going to use the non-SSR Angular project,
because this won't be set up for server-side rendering
initially.
And then, now that I have this set up,
I can actually go out and I can build my ng application--
that's ng build, dash, dash, prod--
and do a Firebase deploy.
So now, I'm going to open hosting site.
So this is my deployed application.
And this is a non-server-side rendered version.
So if we go and actually view source,
we can see that we don't have any of the HTML
that represents our app.
It's all just references to Style Sheets and JavaScript.
So now, if we want to set up server-side rendering,
we need to start off by installing Angular Platform
Server.
And now that that's done, we can go to our app module.
And we need to use the with server transition method,
and specify an app ID that's just
something unique to the page.
So now, I'm to create a new file and call it app
dot server dot module dot ts.
And this is going to be an ng module for the server.
So I'm going to create an ng module here.
And we also want to import from Angular Platform Server,
and we'll grab the server module.
And then, we need to import our actual app.
So we'll import our app components,
and we'll also import our app module.
And this way, we'll be able to create our universal bundle
from this ng module.
So we'll create our app server module,
and we'll import the app module and the server module,
and we'll make sure that we bootstrap up our initial app
component.
So now, we need to export this with a main server ts.
So the main dot server dot ts is how we will
export our app server module.
But right now, we don't have a way
to build this type script to JavaScript.
So we need to create a ts config specifically
for this universal bundle.
So I'll create a new TypeScript file,
call it ts dot config dot server dot JSON.
And I want to actually extend from the ts config
we have from before.
So if you're not familiar with extends,
it's just how we can take one ts config
and then build off of it.
So now, I'm going to specify some compiler
options where I want to say my outDir is out dot TSC slash
app.
And that is what the Angular CLI uses to build out to.
And we need to set a base URL for the current directory.
And we need to specify that the module's common JS.
And we want to exclude any test files.
So it's really important that you
specify that the module is common JS,
because this is the module format that Node.js needs
to use, because currently, our module format is ES 2015,
and that is not understood yet by Node.js.
So TypeScript will do us a big favor
and convert that to common js.
And then lastly, we need to provide Angular compiler
options.
We need to specify that our entry module is app slash
app dot server module, and use a hash
to reach the actual class name.
So now, with this written, we can
export from app slash app dot server dot module,
and export right out our app server module.
So now, what we need to do is we need
to go into dot Angular dash CLI dot JSON
And this is where we can specify how our app actually
gets built. So you can see that there is
this section here called apps.
And currently, there is one entry,
and that is our client site app.
This is the default that the Angular CLI sets up for you.
So what we want to do is we want to actually create
another entry.
And this will be built for the server.
So what I'm going to do is, I'm going
to copy this object right here, and I'm
going to paste a new entry and modify a few things.
So I want to say that the platform is for the server,
and that the outDir is actually called
functions slash dist-server.
And our main, we know, is main dot server dot ts.
We can get rid of polyfills.
We need to change the ts config.
And with that, we can go and say, ng build, dash dash prod,
dash dash app, 1, because it's an array,
and 1 being the second index in the array,
we will build that specific app.
So now, it'll go out and build it.
And we have our server bundle.
So if we go out to our files, we can
see in functions dist-server.
So in dist-server, we can see that we
have our universal bundle generated by the Angular CLI.
So now that we have our universal bundle,
we need to start working on our server code.
So the first thing I'm going to do
is I'm going to initialize Cloud Functions.
So I going to use the Firebase CLI
and say, Firebase init functions.
So it's going to go and do some initial set up for us.
And it's going to ask if I want to install
the dependencies right now.
So I'm just going to go forth and do that.
And now that that's set up, it has created an index js
file in our functions folder.
And for this example, I actually don't want to use JavaScript.
I'm going to want to write everything in TypeScript.
So I'm just going to delete this.
And if I'm going to write it in TypeScript,
that means I'm going to need a ts config.
So inside of a server folder, I'm
going to create an index dot ts for my server code,
and a ts config dot functions dot JSON.
And then from here, I can create my compiler options,
and I want to compile to common js for node.
My target is going to be ES 2015.
And then my rootDir is the current directory.
And from here, I just want to compile out the index ts
file as my entry file.
And lastly, I need to specify an outDir,
and I want it to go to the functions directory,
since that is where we will deploy our server code.
So now, in index ts, I'm going to create my server.
So first, I'll import from Firebase functions.
And then, I'm going to import from Express.
And then, now, I need to import from Angular Platform Server,
and import the render module factory function.
And it's this function which will allow us
to generate our HTML and CSS.
And I'm also going to need to import from the file system,
so I can read files.
And lastly, we need to import zone js for node.
And this is really important.
So if you don't import this, this
is going to totally blow up on you.
But this is what Angular needs to know to do
change detection on the server.
So now, I'm going to read my index dot
HTML file from the file system.
And when you get that from the current directory--
and make sure it comes back as UTF 8--
and so, to do that, I actually just
need to drag it into my functions,
so it reads it from the correct directory.
So now, I need to require my universal bundle that was
generated by the Angular CLI.
And we know that was generated in the dist-server folder.
So I can require from the dirname slash dist-server
slash main dot bundle.
And this require will have an object
that we can get called dot app server module ng factory.
And we can use this to generate our HTML and CSS.
So there's a bit of a problem.
When we generate our universal bundle,
it will be generated with a hash--
so main dot hash dot bundle dot js.
But we don't need this hash, because the hash
is used for browser caching.
And there's no browser caching needed on the server.
So we actually can get rid of this hash.
So I'm going to open up the dist-server folder.
And I'm going to rename main, so it's main dot bundle dot js.
And ideally, we'd want to do this in an automated way.
But for now, I'm just going to rename things.
So now that we have our documents
and our universal bundle, we can go and create our server.
So I'll create an Express app by calling Express as a function.
And now, I'm going to create an HTTP handler.
And I'm going to create a get route
for star star, which means, intercept every single route.
And so, from here, I can use the render module factory method
and pass in my app server ng module factory.
And then I can use the document.
And then I need to get the current URL.
And I can use that by using the incoming request path.
Render module factory returns a promise So
I'm going to call dot then, and get
the HTML that was generated.
And now, from here, I can use Express to send back this HTML.
But I want to set caching.
So I'm going to set a cache control for max age 600 and s
max age for 1200, which effectively means that this
content will be cached in the browser for 600 seconds--
or 5 minutes--
and will be cached for 10 minutes on the CDN level.
So this cache control header is really important,
because it keeps us from having to go out to the server
every time the user makes an HTTP request for this document.
So instead, we will either deliver from the browser cache,
or we can deliver from the CDN level, which
will be much faster, and will keep the work off the server.
Now, once the 1200 seconds expires,
we'll go out do this process again.
But for that time frame, we will have
our apps served from the cache.
So now that we have our server [INAUDIBLE],,
I need to export the function for Cloud Functions.
Sp I'm going to call it SSR app, and say,
functions dot HTTPS on request, and pass through the app.
So now that that's done, I need to go into Firebase dot JSON
and create my rewrite, so Firebase Hosting
knows about our function.
We'll use star star as the source,
and then our function name-- as we know, we call it SSR app,
because right here, export let SSR app.
So now that I have my server code written,
I need to trans-pilot from TypeScript to JavaScript.
So I'm going to use the TypeScript command line
and say, ts dash p, and use the server ts config.
Now, right now, I get this error.
And this is only due to the version of TypeScript
I'm using, and the version of Firebase admin.
And hopefully, you won't see this error.
But if you do, it's benign.
And we can actually move past this.
But hopefully, by the time this video comes out,
you won't see it.
So if we go to our functions directory,
we can see that our index js is the common js
version of our server code.
So since we have this, I'm going to serve,
and I'm going to serve locally.
So on local host 5,000, you can see that we
have our app up and running.
And if we go and view the page source,
you can see that it actually uses HTML
that was server-side rendered.
So this isn't a lot of code.
But there's actually a faster way of doing this.
And we can use the npm module called Angular Universal
Express Firebase.
And this is just a little simple module
I wrote to help make server-side rendering your Angular
apps a lot easier.
So I also need to install this into the Functions folder,
because this will have to be deployed out
to Cloud Functions.
And now, I can actually go and delete almost all of this code.
And I'm going to import star as Angular Universal from Angular
Universal Express dash Firebase.
And rather than use this, I can say,
Angular Universal dot trigger.
And then from here, I can provide some config options.
So I can say that the index page is
located at the current directories index dot HTML.
The main is located at dist-server main dot bundle.
I'm going to want to enable prod mode.
I'll set a CDN cache expiry for 1200,
and the browser cache expiry for 600.
So I want to go and deploy this app to production.
But before I can do that, I need to make sure
that I have all the needed modules in the package JSON
in my Cloud Functions folder.
And that's because when you deploy your app to Cloud
Functions, it's going to install all the dependencies needed
to run your server code.
And if it's not listed in that package dot JSON,
then it's going to have an error and not be able to run.
So a simple way to do that is, I go into my main package
dot JSON for my Angular CLI app.
And I go and I copy all of these dependencies.
And now, I'll go to my functions, package dot JSON,
and I just paste it in.
And then also, I'm going to need Express.
So now, I'll open up the terminal, CDN to functions,
and install.
And then now, I can deploy by using Firebase deploy.
So now, our app is deployed.
And so, I'll go and visit this in my browser.
So now, here in production, you can
see my URL is SSR dash Angular dot Firebase app dot com.
And we have our app up and running.
And if I view the page source, you
can see that we have our server-side rendered content.
[BEEP]
So that's how you set up Angular Universal on Firebase Hosting.
In the next video, we're actually
going to go and profile this website using the Chrome
DevTools and Webpagetest.
And we're going to see how it performs
compared to the non-server-side rendered version.
So make sure to subscribe so you are notified
when that video gets out.
So that's all for today.
If you like this video, make sure to hit
that thumbs up button.
And otherwise, I will see you in the next video.
In this project, I'm going to be building a little app called--
[GROAN] that's not what it's called!
[BLEEP]
We are inside the computer.
[BLEEP]
All right, this is it.
Just keep rolling.
We'll do it live!