I need to implement a feed with ng-repeat. The feed items have to be sorted by recency. However, I can't possibly use the same template for each item, because the events and behaviors are different. Would using ng-if to render the feed events like this make sense?
<ul ng-controller="FeedController as FeedCtrl">
<li ng-repeat="feedItem in FeedCtrl.feedFactory.feedItems | orderBy '-date_created'">
<div feed-comment-liked ng-if="feedItem.type == 'comment_liked'" item-info ="feedItem">
<div feed-comment-reply ng-if="feedItem.type == 'comment_reply'" item-info ="feedItem">
<div feed-friend-request ng-if="feedItem.type == 'friend-request'" item-info ="feedItem">
</li>
</ul>
This would of course necessitate a number of directives called feedCommentLiked, feedCommentReply, and feedFriendRequest, each with their own template, and getting their data through an item-info binding.
I think this is ok, but I'm hoping others have been down this road. The most obvious constraint in why I don't include multiple ng-repeat lists is that they need to be ordered by the same "date_created" attribute.
You can use thin directive approach so you would have one directive.
<ul ng-controller="FeedController as FeedCtrl">
<li ng-repeat="feedItem in FeedCtrl.feedFactory.feedItems | orderBy '-date_created'">
<div thin-directive="{{myConfig[feedItem.type]}}" item-info ="feedItem">
</li>
</ul>
$scope.myConfig = {
comment_liked: 'template url here',
comment_reply: 'template url here',
friend-request: 'template url here',
}
-----
.directive('thinDirective', function($compile,$templateRequest) {
return {
restrict: 'A',
scope: false,
compile: function(element, attrs) {
return function (scope, element, attrs) {
$templateRequest(attrs.thinDirective).then(function(html){
var template = angular.element(html);
element.append(template);
$compile(template)(scope);
});
}
}
};
or you can use ng-include instead of thin directive but it will create N subscopes. Your call
<ul ng-controller="FeedController as FeedCtrl">
<li ng-repeat="feedItem in FeedCtrl.feedFactory.feedItems | orderBy '-date_created'">
<div ng-include="myConfig[feedItem.type]" item-info ="feedItem">
</li>
</ul>
Related
Hello All: This is my first time asking a question here....
billWatch.HTML
<ul class="border-and-bg vote-unordered-list bill-link-font">
<li><a ng-click='bill.yea = bill.yea + 1;ctrl.voteYea(1, bill.id, bill.yea)'>Yea</a>:{{bill.yea}} | <a ng-click='bill.nay = bill.nay + 1; ctrl.voteYea(0, bill.id,bill.nay)'>Nay:{{bill.nay}}</a> | **<a ng-click="showComments()">{{filtered.length}} comments</a>**
</li>
</ul>
<div ng-mdoel='ctrl.commentsSection' ng-repeat='($index, comment) in ctrl.billComments | reverseComments | filter: comment.bill_id = bill.id as filtered' class="comments-container" >
<div>
<show-comment></show-comment>
<ul>
<li>{{comment.user_name}} | {{comment.comment}} </li>
</ul>
<!-- <ul>
<li ng-bing-html>
<my-comment></my-comment>
</li>
</ul> -->
</div>
</div>
billwatch.ctrl.js
(function(){
angular
.module('ccApp')
.controller("BillWatchCtrl', function BillWatchCtrl(){
})
})();
showComment.dir.js
angular.module('ccApp')
.directive('showComment', function(){
function link(scope,element,attrs){
scope.showComments = function(){
console.log('showComment');
}
}
return {
restrict: 'EA',
link:link
};
})
I've omitted most of the controller code. I'm only trying to log 'showComment' in the console on-click of a anchor tag. It works outside of the ng-repeat block but not inside of it. Can someone help?
Thanks!
Disclaimer:
We could make a much tinier example or actually recommend to use different approaches, but since it seems you are still in the 'technical exploration' phase of your project, I'm simply gonna give you a hand with exactly what you ask :).
The problem you have is that the scope that gets passed to the link function of your directive is not the same as the one outside your ng-repeat.
That is because the ng-repeat directive creates a new scope for each element.
If you want to attach a function showComments on the outside scope from within your ng-repeat, you'd have to do, in the link callback:
scope.$parent.showComments = function () {/*...*/};
Instead of:
scope.showComments = function () {/*...*/};
However, this way, you're re-assigning your parent's $scope.showComments function for every comment. Once would be enough!
Thus, I suggest simply pulling your <show-comment /> element outside the ng-repeat. The directive will be attached to its container's scope and will properly set your showComments function where you expect it to be, once.
Check out the working snippet below, where I simply:
concatenated all the code you gave us
moved the <show-comments /> element up
angular
.module('ccApp', [])
.controller('BillWatchCtrl', function BillWatchCtrl() {
})
.directive('showComment', function() {
function link(scope, element, attrs) {
scope.showComments = function() {
console.log('showing comments...');
}
}
return {
restrict: 'EA',
link: link
};
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.8/angular.min.js"></script>
<div ng-app="ccApp">
<ul class="border-and-bg vote-unordered-list bill-link-font">
<li><a ng-click='bill.yea = bill.yea + 1;ctrl.voteYea(1, bill.id, bill.yea)'>Yea</a>:{{bill.yea}} | <a ng-click='bill.nay = bill.nay + 1; ctrl.voteYea(0, bill.id,bill.nay)'>Nay:{{bill.nay}}</a> | **<button ng-click="showComments()">{{filtered.length}} comments</button>**
</li>
</ul>
<show-comment></show-comment>
<!-- Moved show-comments HERE -->
<div ng-model='ctrl.commentsSection' ng-repeat="($index, comment) in ctrl.billComments | orderBy: '[]': true | filter: comment.bill_id = bill.id as filtered" class="comments-container">
<!-- Instead of THERE -->
<div>
<ul>
<li>{{comment.user_name}} | {{comment.comment}}</li>
</ul>
</div>
</div>
</div>
Okay, so let me start off saying that I have a directive that creates a JQueryUI tab setup with a backing field that populate the tab name and the tab contents basically.
<div id="summaryTabPanel">
<ul>
<li ng-repeat="category in SummaryCategories">
{{category.Name}}
</li>
</ul>
<div ng-repeat="category in SummaryCategories" id="tabs-{{$index + 1}}">
{{category.Content}}
</div>
</div>
So as you can see, I have a <ul> with an ng-repeat in the <li>.
As I will be applying JQueryUI Tab function to the parent container id="summaryTabPanel", I need to wait until all the dom is rendered.
So I do some research, and find out I need to create a directive that kind of looks like the following:
Application.Directives.directive("repeatDone", function () {
return {
restrict: "A",
link: function (scope, element, attrs) {
scope.$watch("repeatDone", function () {
if (scope.$last) {
$("#summaryTabPanel").tabs({
event: "mouseover"
});
}
;
});
}
};
});
This watches to see if the last repeated item is found, and then applies the DOM changes via the statement $("#summaryTabPanel").tabs()
This is all fine and good, but it only works if I apply the directive to the last ng-repeated child item of the summaryTabPanel container.
<div id="summaryTabPanel">
<ul>
<li ng-repeat="category in SummaryCategories">
{{category.Name}}
</li>
</ul>
<div ng-repeat="category in SummaryCategories" id="tabs-{{$index + 1}}" repeat-done>
{{category.Content}}
</div>
</div>
If I move it to the previous ng-repeat item things work but not quite right. If I move it to the parent container, things don't work at all.
It seems wrong to me to apply this directive to the last child, and instead have a way to apply it to the root and somehow accomplish the same thing.
<div id="summaryTabPanel" repeat-done>
Is this possible somehow with Angular?
According to this link you should write your directive like this
Application.Directives.directive("repeatDone", function () {
return {
restrict: "A",
link: function (scope, element, attrs) {
$timeout( function () {
$("#summaryTabPanel").tabs({
event: "mouseover"
});
});
}
});
Please have a look at the jsfiddle I created to illustrate your case.
I have a simple list item being parsed with ng-repeat:
<ul>
<li ng-repeat="item in items" class="commonClass" ng-class="{'on': on_var}" ng-click="on_var=!on_var">
{{item.name}}
<li>
</ul>
clicking on a list-item will add the class name 'on' as expected. but I want to remove all other 'on' classes as soon as you click on another list-item and only add it to the one clicked. I come from a jQuery background and I am new to angular. All I want to do is something like this:
$("li.commanClass").click(function(){
$("li.commonClass").removeClass('on');
$(this).addClass('on');
})
I want to know what is the "angular way" of achieving this result
jsfiddle
Thanks.
In angular you want to pass object references to child scopes as much as possible. This way you can use prototypical object inheritance that can branch down many levels.
Think of a scope as being a model. In the case of ng-repeat it creates a child scope for each element. So if the parent scope holds a model it will be passed as reference to the child scopes. Each of these child scopes will hold a reference to the parent scope model object.
Thus you can do:
<li ng-repeat="item in model.items"
ng-class="{'on': model.selected==item}"
ng-click="model.selected=item">{{ item.name }}</li>
Then in controller:
$scope.model = {
selected: null,
items = [
{name: "Apple"},
{name: "Banana"},
{name: "California"}
]
};
Try to avoid using functions as above answer does. These functions will get called many times and will add unnecessary extra overhead.
Valuable post to read: "Thinking in AngularJS" if I have a jQuery background?
DEMO
You can add a variable to your scope to maintain which item is selected, and a function on your scope that toggles the variable.
Controller:
app.controller('myCtrl', function($scope) {
$scope.items =
[
{name: "Apple"},
{name: "Banana"},
{name: "California"}
]
$scope.selectItem = function( item ) {
$scope.selectedItem = item;
};
})
HTML:
<div ng-app="myApp">
<ul ng-controller="myCtrl">
<li ng-repeat="item in items" class="commonClass" ng-class="{'on': selectedItem === item}" ng-click="selectItem(item)">
{{ item.name }}
</li>
</ul>
</div>
Fiddle coming at ya of jsparks answer:
http://jsfiddle.net/eHDTF/
See fiddle for code!
I think you should refer a directive for whole your project instead of controller.
<div class="nav-item">
<ul>
<li active-me="on">Item 1</li>
<li active-me="on">Item 2</li>
<li active-me="on">Item 3</li>
<li active-me="on">Item 4</li>
<li active-me="on">Item 5</li>
</ul>
</div>
Then create activeMe directive:
angular.module('app').directive('activeMe', function(){
return {
restrict: 'A',
scope: {
activeMe: '#'
},
link: function(scope, element, attrs) {
element.bind('click', function() {
if (scope.activeMe !== undefined && scope.activeMe.trim().length > 0) {
element.parent().children().removeClass(scope.activeMe);
element.addClass(scope.activeMe);
} else {
element.parent().children().removeClass('active');
element.addClass('active');
}
});
}
};
});
In my ng-repeat I have a directive called update which takes the current index of repeat. How can I pass index value to my directive ?
This is how my markup looks like:
<ul>
<li ng-repeat="article in articles">
<span class="btn" update="{{ $index }}">update</span>
<span class="btn" ng-attr-update="{{ $index }}">update</span>
</li>
</ul>
Directive:
angular.module('magazine')
.directive('update', function() {
return {
restrict: 'A', // restricting it only to attributes
link: function(scope, element, attrs) {
console.log(attrs.update);
}
}
})
When I run the above code it logs undefined. Angular docs on directive (http://docs.angularjs.org/guide/directive) says that using ng-attr as a prefix to an attribute does the job. But its not happening. I checked my DOM, when I use ng-attr-<directive>="{{ $index }}" as a prefix to an attribute it does not compile it to only <directive>="value".
Is there something I am missing or doing it wrong ?
I have a directive whom's behavior I want to control using attributes. It's a navbar that should should have an active item depending on the value of an attribute
<navbar active="programs"></navbar>
The directive's template
<div class="navbar span12">
<div class="navbar-inner">
<div class="container-fluid">
<a class="brand" href="#">Loopz</a>
<ul class="nav">
<li ng-class="{active: isActive('programs')}">Programs</li>
<li ng-class="{active: isActive('shop')}">Shop</li>
<li ng-class="{active: isActive('profile')}">Profile</li>
</ul>
</div>
</div>
The active class should be put on the element that has the matching active attribute value. The template should be evaluated and the directive's internal scope should have a function that matches the active attribute's value with the value passed to the directive's scope method isActive(value)
The directive
directivesModule.directive('navbar', function(){
return {
restrict: "E",
templateUrl: "partials/navbar.html",
replace: true,
controller: function($scope, $element, $attrs){
$scope.isActive = function(value){
return $attrs.active === value;
}
}
}
});
The isActive function is being called with the correct value but the $attrs object doesn't contain the active attribute's value.
I just dropped all your code into a jsFiddle (it'd be helpful if you did it next time) and everything seems to work: http://jsfiddle.net/rtCP3/110/
Here's the output of the li that should be active:
<li ng-class="{active: isActive('programs')}" class="active">
Programs
</li>