How do I target just one instance of a nested repeat? - angularjs

This is probably pretty basic, but I'm going round in circles.
I have a nested controller in a ng-repeat - I would like to trigger an event in an instance of the repeat that will affect only the nested controller in that instance, not the nested controller in all instances.
Here is a basic example to try and make things clearer:
<div ng-controller="PostsCtrl">
<div ng-repeat="post in posts">
<p>{{post.body}}</p>
<div ng-controller="CommentsCtrl">
<div ng-repeat="comment in comments">
<p>{{comments.body}}</p>
<a href ng-click="showComments(post.id)">Show comments</a>
</div
</div>
</div>
</div>
angular.module('myApp')
.controller('PostsCtrl', function ($scope, Restangular) {
var posts = Restangular.all('posts');
posts.getList().then(function(posts) {
$scope.posts = posts;
});
$scope.showComments = function(id) {
$scope.$broadcast('SHOW_COMMENTS', id);
};
});
angular.module('myApp')
.controller('CommentsCtrl', function ($scope, Restangular) {
$scope.$on('SHOW_COMMENTS', function(event, id) {
var post = Restangular.one('posts', id);
post.get().then(function(post) {
$scope.comments = post.comments;
});
});
});
What would happen in this example is that all instances of the comments controller would be populated with the same comments - I just need to be able to target the relevant one. I'm sure I'm missing something pretty fundamental, and probably going about this the wrong way.
Many thanks.

You're broadcasting "SHOW_COMMENTS" to all repeated posts. You need to isolate the scope per Post.
Isolate the scope using ng-controller="PostCtrl".
<div ng-controller="PostsCtrl">
<div ng-repeat="post in posts" ng-controller="PostCtrl">
<p>{{post.body}}</p>
<div ng-controller="CommentsCtrl">
<div ng-repeat="comment in comments">
<p>{{comments.body}}</p>
<a href ng-click="showComments()">Show comments</a>
</div
</div>
</div>
</div>
Move show comments from PostsCtrl to PostCtrl.
angular.module('myApp')
.controller('PostCtrl', function ($scope) {
$scope.showComments = function() {
$scope.$broadcast('SHOW_COMMENTS', $scope.post.id);
};
});
Do you really need to use events? You could use ng-if instead.
Your comments are embedded in each Post anyway so why request them again. In this case you could do like something this...
<div ng-controller="PostsCtrl">
<div ng-repeat="post in posts" ng-controller="PostCtrl">
<p>{{post.body}}</p>
<a href ng-click="toggleComments()">Show comments</a>
<div ng-controller="CommentsCtrl" ng-if="showComments">
<div ng-repeat="comment in post.comments">
<p>{{comments.body}}</p>
</div
</div>
</div>
</div>
Toggle comments...
angular.module('myApp')
.controller('PostCtrl', function ($scope) {
$scope.showComments = false;
$scope.toggleComments = function() {
$scope.showComments = !$scope.showComments;
};
});

Related

Unable to get content AngularJs dynamic popover inside ngrepeat

