AngularJS Navigation Menu

Once again I’m in need of creating a navigation menu with drop downs. This time I’ve been working with Angular and using Foundation 4 for styling.

The Final Output

Because who doesn’t like dessert first

Here is the fiddle. http://jsfiddle.net/kmussel/evXFZ/

The Styling

The good news is Foundation basically has all the styles already done for you under the class of “top-bar” and “top-bar-section”. But since I needed to have this nav under the top bar I just copied most of the top-bar styles and put it under the “nav-menu” class. You can see some of the styles I copied over at the bottom of the css panel in the fiddle.

You can also test out using only foundation’s top-bar styling by uncommenting out the html in the fiddle and commenting out the other “nav” element. And then change the directive name from “navMenu” to “anavMenu”. Make sure the result panel is wide enough though cause foundation changes the css on small screen sizes when it comes to the top-bar.
Basically if all you do is include foundation’s css the only thing you have to make sure you do is change your html to have a “top-bar” class and then a nested “top-bar-section” class.

The AngularJS Goodness

If you’ve never worked with angular I definitely recommend it. But it can be frustrating especially dealing with directives (which is what we will be doing here). I think this guy is spot on with how learning angular will make you feel.

So I basically knew what I wanted to be able to do and that is in html write this:

<nav menu-data="menu"></nav>

and it spit out my entire navigation.

And so I could resuse the directive with other controllers I added an attribute to it that would reference the scope variable where the menu data would be which led me to have this:

<nav menu-data="menu" menu-data="menu"></nav>

The first directive I create then is the navMenu one.


app.directive('navMenu', ['$parse', '$compile', function($parse, $compile)
{
    return {
        restrict: 'E', //Element
        scope:true,
        link: function (scope, element, attrs)
        {
            scope.$watch( attrs.menuData, function(val)
            {
                var template = angular.element('<ul id="parentTreeNavigation"><li ng-repeat="node in ' + attrs.menuData + '" ng-class="{active:node.active && node.active==true, \'has-dropdown\': !!node.children && node.children.length}"><a ng-href="{{node.href}}" ng-click="{{node.click}}" target="{{node.target}}" >{{node.text}}</a><sub-navigation-tree></sub-navigation-tree></li></ul>');
               var linkFunction = $compile(template);
               linkFunction(scope);
               element.html(null).append( template );
            }, true );
      }

}]);

In here i create the initial template. The template contains 2 other directives. Angular’s built-in “ng-repeat” and my other “sub-navigation-tree”. This is put inside a watch statement so if the menu changes it will be updated. The template is then compiled and linked to current scope and then appended to the current element.
This then compiles and links all the directives within that.

The Inner Directive handles the actual drop down part.



.directive('subNavigationTree', ['$compile', function($compile)
{
    return {
        restrict: 'E', //Element
        scope:true,
        link: function (scope, element, attrs)
        {
            scope.tree = scope.node;

            if(scope.tree.children && scope.tree.children.length )
            {
                var template = angular.element('<ul class="dropdown "><li ng-repeat="node in tree.children" node-id={{node.' + attrs.nodeId + '}}  ng-class="{active:node.active && node.active==true, \'has-dropdown\': !!node.children && node.children.length}"><a ng-href="{{node.href}}" ng-click="{{node.click}}" target="{{node.target}}" ng-bind-html-unsafe="node.text"></a><sub-navigation-tree tree="node"></sub-navigation-tree></li></ul>');

                var linkFunction = $compile(template);
                linkFunction(scope);
                element.replaceWith( template );
            }
            else
            {
                element.remove();
            }
        }
     };
}]);

Here you see the same thing with the template but it’s referencing itself now. The template is being linked to the current scope. This means that the directives within the template will be run using that scope. So ng-repeat=”node in tree.children” will be looping over scope.tree.children.
The ng-repeat then creates new scopes for each item and gives that scope access to the object through whatever you called it in your ng-repeat. In our case “node”. The scope that is created in the ng-repeat is the one that is passed to our directive.
Then if the current scope has children it does it all again with linking the template up with the current scope. It then replaces the current element with the template or removes it all together if there are no children.