AngularJS Services for Views: Using Route Parameters

The URLs I used to define the routes in the previous section were fixed or static, meaning that the value passed to the $location.path method or set in an a element’s href attribute has to exactly match the value I used with the $routeProvider.when method. As a reminder, here is one of the routes that I defined:

$routeProvider.when(“/create”, {

templateUrl: “editorView.html”

});

This route will be activated only when the path component of the URL matches /create. This is the most basic kind of URL that routes can be used with and, as a consequence, the most limited.

Route URLs can contain route parameters, which match one or more segments in the path displayed by the browser. A segment is the set of characters between two / characters. As an example, the segments in the URL http://localhost:5000/users/adam/details are users, adam, and details. There are two kinds of route parameters: conservative and eager. A conservative route parameter will match one segment, and an eager one will match as many segments as possible. To demonstrate how this works, I have changed the routes in the products.js file, as shown in Listing 22-8.

Listing 22-8. Defining Routes with Route Parameters in the products.js File

.config(function ($routeProvider, $locationProvider) {

$locationProvider.html5Mode(true);

$routeProvider.when(“/list”, {

templateUrl: “/tableView.html”

});

$routeProvider.when(“/edit/:id”, {

templateUrl: “/editorView.html”

});

$routeProvider.when(“/edit/:id/:data*”, {

templateUrl: “/editorView.html”

});

$routeProvider.when(“/create”, {

templateUrl: “/editorView.html”

});

$routeProvider.otherwise({

templateUrl: “/tableView.html”

});

})

The first highlighted route URL, /edit/:id, contains a conservative route parameter. The variable is denoted by a colon character (:) and then a name, which is id in this case. The route will match a path such as /edit/1234, and it will assign the value of 1234 to a route parameter called id. (Route variables are accessed through the $routeParams service, which I describe shortly.)

Routes that use only static segments and conservative route parameters will match only those paths that contain the same number of segments as their URL. In the case of the /edit/:id URL, only URLs that contain two segments where the first segment is edit will be matched. Paths with more or less segments won’t match and nor will paths whose first segment isn’t edit.

You can extend the range of paths that a route URL will match by including an eager route parameter, like this:

