Array is not changing - angularjs

So I'm new to angular JS and I am having a problem with editing one object in an array. The liked function below is bring called and the appropriate object is also being passed to it. The loop is even finding the right object and setting its liked value to true. However for some reason when I click the liked button for the second time it still shows the liked value as being false...I'm sure its something super simple.
<div class="thelist" data-ng-repeat="b in data| orderBy:choice">
<h2>{{ b.from }}</h2>
<p>{{b.date}}</p>
<img class="full-image" src="{{b.img}}">
<p>{{b.content}}</p>
<p>{{b.likes}}</p>
<button class="tab-item" ng-click="liked({{b}})"></button>
</div>
App.js:
ref.on("child_added", function (snapshot) {
var newPost = snapshot.val();
$scope.data.push({
content: newPost.content,
date: newPost.date,
from: newPost.from,
img: newPost.img,
likes: newPost.likes,
realdate: newPost.realdate,
id: snapshot.key(),
liked: false
});
});
The liked function:
$scope.liked = function (post) {
if (post.liked == false){
console.log("liked has been run");
angular.forEach($scope.data, function (object) {
if (object.id == post.id) {
object.liked = true;
}
});
//add 1 like on server here
}
}

You must not use templating in ng-click. Change
<button class="tab-item" ng-click="liked({{b}})"></button>
to
<button class="tab-item" ng-click="liked(b)"></button>

To expand on #hege_hegedus's answer, "ng-click" directive takes an expression (javascript expression with access to $scope variables). Since the since it takes an expression and have complete access to $scope, you can think of
<button class="tab-item" ng-click="liked(b)"></button>
as
<button class="tab-item" ng-click="$scope.liked($scope.b)"></button>

Related

ngRepeat doesn't refresh rendered value

I'm having an issue with ngRepeat :
I want to display a list of students in two different ways. In the first one they are filtered by group, and in the second they are not filtered.
The whole display being quite complex, I use a ngInclude with a template to display each student. I can switch between view by changing bClasseVue, each switch being followed by a $scope.$apply().
<div ng-if="currentCours.classesOfGroup !== undefined"
ng-show="bClassesVue">
<div ng-repeat="group in currentCours.classesOfGroup">
<br>
<h2>Classe : [[group.name]]</h2>
<div class="list-view">
<div class="twelve cell"
ng-repeat="eleve in group.eleves | orderBy:'lastName'"
ng-include="'liste_eleves.html'">
</div>
</div>
</div>
</div>
<div class="list-view" ng-show="!bClassesVue">
<div class="twelve cell"
ng-repeat="eleve in currentCours.eleves.all"
ng-include="'liste_eleves.html'">
</div>
</div>
My problem happens when my list of students change (currentCours here). Instead of refreshing the ngRepeat, both lists concatenate, but only in the unfiltered view.
I tried adding some $scope.$apply in strategic places (and I synchronize my list for example) but it doesn't help.
EDIT : the function used to refresh currentCours in the controller. It's called when a "cours" is selected inside a menu.
$scope.selectCours = function (cours) {
$scope.bClassesVue = false;
$scope.currentCours = cours;
$scope.currentCours.eleves.sync().then(() => {
if ($scope.currentCours.classe.type_groupe === 1) {
let _elevesByGroup = _.groupBy($scope.currentCours.eleves.all, function (oEleve) {
return oEleve.className;
});
$scope.currentCours.classesOfGroup = [];
for(let group in _elevesByGroup) {
$scope.currentCours.classesOfGroup.push({
name: group,
eleves: _elevesByGroup[group]
});
}
$scope.bClassesVue = true;
}
});
utils.safeApply($scope);
};
Well, I found a workaround, but I still don't know why it didn't work, so if someone could write an explanation, I would be very thankful.
My solution was simply to open and close the template each time I switch between views.

Creating a "Like" button with Angular.js

