To start, let’s fork the grumblr_angular
repo, then clone down locally
We can get today’s starter code by checking out a new branch:
$ git checkout -b ui-router 1.0.0
Let’s start our server locally:
$ hs
Now if you go to localhost:8080
you should see your app.
You should see the word Grumblr.
ui.router
// app.js
angular
.module("grumblr", ["ui.router"])
Add ng-app
to index.html
<!-- index.html -->
<html ng-app='grumblr'>
ui.router
, and define Router
functionRouter
function
$stateProvider
, and pass it in as an argument to your function// app.js
angular
.module("grumblr", ["ui.router"])
.config(["$stateProvider", Router])
function Router($stateProvider){
console.log("working")
}
Refreshing the page should show “working” in the console.
We’re just going to make one controller for now: the index
controller.
In app.js
, let’s add the following code:
.controller("GrumbleIndexController", [
GrumbleIndexControllerFunction
])
function GrumbleIndexControllerFunction(){
console.log("I'm in the controller!")
}
/grumbles
// app.js
function Router($stateProvider){
$stateProvider
.state("grumbleIndex", {
url: "/grumbles",
controller: "GrumbleIndexController",
controllerAs: "vm"
});
}
ui-view
in index.html<!-- ./index.html -->
<div ui-view></div>
Visit: http://localhost:8080/#/grumbles
You should see “I’m in the controller!” in the console.
/grumbles
We can have Angular load and insert whole HTML files for us – just like with partials in Rails.
Let’s create a folder in which we can put some partials:
$ mkdir js/ng-views
$ touch js/ng-views/index.html
Let’s put a piece of HTML into that index.html
, just so we know it’s working:
<h2>I'm the Grumbles index!</h2>
function Router($stateProvider){
$stateProvider
.state("grumbleIndex", {
url: "/grumbles",
controller: "GrumbleIndexController",
controllerAs: "vm",
templateUrl: "js/ng-views/index.html"
});
}
/grumbles
in url:Things to check:
Before <script src="js/app.js">
, let’s add this:
<script>
var grumbles = [
{
title: "I am Grumble One"
},
{
title: "I'm another Grumble"
}
]
</script>
<script src="js/app.js"></script>
We can access this global variable in all the other files. Set this.grumbles
equal to that variable in our controller:
function GrumbleIndexControllerFunction(){
+ this.grumbles = grumbles;
}
<!-- js/ng-views/index.html -->
<div ng-repeat="grumble in vm.grumbles">
<p>{{grumble.title}}</p>
</div>
You should see each of the grumbles when you refresh the page.
Using ui-sref
Before we make the show pages themselves, we’re going to create some links to them. We use ui-sref
which is a directive that binds a link to a state in order to accomplish that.
The problem is, these grumbles don’t actually have IDs – they’re just items in an array.
Inside ng-repeat
, you automatically have access to a variable called $index
. This refers to the index of the current item in the thing being repeated.
<h2>I'm the Grumbles index!</h2>
<div ng-repeat="grumble in vm.grumbles">
<p><a ui-sref="grumbleShow({id: $index})">{{grumble.title}}</a></p>
</div>
We just invented grumbleShow
- define the state in the next section.
In our application we want to be able to view info about one grumble
, so let’s add a another state for our show
page.
To do this, we can chain an additional .state
onto the earlier one:
function Router($stateProvider){
$stateProvider
.state("grumbleIndex", {
url: "/grumbles",
controller: "GrumbleIndexController",
controllerAs: "vm",
templateUrl: "js/ng-views/index.html"
})
.state("grumbleShow", {
url: "/grumbles/:id",
controller: "GrumbleShowController",
controllerAs: "vm",
templateUrl: "js/ng-views/show.html"
});
}
show.html
page$ touch js/ng-views/show.html
<!-- js/ng-views/show.html -->
The show page
To start the show controller, We’re just going to copy the index controller. We’ll change index
to show
, and change this.grumbles
to this.grumble
since we’re just showing one:
angular
.module("grumblr", ["ui.router"])
.controller("GrumbleShowController", [
GrumbleShowControllerFunction
])
function GrumbleShowControllerFunction(){
this.grumble = {}
}
We’ll update the router accordingly to reference the new controller:
.state("grumbleShow", {
url: "/grumbles/:id",
templateUrl: "js/ng-views/show.html",
controller: "GrumbleShowController",
controllerAs: "vm"
});
$stateParams
Now we need a way of getting the ID from the URL. Angular makes this possible with a module called $stateParams
, included with ui.router
. We’ll inject it into the controller the same way we injected into the router, and add a console.log
so we can see what’s in $stateParams
:
.controller("GrumbleShowController", [
"$stateParams",
GrumbleShowControllerFunction
])
function GrumbleShowControllerFunction($stateParams){
console.log($stateParams);
this.grumble = {}
}
You can see that it’s a small object containing the URL parameters (or the URL’s one parameter, in this case).
So, to get the index of the current grumble, you just need $stateParams.id
:
function GrumbleShowControllerFunction($stateParams){
this.grumble = grumbles[$stateParams.id];
}
<!-- js/ng-views/show.html -->
<h2>{{vm.grumble.title}}</h2>
You should see the grumble in the browser
This data won’t persist since we’re not hooked up to a database: refresh the page and it’s gone.
But being able to CRD grumbles, even if they just exist until you next refresh the page, will be really useful in doing it for realzies later on!
Thanks to two-way data binding, an “update” button is unnecessary! The grumble is updated automatically as you type.
ng-model="GrumbleNewViewModel.newGrumble.title"
ng-click="GrumbleNewViewModel.create()"
ng-submit
this.newGrumble
in the controllergrumbles
array$state.go
to the show view for the new grumbleYou’ve probably never seen an Angular app that has hashmarks in its URLs the way we have here. That’s because they’re ugly and Angular makes them super-easy to remove.
First, inject $locationProvder
into your router. Then, add $locationProvider.html5Mode(true)
. The result should be:
// ...
.config([
"$stateProvider",
"$locationProvider",
Router
]);
function Router($stateProvider, $locationProvider){
$locationProvider.html5Mode(true);
// ...
If you refresh the page now and follow the error link, it’ll tell you that $location
needs a <base>
tag.
This is a standard but little-used HTML tag, the purpose of which is to say what URL all relative URLs should be based on.
Add this to your main index.html
, right below the <title>
:
<base href="http://localhost:8080/" />
Go to localhost:8080
and you should be able to click on URLs without seeing that hash.
Note that if you actually type localhost:8080/grumbles
into your browser’s address bar it won’t work.
That’s because your http-server
considers that to be a completely different route – it doesn’t know that you actually want index.html
.
Remember that Angular is geared toward single-page apps. In the “real world”, you’d probably have the server redirect every page to index.html
.
This can cause some bugs due to browser caching. You can mitigate these bugs in Chrome by disabling caching when you have the console open. This Gif has instructions.