I need to display a small popover which should open on click and goaway on clicking anywhere on the page.
I found a plunker (http://plnkr.co/edit/K7cYQSDEBS3cHvDfJNLI?p=preview) which matches this requirement however, unable to get it to work inside ng-repeat.
I saw several answers and Plunker examples but not able to get this to work.
Here is my html
<div ng-controller="TestController">
<div class="row" style="background-color: #ebebeb !Important; ">
<div style="text-align:center">
<table style="width:100% !important;">
<tr ng-repeat="member in TeamMembers" style="font-size:18px !important; height: 108px;">
<td style="display:block;margin-top:30px;text-align:left;"> {{member.FirstName}} {{member.LastName}} <i class="fa fa-info-circle" aria-hidden="true" ng-show="member.Description != null" popover-template="dynamicPopover.templateUrl" popover-placement="bottom" popover-elem descr="{{member.Description}}"></i></td>
</tr>
</table>
</div>
</div>
...
<script type="text/ng-template" id="descriptionModal.html">
<div class="adp-info-dialog">
<div class="modal-body">
<div class="row">
<div class="col-md-8 col-md-offset-1">
<div class="form-group">
<label class="fieldset-label">Test {{ dynamicPopover.descr }}</label>
</div>
</div>
</div>
</div>
</div>
</script>
Here is the JS
testApp.controller('TestController', function ($scope, $rootScope, $log, $modal, SiebelAccountTeamService, $filter, $element) {
$scope.dynamicPopover = {
templateUrl: 'descriptionModal.html',
descr: null
};
var result = TestService.GetTeamMembers();
result.then(function (data) {
$scope.TeamMembers = data.data;
}, function (e) {
console.log(e);
}).finally(function () {
$scope.CompleteLoading();
});
});
testApp.directive('popoverClose', function ($timeout) {
return {
scope: {
excludeClass: '#'
},
link: function (scope, element, attrs) {
var trigger = document.getElementsByClassName('trigger');
function closeTrigger(i) {
$timeout(function () {
angular.element(trigger[0]).triggerHandler('click').removeClass('trigger');
});
}
element.on('click', function (event) {
var etarget = angular.element(event.target);
var tlength = trigger.length;
if (!etarget.hasClass('trigger') && !etarget.hasClass(scope.excludeClass)) {
for (var i = 0; i < tlength; i++) {
closeTrigger(i)
}
}
});
}
};
});
testApp.directive('popoverElem', function () {
return {
scope: {
descr: '#'
},
link: function (scope, element, attrs) {
$scope.dynamicPopover.descr = scope.descr,
alert($scope.dynamicPopover.descr),
element.on('click', function () {
element.addClass('trigger');
});
}
};
});
Appreciate your help.
Update:
To show the data of the ng-repeat inside the popover content, we need to access the individual objects through the $index of the ng-repeat. Refer the below example.
Plunkr Demo
The problem here is that you are using ng-repeat which creates a new scope read more here.
Since replicating the issue with your code is tedious, I tried replicating the issue with the plunkr!
Solution:
Plunkr Demo
You can simply define a new controller inside the descriptionModal.html like so
HTML:
<script type="text/ng-template" id="myPopoverTemplate.html">
<div class="adp-info-dialog" ng-controller="tester">
<div class="modal-body">
<div class="row">
<div class="col-md-8 col-md-offset-1">
<div class="form-group">
<label class="fieldset-label">Test {{ $parent.$parent.dynamicPopover.content }}</label>
</div>
</div>
</div>
</div>
</div>
</script>
JS:
app.controller('tester', function ($rootScope, $scope) {
console.log($scope.$parent.$parent.dynamicPopover.title);
});
Then, we will be able to access the parent scope, using $parent, the html inside the above script uses the $parent to get the variable!
Please note: It took me two $parent to reach the required $scope to access the scope variable. In your scenario it will also require two, the way to check how many is needed is use console.log($scope), then open the console(F12), then traverse through the objects $parent property till you find the correct $scope. Then count the number of $parent traversed, that will be your required number of $parent to traverse!
P.S:
There is another method you can do this, since this method will require a significant rewrite of your code, I will provide the GIST, you can use the controller as syntax and access the correct scope.
Here is the SO Answer giving the method to do it
SO Answer
I hope this fixes you issue.

How to get param from one controller to another?

My question is best explained when I straight go to the code.
HTML part:
<div class="panel panel-default post" ng-repeat="post in posts">
<div class="panel-body">
<div class="row">
<div class="col-sm-2">
<a class="post-avatar thumbnail" href="/profile#/{[ post.profileID ]}">
<div class="text-center">{[ user.fullname ]}</div>
</a>
</div>
</div>
</div>
When I click on the /profile#/{[ post.profileID ]} link - it takes me to the profile page. All good here.
However, I am using ngView so I have separated it like this:
<div class="col-md-3">
<div>Some HTML stuff</div>
</div>
<div class="col-md-6">
<div ng-view></div>
</div>
<div class="col-md-3">
<div>Some HTML stuff</div>
</div>
My ngView makes use of the /profile#/{[ post.profileID ]} param and I use it to display whatever I have to display.
The problem:
I can get the profileID param in my angular controller but once I get it, how will I be able to pass it onto other controllers?
My controller looks like the below:
var profileApp = angular.module('profileApp', ['ngRoute']);
profileApp.config(function($routeProvider) {
$routeProvider
.when('/:id', {
templateUrl : 'partial/profile/feed.html',
controller : 'mainController'
})
.when('/posts:id', {
templateUrl : 'partial/profile/posts.html',
controller : 'postsController'
});
});
profileApp.controller('mainController', ['$scope', '$http', '$routeParams', function($scope, $routeParams){
console.log($routeParams.id);
}]);
profileApp.controller('postsController', ['$scope', '$routeParams', function($scope, $routeParams){
console.log($routeParams.id);
}]);
As you can see, I get get the param passed from the HTML link and use it in the mainController but how will I get the param to be a link in the col-md-3 (just like the original /profile#/{[ post.profileID ]})?
Hope this makes sense. It's has been driving me nuts!
Thanks
Why don't you just edit your partial HTML pages and put the columns in it.
For partial/profile/feed.html :
<div>
<div class="col-md-6">
<div>feed stuff</div>
</div>
<div class="col-md-3">
</div>
</div>
And partial/profile/posts.html could be :
<div>
<div class="col-md-6">
</div>
<div class="col-md-3">
<div>posts stuff</div>
</div>
</div>
So I did some research into this and I just ended up using services.
See below for the answer:
profileApp.service('globalParams', function() {
var profileID = '';
return {
getProfileID: function() {
return profileID;
},
setProfileID: function(value) {
profileID = value;
}
};
});
You then pass the service into the dependencies in the controllers, like below:
profileApp.controller('mainController', ['$scope', 'globalParams', function($scope, globalParams){
//some code
};
And you can call the functions for getting and setting the variables.

angular does not load my directive

I newly start to use angular.but I have some problem to loading my directive.
I want to load my directive as soon as page loaded.
where I load data-show directive
<div class="row">
<div class="col-md-12">
<article class="row" ng-controller="DataCtrl">
<input type="button" ng-click="getDataList()" >
<h1>Some Content Here</h1>
<ul id="home" bread-crumbs></ul>
<ul class="thumbnails">
<li ng-repeat="data in list" class="col-md-5">
<show-data data="data"/>
</li>
</ul>
</article>
</div>
</div>
showData directive:
app.directive('showData', function () {
return{
restrict: 'E',
replace:true,
templateUrl: 'views/directives/datas.directive.html',
scope: {
data: "="
},
controller:'DataCtrl'
}
})
and template I used in:
<div class="well hoverwell">
<div class="row">
<h2 class="col-md-4">{{data.name}}</h2>
</div>
<div class="row">
<span class="col-md-1">Code:</span>
<span class="col-md-1">{{data.id}}</span>
</div>
<div class="row">
<span class="col-md-1">accountability:</span>
<span class="col-md-1">{{data.parent}}</span>
</div>
<div class="row">
<span class="col-md-1"> :</span>
<span class="col-md-1">{{data.definition}}</span>
</div>
</div>
and my controller
'use strict';
angular.module('app')
.controller('DataCtrl', function ($scope, DataService, $log) {
$scope.getDataList = function () {
var list = DataService.getDataList(1);
list.then(
function (result) {
$log.info(result);
$scope.dataList = result;
}, function (status) {
$log.error(status)
$scope.msg = "error " + status + " has been occur,please report to admin ";
});
};
});
and when I run my app it does not work .
when I watch it in chorome development tools my directive is comment
what is my problem.How can I call this directive as soon as page load.
thx
As you already noticed, you see empty list because your dataList in ng-repeat is not filled yet.
But you have some errors in your code:
First of all - you should never use one controller twice. So you need to create separate controller for your directive.
replace directive parameter is deprecated, better not to use it.
In your DataCtrl you set the dataList variable: $scope.dataList = result;, but in HTML you refer to list variable: <li ng-repeat="data in list" class="col-md-5">.
Maybe that example will help you to figure out with your code.

Having a hard time inheriting scope from parent in AngularJS

So I have the following two controllers, a parent and a child, and I need a value that is dynamically added to the parent scope passed on into the child scope. I really can't understand how the service injection works either. Here is my parent controller:
'use strict';
angular.module("myApp").controller("singleTopicController", ["RestFullResponse", "Restangular", "localStorageService", "$scope", "$window", "$stateParams", function(RestFullResponse, Restangular, localStorageService, $scope, $window, $stateParams){
var topics = Restangular.one('topics', $stateParams.id);
var Topics = topics.get({},{"Authorization" : localStorageService.get('***')}).then(function(topic){
$scope.topic = topic;
$scope.topic.id = topic.id;
$window.document.title = 'Example | ' + $scope.topic.topic_title;
console.log($scope.topic);
});
}]);
And my child controller which needs the topic.id to work.
'use strict';
angular.module("myApp").controller("commentSingleController", ["RestFullResponse", "Restangular", "localStorageService", "$scope", "$state", "$stateParams", "$timeout", function(RestFullResponse, Restangular, localStorageService, $scope, $state, $stateParams, $timeout){
$scope.topic = {};
var oneTopic = Restangular.one('topics', $scope.topic.id);
oneTopic.get({}, {"Authorization" : localStorageService.get('***')}).then(function(topic) {
topic.getList('comments', {}, {"Authorization" : localStorageService.get('***')}).then(function(comments){
$scope.comments = comments;
console.log($scope.comments);
});
});
$scope.isCollapsed = true;
var comments = Restangular.all('comments');
$scope.commentData = {
//topic_id: $scope.parent.topic.id
};
$scope.postComment = function(mood) {
$scope.commentData.mood = mood;
comments.post($scope.commentData, {}, {"Authorization" : localStorageService.get('***')}).then(function(response){
$state.transitionTo($state.current, $stateParams, {
reload: true,
inherit: false,
notify: true
});
}, function(response){
$scope.error = response.data.message;
})
};
}]);
If I add
console.dir($scope.$parent);
to the child controller, topic is obviously there.
But if I try
console.dir($scope.$parent.topic);
I get undefined.
I tried wrapping the Restangular.get() of the child controller in a watcher but that didn't do anything.
$scope.$watch('topic', function{
Restangular.one() ...})
Where am I going wrong here.
<div class="main-left-column">
**<div ng-controller="singleTopicController">** //my main controller
<div class="topic-full">
<div class="topic-left">
<div class="topic-left-inner">
<a ui-sref="main.topic({id: topic.id})"><h4>{{ topic.topic_title }}</h4></a>
<hr>
<img ng-src="{{ topic.image_url }}" class="img-responsive img-topic" tooltip="{{ topic.topic_title}}"/>
<hr ng-if="topic.image_url">
<p>{{ topic.topic_content }}</p>
</div>
</div>
<div class="topic-right">
**<div class="topic-right-inner" ng-controller="commentSingleController">** //child controller
<img ng-src="{{ topic.profile_pic }}" width="60px" class="profile-pic"/>
<div class="topic-data">
<h4 class="topic-data">{{ topic.author_name }}</h4>
<h5 class="topic-data">- posted on {{ topic.created_at }}</h5>
</div>
<div class="comment-count">
<div class="comment-count-mood red" tooltip="Negative comments"><p>{{ topic.comments_angry }}</p></div>
<div class="comment-count-mood yellow" tooltip="Neutral comments"><p>{{ topic.comments_sad }}</p></div>
<div class="comment-count-mood green" tooltip="Positive comments"><p>{{ topic.comments_happy }}</p></div>
</div>
<div class="clear"></div>
<hr>
<p ng-if="comments.length == 0">No comments</p>
<div class="line-through">
<div ng-repeat="comment in comments">
<div class="comment-left">
<img ng-src="{{ comment.profile_pic }}" width="40px" class="comment-pic"/>
<div class="comment-mood" ng-class="{'red': comment.mood == 2, 'yellow': comment.mood == 1, 'green': comment.mood == 0}"></div>
</div>
<div class="comment-right">
<h4 class="comment-data">{{ comment.author_name }}</h4>
<p>{{ comment.comment }}</p>
</div>
<div class="clear"></div>
<hr class="dotted" ng-if="!$last">
</div>
</div>
</div>
</div>
<div class="clear"></div>
</div>
</div>
</div>
UPDATE: Added HTML. Controller wrappers are in asterisks.
Your topic won't be created until your .get in your parent controller resolves, which is probably after the initialisation of the child controller.
If you remove the $scope.topic = {} from the child controller and add a $watch for topic.id you should be able to use the id in there.
Really you shouldn't need $scope.$parent as $scope should have everything the parent does unless it's been hidden by something in the child scope or there's an isolate scope in the way (there isn't in this case).

How do I add an object to a nested array in AngularJS?

I'm new to AngularJS. I've researched the theory behind ng-repeats but I cannot find any good examples of 2-way data binding or object creation for nested ng-repeats. I understand that the syntax has changed in recent versions. I'm using 1.1.5.
I have post objects that have a nested array of comment objects. I want to be able to add new comment objects to the array of comments in the post. I've tried lots of different versions.
This is my HTML:
<div id='posts' ng-controller="PostsCtrl">
<ul>
<div id='post' ng-repeat="post in posts track by post.id">
<div id='postMedia'>
<img ng-click="" ng-src={{post.attachment}} />
</div>
<div ng-click="">
<div ng-click="">{{post.message}}</div>
<div ng-click="">{{post.created_at}}</div>
</div>
<h1>Comments</h1>
<div id='comment' ng-repeat="comment in post.comments track by post.comment.id">
<div ng-click="">{{comment.body}}</div>
</div>
<form ng-submit="addComment()">
<input id="body" type="text" placeholder="Reply…" ng-model="post.comment.body">
<input type="submit" value="Add">
</form>
</div>
</ul>
</div>
This is the controller:
myApp.controller('PostsCtrl', ['$scope', 'angularFire',
function MyCtrl($scope, angularFire) {
var url = 'https://inviter-dev.firebaseio.com/posts';
$scope.items = angularFire(url, $scope, 'posts', {});
$scope.commentProp = 'comments';
$scope.addComment = function() {
$scope.comments.push($scope.newcomment);
}
}
]);
SOLUTION:
Thanks to Chandermani answer for solving this. I modified the controller slightly because I wanted the to use a key called body in the data store -
myApp.controller('PostsCtrl', ['$scope', 'angularFire',
function MyCtrl($scope, angularFire) {
var url = 'https://inviter-dev.firebaseio.com/posts';
$scope.items = angularFire(url, $scope, 'posts', [] );
$scope.addComment = function(post,comment) {
post.comments.push({body:comment});
}
}
]);
The problem with you addComment method is that it does not know which post it needs to add comment to. Also the comment input that is part of you ng-repeat for comments can have a independent model which can be passed to the controller method.
You need to make the following changes. In html
<form ng-submit="addComment(post,commentData)">
<input id="body" type="text" placeholder="Reply…" ng-model="commentData">
<input type="submit" value="Add">
</form>
In your controller
$scope.addComment = function(post,comment) {
post.comments.push(comment);
}

Resources