Im trying to create a like button with Angular.js.
(It is just a heart icon. default color is white = NOT liked. It is red when liked. Like/unlike is toggled by a click)
I get some data from my web service that has also an array of some ID's. These ID's are the ones that clicked the like button before.
Then i populate the DOM with the ng-repeat directive according to the data retrieved from the web service.
I attach the button a ng-class that sets the proper class and a ng-click directive that is supposed to somehow change the class too.
* I cant connect between the ng-class and the ng-click result.
some code:
<div ng-repeat="photo in photos track by photo._id">
<button ng-class="{carouselFooterButtonLikeActive : initLike(photo)}" ng-click="like(photo, this)">
<i class="icon ion-heart"></i>
</button>
</div>
Controller:
// Handle like button click
$scope.like = function(photo, photoScope){
HOW CAN I AFFECT THE NG-CLASS FROM HERE?
}
$scope.initLike = function(photo){
if(photo.likes.indexOf($localstorage.getObject('userInfo').id) > -1) {
$scope.liked = true;
return true;
}
$scope.liked = false;
return false;
}
Edit: added a possible data retrieved from the web service
{
photos: [
{
src: "src1.jpg",
likes:[111,222,333]
},
{
src: "src2.jpg",
likes:[]
}
]
}
You can use as a flag some additional property that will be initially undefined on each photo element - say photo.liked. When user clicks it, $scope.like function sets this property to true. Then ng-class evaluates photo.liked to true and adds carouselFooterButtonLikeActive class to button element.
The code is as follows:
In the template:
<button ng-class="{'carouselFooterButtonLikeActive' : photo.liked}" ng-click="like(photo, this)">
In the controller:
$scope.like = function(photo, photoScope){
photo.liked = true;
}
UPD
Say you have photos array:
[
{'src':'bla-bla.jpg', liked: true, id: 8347},
{'src':'foo-bar.jpg', id: 45},
{'src':'baz-baz.jpg', id: 47}
]
then only the first one will be shown with button.carouselFooterButtonLikeActive class, thanks to ng-class evaluation expression.
UPD2
If photo.likes is an array, you can use:
//template
ng-class="{'carouselFooterButtonLikeActive' : (photo.likes && photo.likes.length >0)}"
//controller
$scope.like = function(photo, photoScope){
photo.likes.push(someUserID);
}

Delete a record in NG-repeat protractor

So I'm doing e2e test with protractor and angular,
My first test was adding an element to the list,
Now I'm trying to delete it, and I'm having trouble doing so
So this is what I need to do:
Find the name of the record I want to delete
click on the trashcan icon of that same row and delete it
press ok on the pop up window that appears
Try as much as possible to avoid the use of By.css and prefer everything related to angular( byBinding, model, etc) . This is because this part of the app migh eventually change, hence I will have to redo all this cases.
HTML:
...
<div class="list-group-item ng-scope" ng-repeat="item in teamList">
<span class="glyphicon glyphicon-user"></span>
<span ng-bind="item.name" class="memberName ng-binding">nuevo Team</span>
<a ng-click="editTeam(item._id)" class="hand-cursor">
<span class="glyphicon glyphicon-edit memberRemoveBotton"></span>
</a>
<a ng-confirm-click="Would you like to delete this item?" confirmed-click="deleteTeam(item._id)" class="hand-cursor">
<span class="glyphicon glyphicon-trash memberRemoveBotton"></span>
</a>
</div>
JS:
describe('Testing delete Item',function() {
it('Should delete the Item that just got Inserted',function() {
element(by.css('a[href="#!/item-create"]')).click(); //Opens up the Item dashBoard
element.all(by.repeater('item in itemList')).then(function(table) {
table.element(by.binding('item.name')).each(function(names) {
console.log('the names',names.getText());//I'm trying to find the name of the item that just got inserted
// is there like a nested chaining of elements in here ??
});
});
});
});
Any hints on how to solve this are appreciated
describe('Testing delete Item',function() {
it('Should delete the Item that just got Inserted',function() {
// Assume you know the name of the item you want to delete.
var nameToDelete = 'some name';
// Get the row that has the name by using filter.
element.all(by.repeater('item in itemList')).filter(function(row){
return row.element(by.css('.memberName')).getText().then(function(name){
return name === nameToDelete;
});
})
// Now you should have one row.
.get(0)
// Get the row and click find the remove button.
.element(by.css('.memberRemoveBotton'))
.click();
// Make sure it was deleted.
var names = $$('.list-group-item .memberName').getText();
expect(names).not.toContain(nameToDelete);
});
});
Let me know if it works.
Thank's Andres your answer helped me sort out some doubts I was having.
Anyway here is the answer, one of the most tedious part was the popUp window, let's hope it wont brake later on
it('Should delete the Team that just got Inserted',function() {
element(by.css('a[href="#!/team-create"]')).click(); //Opens up the Team dashBoard
element.all(by.repeater('item in teamList')).filter(function(row) {
return row.element(by.css('.memberName')).getText().then(function(name) {
return name === teamName;
});
}).first().element(by.css('.glyphicon-trash')).click();
testHelper.popUpHandler(ptor);
element.all(by.css('.list-group-item .memberName')).each(function(list) {
expect(list.getText()).not.toContain(teamName);
});
});

How Do I Move Objects Inside An ng-Repeat on Button Click?

