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!