$routeProvider.when(“/edit/:id/:data*”, {

An eager route parameter is denoted by a colon, followed by a name, followed by an asterisk. The example will match any path that has at least three segments where the first segment is edit. The second segment will be assigned to the route parameter id, and the remaining segments will be assigned to the route parameter data.

Tip Don’t worry if segment variables and route parameters don’t make sense at the moment. You will see how they work as I develop the examples in the following sections.

1. Accessing Routes and Routes Parameters

The URLs I used in the previous section process paths and assign the contents of segments to route parameters, which can then be accessed in code. In this section, I am going to demonstrate how to access those values using the $route and $routeParams services, both of which are contained in the ngRoute module. My first step is to change the button that edits product objects in the tableView.html file, as shown in Listing 22-9.

Listing 22-9. Using Routing to Trigger Editing in the tableView.html File

<div class=”panel-body”>

<table class=”table table-striped table-bordered”>

<thead>

<tr>

<th>Name</th>

<th>Category</th>

<th class=”text-right”>Price</th>

<th></th>

</tr>

</thead>

<tbody>

<tr ng-repeat=”item in products”>

<td>{{item.name}}</td>

<td>{{item.category}}</td>

<td class=”text-right”>{{item.price | currency}}</td>

<td class=”text-center”>

<button class=”btn btn-xs btn-primary”

ng-click=”deleteProduct(item)”>

Delete

</button>

<a href=”/edit/{{item.id}}” class=”btn btn-xs btn-primary”>Edit</a>

<increment item=”item” property-name=”price” restful=”true”

method-name=”$save” />

</td>

</tr>

</tbody>

</table>

<div>

<button class=”btn btn-primary” ng-click=”listProducts()”>Refresh</button>

<a href=”create” class=”btn btn-primary”>New</a>

</div>

</div>

I have replaced the button element with an a element whose href element corresponds to one of the routing URLs I defined in Listing 22-9, which I achieve using a standard inline binding expression within the ng-repeat directive. This means that each row in the table element will contain an a element like this one:

<a href=”/edit/18d5f4716c6b!acf” class=”btn btn-xs btn-primary”>Edit</a>

When this link is clicked, the route parameter called id that I defined in Listing 22-8 will be assigned the value 18d5f4716c6b1acf, which corresponds to the id property of the product object that the user wants to edit. In Listing 22-10, you can see that I have updated the controller in the products.js file to respond to this change.

Listing 22-10. Accessing a Route Parameter in the products.js File

.controller(“defaultCtrl”, function ($scope, $http, $resource, $location,

$route, $routeParams, baseUrl) {

$scope.currentProduct = null;

$scope.$on(“$routeChangeSuccess”, function () {

if ($location.path().indexOf(“/edit/”) == 0) {

vax id = $routeParams[“id”];

for (var i = 0; i < $scope.products.length; i++) {

if ($scope.products[i].id == id) {

$scope.currentProduct = $scope.products[i]; break;

}

}

}

});

$scope.productsResource = $resource(baseUrl + “:id”, { id: “@id” },

{ create: { method: “POST” }, save: { method: “PUT” } });

$scope.listProducts = function () {

$scope.products = $scope.productsResource.query();

}

$scope.deleteProduct = function (product) {

product.$delete().then(function () {

$scope.products.splice($scope.products.indexOf(product), 1);

});

$location.path(“/list”);

}

$scope.createProduct = function (product) {

new $scope.productsResource(product).$create().then(function (newProduct) {

$scope.products.push(newProduct);

$location.path(“/list”);

});

}

$scope.updateProduct = function (product) {

product.$save();

$location.path(“/list”);

}

$scope.saveEdit = function (product) {

if (angular.isDefined(product.id)) {

$scope.updateProduct(product);

} else {

$scope.createProduct(product);

}

$scope.currentProduct = {};

}

$scope.cancelEdit = function () {

if ($scope.currentProduct && $scope.currentProduct.$get) {

$scope.currentProduct.$get();

}

$scope.currentProduct = {};

$location.path(“/list”);

}

$scope.listProducts();

});

There is a lot going on in the highlighted code, so I am going to break down each major part and explain them in turn in the sections that follow.

Note I have removed the editProduct behavior from the controller, which was previously invoked to initiate the editing process and displayed the editorView.html view. The behavior is no longer required since editing is not initiated through the routing system.

1.1. Responding to Route Changes

The $route service, for which I added a dependency in Listing 22-10, can be used to manage the currently selected route. Table 22-3 shows the methods and properties that the $route service defines.

I didn’t use any of the members described in Table 22-3, but I did rely on another aspect of the $route service, which is a set of events used to signal changes in the active route, as described in Table 22-4. Handlers for these methods are registered using the scope $on method, which I described in Chapter 15.

Most of the $route service isn’t that useful. You usually need to know about two things: when the route changes and what the new path is. The $routeChangeSuccess method provides the first piece of information, and the $location service (not $route) provides the second, as demonstrated by this fragment that repeats the key statements from the products.js file:

$scope.$on(“$routeChangeSuccess”, function () {

if ($location.path().indexOf(“/edit/”) == 0) {

// …statements for responding to /edit

}

});

I register a handler function that is invoked when the current route changes, and I use the $location.path method to figure out what state the application is in. If the path starts with /edit/, then I know I have to respond to an edit operation.

1.2. Getting the Route Parameters

When dealing with a path that starts with /edit/, I know I need to get the value of the id route parameter so that I can populate the fields of the editorView.html file. Route parameter values are accessed through the $routeParams service. The parameter values are presented as a collection that is indexed by name, as follows:

$scope.$on(“$routeChangeSuccess”, function () {

if ($location.path().indexOf(“/edit/”) == 0) {

var id = $routeParams[“id”];

for (var i = 0; i < $scope.products.length; i++) {

if ($scope.products[i].id == id) {

$scope.currentProduct = $scope.products[i];

break;

}

}

}

I obtain the value of the id parameter and then use it to locate the object that the user wants to edit.

Caution For simplicity in this example, I assume that the value of the id route parameter is in the right format and corresponds to the id value of an object in the data array. You should be more careful in a real project and validate the values you receive.

Source: Freeman Adam (2014), Pro AngularJS (Expert’s Voice in Web Development), Apress; 1st ed. edition.

Leave a Reply

Your email address will not be published. Required fields are marked *