Two-way binding for URL parameters in AngularJS

Thomas Uhrig · November 19, 2015

URL parameters are an essential part of single page applications. They hold the current state of an application and enable users to open the application in a certain state. Here’s an example:

http://localhost:8080/index.html#/page?showAll=true&filterByName=Thomas
                                |                                     |
                                +--------- for on the client ---------+
                                             (anchor part)

In this example, showAll=true and filterByName=Thomas are URL parameters. The hash (#) divides the URL into two parts: a part which goes to the server (before #) and a part which stays on the client (behind #). This is the so called anchor part which holds our URL parameters. This part of the URL is purely for our AngularJS client!

Two-way binding

Two-way binding means that we bind a parameter of our URL (e.g. filterByName) to a parameter of the controller (let’s say it has the same name: myController.filterByName). To achieve this, we need to update our controller if the URL parameter changes and we need to update our URL parameter if the controller changes vice versa. That’s what two-way mean - controller to URL, URL to controller.

Controller » URL

Let’s start with the first way: controller to URL. Everytime a property of the controller changes, we want to update the corresponding property of the URL. To do so, we use Angular’s $watch method (see here). The method $watch allows us to watch a value and to do something if it changes. Exactly what we want! The code is pretty straight forward:

angular
    .module('my.module')
    .controller('MyController', function($scope, $location) {
        var self = this;
        var filterByName = null; // optional declaration!
        // Controller to URL
        $scope.$watch(function() { return self["filterByName"] }, function (newVal) {
            console.log("Property changed!");
            $location.search("filterByName", newVal);
        });
    });

This code will do the following: It will create a watcher which listens on the property filterByName of our controller. If the property changes, we get the new value and set it to the URL (by $location.search("filterByName", newVal);). To use the URL parameters we use a service called $location provided by Angular. That’s the default way to access the URL.

URL » controller

Now we only need the way back: URL to controller. We can use another watcher for that. But in this case we use Angular’s $on. This looks like that:

angular
    .module('my.module')
    .controller('MyController', function($scope, $location) {
        var self = this;
        var filterByName = null; // optional declaration!
        // Controller to URL
        $scope.$watch(function() { return self["filterByName"] }, function (newVal) {
            console.log("Property changed!");
            $location.search("filterByName", newVal);
        });
        // URL to controller
        $scope.$on('$locationChangeSuccess', function(event) {
            console.log("URL changed!");
            self["filterByName"] = $location.search()["filterByName"];
        });
    });

What $on does is simple: It listens for events, e.g. the $locationChangeSuccess event, which is thrown after the URL has changed. If this happens, we use the $location service to read the value from the URL and assign it to our controller.

Put it to a method!

We already have achieved what we wanted: we have a two-way binding between our controller and the URL. One last thing we might do right now, is to put this into a nice method we can reuse:

angular
    .module('my.module')
    .controller('MyController', function($scope, $location) {
        var self = this;
        var filterByName = null; // optional declaration!
        bind("filterByName");
        function bind(valueName) {
            // Controller to URL
            $scope.$watch(function() { return self[valueName] }, function (newVal) {
                console.log("Property changed!");
                $location.search(valueName, newVal);
            });
            // URL to controller
            $scope.$on('$locationChangeSuccess', function(event) {
                console.log("URL changed!");
                self[valueName] = $location.search()[valueName];
            });
        }
    });

If the property of our controller changes (e.g. because the user enters data) the URL will change. And if the URL changes (e.g. because the user clicked on a link) the controller will change. We have a two-way binding between controller and URL!

More

Best regards, Thomas.