Custom Directive Not Working Within ng-repeat - angularjs

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>

Related

different templates and objects in ng-repeat

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>

ng-click inside ng-repeat doesnt work

I have html which looks like one below, I have 2x ng-click in whole code in both situation I call same function. Both functions are in same controller.
<div class="tagselect tagselect--frameless">
<div class="combobox__body combobox__body--open combobox__body--frameless" ng-show="focus">
<ul class="list-unstyled">
<li class="combobox__item" ng-repeat="pos in listCtrl.positions | filter:query as results"
ng-click="listCtrl.choosePosition(pos)">{{pos.name}}
</li>
</ul>
</div>
</div>
<div class="col-md-2 no-padding">
<button type="button" class="btn btn-success" ng-click="listCtrl.chosenPositions(789456)">Add</button>
</div>
controller looks like:
myApp.controller('ListCtrl', ['$scope', '$cookies', '$http', function ($scope, $cookies, $http) {
var listCtrl = {
candidates: [],
positions: [],
chosenPositions: [],
init: function () {
listCtrl.getCandidates();
listCtrl.getPositions();
},
getCandidates: function () {
$http.get('candidates.json').then(function (res) {
listCtrl.candidates = res.data;
});
},
getPositions: function () {
$http.get('positions.json').then(function (res) {
listCtrl.positions = res.data;
});
},
choosePosition: function (position) {
console.log(position);
}
};
listCtrl.init();
$scope.listCtrl = listCtrl;
}]);
I double check for missspells and make sure its not because of function (I create a new one with simple console log).
Problem is that button click correctly call function but ng-repeat <li ng-click=""> doesnt do anything. I read in angular documentation that ng-repeat create new scope but this should be still okey in my opinion as soon as I use reference to object listCtrlchoosePosition()
Can someone tell me what I am doing wrong?
Thanks
EDIT: Plunker example:
http://plnkr.co/edit/ooUQA2n1Vyj8RZtsQ1Pj?p=preview
ng-blur is doing something weird, so I'm going to suggest you to change the $scope.focus value from the ListCtrl instead of using the ng-blur.
html file
<!-- more html code -->
<!-- input without ng-blur directive -->
<input class="tagselect__input" placeholder="Position" ng-focus="focus=true" ng-model="query">
<!-- more html code -->
<li class="combobox__item" ng-repeat="pos in listCtrl.positions | filter:query as results" ng-click="listCtrl.choosePosition(pos)">{{pos.name}}
<!-- more html code -->
js file
// more code goes here.
choosePosition: function (position) {
//alert('Going to choosen position');
//$scope.query = position.name;
$scope.focus = false; // Hide div options from here.
// rest of your code.
},
// more code goes here.
Working in this plunkr

Use an Angular directive to generate html from an array

