WARD:
This week, I'm not sure, I'm compos mentis, but I'm here...
CHUCK:
[Laughs]
[This episode is sponsored by Hired.com. Every week on Hired, they run an auction where over a thousand tech companies in San Francisco and New York and LA get on JavaScript developers providing to put the salary and equity upfront. The average JavaScript developer gets an average of 5-15 introductory offers and an average salary of over $130,000 a year. You just can either accept an offer and go right into interviewing with the company and neither with that any continuing obligations. It's totally free for users, and when you're hired, they'll also give you a $2,000 signing bonus as a "Thank You" for using them. But if you use the Adventures in Angular link, you'll get a $4,000 bonus instead. Finally, if you're not looking for a job but know someone who is, you can refer them to Hired to get a $1,337 bonus if they accept the job. Go sign up at Hired.com/AdventuresinAngular.]
[Does your team need to master AngularJS? Oasis Digital offers Angular Boot Camp, a three-day, in-person workshop class for individuals or teams. Bring us to your site or send developers to ours -AngularBootCamp.com.]
[This episode is sponsored by Wijmo 5, a brand new generation of JavaScript Controls. A pretty amazing line of HTML5 and JavaScript products for enterprise application development. Wijmo 5 leverages ECMAScript 5 and each control ships with AngularJS directives. Check out the faster, lighter and more mobile Wijmo 5.]
[This episode is sponsored by Digital Ocean. Digital Ocean is the provider I use to host all of my creations. All the shows are hosted there, along with any other projects I come up with. Their user interface is simple and easy to use. Their support is excellent. And their VPSes are backed on solid-state drives and are fast and responsive. Check them out at DigitalOcean.com. If you use the code “angularadventures” you'll get a $10 credit!]
CHUCK:
Hey, everybody! Welcome to Episode 56 of the Adventures in Angular show. This week on our panel, we have Ward Bell.
WARD:
Hello! Hello!
CHUCK:
Lukas Reubbelke.
LUKAS:
Hello! Hello!
CHUCK:
I'm Charles Maxwood from DevChat.tv. I have a real quick announcement. Angular Remote Conf, when this comes out, you've got a few days to get your early bird tickets for $100. If you missed the deadline, then that's going to go up to $200, so go ahead and get it now. Lukas is actually going to be speaking at the conference. Or, at least, he has agreed to.
LUKAS:
Yay!
CHUCK:
[Laughs]
LUKAS:
I have agreed.
CHUCK:
Yes! We also have two special guests this week. We have Jeff Whelpley.
JEFF:
Hello!
CHUCK:
And Patick Stapleton.
PATRICK:
Hey, guys!
CHUCK:
Do you want to introduce yourselves?
JEFF:
Sure! I'll go first. My name is Jeff Whelpley, I'm the chief architect at GetHuman. For the past year or so, I've been heavily involved with trying to get server rendering working both in Angular 1 and now, I'm working with Patrick in Angular 2, trying to get server rendering working.
PATRICK:
And I'm Patrick Stapleton, also known as PatrickJS. Currently, I'm working with my friend Scott Moss on Angular Class, also doing open source, and working with Jeff twice and the Angular team to get Universal JavaScript up and running with servcer rendering.
CHUCK:
Patrick, I have to just ask, is your middle initial J? Because that would just be cool.
PATRICK:
Yeah! Everyone always assumes that I just... to think it's JavaScript. But, it's actually my initials.
CHUCK:
That's awesome.
PATRICK:
Yeah.
WARD:
I'm thinking to have my name legally changed to JavaScript.
[Laughter]
CHUCK:
First name Java, last name Script. I hear all kinds of things about Isomorphic JavaScript, Universal JavaScript, and JavaScript that works on the frontend and the backend, and it sounds like you're pulling this frontend stuff into the backend. Do you want to talk about some of the advantages you see to doing the server rendering and how it simplifies our lives?
JEFF:
There are a number of different reasons that matter to individual niches. But, the main reason that affects everybody is Performance. It's specifically the initial rendering performance of your application. When I started off getting into this a year and half ago or something like that, it was for a different reason; it was SEO, which is one of the niche reasons that if you have an externally facing site and you want a really strong SEO, you need to have that server-rendered view in addition to your client-rendered app.
But as I got into it more, I realized that, actually, the biggest benefit and the one that benefits the most people is the performance gains because with client-side web apps, what you have is, no matter how quickly your framework is, how fast it is one tip loads, that initial loading time is always fairly slow, relatively speaking, usually on the order magnitude of 4-6 seconds, sometimes more for larger applications. And even though a lot of people discount that, that actually does have a very big impact on user experience especially for external facing sites, but even for internal sites. So, a lot of the work that we do with server rendering helps to alleviate that because it allows you to use the same code, the same app that you're creating, client-side app in Angular instead of just waiting until the client bootstraps and load after 6 seconds.It will be able to run your same code on the server, generate that View, and then with the initial server response, which is usually on something like a couple of hundred milliseconds, you'll get fully-rendered View that the user can see and it appears almost instantly in their perception while the client-side app is loading in the background.
Does that make sense?
CHUCK:
Yeah, it makes sense.
WARD:
Well, the claim does...
CHUCK:
[Chuckles] Yeah.
WARD:
Let's put some pressure on that. I just, now as you were talking, I loaded Gmail, which I assume is not Isomorphic...
JEFF:
Right, it is not.
WARD:
...into my browser, and the wall clock time 2 seconds. I'm about to try it on my phone, but that's not really the point. The point I'm making is, that if it is an app -- I've loaded a lot of desktop apps, almost any editor I load except for the really fast ones like Sublime, I'm waiting more than 2 seconds -- my desktop experience of an app that I want to run that I know I'm going to use for work, that I know I'm going to live them, if I'm under 10 seconds, I'm throwing a party. I'm so used to having that as my experience. Now, what I hope is that once it has loaded, that its [incomprehensible] has all get out. But, I'm used to, at least for my business or utility apps, that I'm going to have that cost. I know, and you will tell me, about all the stats that show that if I'm shopping at Amazon, how many things can be lost? How much business is lost if I'm going to a shopping site and it takes a while to get started? So, this performance question is really to into our expections, don't you think?
JEFF:
Well, Ward, if you're tellling me that you are okay with waiting 10 seconds for an app to load, there's no chance I'm going to convince you that you want to use server rendering.
WARD:
Well, it depends on the app, right?
JEFF:
Sure.
WARD:
It really, really depends on the app. What I was driving at is -- because this is where I think that the skepticism that you may be facing is really conditional upon the nature of the app. When I'm doing it, when we make broad statements about whether I would or would not wait for 10 seconds, I know for a fact that you wait for 10 seconds for all kinds of things. You may not like it, but you may just do it anyway because you're going to live with that, it's part of the business, and you just hope the darn thing works. Whereas, there are others, other kinds of applications, where you say, "Hey, if this thing doesn't perform, I'm moving on." And we, as consumers -- I don't mean like consumers of the world, but I mean the consumers of this kind of technology -- we have different expectations, different demands for different kinds of apps depending on how we're using them, don't you think?
CHUCK:
I'm completely different on this. For me, I have so many things going on that 1, 2, 3, 4, I'm going to go load another page while I wait for it to come back, and then I forget to come back.
WARD:
Which kind of things are you...
CHUCK:
Just about everything, honestly [chuckles].
WARD:
Really?
CHUCK:
Yeah.
WARD:
So you go to... This [incomprehensible] to me because I'm so used to waiting for just about anything including a range of Google products, Microsoft products, just about anything. Yes, I've got 3 monitors up and I'm moving around, and if it's casual, not a chance.
CHUCK:
Yeah.
WARD:
Not a chance if it's a casual interaction, but if it's going to be a long running interaction...
CHUCK:
Uhmm-hmm. If it's critical, then I'll wait. But, yeah.
WARD:
GitHub, I mean, come on! GitHub didn't come up in a flash.
PATRICK:
Your product are really get coined. There's actually two things going on here. There's the casual application, and then there's the actual application that you have a long life cycle. This is why Gmail, for example, as you said it, takes a few seconds. But then after that, you can even refresh everything and everything is such [incomprehensible]. That's because it's loading everything in memory.
We know this experience with native applications, mobile apps, but there's actually a huge difference between a web application experience and something like the web. With the web app, you actuallly need to download it, you need to install it, then you need to wait a few seconds loading for your application set. But with the web, it's more based on discovery, again, like what you're saying, with casual interactions with things. A good example of that is Google docs. You probably have Google docs open right now, and that's implementing something that's very similar, and that's two, what we try to teach today that's like progressive loading, essentially. Well, that's probably a term, but essentially, it's just loading different bits, parts, initially and then loading everything else around it asynchronously. Twitter does that as well, but you don't really notice that because you take it for granted.
The web is really about discovery and they initially trying to move everything that's good about the web into its native space and trying to have these apps interact to each other. So, more to your point about load times, it's true like eventually, what's going to happen is that we would all want applications on our phones and everything, but we just don't have that contstraint. We have access on the internet world, but we don't have unlimited storage. That's where the web is all set, that is loading everything just in time. So, why can't you have the hybrid approach of having the initial load time extremely fast and still maintain the persistence of that. That's where something like service work or place, so it's really just a progression of how application development happens. For example, if you booted your computer, wouldn't it be nice if you just press the power button and then everything is there? That's essentially what we're reaching. We're trying to make web development influence everything else by bringing this concept to other ways of developing applications.
JEFF:
I'll add one more thing to what he's saying, which is that, yeah, sure for a certain applications where the user doesn't know the app or it's [incomprehensible] discovery thing that you're just entering for the first time, there's huge benefit there and that's where the primary use case, I guess. But, I think it would be hard to argue in any use case that, like an app that loads fast, is not fundamentally better than an app that loads slowly. I think the thing you're touching on, though, is unspoken thing. It's that, the reason why you're okay with the apps that loads slowly is because there's so much effort that's needed just for that load time like making it faster, and it's not really worth it becasue you don't get that much benefit for certain use cases like the ones that you're talking about where like Gmail that you're actual expectations are already set. But that doesn't mean it wouldn't be better. It would be better.
WARD:
Let me just say you got it exactly right there, Jeff. I love instantaneous. I wish when I open my computer, it was instantly on, like it says it's going to be, never is. That would be fabulous! You're absolutely right, it's the cost trade-off of what it takes to make something instantaneous, and also what I might potentially give up by making it instantaneous,what other features I might give up because I'm depending on server-side rendering, I hope we'll get into that.
is:
One is incremental loading, and the other is Isomorphic. I can do incremental loading, thus, giving a pretty quick initial experience by not downloading everything and then sneak in behind and backfill. I don't need isomorphic for that. I guess we haven't defined isomorphic yet, so we should circle back to that. But, I just want to put that out on the table because I agree, it's an economics issue as much as it's a technical issue. Do I invest in this? What do I gain? What do I lose for the cost (and we'll get into what I might lose)? And for the sake of perf, if perf is free, I'll take it. If perf is not free, then I want to know what I have to pay. And now, I'll step off my soap box, and let's get on!
JEFF:
[Laughs]
CHUCK:
Actually, I want to challenge another premise on this because you're talking about performance as far as server-side rendering goes; it's faster, it takes less time and less resources. But when I talk to most people about single-page apps, what they want is they're saying, "Look, I'm off loading all this work to the client," and it seems like with server-side rendering, you have to do that work anyway. I know, I'm asking like we've hit a whole bunch of different topics, but I guess what I'm saying is, is it really that much faster to do server-side rendering?
WARD:
That is such an interesting point, Charles. I hope we get a chance to answer that question, also about when you're really getting an advantalge and when you're not. So, we got you guys all tied up with all kinds of aggressive questions, let's see how you'd do.
PATRICK:
The thing about Universal JavaScript (I'll discuss the first point), say that you don't have Universal -- again, I guess we should define Universal and Isomorphic. The correct term for this is, Universal JavaScript - that is JavaScript that runs on the clients and server. People called in Isomorphic before, but the correct term is Universal, so we're trying to push that. And, with...
WARD:
Sorry, I never loved Isomorphic, but that makes Universal better because Microsoft has their Universal, too, and it's like, "Oh, God! Who's not Universal? I want to be Galactic!" [Laughter]
CHUCK:
Universal is more universal.
WARD:
Ah, that's true.
PATRICK:
Yeah. It's more universal.
JEFF:
To be honest, there's no good... [Chuckles] It's just a name.
PATRICK:
Yeah.
JEFF:
It's a name, and I think this is just one that people seem to like more, so why not.
WARD:
Okay. Alright.
PATRICK:
Yeah. So, going back to the point, you said that why can't I just start to switch my apps to lazy load rather than having everything around in both environments. The problem in here that we're really solving is an environment problem. That is having your application run in multiple environments. Why can't you configure your application on the server? Or, statically, to figure it and say, "I want these components over here to load synchronously and I want everything else over here to load asynchronously when I hit this portion of page, this portion of page, this portion of page." Now, we do this manually in Angular 1. We do that exact same technique for React and that is injecting these Promises everywhere, and everything is really hard coupled with framework. But wouldn't it be nice to have a way to declare it, we say, "I want these things to be asynchronous for this, this, this," and declaratively configure how you want your applications to load without changing anything of your application and you just have that there.
Now, the only way to actually recycle is have full control over these environments. The benefit of Universal JavaScript is that we can take advantage of a lot of control on the server because we don't have a lot of the browser constraints like amount of scripts at the time, etcetera, because on the server, you just wait it up but then when it's done, it starts serving people.
JEFF:
And you can leverage caching...
PATRICK:
Yeah, and you can leverage a lot of caching. That's essentially where we're trying to get at; we're providing a better experience. Maybe this is a little bit of...
CHUCK:
I'm going to stop you, guys, because I'm still a little bit lost [laughs].
PATRICK:
Yeah.
CHUCK:
Maybe I can try and restate what you're saying and then you can tell me what I'm missing. First of all, what you're trying to tell me is that I have more control on the server-side than on the clientside, and fewer constraints because I'm not working in the browser? So, that makes it better somehow than running it into the browser?
WARD:
I'm lost at that one, too, because I could put all of the stuff on the CDN, right? I don't lose any caching there. The only thing I'm not getting is the initial data that might flood into the...
JEFF:
Let me frame things in the long term. What Patrick is starting to get into was long-term where in an ideal world, it would be great if you can pick and choose which pieces are server-loaded while you leverage, caching on the server versus lazy loaded on the client and basically, on a case by case basis, for your particular whatever app is doing, you can do the best of both worlds and have it optimally load because it's great to have all those options available.
Now, what I'd say for the short-term, though, is this is where actually two other things that were brought up come into effect, both cost and speaking about lazy loading. So, I agree that actually the concept of lazy loading is great, that having the client just download what he specifically needed for that one request and then everything else downloads in the background. When you do that, the initial load is actually really fast. But the problem is that it is extremely difficult to do from a framework perspective. There is no framework out there where you can just write your code and allow the framework to handle all the lazy loading for you.
WARD:
No question.
JEFF:
Because it's so particular to what you are doing. You can set up your own custom solution to do that, but the framework is not going to be able to do that for you today.
WARD:
Absolutely. The way in which you get how to decide, how to sneak a lead aside, what should be the load while they're looking at this other thing because you're anticipating they're going there, what should be in each container that represents what you're doing in asynchronous download, all that stuff is wacky hard. And if somebody had a great solution for that, we'd like it, I think.
JEFF:
Here's where the cost comes in: the value proposition and the big one for the short-term with server rendering is specifically with you don't [inaudible] two solution because we're trying to make cost practically nothing. Like in my own server rendering in Angular 1 that I've actually spoken on the previous show about, the cost are pretty high. It's valuable for me because I need it for SEO reason that type of thing, but I wouldn't recommend it to anyone else, to be quite honest, because the cost was high.
But, with what we're tyring to build in Angular 2, the cost is going to be extremely low so that you almost get it for free, and that's where it is valuable where you have something that is better, fundamentally, and if you're going to get it for free, why not.
WARD:
Yeah, free is always good. Or, at least, it seems good. I'll hold off my question about offline scenarios until later. So, you're saying that it is going to be a way to, without me having to do too much, just a little declaration here and there, I'm going to be able to have something figure out what needs to be loaded when?
PATRICK:
The benefit of Angular 2 is that you're able to statically analyze the dynamic nature of JavaScript because it's using TypeScript and because the way it's structured, it forces some constraints on the application in order for you to statically declare its dependencies. We see this happening with React-Relay where they're doing that, and their industry is like realizing that that's a good idea now. But the benefit there is that we could take advantage of that on the server. I was suggesting that we could actually determine what is needed and how to re-configure your application so it runs for that environment, in our case, the browser.
WARD:
That's like by looking at my imports and exports and my injectables and things like that?
PATRICK:
Yeah. Essentially, the benefit of ES6 module syntax is that it supports a static analysis so you can do that.
WARD:
Yeah, so you could figure out what my dependency trees are statically, it's what you're getting at?
PATRICK:
Yup!
WARD:
And then maybe draw some fences around things and say, "Alright, we'll give them this at the frontend," and then come back later and grab the other stuff?
PATRICK:
Yeah, that's the ultimate goal that a few people are trying to reach. The way that we're searching it will allow us to do that in the future. But, yeah, all of that is completely possible.
JEFF:
So like today, right this second, just be asked and open here like we have everything out on Angular github/angular/universal. But if you try to use the code right now, I wouldn't say that it's for free right this second because we're not done yet. But, when we get to the point that everybody were using it, the specific cost will be, (1) ingest a very simple guideline that you do need when you're developing in Angular 2, which is to use the Angular 2 API for basically your interactions with the DOM. If you explicitly reference the window object or whatever, it's harder to mock that out. But if you use the API that's built in to Angular 2 for doing, selecting elements from the DOM or making changes or whatever else, then that's going to be built into the framework to work on the serverside just like, again, for free or whatever, so just not use the window or browser elements or anything like that.
And then the other thing is that, yeah, there will be a server-side component. We are trying to work on that now, basically, patching it up so that it'll be as simple as like an npm install on the server and hook into the various frameworks that you might want to use on the server-side.
WARD:
Well, you got my attention now [laughs] because, like I said, you reduce the cost and you're giving me that benefit of managing the load, the incremental load for me, I'm starting to feel it, and I have no problem with the constraint of not using window or document or any of those things directly because I've already learned...
CHUCK:
Yeah, that's a world of pain, right?
WARD:
Yeah, yeah, we've already learned actually. Angular 1 encouraged us to use their obstruction for that anyway in order to insulate ourselves from that from a testing perspective and through $ window and so forth. What you're saying is that we should have the same motivation or additional motivation to use those wrappers in this world?
JEFF:
Yup, definitely.
CHUCK:
I want to go back to the question I asked earlier. I totally understand now that if you're using this Universal setup so that it does the server-side rendering, you can take advantage of a lot of serverside caching so that instead of having to have it render the section of the page every time, you can effectively pull in something that already has the right things in it.
The question I have, though, is besides the caching, is it really still that much more performant to do it on the server?
PATRICK:
Are you saying if without any caching at all?
CHUCK:
Yeah.
PATRICK:
Angular itself does caching for its Views. For example, if you bootstrap an Angular app on the server, the initial boot time is going to take a hundred milliseconds because it load up your application and then put all of its templates in the cache. Now, there's actually another step that you could do that you don't technically need to do on the server, and that is like pre-compiling your Angular application. That's basically allowing you to precompile all of your templates into just JavaScript. Again, you don't have to do this on the server, it's a benefit for the client. But, that's another way to just make the initial bootstrap for the server pass through even though that it doesn't really matter there.
WARD:
Other way, we should call that out as one of the things that's recommended for turning your Angular 1 app into a production-ready app -- push all those, definitely want to bubble up all of your little HTML templates into one JavaScript file like that...
CHUCK:
Yeah.
WARD:
...and use that. Hey, people out there, learn that trick!
CHUCK:
Yup. And there are a lot of reasons for that.
PATRICK:
Yeah. Essentially, you definitely want to cache templates. Caching as in like being able to say, "This table is so large, I need to..." rather than recomputing it every single time. Again, you would precompile everything. You could allow Angular 2 to do that, or your build system to do that, to convert it. The build system would precompile Angular and convert it into your function, and that would stay there. And then, you just invoke this funtion essentially with data, and then outside, you get your new application state.
CHUCK:
Yeah, but when I make a request, I want that -- you made it look like, in your talk in the way we've been talking, that I'm going to get a response back very quickly with all of the right data in the right places.
So, I get a request to my server and it's, "Load this page," so typically, on the client, what it's going to do is it's going to go, "Okay, I load in all of the static assets," the JavaScript files, CSS files, all that stuff, and it runs all that, then it has to run the JavaScript and bootstrap all the data in, probably has to make some calls to the server to get the data back. That takes time -- that's your 3 or 7 or 10 seconds or whatever you said on the client. And, you're talking like I'm the server where you can do that in like under a second.
PATRICK:
Is the question about...
CHUCK:
How do you do that? Is it just caching? Or, is it the fact that you don't have to do end-runs to get data? Or, is there more to it? Is there some more performant way of rendering that you're using that doesn't involve the DOM?
PATRICK:
There's two questions here. One is, rendering the application, and then the other one is running the application with data. Is that correct?
CHUCK:
Okay.
PATRICK:
Yup. I'll start with rendering the application in general. So you bootstrap your application -- let's just talk about this in terms of the client -- you bootstrap your application and your components has all these templates, but then you make the request to the server, it grabs all these templates, then it injects all the data inside of it, and everything works.
In Rails -- let's step back a little bit, I guess -- in Rails, in order to increase performance, you would do something like document, fragment caching so it's practically the same thing. Or, you would say, "This portion is document, I'm going to cache that," and then piece everything together. We don't have to do that anymore on the server because that's handled already by what JavaScript is doing on the client because of the Universal approach. Now that we don't have to technically do that on the server (all of that is there), the next question is, how do you manage data, right?
CHUCK:
Right.
PATRICK:
This is the biggest problem to interact right now, and that is that there are server rendering solution
at the moment that's really slow because it's parsing issue now then it's also grabbing data. For them, it's very questionable how to resolve the asynchronous of, "I'm going to get data and then put in the template," and it's waiting for the data to be down before it continues rendering.
With Angular 2, we have this notion of asynchronous templates. This is something that now a lot of frameworks have, and this is something that something like Falcor [incomprehensible] was great about Angular is that there is this one notion and architecture that's not asynchronous and that's the combination of the model and the View, the template. That's the only part in most frameworks where it's synchronous; everywhere else in the triad is actually asynchronous.
Now we go on the server, we have all these templating and it's doing all this caching and fragmenting. So what you could do -- let's just put it in the simple term, let's just say, I have a model and it has a cache. Let's just say that I'm going to put something in that cache. When the Angular app bootstraps, it's going to interact with this models and it's going to grab stuff in the cache first. This is similar to how the browsers work and how REST works, and it's actually really good design. Basically, that's how it would work -- you would set the cache to whatever data you need and basically, whenever your application bootstraps or grab the cache first, then realize that it's there, and then continue on. If it's not there, it will then make a request, which is asynchronous, and then from there, you have a lot more options rather than other frameworks where it's all synchronous.
WARD:
Can we play that out slowly? I think it's very interesting.
CHUCK:
Uhmm-hmm.
PATRICK:
Yeah!
WARD:
Let's play that a little bit slowly so people at home like me can follow along and let's separate it in two very different kinds of data requests. One of them is request for data that a lot of people are going to want, let's say a table of products that I want to pick from.
PATRICK:
Yeah!
WARD:
Then later on, we'll deal with this scenario in which the data that's on that first page is data-only that a particular user wants like, say, my email or my posts. But let's take the easy one first, the products. Imagine that what we're talking about is this thing application is supposed to open up and it's supposed to show a list of products. How would that work?
PATRICK:
Again, you would have this approach of an isomorphic model, and that is essentially a way of interfacing with this model and it has a catch. So essentially, the application would bootstrap, and it would run its regular code as you would. And inside of your controller or whatever, you would do a find for this particular list, and what it would do is it would actually look for the cache first. Now, on the server...
WARD:
Now, this is the cache of products, right?
PATRICK:
Yeah.
WARD:
And where is that cache? Is that cache of products on the server? Is this happening on the server? Or, I don't have to know?
PATRICK:
Yeah, sorry, this is the isomorphic model. This is the model that's inside the framework that runs on both ends.
CHUCK:
Right. So, when this request is made, it comes in and when it's rendering the page, it checks the cache to see if it has that list of products in the cache.
WARD:
I'm wondering who it is. I'm with you, Chuck. You got to play like we're DOM here, guys, because the client makes it a request to the server and says, "Give me the page of products." What's happening in [incomprehensible]?
JEFF:
Let me frame things just like slightly differently that I think might make more sense, because the original question was just talking about how things are kind of fast on the server...
CHUCK:
Uhmm-hmm.
JEFF:
Patrick mentioned the one thing about the asynchronous components, which actually that helps a great deal because it doesn't wait for each component to load data; it can all do it at the same time. That's just like one thing of itself. But then, the second thing, which is we're talking a little bit about one aspect of it is there's various levels of caching. Chuck mentioned like what if there's no caching? I think you were specifically saying no page caching, which is that highest level of caching...
CHUCK:
Yeah.
JEFF:
But, there's always, when you go down the stack, there's caching at many different levels down the stack. And data caching, the one thing that Patrick was just talking about, which is a possibility where your both server-side and client-side are using shared model. But, one of the great things about your Angular 2 is how, even on the lower level that even before you get to the data -- we did talk a little bit about the templates -- but the way I like to think about it is if you just think about a component like you define a component in Angular 2, that component, when it's running in your client, that component is like a object, like it's a object in memory that represents that component. And the instantiation of that component can occur many different times on the page so they share the same -- it's called the ProtoView, which is the object in memory that allows you to take data and slap it into that component to show the instantiated view for that thing. On the server-side, the one additional benefit you get is that you not only get to share those ProtoViews, like if you would have a list on the client-side, you would share your ProtoViews for each item, it would be the same exact thing in memory just like applied in different instances, which is one of this Angular 2 optimizations. But on the server, you get an additional benefit because you can re-use those ProtoViews among different users.
CHUCK:
The cross-users, right?
JEFF:
Yes, exactly.
CHUCK:
Okay. I think I'm seeing where this is going. So you have the shared cache at different levels, like you said, that you can take advantage of a cross multiple requests for multiple users or clients or whatever you want to call them, so you get that benefit. The other thing is that you don't have the network latency to make any request to get data if there's a cache missed, essentially; you can just work that out on the server. I can definitely see where you get some performance gains from this.
The other question I have related to this, though, is then, does this increase the amount of work I have to do on the server such that I may have to scale up a little bit in order to implement this particular practice?
PATRICK:
Let me just add something that makes Angular a lot better at Universal than any other frameworks.
Do you know Angular 1?
CHUCK:
Uhmm-hmm.
PATRICK:
Do you know the DI system just briefly?
CHUCK:
Uhmm-hmm.
PATRICK:
Alright. Do you know $http and the backend service?
CHUCK:
$http, yes. The backend service, I'm not sure...
WARD:
It's usually invisible to the usual developer, but yeah, you run into it when you do testing against it, sure.
PATRICK:
Yeah. So, you have this -- I'll just briefly explain it -- you have this HTTP Class and you inject another class that's the backend, and you interact with that whenever you want to make a request to the backend. We could think of the model as that, but you inject the cache. Now, the cache will just call it cache, generally, but on the client, we could call it local storage. So we change the bindings there to look a storage. So whenever you make an HTTP call, it's interacting with the cache, it's going to hit local storage, that's either hit or miss, then makes a call.
CHUCK:
Right.
PATRICK:
Now on the server, we do the exact same thing but we change the bindings there. And the cache on the server coulb be represented as Redis. It's the same type of cache, just different environment.
CHUCK:
Right.
PATRICK:
It's going to do the exact same thing. It's going to make HTTP call, hit the cache, see if it's there; if it's not, it'd make a request. Now, I guess to answer more of your question, what's going to happen if there's a miss? There's a few different ways to go about this, but I'll just explain two. Let's just say that there's a asynchronous request, but you need to send something to the client really, really fast. Now, because we have control over the environment, say that we know that this is a user not a web caller, in that regard, then we are able to ensure that we don't need to wait until everything is done. So we could push the template onto the client intially and we could then, right after the bootstrap line inside of HTML after the script, we could then inject the data later. I guess a better way to think about it, just think about it where the HTTP, too. Essentially, you send down the initial client version and they need to push down the value to the user after that request is done, and then that will again be sort in the local storage cache so next time there's a request, it's going to hit that first.
Does that make things a little bit more clear on that?
CHUCK:
You had me up to the point where you talked about redis and hitting redis as the cache instead of local storage, which made sense to me, and then everything after that, I completely didn't understand.
WARD:
Let me see if I've got it right, I can imagine how this works. The client realizes that it doesn't have anything -- if I just did the client-side -- I realized that I don't have any cache, but I want to display something. So, I immediatly return an empty container that has no products in it. But I've got that set up so then now, asynchrounously, I'm making the call and when the products arrive, I know what the container is, so when that Promise resolves as saying that the data have arrived, I'll fill that container and suddenly, the screen will fail. That's the idea, isn't it?
PATRICK:
Yeah.
CHUCK:
Right. What about on the server?
PATRICK:
Well, it's Universal, so it's the same thing, the same thing will happen.
CHUCK:
Yeah, but it's not making an HTTP request, so it doesn't just go down to my PostgreSQL server.
PATRICK:
I think the problem here is not thinking about the network there.
CHUCK:
Right.
PATRICK:
If you just ignore the network there and just assume stuff works like that, then you could see all the components living around. But, replace HTTP call with a function call to the database...
CHUCK:
Right.
PATRICK:
Then that takes a long running task.
JEFF:
The baseline for data in a Universal app with Angular 2 is that, right out of the box, you don't do any other optimizations. You will actually fetch the data twice on the server-side, because you're running the exact same thing on the server, and the client, it will go get the data and then the client will do it again. But, it's very easy to -- that's like the no-cost, right out of the box, get it going -- but it's very easy to take some additional steps and start adding small optimizations so that you start sharing data and you start like, "Okay, we've already made this call once, let's pass date down," so we are going to be adding stuff into the solution that allows for those advanced users who want to configure things, for those different use cases, to do stuff like that as well.
CHUCK:
Yeah. The other question I was trying to ask was, if I'm doing server-side rendering, does that increase the load on my server?
JEFF:
Yeah, it definitely does. You could serve up a client-side app just from CDN, right?
CHUCK:
Right.
JEFF:
So clearly, if you are going to be creating an actual app on a server-side, it's going to have load. But it doesn't have to be that great. I mean, obviously, Chuck, you know very well from the many different Rails sites you created that, depending on the app I guess, most of the ones that I've looked at, you can use like edge caching and that type of thing on the server-side so that the load isn't too bad, but it's definitely going to be more than just a straight client-side app.
CHUCK:
Yeah, you can definitely use until you're used of caching and load balancing, and caching in the load balancer and a million other different things...
JEFF:
Right, right.
CHUCK:
Optimize your database so that it's caching and doing other intelligent things so that you don't have to do nearly as much work to give out the same data multiple times.
PATRICK:
But also remember that, first -- I'd get example of this -- they were initially all server-rendered, Ruby and Rails, and they switched to the client-render, then they render to a lot of problems. But then they switched to this hybrid approach, this is the approach that we're proposing, and that is the same that the initial load, you get everything and you get it in a very snappy experience. Now, if you tuck on something like service worker, then you're going to interact with service worker rather than hitting the server every single time. The service worker will basically allow you to maintain your assets on the client. That's the world that we want to get to...
CHUCK:
Can I stop you? What is service worker?
PATRICK:
Yeah, sorry. Service worker is a way to manage your resources on a client. It's basically allowing native web application features for web applications.
JEFF:
I should explain...
PATRICK:
Yeah, if you could.
JEFF:
Well, I was going to say I should explain this point just for like explicitly say that Patrick loves to... he's a thinker in our group, he tries out million different things, and service worker is definitely going to help with some stuff here, but that isn't a vital part of the "out-of-the-box" solution. There's all sorts of different variations and cool add ons in order to create a full stack app with Angular 2 and really awesome optimizations for advanced users and that type of thing. But most people who use this won't even have to care about that at all. You just distinguish, if you're using this, you don't have to think about the data caching and the service worker and everything else. You just have to do a simple install and have the baseline working. But if you do really care about those, there's a really fine tune stuff like there's going to be a lot of additonal stuff that will be available.
CHUCK:
Got you. And I like the idea that, yeah, for the most part, it's install this, set it up, make sure that the basic case works. It can talk to the database and talk to the cache and whatever, then it's ready to go.
JEFF:
Definitely.
CHUCK:
The other question I have, you've talked about, in your talk (I will put a link to the talk in the show notes), you talked about how on the backend, you've got it hooked up so that you can run it on Express and Hapi and a couple of others. Are there just bindings that hook into those frameworks to make this work? Or, is there more to it than that?
JEFF:
Patrick, I'll let you talk about the details, but just at a high level, I wanted to explain that our primary use case is JavaScript backends that any sort of framework or whatever, we're building adapters for each of the popular frameworks, Express and Hapi or whatever, but Patrick tells the details, but we eventually want to, and this will be down the road, eventually allow non-JavaScript backends to also participate.
PATRICK:
What we're doing is we're just making a baseline experience for everyone in every framework. That's allowing you to buy into this Universal roads in any stage you want and basically at any level, and that's the level of obstruction that you want. That's saying that, if I include this adapter, I could either make my application Universal with one line, or I can have more controller over the lifecycle of the application by doing it another way, but that involves more lines of code and understanding how everything works. So the initiative of making sure that we have this working in other frameworks is just ensuring that we have a baseline experience for everyone because that's the thing that we found out about server-side rendering -- everyone has a different backend, not everyone has JavaScript and everything. That's why we're also trying to do another initiative of rendering Angular 2 in other languages.
CHUCK:
Right. But essentially then what it is is it's you install maybe an npm package that's Angular Universal express. And then, you included somewhere, in your application, you included the module. What it does from there is that it just sets up the binding so that when the request is made, it does all the right things and use these mechanisms that express already exposes to get the data and interface with the cache and all the other things we've already been talking about.
And then if you want more control, then there's some API that you can grab on to within that module to actually give it a little bit more new wants behavior.
PATRICK:
Yup, that's correct.
JEFF:
I think we're still working on the specific API, but what we've discussed at a higher level is something along the lines that if you just do a simple include with no configuration options, then you would basically take over either routing as well, they would just route all request, it would assume that you're just doing everything to Angular to, the service set version of Angular to handle it. But, obviously, you could get more fine-tune from there if you want to only route certain request, if you want to really like just picking shoes, certain routes that get rendered with Angular on the server, whatever, we will have the ability to configure it, add more granule level as well.
WARD:
I'm confused about so many things, but let me ask you this one: I understand, or at least I think I understand, that when you're talking about us helping build a caching obstruction for various server technology such that the request comes in and you can rather you usually figure out whether you got the data or whether you should go off some place else, I got that idea. But we keep talking about rendering at Angular, which suggest to me, and I'll go back to my product example, that if I
was asking for a page, the product on it, if you were rendering that on the server, then I would expect something to flow over the wire and that it would have product data in the table, let's suppose it was a table list or a table of products. And when it arrived, it would somehow have something that would be the data itself in the page? Or, what's the rendering doing for me? How was it rendering on the server and I'm not re-rendering in some fashion on the client? Help me out.
JEFF:
I will, by default, both the client and the server will pull the data and will render the page. But the difference between a re-render where you would just blow everything away that's where preboot comes in where we built a library called "preboot" that bridges the gap that you have at server-side view. And right from the moment that the server-side view is loaded in your browser for those 5 seconds or 6 seconds while the client-side is downloading, the preboot library is just a small piece of inline JavaScript, which records all events on the page. It basically captures everything that's occuring on the page for those 5 or 6 seconds until Angular is live. And then once Angular is live, it replays all those events to Angular, show that Angular can actually take actions on that. The reason why it's important with this setup is because it allows Angular, if by at fault, you do the default behavior of just re-rendering everything, it won't blow away what's there because preboot will transfer over all of the state of what the server-side view put on the page and make sure that it's there or in the client-side.
So if you, for example, put your mouse in the textbox and start typing something, you clicked a button, whatever, it will seemlessly be handled by Angular once it's ready.
CHUCK:
So basicallly, you get a fully-formed page, and then as Angular loads up, then it basically repopulates all that stuff, and if nothing is done, then it's probably going to re-populate it with the same data and it'll be mostly transparent to the user. But if something, they clicked on something to interact with the page in some way, then it records those events, and then once everything has been played in, then it'll replay those events.
JEFF:
Right.
CHUCK:
I have another question. Since I mostly don't write my backends in JavaScript, can I prerender the page by essentially doing the rendering I would normally do in Rails and then have the frontend do what it would normally do to, like I just said, it would essentially re-render or plug in all that data into the same places and it'd be mostly transparent, and then use prerender, you prerender JS, I forgot what it's called, on my Rails apps.
JEFF:
It's preboot.
CHUCK:
Preboot! That's what it was. Can I get away with that?
PATRICK:
Let me briefly touch -- there's one technique of getting JavaScripts around in different languages, and that is just like a JavaScript environment that basically just runs node, but in that language, in another's route, but then it parks it over. That's like the baseline experience. But the ideal solution is what Angular is doing already with Dart, and that is compiling the framework to another language. I'm not saying you should do this, but I'm saying Angular allows for this.
Now, there's another approach, and that is writing the renderers in a different language. We already see this with native script at Angular. The idea is like portion of the framework is actually written in another language in order to optimize there. There's a few techniques that you could use. Again, this would involve a lot more community effort, but essentially, that would be the ideal solution.
CHUCK:
So I can write a prerender in Ruby, that's what you're telling me.
PATRICK:
Yeah, you could write your render in Ruby and you're able to, as long as it outputs everything correctly, yeah, it could actually take advantage of that.
JEFF:
But that is something that is for further down, any of those different options for non-JavaScript backends, we've really purposely held off in a lot of them even though there are many people who are interested because we want to perfect the JavaScript backend and have that fully working. But like Patrick is saying, it is true that there are a number of different possiblities for the future of hooking into this solution.
PATRICK:
Yeah, and it's just worth-noting now, Angular allows anyone to do this, we're not going to be writing that at all just because it's a lot of effort. Again, that's what I'm saying, there really needs to be a community effort to do that. And the way that we see of bootstrapping that effort is providing the minimum amount of way of allowing people to hook into this, and that is just like our technique of rendering Angular 2 on a different backend. You could almost think of it as the lowest level as the command-line utility where you just include your component and then it just outputs the string.
CHUCK:
Uhmm-hmm. Yeah, I guess what I was asking... that'll make sense. I like the idea of, yeah, essentially writing my view so that they're Angular enabled and then have my Rails app, essentially render them and then be able to do that. But, can I just render a fully-formed page without the Angular-enabled stuff to put the data in and then just have it Angular-enabled on the client so that it gets the rest of the optimizations, so to speak, on the frontend and use preboot JS there?
PATRICK:
Yes.
JEFF:
Definitely from today, we'll be able to use preboot to help out with certain aspects to that. I think that, what you're suggesting, I think...
CHUCK:
Yeah, I have to render it twice, two different ways, but yeah...
JEFF:
Yeah, it just requires extra effort.
CHUCK:
Yeah.
JEFF:
You're going to have to do it.
PATRICK:
Yeah, sorry, I was suggesting optimizations, but yeah, you could form your template in a certain way. So long as the structure is the same like Angular, when it care, and actually hook into your thing correctly.
CHUCK:
Yeah, that makes sense.
WARD:
Our buddy, Joe, who couldn't be here, had a question for you, guys, and I'll just read it verbatim. He asks, "Does Angular 2 have a concept of a virtual DOM? And if not, how do you perform isomorphic rendering in Angular 2 without there being a virtual DOM?"
JEFF:
I'm going to let Patrick answer this, but let's just try to hold them back as he might talking for the next hour.
CHUCK:
[Laughs]
PATRICK:
[Chuckles] Yeah.
CHUCK:
We want the 5-minute version, Patrick.
PATRICK:
Alright. Angular and Reacts do things quite differently. With Reacts, what you're doing is you're interacting with this virtual DOM and you do [incomprehensible] stuff and you change everything around. And then after all that's done, you render it. That's the synchronous templating thing that I was mentioning. You basically do all your changes, get all your model data, and then when all that's done, you render it. That's probably a deeper answer, but yeah.
With Angular 2, we have like, I don't want to say virtual DOM, but we have like a JSON DOM where it's just the DOM represented at JSON. You could think of it as just the virtual DOM without any of the other thing. That's because Angular as renderer has a different method of doing that. So on the server, we're just interacting with JSON DOM, and you just think of it as just JSON and we're just make a change to that. It's similar, but the rendering approach is different.
WARD:
Is that like a representation in JSON of a virtual tree or something like that?
PATRICK:
Yeah.
WARD:
Okay. So, it's not trying to be HTML, but it's trying to be a representation of something and then..
PATRICK:
Of HTML.
WARD:
Subsequently be translated into HTML.
PATRICK:
Yeah.
CHUCK:
Uhmm-hmm.
PATRICK:
Basically, there is this whole virtual DOM hype around Reacts, then we realize that it was supposedly just hype, and that there are a lot of good things there, but there's some constraints with it, and that is the synchronous versus asynchronous approach. With Angular 2 and it's rendering method, it actually does things differently and you have this asynchronous approach to your templates. This is the reason why I like rendering on the server as faster with Angular 2. There's various of other reasons, its caching, etcetera, but this is one of the core reasons why Angular 2 is faster than any other thing that has a virtual DOM -- that's the way it's rendering and reconciling the JSON DOM.
CHUCK:
Right. So on the server, it just interacts with the JSON DOM and it doesn't worry about whether or not there's an actual DOM backing it because it has a way of rendering that to HTML that gives pass backup to the server. So, it outputs a string instead of interacting with an existing DOM.
PATRICK:
Yeah. At the end of everything, we basically, say, we just traverse through this JSON DOM and then we just two string it, and then we reconstruct the HTML from that.
CHUCK:
Right.
PATRICK:
And it's also worth-noting that I'm just calling it JSON DOM just because there is this whole thing that comes with whenever you say virtual DOM, people would misinterpret that for being something else. That's the reason why I say that.
CHUCK:
It makes sense. And it doesn't work the same way as the virtual DOM, but it's a representation of the DOM and it's an obstruction layer above the DOM in the same way that the virtual DOM is; it just doesn't do the same thing in the same way.
WARD:
I think of a virtual DOM as literally looking like a DOM for HTML, just trying to emulate the DOM that you would see or whatever, that you could interact with if you're talking to a browser. And, you got to end up there when you finally display it, but that may not be the optimal form of representation of the visuals and whatever the things that are actually operative in their handlers, all the other stuff that hides in the DOM. It might be a more optimal way of representing that when you're trying to manipulate it. And you're suggesting I think, Patrick, that there is some other representation of what will ultimately become the DOM that Angular has and that it's working with and that you can work with, and that has different characteristics including some level of asynchrony. Is that a paraphrase?
PATRICK:
Yeah. You could think of it as, yeah, like a virtual DOM as, well, just think of it in terms of like one element. In a virtual DOM, you would interact with that element through whatever interface it gives.
WARD:
Then you put box, I'd say you put that text, the sign, right?
PATRICK:
Yes.
WARD:
Just while I was talking to the DOM.
PATRICK:
Yeah. Well, with the virtual DOM, that's worth at the JSON DOM. So the JSON DOM, you could manipulate it as much as you want exactly like an actual DOM. You could do like .val(this) and then we'll just like [incomprehensible] that later. But yeah, they're very similar.
CHUCK:
I don't know if it's worth getting into the weeds on how the JSON DOM works, and we're already over our time.
WARD:
[Laughs]
PATRICK:
[Chuckles] Yeah, that's...
CHUCK:
So long I had a way of over that and just say that it offers certain advantages that make this all work very nicely.
PATRICK:
There's a lot of concepts there that are just new that are being brought forward. That's what we're trying to solve; it's trying to make it easy as possible for people to jump in and understand these concepts. For example, the whole model thing, if I could just include a different bindings -- we did this already with HTTP, on the client, it's require HTTP injectables and then that's where you get the client version. On the server, you do require Angular Universal and then you get out $http, and then that's the server version. All you do is you put that inside of the bindings, and then on the client, you do the exact same thing. The difference there is how we bootstrap the application.
CHUCK:
Yup.
WARD:
Do you guys have a time table for seeing all this stuff?
PATRICK:
It's more of just like making sure that the developer experience is good, then actually distributing it on npm...
JEFF:
We don't have a specific time, it's just like the Angular team always helps us for never giving date just because it incorrectly sets expectations. But the one thing I would say is that you can follow our progress on angular/universal, number one. And number two, Patrick and I are motivated, highly motivated to get stuff done as quickly as possible because we have to talk about it at AngularConnect in a couple of months. It won't be nearly as good if we don't have it in a much better state where it's almost ready to be used by everyone. So, conference-driven development, I guess. [Laughter]
CHUCK:
Yeah, but will it be ready when Angular 2 comes out of beta?
PATRICK:
Comes out of beta? Of course. In turning beta, yeah, of course.
WARD:
It's not even beta yet, though, it's still alpha.
[Laughter]
PATRICK:
You say that now.
CHUCK:
Is this something that we can play with now if we're playing with the Angular 2 alpha?
PATRICK:
Technically, you can play with it inside of the repo. The only restriction is that it's just rendition. There's a time frame difference when these episodes release and what we're talking about right now. But right now, as we're talking on here, the only way to actually use this is actually inside of the repo and then making your example app inside of there.
JEFF:
But the answer is, yes. You just have to clone the repo and build it from there.
CHUCK:
Okay! Well, I'm going to cut this off because we are way over of what we usually talk... [laughs].
PATRICK:
I think, the model explanations, that one is a little bit much [chuckles].
CHUCK:
Yeah, but there are things that are important to us when we're building our Angular apps, so totally worth talking about. But let's go ahead and do some picks. Lukas, do you want to start this off with picks?
LUKAS:
Sure! I would love to start this off with picks. I have recently been reading a book called "14" by Ernest Cline, and it is quite, quite engaging. If you like weird science fiction, interesting mystery type stuff, then -- it's some kind of Frenchish in a way, but a very cool book. I highly recommend it.
CHUCK:
Alright. Ward.
WARD:
Okay, well, next week, I am going to hike in Mount Whitney, the mountain in nearest route, which is the tallest mountain in the lower 48 states. I'm kind of at this lightweight backpacking thing so I had to turn in my old backpack and save 8 ounces, I spend $300 plus to save 8 ounces to buy a new pack! And it feels like I've got a papar bag on my back, it's so light. It's by ZPacks. It's fun, this whole ultra-light backpacking thing. I'll tell you more about it someday. But anyway, here comes Whitney and here comes my ZPack 22 ounce pack.
CHUCK:
Isn't the ZPack also like antibiotics or something?
WARD:
Wow! That's an interesting thought! I hope I don't catch anything up there.
CHUCK:
[Chuckles] Alright, I'm going to throw out a couple of picks. I just want to remind everybody about Angular Remote Conf again. That's going to be September 24th-26th. The call for proposals is open through the 31st. The early bird tickets are available through the 25th. And, I'm also going to throw out discount code for listeners to the show. You can get 25% off if you use the code "adventures". So, go over to angularremoteconf and use the coupon code.
I also have another pick. I'm a member of a mastermind group that I really, really enjoy and has made a big difference for me. It's called "Iron Sharpens Iron", it's done by Aaron Walker. He also has a community called "View from the Top", I'll put a link to that in the show notes as well. I just want to point out a couple of things. The guys in the group have just been terrific and have really supported me in a lot of things that I have going on. It's a men's group, I just want to put that out there as well, and that's just because Aaron is much more comfortable coaching men. Anyway, I've really, really gotten a lot out of the group and a lot out of the discussions that I've had with these guys. So, I'm going to pick Iron Sharpens Iron, but I'm also just going to encourage people, whether you're in your own business or not, to go find a group of people that you can have deep and meaningful conversations about life, about business, about work, about family, about whatever it is that your concerns are, and make the most of that because having a group of supportive people around you makes a huge, huge difference. That's mostly my pick; it's mastermind groups.
Jeff, do you have some picks for us?
JEFF:
Yeah, two picks. First is, two years ago, Lukas was actually working with me at GetHuman to create the initial version, prototype version, an Angular-based version of GetHuman.com. It took us an additional a year and a half after he left, but we finally launched. So, my first pick is "GetHuman.com". If you are trying to contact any customer service at any company, you could use us and not have to wait on hold. You just get into your phone number and we can call them for you and call you back once we have a live person. So, it's very useful.
The other thing is, our "Angular Universal" repo. I wanted to solicit. If anybody is interested in helping us out, we have a number of different issues listed in the repo that we need help on. It's going to change over the course of next couple of weeks as we work on stuff. So definitely, when you hear this, just go to github/angular/universal and click on the issues and look through. If you're interested, we would love to have additional help.
CHUCK:
Alright. Patrick, do you have some picks for us?
PATRICK:
Yeah! My first pick is "Open source". Open source is great. Again, you should also help us in Angular Universal. Pretty much all of your constraints, use cases, just bombard us with issues about that, it will find a way to integrate it or provide a way because I think, it's a good idea for us to make that it would work for you.
The other two are conferences, "Angular Summit" and "AngularConnect". AngularConnect is going to be really, really interesting. I recommend everyone to tune in or get a ticket if possible.
Those are my picks.
CHUCK:
Alright! If people want to know more about Angular Universal or about what you guys are up to or any podcast you happen to be on regularly, where do they go?
JEFF:
For Angular Universal, just github/angular/universal. Both Patrick and I, one thing we didn't mention that, you're right, Chuck, both Patrick and I are on Angular Air if you go to angular-air.com. I have my website as well, jeffwhelpley.com, if you want to check out the latest blogs.
CHUCK:
Alright, terrific! Well, thank you all for coming, and we will have another episode out next week!
[Hosting and bandwidth provided by The Blue Box Group. Check them out at bluebox.net]
[Bandwidth for this segment is provided by Cache Fly, the world’s fastest CDN. Deliver your content fast with Cache Fly. Visit cachefly.com to learn more.]
[Do you wanna have conversations with the Adventures in Angular crew and their guests? Do you wanna support the show? Now you can. Go to adventuresinangular.com/forum and sign up today!]