AngularJS contains basic client-side routing functionality in-the-box. This is great for small applications and simple websites. However, the functionality needed for larger enterprise solutions is lacking. UI-Router is a third-party solution built for large AngularJS applications. At a recent St. Louis Angular Lunch Mark Volkmann of OCI gave a talk on using UI-Router in your Angular applications. A video of the talk is available below.
Here is the talk, transcribed to text. This is a lightly edited, draft transcription, so any errors are probably from that process.
Mark: But I'm going to go over some basic things and then what I think are some more advanced things. So you can be the judge of that, and then we'll have some time for discussion after this. We've got 37 slides. We can't go through 37 slides in half an hour. So I'm going to skip some things and really try to focus on demonstrations and looking at code that runs those things.
So the basics are that in older versions of AngularJS, it came with the routing service, this thing called $routeProvider. And you can still use that but it doesn't come with Angular, and couple of the problems with that is that it didn't support nested views or sibling views. So now let's pull out core, you download it separately if you wanted but there are other alternatives and ui-router seems to be the most popular one probably because it does work nested views and sibling views.
So in the older router service, states were defined by URL. You say you want to go to that URL now, you see that route. In ui-router, your states have names, and when you say you want to transition it to the state, that can be in a lot of things. In the most basic case, it means you're going to a route that has a certain URL associated with it. You want to use a certain controller and you want to use this template to display something. That's the basics. But beyond that, there are a lot of options we can specify when you configure a state and I've got four slides on this. I don't know if I have the time to talk about any of it. So blah blah blah blah and so let's see some real examples of using some of those options.
The most basic example is just using the anchor tags that I talked about. So I have this kind of dummy weather forecast here and there are two ways you can look at this. There's the five-day forecast and the hourly forecast. If you just click the hyperlinks to switch between them. And so let me demonstrate this thing working. And so here it is, then I can click to look a the hourly forecast, and I can go back to the five-day forecast. Pretty basic stuff. So my view is that main part of the page, not the heading at the top. And then I'm able to switch between those two states.
Here are my two partials. So when I set up the states, I'm going to say these the templates I want to use, and so one for the five-day and one for the hourly. And this is some pretty basic Angular stuff here. I'm assuming that on my scope, I'm going to have day forecast which is a collection of day forecast objects. And those have properties on them of day, and the high and the low and similarly for the hour forecast. That's going to be on my scope and I can pull out the hour and the temperature.
Kyle: Other than it's a ui-router, but there's this thing called factory, this thing called service. And unfortunately, it seems like your standard terminology that every [6:46 inaudible] pops is the safe service literally using factory. Do you have any insight deeper than what you get from the factory application that not touch on whether you’re supposed to use factory and service? You kind of accomplish the same thing in fact.
Mark: So it turns out that there are four different ways to define service and Angular and I have slides that show examples of these four different ways. But in my experience, it seems like 99% of the time people use factory. And I have yet to run across the case where I wanted to use one of the other three ways doing it. So maybe that could be a future talk or kind of a group discussion and talk about these different ways of…
Kyle: Paul is always using [inaudible].
Mark: I do. Yeah.
Male 1: One of that was on both parts of this is things people like to at least talk about the [7:30 inaudible]. Kind of a philosophy behind when you use different . . . .
Mark: Right, so even though I had value than the other three ways if I show examples of using them, other people might say, “Oh that's why I would use that.” So yeah we'll give you that. Then we get down to my controller, my weather controller at the bottom and I'm injecting into this, my weather service and then a colon, and this function. I'm filling that data on this scope so that I can display it in my view.
And then the most important part of this is defining my states. And so I'm setting that the default is to show me the daily view and then I have my two states defined. And each state has a URL associated with it. I'm telling it what controller I want it to use for getting the data that I'm going to render and then what is the template they'll use for displaying that view. Any question about that? This is really like the most basic thing here to do with ui router.
And then we'll talk about having sibling views when you switch to a state. It could be that your page contains multiple views and you want to update all of them when you go into this new state. And so this stage here has four views on it obviously, two borders up there. And there's a little bit of functionality here. When I'm in this state on the left and I click, red, green, and blue, it's going to change the color of the text that's right there. And when I'm in this state, and I click these, it's going to change the size of that text. So let me demonstrate that. Local 300. I can change the color of that text. And I'd switch to the other view, change the size of that text.
Okay so here is my main HTML and a big difference you'll notice here is they don't have just one UI. I have four of them and they have names. So I'll be referring to those names when I set up the configuration from my states.
So here's the CSS, we can skip past that. And then my partials, I have a lot more partials this time. I have four areas of the page so I have two partials for each of those areas. The headers and the body part, and the footer are all pretty simple and then that nav that was off to the left where I had all my functionality has some hyperlinks. And when you click those hyperlinks, and when you click those hyperlinks I want to call some functions that are on the scope to change the color or change the font size and then when I want to change what view I'm looking at when I'm in the first view, I want to be able to switch to the second one, or I should say state. When I'm in the first state, then when I switch to the second state, and when I'm in the second state, I can switch to the first state.
And then for configuring this, that is really the interesting part, I tell it that I want to start in the first state, and then for the state provider, the green part there is what you in the boxes on this slide. Here's the definition of the first state. And the big thing that is different here is that rather than tell it about template URLs and controllers in that top object that I'm passing in, I have a views property which is not just has all of that in it. And the properties of the views I have picked are the names of my views. And so those are the four names I had and then each one has a template URL. The header and the body, and the footer, there's nothing active going on there. So I don't have a controller. But I need a controller for the nav part because I need to call some functions that the controller adds to the scope when you click those things. And so the second stage is very similar to that. Any questions about that? Okay.
Audience: And we have to provide those alternatives that are really the same like headers and shoulders are really the same.
Mark: They're not the same. I mean these two states.
Mark: Because there's one here, that's header one and this one uses header two.
Audience: Yeah I guess maybe I missed something. It looked visually like they didn't change at all.
Mark: Oh no, yeah let me go back to the browser. See it says header 2 and footer 2, and when I switch views, that changes in all four sections.
Audience: But you didn't specify those with [12:57 inaudible] when I switched states?
Mark: Right, nothing would happen.
Audience: So if I had sections on my pages, that are kind of static, I could just not touch it.
Mark: Although, probably in that case, you want what I'm going to talk about next which is nested views.
Mark: Yeah. Alright so nested views, and this is maybe one of the more complicated parts of this. And I don't believe I'm describing all the options here but it seems like the common way of defining nested views is that there's a name of a state that is your parent state. And when you define one that is nested, you use a dot for that. So the presence of a dot in the name is what makes it be considered a child view. These URLS can be parameterized. Just by putting the colon there means that you'd be able to extract some data from it and do something different based on the passed in. One of the things I ran into is that you have to be careful about the order that you define these states. You have to define the parent states before the child, or else you'll get this error message but it doesn't really give you much of a clue of what you did wrong. Okay now, and if you do that parameterized URLs then you use the $stateparamservice to get to those values.
So this is how I want to demonstrate this. We have the Volkmann diner. At the top, I have three menus you can pick from, breakfast, lunch and dinner. And when you pick one, I'll display a different selection of items below that. And then when you click one of the hyperlinks for a food item, then it will show you a picture of it. And so the point of having nested views here is that we've already listed whether I want to see the spaghetti or the pizza, I'm still going to be in the dinner view, and then below that, nested inside that view, I'll show you a picture of the spaghetti or the pizza. So here it is and I'm looking at the dinner menu. I can switch to the lunch menu and the breakfast menu. Notice it says click an item to see the detail. That's the default content in that nested view. And then when I click this link, it gets populated with some other data. But that is the nested view inside the breakfast view.
Let's walk through that code. So in my main HTML, first I have the dinner controllers specified in my HTML because that's going to add to the scope, the string bulk, so it displays who's done or who this is. And then I have my hyperlinks for switching my state for the different meals, and then, finally this is where I'm going to display the menu but this view is going to have a nested view inside it. We'll look at the CSS.
Here are my partials. These are what I have for each of the meals of breakfast and then I'll have a bunch of these. I'm showing just a few here but you can add as many as you want there. And then similarly for the lunch, and for the ones that I do have a picture to show then I have a nested view. You notice the dot in here that's what makes it a nested view. So I have pictures for some of the food items. And then here is where I’m going to to display it. So notice that I have a ui view. It doesn't have a name, and neither did the parent one but it can keep that straight because they're nested. And so when I tell it to display the omelet, it's going to know to put it inside that view. I have my default content for what you see until you click on those.
Here are the partials for the actual food items. So they just have an h4 telling you what it is and then an image stack.
So as in all these examples, the most interesting part is setting up the state provider and so I have the parent state for breakfast and then it's got its own template. And then here's a child state for the omelet and notice the URL is just /omelet. Well you can't go to /omelet but you can go to /breakfast/omelet. So the URLS of the child states are appended on to the URL of the parent state. So the same kind of thing is happening with the lunch and dinner being parent states and lunch.pizza, a child of that, dinner.pizza, a child of that one. And that's all there is to that. So if I go back to the browser, I want to show you that if I go to the lunch menu, I'd see that there is pizza there. So if I go back to breakfast, and I go up to here in the browser, where I can type lunch/pizza and it should switch to showing the lunch menu and show me the picture of the pizza. And that's correct. Any questions about that?
The last thing I want to talk about is resolve. So the point of resolve is that when you switch to a new state, you don't necessarily want to begin rendering immediately. And that's because maybe some of the data that you're planning to render is going to come from a REST service and it's going to take a while for that data to come back. And your page can actually look pretty bad if you don't wait for the data to come back first. So let me show you an example of a bad page. So you notice that the page is going to come up immediately. Then some data is going to fill in and then some other data is going to fill in. Okay, so that's not nice. That's not to say that there won't be cases where you want to have your page up there and data is dynamically being plugged in so this is kind of a design sort of thing whether you want it to behave this way or not. But if I want it to wait until that data comes back, before I display something, I can do that. And so let's walk through the code first and then I'll show you that working in this better way. So I want it to display a table at the top, all that data's coming from a REST service and then I have a list at the bottom that's also going to come from a REST service.
So in my main HTML, there's nothing really special going on there. I have just one view but notice I have some default content that says the view is loading. So when I fix this, that's what you'll see initially. And then when I have the data, it'll all come up at once. And then, this is a partial that I'm going to display and so I’ve got the table at the top where I display all the marathons, and then there's the list of runners at the bottom. So marathons is going to be a collection that is added to the scope at some point. And runners is the same.
So I've got the getMarathons as a function on my service. And normally right here, what you'd be seeing is perhaps a use of the $ http service to make a call-out to the REST service and that would return a promise and then you could wait for the promise to be resolved. But what I'm doing instead is I'm saying I'll create my own defer object and then I'm going to immediately return the promise. So something else can choose to wait on that and in this case, it's going to be the ui router and it's all of the functionality that's going to do that for me, yeah. But it wouldn't be any different if I was using $http.get to call the REST service. So I use the timeout service and say I want to wait 1 and a half seconds and when that's up then I'll create this w array of data. And then I call resolve and now that comes the value to get back from the promise. Similarly for the runners, it created a defer object. Immediately returning the promise. Then when one second elapses, return this array from the promise.
So in my controller, I want to call those two things but I don't have the data available until the promise gets resolved for each of them. So when I call to getMarathons, I'm getting back a promise. I call then on it, and I give it two functions: one for success and one for error. And if it's successful, then I attach that data onto the promise. And notice that the controller we're looking at here is bad controller. That's because it's going to render the data as soon as it becomes available. And it doesn't care about what other services you may be invoking. They'll all be handled independently. So as soon as I get the runner data back, then I’ll add back to the scope. And of course, the way I have it set up, that's the one that will come back first, because that one only waits one second and the other one waited a second and a half. So contrast that with the good controller where I'm injecting into the controller marathons and runners and we're going to see show that happens through resolve on the next slide. But here, we're just trusting that something is going to give me this data and when it gives it to me, I'm not waiting anymore. I've had it and I attach it to the scope and I'm ready to render that view. So at the bottom there I'm starting to set up my states and saying that I'm going to begin in the marathons state. And turns out in this example, I only have one state, but the reason I'm using ui router even with one state is that I want this resolve capability.
So at the top showing I'm showing setting this up to use the bad controller and it's really simple. I just tell it what URL is associated with the state, what template I want to use. Notice both of them are using the same template and then that one has the bad controller that makes the calls and whenever the data comes back, attaches it to the scope. In the good one though, I have this resolve property. And I'm telling it do not try to render this view until all of the promises return down here, come back. So you've the passed the resolve an object where the keys are the names of things that can be injected into the controller. So marathons and runners, and if I go back to the previous slide, that is where these things are coming from. Those names have to match up. And then I return anything that will give me back a promise and these functions give me back promises. So I'm all set and that's going to know that it should wait until all of these things get resolved before it attempts to render that. Yes.
Audience: So I'm a noob at all this but I know [24:53 inaudible] in some places.
Mark: Yes and the way to fix this is that the key thing is wherever you're injecting things like this, —i's a bit ugly—but what we have to do is instead of passing in a function, you have to pass in an array where the things in the beginning of the array are all strings. There are these names just in quotes. And then the last thing it's the function. And so then the identifier and go ahead and change these to something else but because it has the actual name as a string, it's able to view on that. So you have to write the code a little different to prepare that.
Kyle: Mark, we've stopped doing that, and one of the process is switching projects to use a grunt plug-in to perform that transformation programmatically.
Mark: Oh cool.
Kyle: What do you think about that? On one hand, we're kind of like let’s step farther away from us writing the code that runs. In other hand, the duplication between the strings, it’s just morally offensive there, the duplication in there.
Mark: I like what you’re saying because the code is just ugly the other way. Did someone create that plug-in or did you create it?
Kyle: We didn't create it. It's just sitting out there. It's a thousand other pre-1.0 plug-ins on GitHub.
Mark: I'd like to know what the name . . . .
Kyle: Well somebody here, he can probably tell you what it is.
Audience: One of the things that we run into is we resolve very heavily and that means that we have a lot of things like this where we don't have to pester. Can you come up with a good way to [26:24 inaudible] the actual outside of [inaudible] that some people messed up the resolve blocks?
Mark: Well I guess I have to admit that my own personal preferences lean heavily towards integration or end test. And so if I wrote that kind of test, if I'm understanding your question right, that wouldn't be an issue because I'm just going to run the whole code and look at what ended up on the dom.
Kyle: So you're talking about testing whether getMarathons works?
Male 1: Well so here's an example that's very simple for this, just one line to return Marathon service to getMarathons. But like if you were saying doing a resolve that’s taking state parameters something like that and then performance or some service calls off of that, that might actually be something significant enough.
Kyle: So my preference would be if that function was any less trivial than that, it would be to make it delegate to something in some other code.
Male 1: Kind of like the service that . . . .
Kyle: Yeah right and test that.
Mark: Okay so I'm pretty much done. The only thing additional that I want to say is that there's some events you can listen for that are associated with this and it's pretty useful for everybody and especially listening for state change errors and getting some details so that you know that there was an error when you were in this state and you tried to transition to this other state, or maybe you tried to go to a state that you never defined and you get a state that you’re done. So these are things you could listen on and these are all on the root scope and so you could listen to them from any scope and you'll get that. And so there's some examples of the syntax there and how you set that up. And then you can listen for a view to finish being loaded here. You get the view content loaded event. That's all I've got. Did I make it in half an hour? Probably not.