I'm trying to use an Angular directive to create a form where the user can specify the number of children and, for each child, an edit box appears allowing the childs date of birth to be entered.
Here's my HTML:
<div ng-app>
<div ng-controller="KidCtrl">
<form>
How many children:<input ng-model="numChildren" ng-change="onChange()"/><br/>
<ul>
<li ng-repeat="child in children">
<child-dob></child-dob>
</li>
</ul>
</form>
</div>
</div>
Here's the JS:
var app=angular.module('myApp', []);
function KidCtrl($scope) {
$scope.numChildren = 2
$scope.children = [{dob: "1/1/90"}, {dob: "1/1/95"}];
$scope.onChange = function () {
$scope.children.length = $scope.numChildren;
}
}
app.directive('childDob', function() {
return {
restrict: 'E',
template: 'Child {{$index+1}} - date of birth: <input ng-model="child.dob" required/>'
};
});
And here's a jsFiddle
The problem is that it's just not working.
If I enter 1 in the numChildren field then it shows 1 bullet point for the list element but it doesn't show any of the HTML.
If I enter 2 in the numChildren field then it doesn't show any list elements.
Can anyone explain what I'm doing wrong?
Many thanks ...
Your main issue is that the directive childDOB is never rendered. Even though your controller works because 1.2.x version of angular has global controller discover on. It will look for any public constructors in the global scope to match the controller name in the ng-controller directive. It does not happen for directive. So the absence of ng-app="appname" there is no way the directive gets rendered. So add the appname ng-app="myApp" and see it working. It is also a good practice not to pollute global scope and register controller properly with controller() constructor. (Global look up has anyways been deprecated as of 1.3.x and can only be turned off at global level.)
You would also need to add track by in ng-repeat due to the repeater that can occur due to increasing the length of the array based on textbox value. It can result in multiple array values to be undefined resulting in duplicate. SO:-
ng-repeat="child in children track by $index"
Fiddle
Html
<div ng-app="myApp">
<div ng-controller="KidCtrl">
<form>How many children:
<input ng-model="numChildren" ng-change="onChange()" />
<br/>
<ul>
<li ng-repeat="child in children track by $index">{{$index}}
<child-dob></child-dob>
</li>
</ul>
</form>
</div>
</div>
Script
(function () {
var app = angular.module('myApp', []);
app.controller('KidCtrl', KidCtrl);
KidCtrl.$inject = ['$scope'];
function KidCtrl($scope) {
$scope.numChildren = 2
$scope.children = [{
dob: "1/1/1990"
}, {
dob: "1/1/1995"
}];
$scope.onChange = function () {
$scope.children.length = $scope.numChildren;
}
}
app.directive('childDob', function () {
return {
restrict: 'E',
template: 'Child {{$index+1}} - date of birth: <input ng-model="child.dob" required/>'
}
});
})();

angularjs directive calls image effect script

I have a directive to make news item to have an effect like usatoday.com when user hover on the news. I'm new to angularjs :D
Directive:
app.directive('hotEvent', ['$rootScope', function ($rootScope) {
return {
restrict: 'EA',
templateUrl: '/App/Main/views/home/events.html',
link: function (scope, iElement, attrs) {
//attrs references any attributes on the directive element in html
var dathumb = $(iElement).find('.da-thumbs > li a');
//$(function () {
dathumb.each(function () {
$(this).hoverdir();
});
//});
}
};
}]);
View: /App/Main/views/home/events.html
<ul class="row da-thumbs">
<li ng-repeat="news in featuredEvents">
<a href="/">
<img src="abc.jpg" /> >> no effect ???
<div>
<h4>aaa</h4>
<p>
bbb
</p>
</div>
</a>
</li>
<li>
<a href="/">
<img src="abc.jpg" /> >> show effect
<div>
<h4>bbb</h4>
<p>
ccc
</p>
</div>
</a>
</li>
</ul>
On Home.html: (which already binded with controller)
<div hot-event></div>
It works when i don't bind data from the controller <li ng-repeat="news in featuredEvents">, now the effect just doesn't show up. Console.log show 0 error.
UPDATED: i ended up using document ready
app.directive('hotEvent', function () {
return {
restrict: 'EA',
templateUrl: '/App/Main/views/home/events.html',
link: function ($scope, iElement, attrs) {
angular.element(document).ready(function () {
var dathumb = $(iElement).find('.da-thumbs > li a');
dathumb.each(function () {
$(this).hoverdir();
});
});
}
}
});
If you debug your code you'd see that your directive didn't find any elements.
It happens because when the template loads, the directive link function gets called, but the ng repeat didn't have time to populate it self (it starts off empty), there for it's no where to be found.
An easy workaround is to use the jquery find method in a setTimeout 0 function, or use $scope.evalAsync on the function that does the jquery find (it requires angular 1.3).
But the best solution would be to fix the code to actually not require jquery.
P.s. when you see your self using selectors in a directive you are usually doing things wrong in angular (note: usually), please refer to this "Thinking in AngularJS" if I have a jQuery background? awesome question and answer.
note that he actually says that when you first learn angular, don't use jquery at all :)

AngularJS directive for accessing ng-repeat scope of child

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.

Resources