orange
orange

Reputation: 8090

Dynamic menu bar with angularjs

I'm trying to create a menu bar using Angularjs. I've done similar things before with Backbonejs, but I have a hard time getting my head around how to do this with angular.

In my html file, I have the following menu placeholder.

<div id='menu1'></div>
<div id='menu2'></div>
<div id='menu3'></div>
<div id='menu4'></div>
<div id='menu5'></div>

A number of my angular modules add a menu when they are loaded (in run). Each of them only reserves a particular slot (i.e. menu1..5), so they don't clash. When some modules aren't loaded, their menu would not show in the menu bar.

An angular module would conceptually look like:

angular.module('myModule3', [])
  .service('someService', function($http) {
    // get some data to populate menu (use $http)
    this.menuItems = ['orange', 'apple', 'banana']
  })

  .run(['someService', function(someService) {
    // create a rendered menu item
    ...
    // insert it at id="menu3"
  })

For sake of simplicity, the rendered menu item should look like:

  <ul>
    <li>organge</li>
    <li>apple</li>
    <li>banana</li>
  </ul>

I'm fairly new to angular, so I don't really know where to begin. I've been reading up on directives, but don't see how they fit in here, as they require some custom markup (maybe a custom menu tag containing the DOM target (i.e. menu..5). Also, how to connect this to a controller is not clear to me.

Update In addition to the above base template (containing arbitrary anchor points in the DOM) and the directive (which will produce a DOM element which will be inserted at these anchor points), a template will facilitate the creation of the DOM element. This template will be located in a separate file containing the position the directive's DOM element will be inserted to (as opposed to the usual case of directives in which an already existing tag will be replaced/inserted into specific markup that matches the directive's definition:

<menu ng-model="Model3DataService" target="#menu3">
  <ul>
    <li ng-repeat="for item in items"></li>
  </ul>
</menu>

Again, coming from a Backbone/jquery background this makes sense, but this may not be the right thing to do in angular. If so, please let me know how I could keep the base template free of any knowledge about the modules and assumptions of where they put their menu (i.e. which slot of the menu bar they allocate). I'm happy to hear about other solutions...

Upvotes: 1

Views: 15949

Answers (1)

kseb
kseb

Reputation: 702

Each module should have its menu loader defined:

angular.module('module1', []).

factory('module1.menuLoader', function() {
    return function(callback) {
        callback(['oranges', 'bananas'])
    }
});

Your application should contain menu directive which can load menu items for any module only if exists.

angular.module('app', ['module1']).

directive('menu', ['$injector', function($injector) {
    return {
        restrict: 'A',
        template: 
            '<ul><li ng-repeat="item in items">{{item}}</li></ul>',
        scope: {},
        link: function($scope, $element, $attrs) {
            var menuLoaderName = $attrs.menu+'.menuLoader';
            if ($injector.has(menuLoaderName)) {
                var loaderFn = $injector.get(menuLoaderName);
                loaderFn(function(menuItems) {
                    $scope.items = menuItems;
                });
            }
        }
    };
}]); 

Final html:

<div class="content">
    <div menu="module1"></div>
    <div menu="module2"></div>
    <div menu="module3"></div>
</div>

After running the application only module1 menu will be loaded. Other menu placeholders remain empty.

Live demo: http://plnkr.co/edit/4tZQGSkJToGCirQ1cmb6


Updated: If you want to generate markup on the module side the best way is to put the template to the $templateCache in the module where it's defined and then pass the templateName to the application.

angular.module('module1', []).

factory('module1.menuLoader', ['$templateCache', function($templateCache) {
    $templateCache.put('module1Menu', '<ul><li ng-repeat="item in items">{{item}}</li></ul>');
    return function(callback) {
        callback('module1Menu', ['oranges', 'bananas'])
    }
}]);


angular.module('app', ['module1'])

.directive('menu', ['$injector', function($injector) {
    return {
        restrict: 'A',
        template: 
            '<div ng-include="menuTemplate"></div>',
        scope: {},
        link: function($scope, $element, $attrs) {
            var menuLoaderName = $attrs.menu+'.menuLoader';
            if ($injector.has(menuLoaderName)) {
                var loaderFn = $injector.get(menuLoaderName);
                loaderFn(function(menuTemplate, menuItems) {
                    $scope.menuTemplate = menuTemplate;
                    $scope.items = menuItems;
                });
            }
        }
    };
}]);

Upvotes: 5

Related Questions