Menu and other submenus when pressed
I am trying to create menus and submenus in angular. What I want to do is to have two arrays of Menu objects
menu = [{name: 'Name1', link: '/link1'}, {name: 'Name2', link: '/link2'}]
submenu = [[{name: 'SubName1', link: '/Sublink1'}, {name: 'SubName1', link: '/sublink1'}],
[[{name: 'SubName2', link: '/Sublink2'}, {name: 'SubName2', link: '/sublink2'}]]
So when I click Name1
, the first array will be selected SubMenu
, and by pressing Name2
the second array is selected. How can I create two Directives for the main menu and the second one for the second and be able to communicate between them on click. I tried to build this in the controller, I was able to select the submenu with $index
, but the submenu cannot be moved as I like because it has to be under the controller.
I finally managed to solve my problem, here is the solution: http://jsfiddle.net/4kjjyL4s/4/
How can I improve my solution?
Don't reinvent the wheel :) The UI router is a prepackaged solution that handles nested routing for you.
If you have a menu of items and you want to display a different menu of items when one of the items is selected, the UI router does just that. https://github.com/angular-ui/ui-router
It is impossible to give an exact answer because the information is missing, but for example if you use directives with different menu items elsewhere in your application, I would recommend passing the menu array from the controller (ng -controller, not directive controller) through the scope.
Also, if you're looking for a standard way for directives to communicate directly (in your case, the link between a menu and a submenu directive to notify an item selection change), use a directive controller. Here's a good tutorial.
https://thinkster.io/egghead/directive-to-directive-communication/
For communication between controllers or directives, you must use services .
From the angular manual ( https://docs.angularjs.org/guide/services ):
Angular services are replaceable objects that are chained together using Dependency Injection (DI). You can use services to organize and share code in your application.
I checked the code you posted on the jsfiddle ( http://jsfiddle.net/4kjjyL4s/4/ ) and I tried to make the most of it. Below are my changes to the JavaScript file (please read the comments in the code).
var app = angular.module("app",[]);
app.controller('main', function(){});
// The service will be responsible for the shared objects and logic
app.service('MenuService', function () {
var list = [
{
name: "Menu1", link: "#menu1",
submenu: [
{ name: "Menu1Sub1", link: "#submenu1" },
{ name: "Menu1Sub2", link: "#submenu2" }
]
},
{
name: "Menu2", link: "#menu2",
submenu: [
{ name: "Menu2Sub1", link: "#submenu1" },
{ name: "Menu2Sub2", link: "#submenu2" }
]
}
];
var selected = [];
// methods and attributes published under the **this**
// keyword will be publicly available
this.getMenu = function () {
return list;
};
this.getSubmenu = function () {
return selected;
};
this.select = function ( menuItem ) {
// this does the *trick*!
// if we use the assignment operator here, we would replace the
// reference returned in the getSubmenu() method, so as the original
// reference did not change, angular dirty checking would not detect it.
// using angular.copy() method, we are copying the contents of the
// selected submenu over the same reference returned by getSubmenu()
angular.copy( menuItem.submenu, selected );
};
});
// No $rootScope injection results in better re-usability. When you were
// relying in $rootScope sharing, both directives should live in the
// $rootScope, so if you add them inside a ng-controller created scope
// they would not work anymore
app.directive("menu", function() {
return {
restrict: "E",
// no need to isolate scope here, *scope:true* creates a new scope
// which inherits from the current scope
scope: true,
// with controllerAs (introduced in angular 1.2), you can skip
// polluting the scope injection.
controllerAs: "ctrl",
controller: function( MenuService ) {
this.list = MenuService.getMenu();
this.changeSub = function ( menuItem ) { MenuService.select( menuItem ); };
},
template: "<div ng-repeat='menu in ctrl.list'><button ng-click='ctrl.changeSub(menu)'>{{menu.name}}</button></div>"
};
});
app.directive("submenu", function() {
return {
restrict: "E",
scope: true,
controllerAs: "ctrl",
controller: function( MenuService ) {
this.sublist = MenuService.getSubmenu();
},
template: "<span ng-repeat='menu in ctrl.sublist'>{{menu.name}} | </span>aa"
};
});
And here is an updated HTML file to show that both directives are now not directly inserted into $rootScope
<div ng-app="app">
<div ng-controller="main">
<menu></menu>
<h1>Hello World!</h1>
<div class="main-content">
<submenu></submenu>
</div>
</div>
</div>
Hope it helps!
Try this code:
function MyCtrl ($scope) {
$scope.subMenu = []; // default is false
$scope.toggleSubMenu = function (index) {
$scope.subMenu[index] = !$scope.subMenu[index];
};
}
Html
<ul>
<li ng-class="{active: subMenu[0]}"> <a href="#hello" ng-click="toggleSubMenu(0)">Name1</a>
<ul>
<li>test</li>
<li>test</li>
<li>test</li>
</ul>
</li>
<li ng-class="{active: subMenu[1]}"> <a href="#foo" ng-click="toggleSubMenu(1)">Name2</a>
<ul>
<li>bar</li>
<li>bar</li>
<li>bar</li>
</ul>
</li>
</ul>
Also check this