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: '='
}
};
}
Related
I know I can do this:
<my-directive attr="myAttr"></my-directive>
and then have the attr to be accessed via the my-directive side.
But I want to do something like this:
<my-directive attr="myAttr">
Some link that will apply with the my-directive directive too
</my-directive>
Is this possible?
Using ng-transclude can solve the problem:
// html
<my-directive my-attr="Hello">
My link
</my-directive>
// my-directive.js
app.directive("myDirective", function() {
return {
transclude: true,
template: "<h1>{{myAttr}: <ng-transclude></ng-transclude></h1>", // <h1>Hello: <a href...>...</a></h1>
scope: {
myAttr: "#"
},
link: ($scope, element, attrs) => {
console.log($scope.myAttr); // Hello
}
}
});
Read more here
I have a list and want it to filter by my custom filter. But the value to it I want to put from custom directive with it's own scope. How to do it?
Body:
<body ng-controller="test">
<tr ng-repeat="item in list | myfilter: HowToPuttHereValue? >
Here is my custom filter:
.filter('myfilter', function(){
return function(array, num){
return array.slice(num, num+1);
}
})
And here is my custom directive:
.directive('mydirective', function() {
return {
restrict: "E",
template:"<input ng-model='counter'><button ng-click='getIt(counter)'>PRESS</button>",
scope:{
item: '='
},
link: function(scope, element, attr){
scope.getIt = function(counter){
console.log(counter);
}
}
}
})
Please see the Example:
JsFiddle Example
P.S. I guess I've already found a solution using "scope.$parent" . But is there a possibility to pass the value straight to a "myfilter: here? "
Here it is.
<div ng-app="hello">
<div ng-controller="forExampleController">
<ul>
<li ng-repeat="num in list | myfilter: howToPutHereFromDirective ">{{num}} </li>
</ul>
<mydirective item="list.length" filter-value="howToPutHereFromDirective"></mydirective>
</div>
</div>
function forExampleController($scope){
$scope.list = [1,2,3,4,5,6,7,8,9];
$scope.howToPutHereFromDirective = 3;
}
angular.module('hello', [])
.filter('myfilter', function(){
return function(array, num){
return array.slice(num, num+1);
}
})
.directive('mydirective', function() {
return {
restrict: "E",
template:"<input ng-model='counter'><button ng-click='getIt()'>PRESS</button>",
scope:{
item: '=',
filterValue: '='
},
link: function(scope, element, attr){
scope.getIt = function(){
scope.filterValue = parseInt(scope.counter);
}
}
}
});
Doing scope.$parent within isolated scope isn't too good indeed, because the objective of isolated scope is exactly the opposite.
I'm not sure what purpose item="list.length" two-way binding has to serve, but it is a bad idea.
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
I have a TimelineController that has a publish function on the scope, that will send some data to the server.
My timeline is composed by 2 directives
timeline (Element)
share-post (Element)
I would like to be able to call from the share-post directive the publish function on my TimelineController is that possible?.
Controller
function TimelineController($scope,TimelineService)
{
$scope.posts = [];
$scope.publish = function(wall_type,wall_id,text,video,image) {
console.log('click controller');
TimelineService.publishPost(wall_type,wall_id,text,video,image).$promise.then(function(result){
$scope.posts.push(result.response);
});
};
}
Timeline Directive:
function timelineDirective() {
return {
restrict: 'E',
replace: true,
scope: {
type: '#',
ids: '#',
postime: '&',
posts: '='
},
templateUrl:"/js/templates/timeline/post-tmpl.html",
controller: function($scope,$element) {
this.type = $element.attr('type');
this.ids = $element.attr('ids');
}
}
};
Timeline Directive Template
<ol class="timeline" ng-init="postime({wall_type:type,wall_id:ids})">
<li>
<share-post></share-post>
</li>
<li ng-repeat="post in posts">
{{post.text}}
</li>
</ol>
SharePost Directive: From this directive I would like call the publish on the TimelineController
function sharePost() {
return {
restrict: 'E',
replace: true,
require: "^timeline",
templateUrl:"/js/templates/timeline/share-tmpl.html",
link: function($scope,$element,$attr,ctrl) {
$scope.pub = function() {
// This does not work because it call the parent directive
// Instead of controller
$scope.publish(ctrl.type, ctrl.ids, $scope.text);
}
}
}
};
Sharepost Directive Template
<div class="module comment">
<div class="content">
<textarea class="form-control" ng-model="text" placeholder="What is going on..." rows="2"></textarea>
</div>
<button type="button" class="btn" ng-click="pub()"> Share</button>
</div>
well you use your directive just to bind the event click from the controller, something like:
angular.module('module').directive('sharePost', [
function(){
return {
link: function (scope, element, attr) {
var clickAction = attr.clickAction;
element.bind('click',function (event) {
scope.$eval(clickAction);
});
}
};
}]);
html
<a sharePost click-action="publish(wall_type,wall_id,text,video,image)"> publish</a>
Change your directive to have an extra item in the scope like this onPublish: "#" then in your html you can pass a pointer to the controller function you want to invoke like this:
<share-post on-publish="publish"></share-post>
to call this from the directive you have to do:
$scope.onPublish()(ctrl.type, ctrl.ids, $scope.text)
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!