I have a nifty list of items in an ng-repeat with an up and down button on each. I just want the up button to move the list item up one place and the down button should move it down one place.
The problem is that I get an error saying "Cannot read property 'NaN' of undefined."
It seems "position" is undefined on the second line. What can I do to fix that?
Heres the javascript I'm working with (thanks to Rishul Matta):
$scope.moveUp = function(ind, position) {
$scope.temp = $scope.list[position - 1];
$scope.list[position - 1] = $scope.list[position];
$scope.list[position = temp];
};
Here's my HTML:
<ul>
<li class="steps" ng-repeat="step in selectedWorkflow.Steps track by $index" ng-class="{'words' : step.Id != selectedStep.Id, 'selectedWords' : step.Id == selectedStep.Id}" ng-model="selectedWorkflow.Step" ng-click="selectStep(step, $index); toggleShow('showSubStep'); toggleShow('showEditBtn')">
{{step.Name}}
<input class="orderUpBtn" type="button" ng-click="moveUp($index, step)" style="z-index:50" value="U" />
<input class="orderDownBtn" type="button" style="z-index:50" value="D" />
</li>
</ul>
Thanks!
Thanks for posting this question (+1) and the answer jtrussell (+1). I wanted to share what I believe to be a more re-usable/modular answer for other folks (inspired by odetocode.com post).
For the HTML, jtrussell's code is perfect because he fixed/simplified everything. For a better user experience I just added ng-disabled for the first/last elements.
HTML:
<ul ng-controller="DemoCtrl as demo">
<li ng-repeat="item in demo.list">
{{item}}
<button class="move-up"
ng-click="listItemUp($index)"
ng-disabled="$first">
Move Up
</button>
<button class="move-down"
ng-click="listItemDown($index)"
ng-disabled="$last">
Move Down
</button>
</li>
</ul>
For the JS, Notice the moveItem() function which I believe to be more re-usable. You can use this function for other drag+drop swapping functionality as well.
JS within Controller (tested on Angular 1.3.15):
// Move list items up or down or swap
$scope.moveItem = function (origin, destination) {
var temp = $scope.list[destination];
$scope.list[destination] = $scope.list[origin];
$scope.list[origin] = temp;
};
// Move list item Up
$scope.listItemUp = function (itemIndex) {
$scope.moveItem(itemIndex, itemIndex - 1);
};
// Move list item Down
$scope.listItemDown = function (itemIndex) {
$scope.moveItem(itemIndex, itemIndex + 1);
};
I hope it is helpful to someone out there. Thanks SO community!
A simple list with up/down buttons is pretty straightforward, here's some rough generic code. The ngRepeat directive will honor the order of items in your array so moving things around the view is just a matter of moving them in the array itself.
view:
<ul ng-controller="DemoCtrl as demo">
<li ng-repeat="item in demo.list">
{{item}}
<button ng-click="demo.moveUp($index)">up</button>
<button ng-click="demo.moveDown($index)">down</button>
</li>
</ul>
controller:
app.controller('DemoCtrl', function() {
this.list = list = ['one', 'two', 'three', 'four'];
this.moveUp = function(ix) {
if(ix > -1 && ix < list.length - 1) {
var tmp = list[ix+1];
list[ix+1] = list[ix];
list[ix] = tmp;
}
};
this.moveDown = function(ix) {
// similar...
};
});
There were a few strange items in your code (for example did you mean $scope.list[position] = temp; when you wrote ($scope.list[position = temp];), my example isn't perfect but it should get you going on the right path. Here's the full working demo: http://jsbin.com/vatekodeje, note that in my code I use "up" to mean increasing index rather than toward the top of the page.
Also in your controller you use position as an index (it's not clear that it should be) and make reference to, presumably, an array called $scope.list when in your view you use selectedWorkflow.Steps. Maybe your $scope.list and selectedWorkflow.Steps are meant to be the same thing?

ng-repeat and ng-href - when should it update?

I am having an issue where a value in an object in a list is changing, but the change is not being reflected in ng-href.
I have the following HTML:
<div class='tabrow docRow'>
<span data-ng-repeat='f in dp.source.files'>
<span class='linkedDoc' >
<a class='attachedCaption' target='_blank'
data-ng-href='{{f.vpath}}'>{{f.caption}}
<img class='attachedSrc' data-ng-src='{{ f.vimg }}'/>
</a>
</span>
</span>
</div>
I have a watch on dp.source, and when it changes a call a function that loops through each f in dp.source.files and adds v.img and v.path.
vimg is updated before the callback in the watch exits. vpath is updated asynchronously, maybe a second or two after the callback completes. Since it is updated in an asynch call, I use $apply.
$scope.$watch('dp.source', function() {
for (var idx = 0; idx < $scope.dp.source.files.length; idx++) {
var f = $scope.dp.source.files[idx];
f.vimg = _ev.imgMapper(dataProvider.imgMap, f.path); <--- immediate
f.vpath = '#';
attachService.getURL(f); <-- asynch call that updates f.vpath
}
}
attachService:
myObj.getURL = function(obj) {
syncService.getURL(obj, function(url) { <-- asynch return
$rootScope.$apply(function() {
obj.vPath = url;
});
});
};
vimg is present on the rendered page immediately, while the change in vpath is NEVER reflected on the page.
What am I missing?
To be clear, I have verified that the variable f.vpath IS getting updated.
OMG is was a spelling error!
the HTML expects f.vpath and attachService updated obj.vPath
It was that simple

Resources