Im working on a small web app, and there is a side menu that has nav links in it. Each link when clicked pulls out a hidden panel and should display a list of items specific to that link.
I have most of the functionality working except Im stuck on how to append either a templateURL or just html to the panel.
Any guidance would be great.
heres what I have so far:
html
<!-- Pullout menu -->
<nav id="sidebar-pullout">
<div id="menu-list"></div>
</nav>
app.js
var configApp = angular.module("configApp", ['ngRoute','ui.bootstrap'])
.config(function($routeProvider){
$routeProvider..when('/organizations', {
templateUrl: 'templates/dashboard/organizations/organizations-title.html',
controller: 'OrganizationController',
activetab: 'organizations'
})
.otherwise( {redirectTo: '/dashboard'} );
});
// Side Nav Link Controllers
configApp.controller('OrganizationController', function($scope) {});
configApp.controller('SideNavCtrl', function($scope, $location) {
$scope.isActive = function(route) {
return route === $location.path();
}
});
// adding html to the menu-list
configApp.directive('menu-list', function(){
return {
template: '<span ng-transclude >append som html here</span>',
replace: true,
transclude: true,
controller: 'OrganizationController'
};
});
Here is another way you might be able to go about it. By keeping a reference to menu items and contents. You could keep the side panel content in separate HTML files.
configApp.directive('menuList', function() {
return {
restrict: 'EA',
link: function(scope, el, attr) {
var activeId = null;
scope.showContent = function(id) {
activeId = id;
};
scope.isActive = function(id) {
return activeId === id;
}
scope.menuItems = [{
id: 'item1',
name: 'Menu Item 1',
content: 'path/to/menuItem1content.html'
}, {
id: 'item2',
name: 'Menu Item 2',
content: 'path/to/menuItem2content.html'
}]
}
};
});
Then in you HTML maybe something like this.
<div menuList>
<nav id="sidebar-menu">
<ul>
<li ng-repeat="item in menuItems">
<a ng-click="showContent(item.id)">{{ item.name }}</a>
</li>
</ul>
</nav>
<div id="sidebar-content">
<div class="content"
ng-repeat="item in menuItems"
ng-include="item.content"
ng-show="isActive(item.id)"></div>
</div>
</div>
This is just an idea and you could use angular animation to animate the sidebar sliding and stuff.
You are specifying your ng-transclude directive on the wrong element. You are placing it on your span tag. Try something like this instead:
<div>
<span>/*My template html here*/</span>
<div ng-transclude></div>
</div>
It also looks like you're specifying your directive incorrectly. Try this:
configApp.directive('menuList', function () {
return {
restrict: 'A',
replace: true, // note: this syntax will soon be deprecated
template: '<see above snippet>'
};
});
In particular, notice restrict, which specifies how this directive will be used (A: attribute on html element, E: as an element itself, C: as a class). Here we are saying we want to use our directive as an element, E. Also, note that I used the name menuList instead of menu-list. AngularJS uses camel-case in directive definition, and maps the directive names found in the html into their camel case counterparts. So, in the html we will still use this directive like this: menu-list, but we will declare it using camel-case.
Hope this helps!
Related
Im trying to write a 'comments' directive in angular to load nested comments(from Json data) and reuse the same directive for child comments/replies.
The parent comments load just fine by themselves, however when I try to show child comments by using the 'comments' directive again inside its own template, the app just freezes and I have to close it down.
Below is some of my code:
app.html: ---
<ul ng-repeat="comment in View2.postItems | limitTo: 10">
<comments collection="comment"></comments>
</ul>
comments.html (directive):----
<li>
<span>
{{ collection.data.commentText }}
<ul ng-show="{{collection.data.replies}}"
ng-repeat="comment in collection.data.replies.data.children">
<!-**child comments: this line causes the app to freeze:**-->
<comments collection="comment"></comments>
</ul>
</span>
</li>
comments.js:---
var comments = function(){
return {
templateUrl : 'modules/comments/comments.html',
restrict:'E',
scope:{
collection: '='
},
link: function(scope, element, attrs){
}
};
};
You can build recursive directives with the $compile service to conditionally append the child directive. Example: (http://plnkr.co/edit/WpNp20DSjJhO412j3cSw?p=preview)
function comment($compile) {
return {
template: '<span ng-bind="comment.text"></span>',
link: function(scope, element) {
if (angular.isArray(scope.comment.collection)) {
element.append($compile('<comments collection="comment.collection"></comments>')(scope));
}
}
}
}
function comments(){
return {
template : '<ul><li ng-repeat="comment in collection"><comment></comment></li></ul>',
scope:{
collection: '='
}
};
}
In one of my Angular.JS controllers, I have the following:
app.controller("MyController", ["$scope", function($scope){
$scope.messages = [
new Message(1),
new Message(2)
];
$scope.addMessage = function(x) { $scope.messages.push(new Message(x)); }
}]);
Then in my main HTML page, I have
<message message="message" ng-repeat="message in messages">
This is bound to a directive:
app.directive("message", function() {
return {
restrict: "E",
scope: {
message: "="
},
templateUrl: "js/Directives/message.html"
};
});
The template file is:
<li class="message">{{message.msg}} </li>
However, when I call addMessage on the controller, while it does add to $scope.messsages, it doesn't actually refresh the ng-repeat and display the new message. How can I do this?
I would suggest some structural changes in your directive.
First of all, why not refer the original array itself instead of referring value at each iteration ??
<message messages="messages">
Then you can actually move ng-repeat part in your directive template, [You must note that since you're using = in message: "=", = binds a local/directive scope property to a parent scope property. So with =, you use the parent model/scope property name as the value of the DOM attribute. ].
Hence your directive will look like :
app.directive("message", function() {
return {
restrict: "E",
scope: {
messages: "="
},
templateUrl: "js/Directives/message.html"
};
});
and the subsequent template will look something like this :
<ul>
<li ng-repeat="message in messages" class="message">{{message.msg}} </li>
</ul>
You can find a demo plunker here
In my app, I have a main controller as usual.
Let's say it's a webshop with different products and I want to compare X products. The HTML to display 1 product, is re-usable for all other products that get compared.
This means this HTML should be in a template (this is the point where I would've used Handlebars in a jQuery driven app).
I created a partial called itemDetails.php.
This template needs to get inserted into my view - let's say: - two times with different data (normally that'd be the model but in Angular that's the scope?).
So I tried it with two directives like this:
JavaScript
myApp.directive('activeItem', function() {
return {
restrict: 'A',
templateUrl: 'partials/itemDetails.php'
};
});
myApp.directive('activeCompareItem', function() {
return {
restrict: 'A',
templateUrl: 'partials/itemDetails.php'
};
});
Inside the main View
<div class="product left" active-item>{{item.name}}</div>
<div class="product right" active-compare-item>{{item.name}}</div>
This - of course - doesn't work as both products will have the same data from the parent scope.
So I tried to isolate the scope:
myApp.directive('activeItem', function() {
return {
restrict: 'A',
scope: {
item: '#itemOne'
},
templateUrl: 'partials/itemDetails.php'
};
});
myApp.directive('activeCompareItem', function() {
return {
restrict: 'A',
scope: {
item: '#itemTwo'
},
templateUrl: 'partials/itemDetails.php'
};
});
But that doesn't work either because apparently I can use "item" only as an HTML attribute now, not as an expression {{item.name}}.
Are directives even the right approach to templating? If yes, how can I pass data from the parent scope to a directive, keep them both in sync and update/re-render the directive when the objects get changed?
It seems odd to me that I have to create new directives for each time I want to use the template.
Directive declaration:
myApp.directive('activeItem', function() {
return {
restrict: 'A',
scope: {
item: '=data' //use "data" attribute to add the different data into the directive.
},
templateUrl: 'partials/itemDetails.php'
};
});
To use it with different data
<div class="product left" active-item data="itemOne">{{item.name}}</div>
<div class="product right" active-item data="itemTwo">{{item.name}}</div>
New to Angular. Trying to create a reusable directive so that I can share a navigation header bar across pages. Each separate html page should be able to specify what the current or active page is, thus a css property is applied distinguishing that page.
All the logic should be wrapped up in the directive so that each html page using this navigation bar should only have to set the 'property' of ActivePage.
However, I cannot seem to get the conditional ng-class in my template to apply the CSS class. Can someone help me spot my problem?
Example on Plunker: http://plnkr.co/edit/JTI8wLOhN5xWeDdD9i2l
In Use:
<section ng-app="NavBarApp" ng-controller="NavBarCtrl">
<nav-Bar ActivePage="{{Pages.Navigation.Page2}}"></nav-Bar>
</section>
Javascript:
var Pages = {};
Pages.Navigation = {};
Pages.Navigation.Page1 = {
Name: "Page 1",
BaseLink: "Page1.html"
};
Pages.Navigation.Page2 = {
Name: "Page 2",
BaseLink: "Page2.html"
};
Pages.Navigation.Page3 = {
Name: "Page 3",
BaseLink: "Page3.html"
};
var NavBarApp = angular.module('NavBarApp', []);
NavBarApp.controller('NavBarCtrl', function NavBarCtrl($scope){
$scope.Pages = {};
$scope.Pages.Navigation = Pages.Navigation;
});
NavBarApp.directive('navBar', function(){
return {
restrict: 'E',
replace: true,
scope: {
strActivePage: "#ActivePage"
},
controller: function($scope){
$scope.aryPagesList = [];
for(var strProp in Pages.Navigation){
$scope.aryPagesList.push(Pages.Navigation[strProp]);
}
},
templateUrl: 'NavBarTemplate.html'
};
});
Template:
<div id="NavBar" class="navigation">
<ul>
<li ng-repeat="oCurPage in aryPagesList">
<a id="{{oCurPage.Name}}" ng-class="{active: oCurPage.Name==strActivePage}" href="{{oCurPage.BaseLink}}">{{oCurPage.Name}}</a>
</li>
</ul>
</div>
Thanks in advance!
Html/Angularjs doesn't allow for capital characters in attribute names so change your nav-bar html to this:
<nav-bar active-page="{{Pages.Navigation.Page2.Name}}"></nav-bar>
And then in your scope for your directive, you have to change the #ActivePage to #activePage
NavBarApp.directive('navBar', function(){
return {
restrict: 'E',
replace: true,
scope: {
strActivePage: "#activePage"
},
controller: function($scope){
$scope.aryPagesList = [];
for(var strProp in Pages.Navigation){
$scope.aryPagesList.push(Pages.Navigation[strProp]);
}
},
templateUrl: 'NavBarTemplate.html'
};
});
Your plunkr modified: http://plnkr.co/edit/6fMkmfxBd2I8nlfG8eLH
Here is the demo I have created for asking this question and the inline code as requested: http://jsfiddle.net/Gncja/1/
<script type='text/ng-template' id='root.html'>
<list template-id='sidebar.templateId' selected-item-id='sidebar.selectedItemId'></list>
</script>
<script type='text/ng-template' id='sidebar.html'>
<ul style='width:100%;' class='nav nav-list bs-docs-sidenav'>
<li ng-repeat='item in data' ng-class="{active:item.id==selectedItemId}">
<a ng-href='#/{{item.id}}'>
<i class=icon-chevron-right></i>
<span ng-bind='item.text'></span>
</a>
</li>
</ul>
</script>
<body ng-app='main'>
<div ng-view></div>
</body>
function RootCtrl($scope, $routeParams){
$scope.sidebar = {
templateId: 'sidebar',
selectedItemId: $routeParams.navItemId
};
}
RootCtrl.$inject = ['$scope','$routeParams'];
angular.module('main', []).
config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/:navItemId', {
templateUrl: 'root.html',
controller: RootCtrl
}).
otherwise({redirectTo: '/1'});
}]).
directive('list', function(){
return {
restrict: 'E',
replace: true,
scope:{
'templateId': '=',
'selectedItemId':'='
},
template:'<ng-include src="templateUrl"></ng-include>',
controller: function($scope, $element, $attrs){
$scope.templateUrl = $scope.templateId + '.html';
$scope.data = [
{'id':'1', text:'lorem ipsum'},
{'id':'2', text:'dolor sit amet'},
];
}
};
});
This is a small piece of the application I have been working on, but it clearly shows what it is doing. There is the navigation menu in the page, the menu item is a link to the hash that is handled by angular.js routing that initializes the root controller, etc., it's quite tricky to describe, but the code sample clearly shows it.
The problem the entire page content is re-rendered each time when I click the navigation menu item - the routing is stateless and it does know nothing about the previous state. I would like to avoid this by re-using the navigation menu data/template rendering result when a user just navigates between the menu items(or browser history). Is it possible? I am sure that it is, just want to check out whether someone has good ideas. Thanks!
UPDATE:
I have found something that might help me:
http://www.bennadel.com/blog/2420-Mapping-AngularJS-Routes-Onto-URL-Parameters-And-Client-Side-Events.htm
I would put the navigation menu outside ng-view (so it doesn't re-render), but use ng-class in conjunction with location.path() to differentiate the currently selected item. E.g.,
<div ng-controller="navCtrl">
<ul ...>
<li ng-repeat='item in navData' ng-class="{active:isActiveRoute(item.id)}">
...
</ul>
</div>
<div ng-view></div>
Then in navCtrl:
$scope.navData = [
{'id':'1', text:'lorem ipsum'},
{'id':'2', text:'dolor sit amet'},
];
$scope.isActiveRoute = function(route) {
return '/' + route === $location.path();
};
Fiddle