“Imagination is everything. It is the preview of life's coming attractions”

- Albert Einstein

  • How To Adjust Parent Scope On Route Change In AngularJS


    I've been approched by an issue concerning manipulating a header/footer of an AngularJS application that exceeds the scope of certain controller which is limited to only central content box. What if we would like to highlight anchors located in header or footer when we access certain route? Now this may be an issue.. but kaboom, it's not! This tutorial explains how to handle such situation with a bit of charm.

    Below instructions will show you how to highlight header's menu anchors that are contained in the parent scope of MainCtrl by calling one function in child controllers. Whole source code is available above, this tutorial will cover most important parts.

    The first core element of the application is the main controller, that's attached to the <body> using ngController directive.

    'use strict';
    
    /**
     * Main application controller attached to "body" element
     */
    angular.module('AngularApp1')
      .controller('MainCtrl', ['$scope', function ($scope) {
        // setActivePage will be available to all children 
        // scopes of this controller
        $scope.setActivePage = function(name) {
          $scope.activePage = name;
        };
      }]);

    Here we attach a function to the controller's scope that'll allow to adjust the value of activePage. Since you can't just add properties to parent's scope from child controllers, we can expose such function to easily allow it for them. Of course we could create a blank field in the scope with value like null and then adjust it in child scopes (if you operate on a parameter of a scope, Angular first searches current scope and then checks it's parents, just like prototype inheritance). But that would be nasty and could occur in printing "null" in the view when not used properly.

    Let's now take a look at about page controller. It's content is rendered inside a div with ng-view attribute (index.html line 23).

    'use strict';
    
    /**
     * About page controller
     */
    angular.module('AngularApp1')
      .controller('AboutCtrl', ['$scope', function ($scope) {
        $scope.setActivePage('about');
      }]);

    And the news page controller. Rendered in the same place as about page.

    'use strict';
    
    /**
     * News page controller
     */
    angular.module('AngularApp1')
      .controller('NewsCtrl', ['$scope', function ($scope) {
        $scope.setActivePage('news');
      }]);

    As you can see nothing strange is happening here - both controllers are using previously created function in the MainCtrl. The string passed to this function must correspond to the class attached to certain anchor in the header's menu.

    To make everything work by the book, we'll need a directive. It will manipulate header's menu anchors on each activePage property change. Since directives are Angular's DOM manipulator, that the best choice here. Code of directives/headerMenu.js is available below.

    'use strict';
    
    /**
     * A simple directive used to adjust the class of header 
     * menu anchors based on their classes
     */
    angular.module('AngularApp1')
      .directive('headerMenu', function () {
        
        return function(scope, element) {
          // Watch "activePage" variable created in MainCtrl 
          // controller's scope
          scope.$watch('activePage', function(value) {
            // On "activePage" variable change in scope 
            // - adjust currently active page anchor in 
            // the header
            var activeLink = element.find('.' + value),
                activeClass = 'active';
            // Strip all anchors elements from "active" class
            element.find('.' + activeClass)
                    .removeClass(activeClass);
            activeLink.addClass(activeClass);
          });
        };
      });

    As described in the comments, based on activePage value it searches for an anchor with the same name in the header and sets it's as active. Keep in mind that element is actually an jQuery object, not a pure element.

    To wrap things up we'll need a nice partial which will represent header. There it will be possible to attach our little directive.

    <div class="header">
        <!-- Header menu directive is attached here to 
             control currently active page -->
        <div class="menu" data-header-menu="">
            <!-- Class equals the "activePage" value in 
                 the scope used to highlight buttons -->
            <a href="/#/news" class="news">News page</a>
            <a href="/#/about" class="about">About page</a>
        </div>  
    </div>

    And that's it. Routes are simply configured in scripts/app.js file. When you'll go to "/#/news" the "News page" button will be highlighted, same goes for "About page" button. Kaboom baby!

    Back