I have an MVC/Angular project. In the shared MVC _layout file I'm creating a UI menu based on JSON from a Web API call. I'm binding that data in the layout file using angular.
The issue I'm having is when I navigate to a new page MVC reloads the _layout file so the $scope.menu variable in the that file no longer exists so my UI menu disappears. The controller then calls the web API call again and repopulates everything so my UI menu is recreated.
Basically I need a way to keep my menu from being reloaded each time I navigate to a new page. Since I'm new to using angular and MVC together I'm not sure of the best approach. Is there a way to keep my $scope.menu variable from being removed when the shared _layout page is reloaded, can I store the JSON in in sessionStorage and recreate the $scope.menu so my UI menu doesn't disappear?
Any help would be appreciated.
//Angular code that creates the menu in the MVC shared _layout file.
<li class="dropdown" ng-repeat="item in menu" menu-item="item">
<a class="dropdown-toggle" role="button" aria-expanded="false" aria-haspopup="true" href="#" data-toggle="dropdown">{{item.Name}} <span class="caret"></span></a>
<ul class="dropdown-menu">
<li>
<div class="dropdowncontainer">
<div class="row" style="padding: 10px; height: 100%">
<div class="col-md-4 col-lg-4 col-sm-12 no-float box-content right" ng-show="item.Places.length" style="height: 100%">
<h4 style="vertical-align:top">Places</h4>
<ul class="list-unstyled">
<li ng-repeat="plc in item.Places">{{plc.Name}}</li>
</ul>
</div>
//Controller that populates the menu with a web api call
mainModule.controller('navCtrl', function ($scope, dataService, modelService) {
if (!$scope.menu) {
$scope.menu = modelService;
dataService.getJson().then(
function (res) {
angular.copy(res.data, modelService);
},
function () {
alert('Error Loading Navigation !');
}
);
}
});
angular.module('main').value('modelService', []);
angular.module('main')
.factory('dataService', ['$http', function ($http) {
return {
getJson: function () {
return $http.get('web api call');
}
};
}]);
Related
In angularjs to show/hide dynamic submenu, I am adding and removing dynamic classes in js file. Every time when the state changes in url (i.e ui-sref={{mymenu.url}}) sub menu is not visible. If there is no state change sub menu is working properly. Can anyone please suggest.
Html
<li ng-repeat='mymenu in menuItems' ng-click="showHideMenu($event);" >
<a class="iconsize" ui-sref={{mymenu.url}}>
<i class={{mymenu.image}}></i> <strong>{{mymenu.menuName}}</strong>
<span class="fa fa-chevron-down"></span>
</a>
<ul class="submenuHide">
<li class="" ng-repeat='submenu in mymenu.submenu'>
<a>{{submenu.submenuName}}</a>
</li>
</ul>
JS
$scope.showHideMenu = function(event) {
var classname = event.target.parentElement.parentElement.children[1].className;
if(classname == 'submenuHide'){
$(event.target.parentElement.parentElement.children[1]).removeClass('submenuHide');
$(event.target.parentElement.parentElement.children[1]).addClass('submenuShow');
}else if(classname == 'submenuShow'){
$(event.target.parentElement.parentElement.children[1]).removeClass('submenuShow');
$(event.target.parentElement.parentElement.children[1]).addClass('submenuHide');
}
}
A couple things. One, you'll need to make sure your menu is outside of the individual templates you're working with. Two, using ng-class bound to an ng-model ensures that your menu state is included in the digest cycle. This will save you the jqLite and dom parsing logic.
Here's an example plunker.
So your code might look like this:
<body ng-controller="MainCtrl">
<a ui-sref="hello">Hello</a>
<a ui-sref="about">About</a>
<button ng-click="toggleMenu()">Show / Hide Menu</button>
<ui-view></ui-view>
<ul ng-class="{showMenu: show, hideMenu: !show}">
<li ng-repeat="letter in ['a','b','c','d']">{{letter}}</li>
</ul>
</body>
With this JS:
app.controller('MainCtrl', function($scope) {
$scope.show = false;
$scope.toggleMenu = function() {
$scope.show = !$scope.show;
};
});
I'm newbie in AngularJS, I have a question, please explain to me the reason of my mistake. At first, I have a factory name CategoryParent with this function declared like this,
routerApp.factory('CategoryParent', function($http) {
var categoryParentFactory = {};
var hostCMSAPI="http://***.***.***.***:****";
// get all categories
categoryParentFactory.allCategoryParents = function() {
console.log("call get allCategoryParents");
return $http.get(hostCMSAPI+'/api/categoryparents/');
};
.......
and a controller call this function named categoryParentController:
.controller('categoryParentController', function(CategoryParent,$scope) {
console.log("cateParent ctrl");
$scope.processing=true;
$scope.dataList=[];
$scope.getAllCategoryParents=function(){
CategoryParent.allCategoryParents().success(function(response){
$scope.processing = false;
$scope.list=response;
});
I'm using ui.router like this (nested view):
.state('home.cateParentMenu',{
url:'/cateParentMenu',
templateUrl:'categoryParentTop.html',
controller:'categoryParentController',
controllerAs:'categoryParent'
})
Parent view trigger controller function here:
The Homey Page
This page demonstrates nested views.
<a ui-sref=".list" class="btn btn-primary">List</a>
<a ui-sref=".paragraph" class="btn btn-danger">Paragraph</a>
<a ui-sref=".cateParentMenu" class="btn btn-warning" ng-click="getAllCategoryParents()">cateParentMenu</a>
and last, the children view come here
data show
{{processing}}
{{list}}
{{item.cate_parent_name}}
<ul class="nav navbar-nav" >
data show
{{processing}}
{{list}}
<li data-ng-repeat="item in list.data"><a ui-sref=".detail({cate_parent_id:item.cate_parent_id})" ng-click="getById(item.cate_parent_id)" ui-sref-active="active" id="{{item.cate_parent_id}}">{{item.cate_parent_name}}</a></li>
</ul>
<div class="col-sm-6">
<div ui-view=""></div>
<!--<div ui-view="serviceRef"></div>-->
<!--<div ui-view="categoryRef"></div>-->
</div>
I set $scope.list and $scope.processing to transfer result to view. But in view I show {{list}}, nothing appears and {{processing}} = true ???
Why? please help me, many thanks to all your suggests.
Controller
Fix $scope.dataList=[]; to $scope.list=[];
how to write a service or factory for bootstrap tabs using angularJs.
I placed in one controller but need a common function(repeated code) for different controllers.
<ul class="nav nav-tabs">
<li data-ng-class="{active:tab===0}">
<a href ng-click="changeTab(0)"> <i class="fa fa-list"></i></a>
</li>
<li data-ng-class="{active:tab===1}">
<a href ng-click="changeTab(1)"><i class="fa fa-user"></i></a>
</li>
</ul>
<div data-ng-class="{activeTab:tab===0}" ng-show="isActiveTab(0)">
tab1
</div>
<div data-ng-class="{activeTab:tab===1}" ng-show="isActiveTab(1)">
tab2
</div>
controller
$scope.tab = 0;
$scope.changeTab = function(newTab){
$scope.tab = newTab;
};
$scope.isActiveTab = function(tab){
return $scope.tab === tab;
};
Managing tabs is a view concern. Rather than implementing a factory, I recommend creating two directives: tabContainer and tab. The tab directive registers itself with the parent tabContainer using the require attribute to access the parent directive's controller API.
Demo
Usage
<tab-container selected="tab2">
<tab name="tab1">tab1</tab>
<tab name="tab2">tab2</tab>
</tab-container>
Parent Directive
The parent directive publishes the following controller API that the child tab directives will access:
tabContainer controller
// registers this tab with the parent tabContainer
this.register = function(element) {
$scope.tabs.push(element);
}
// returns the selected tab object whose
// name property indicates which tab is active
this.getSelected = function() {
return $scope.selectedTab;
}
Child Directive
The tab directive is able to access the parent controller by requiring the parent directive in its directive definition, and accessing the parent directive's controller as the 4th argument to the tab directive's link function:
tab directive definition
scope: true,
require: '^tabContainer',
link: function(scope, element, attr, tabContainer) {
// set the tab so that it is visible in the tab directive's scope.
scope.tab = { name: attr.name, element:element};
scope.selectedTab = tabContainer.getSelected();
tabContainer.register(scope.tab);
}
The scope is set to true so that each tab will create its own child scope and not interfere with the scope of other tabs.
Template Files
For example purposes, the directive templates are embedded in the HTML:
<script type="text/ng-template" id="tabContainer.html">
<ul class="nav nav-tabs">
<li ng-repeat="tab in tabs" data-ng-class="{active:selectedTab.name===tab.name}">
<a href ng-click="changeTab(tab)"> <i class="fa fa-list">{{tab.name}}</i></a>
</li>
</ul>
<ng-transclude></ng-transclude>
</script>
<script type="text/ng-template" id="tab.html">
<div data-ng-class="{activeTab:selectedTab.name===tab.name}" ng-show="selectedTab.name === tab.name">
<ng-transclude></ng-transclude>
</div>
</script>
It is recommended to move these to dedicated HTML files.
Changing the Active Tab
The user is able to change the active tab by clicking the tab link. This is achieved by publishing a $scope function in the parent controller:
$scope.changeTab = function(tab) {
$scope.selectedTab.name = tab.name;
}
Creating a Tabs Module
The beauty of AngularJS and its pluggable modular architecture is that you can extend the AngularJS directive ecosystem, and have the directives work together seamlessly. For example, you could encapsulate the above tabs directive into a tabs module, and even use the ngRepeat directive to bind the tabs.
Demo
Controller
var app = angular.module('app',['tabs']);
app.controller('ctrl', function($scope) {
$scope.tabData = [
{ name: 'tab1', body: 'You selected tab1!'},
{ name: 'tab2', body: 'You selected tab2!'},
{ name: 'tab3', body: 'You selected tab3!'},
{ name: 'tab4', body: 'You selected tab4!'},
];
});
View
<div class="container" ng-controller="ctrl">
<tab-container selected="tab1">
<tab ng-repeat="tab in tabData" name="{{tab.name}}">{{ tab.body }} </tab>
</tab-container>
</div>
Hi I write it with out using any service or factory
see the example
<ul ng-init="tab = 1">
<li ng-class="{active:tab===1}">
<a href ng-click="tab = 1">Tab1</a>
</li>
<li ng-class="{active:tab===2}">
<a href ng-click="tab = 2">Tab2</a>
</li>
<li ng-class="{active:tab===3}">
<a href ng-click="tab = 3">Tab3</a>
</li>
<br><br>
<p ng-show="tab === 1"> Tab1 content </p>
<p ng-show="tab === 2"> Tab2 content</p>
<p ng-show="tab === 3"> Tab3 content</p>
</ul>
Dynamically change it through controller see the working example here
The structure for controllers/services in Angular are explained well here:
https://github.com/johnpapa/angular-styleguide
In app.js, we declare an angular application, give it a name, and any dependencies (ng-route / ng-grid). $http calls should be made using a factory or a service, and the controller should call the service to fetch or post data. From the angular documentation, "services are substitutable objects that are wired together using dependency injection (DI). You can use services to organize and share code across your app."
https://docs.angularjs.org/guide/services
app.js:
var app = angular.module('appname', []);
app.factory('httpResponseErrorInterceptor'...
app.config(['$httpProvider',...
Controller:
angular.module('appname').controller("NameCtrl", ["$scope", "$log", "$window", "$http", "$timeout", "SomeService",
function ($scope, $log, $window, $http, $timeout, TabService) {
//your controller code
$scope.tab = 0;
$scope.changeTab = function(newTab){
$scope.tab = newTab;
};
$scope.isActiveTab = function(tab){
return $scope.tab === tab;
};
}
]);
Service:
angular.module('appname').service("TabService", function () {
//Your code for shared functionality regarding tab service
var currentTab = {};
return {
currentTab : currentTab
}
});
In my AngularJS application, I'm using a router with an ng-view directive.
Besides it I would like to add a generic header (same for all the views). So I did the following:
<!-- index.html -->
<body ng-app="myApp">
...
<div ng-controller="TopmenuCtrl" class="header">
<div ng-include="template.url"></div>
...
</div>
<div ng-view></div>
</body>
The view is dynamic depending on the session token:
<!-- views/topmenu.html -->
<ul class="nav nav-pills pull-right">
<li class="active"><a ng-href="#">Home</a></li>
<li><a ng-href="#">About</a></li>
<li><a ng-href="#">Contact</a></li>
<li ng-show="token"><a ng-href="#" ng-click="doLogout()">Logout</a></li>
</ul>
And the topmenu controller with the logout method:
// controllers/topmenu.js
$scope.template = {url: 'views/topmenu.html'};
$scope.doLogout = function() {
localStorageService.clearAll();
$window.sessionStorage.token = '';
$location.path('/login');
};
The problem is: When I click on "logout" in the app, the topmenu controller is called and destroy the session but the main one from the router is called too and display an error because the session was destroyed!
The only (not satisfactory) solution I found to prevent this is to add this code in each controllers of the app:
if ($window.sessionStorage.token = '') {
return;
}
Is there a way to execute the header controller but not the main ng-view controller?
I finally found my answer: https://stackoverflow.com/a/11672909/900416.
Now my logout link looks like: <a href ng-click="doLogout()">Logout</a>.
There is two similar questions:
dynamic menu items using AngularJS
How can I show or hide some buttons depend on the user's rights, in angularjs?
I need to create a dynamic menu exactly like in the first similar question exept I cannot hardcode the rights in my page. An adminitrator can create custom roles and chose wich menu item this role can see.
<li class="dropdown"><img role="button" class="dropdown-toggle" data-toggle="dropdown" ng-src="{{avatarUrl}}" />
<ul class="dropdown-menu pull-right" role="menu">
<li ng-show="???"><a ng-click="action01()">Action one</a></li>
<li ng-show="???"><a ng-click="action02()">Action two</a></li>
<li ng-show="???"><a ng-click="action03()">Action tree</a></li>
<li ng-show="???"><a ng-click="action04()">Action four</a></li>
</ul>
</li>
How should I imagine my strategy?
I would create something like a HeaderController an attach to it a function that tells if a given role can do the given action. Presumably you have the ACL stored somewhere so perhaps you can create a service for it. Something like this:
controller('HeaderController', ['$scope', 'Acl', function($scope, Acl) {
$scope.roleCanDo = function(role, action) {
return Acl.roleCanDo(role, action);
}
}])
and your view would be like this:
<li class="dropdown"><img role="button" class="dropdown-toggle" data-toggle="dropdown" ng-src="{{avatarUrl}}" />
<ul class="dropdown-menu pull-right" role="menu">
<li ng-show="roleCanDo(currentUser.role, 'action01')"><a ng-click="action01()">Action one</a></li>
<li ng-show="roleCanDo(currentUser.role, 'action02')"><a ng-click="action02()">Action two</a></li>
<li ng-show="roleCanDo(currentUser.role, 'action03')"><a ng-click="action03()">Action tree</a></li>
<li ng-show="roleCanDo(currentUser.role, 'action04')"><a ng-click="action04()">Action four</a></li>
</ul>
</li>
Obviously the actual code will depend on your current system but you get the idea.
It's very simple to implement Creating dynamic menu from database data using AngularJS
Suppose we have a table in our database for navigation menus like,
Table structure for dynamic menu
Write below code for fetch menus from database
public JsonResult GetSiteMenu()
{
using (MyDatabaseEntities dc = new MyDatabaseEntities())
{
var menu = dc.SiteMenus.ToList();
return new JsonResult { Data = menu, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
}
}
and here is the angularjs controller
var app = angular.module('MyApp', []);
app.controller('menuController', ['$scope', '$http', function ($scope, $http) {
$scope.SiteMenu = [];
$http.get('/home/GetSiteMenu').then(function (data) {
$scope.SiteMenu = data.data;
}, function (error) {
alert('Error');
})
}])
HTML Code
<div ng-app="MyApp">
<div ng-controller="menuController">
#* Here first of all we will create a ng-template *#
<script type="text/ng-template" id="treeMenu">
{{menu.Name}}
#* We will create submenu only when available *#
<ul ng-if="(SiteMenu | filter:{ParentID : menu.ID}).length > 0">
<li ng-repeat="menu in SiteMenu | filter:{ParentID : menu.ID}" ng-include="'treeMenu'"></li>
</ul>
</script>
<ul class="main-navigation">
#* Here we will load only top level menu *#
<li ng-repeat="menu in SiteMenu | filter:{ParentID : 0}" ng-include="'treeMenu'"></li>
</ul>
</div>
</div>