Selecting and accessing items in ng-repeat - angularjs

I have an array of objects in a service that I want to display in a view like this
<li ng-repeat="item in filteredItems = (resource.items | filter:multipleFilters...">
<span class="title">{{item.escaped_name}}</span>
</li>
I want these objects to be selectable which is the easy part. Then I'd need to get the count of the selected items and be able to iterate over all of them to process/change data.
What is the best method to save and access the selected items?
Note that the selected items could change, for example a selected item could fall away due to changed filters. Also, I don't want to set a selected property on the objects in the array directly - the array of objects is in a service and used throughout the app in many lists and I don't want to "clean up" the selected property for each view.

I usually include a ng-click tag:
<li ng-repeat="item in ..." ng-click="select_item(item)">
The item passed to select_item() will be the one the user selected.

You can take the object oriented approach and create models to do your work for you basically. I've been in the same scenario and it's worked for me, plus I think it's a good tool angular gives you to work with. You would have a listModel and an itemModel - the list model would have a list of your single item itemModels. The list model you would use 1 instance of between where ever you are using this list of items. Take this with a grain of salt because this is just an example.
So you'd have the listModel
.factory('listModel', [singleItemModelInject,
function(singleItemModel) {
function listModel(items) {
this.items = _.map(items, listModel.create);
}
listModel.create = function(value, name) {
return new listModel(value);
};
listModel.prototype = {
get whatever() {
},
set whatever() {
}
}
return listModel;
}
]);
Notice it injects singleItemModel - this would be the individual item model it would look the same except it would have all your information on creation with what you pass it like
.factory('singleItemModel', [whateverYouNeedInjected,
function() {
function singleItemModel(item) {
this.name = item.name;
//default to not selected
this.selected = item.selected || false;
this.whateverElseYouNeed = item.whateverElseYouNeed
}
singleItemModel.create = function(value) {
return new singleItemModel(value);
};
So then you would have a single instance of the listModel you would use across your app, you can toggle a hide or show with whatever property, as long as you have setters and getters for your properties you want to access (like the name and isSelected or whatever you want) and have it 2 way bound, if it is changed to selected anywhere, it is universal because of the single instance you are using.
You can also you individual instances if you dont want the selected values to persist across the app, and only in that one page (or where ever you are using it)

Related

2 Dropdowns, 1 object ids, other with lists from lists in objects

I've got two multiselect dropdowns on a page that uses angularJS. The first is populated from a service call which returns a lists of objects which are basically category objects. The second dropdown is a list of all the specifics from all those categories. Let's call them Groups and Items for simplicity. Originally, I just populated the second list with a second api call which returned a list of those Items. But since the Group objects already contain a list of the Items which are related, this seems unnecessary. (And I'd like to be able to tie the two together, so that if a group is selected, only the items which fall into that group are displayed in the second dropdown, but that's secondary...) My first dropdown get's populated with this code:
$scope.promiseGetGroupNames = providerContactInfoService.getGroupNames();
$scope.promiseGetGroupNames.then(function (pl) {
$scope.GroupDDLdata = pl.data;
}, function (errorPl) {
//error message...
});
So I would assume that in there I could loop through the data and generate a list there, but it seems like angular would have a simpler way of doing things... Is there a way that I can set the options of the second dropdown to be a complete list of items from all of the groups in the first, and then when a user selects one of those groups, remove all but the items which fall into that category?
Additionally, I have researched this, and found some answers that looked like there was a way to connect the ng-options of 2nd dropdown to the ng-model of the first, which seems like what I want to do, at least for the 2nd half, but I still need to get all options in there at page load...
The other idea I had was to simply make two service calls... the first pulls all of the possible items, the 2nd would pull a subset that only falls into the group select... since only one group is ever allowed to be selected, it's not terribly inefficient, but if the person selects one group, then a different one... seems like that would all be unnecessary if it's possible to link all the objects/options/models together...
What is the best practice for this type of scenario, and if it's the first, could someone explain a little about how to set up that connection? I understand the basics of pulling the possible options from the model, but not exactly sure on the syntax (I'm relatively new to angularJS.)
How about something like this ?
1) Populate your first dropdown list :
<select ng-options="groups" ng-model="selectedGroup">
$scope.init = function () {
service.getGroups.then(function (groups) {
$scope.groups = groups;
});
};
$scope.init();
2) Use ng-change directive on your first dropdown list :
<select ng-options="groups" ng-change="fillSecondList()" ng-model="selectedGroup">
<select ng-options="secondList" ng-model="selectedThing">
$scope.fillSecondList = function () {
// Whatever you want, you can use $scope.selectedGroup here
$scope.secondList = ...
};
$scope.fillSecondList will fire when your selection changes so you can update $scope.secondList according to what's selected in your first list ($scope.selectedGroup), for example using a service call using selectedGroupas a parameter.
EDIT :
If all your needed data is fetched from the first service call, you can use a filter, as you don't actually need to modify your secondList model but rather change what is displayed in your second list.
$scope.init = function () {
service.getGroups.then(function (groups) {
$scope.groups = groups;
// Extract the second list from the first one
$scope.secondList = groups.doSomething();
});
};
<select ng-options="secondList | filter: { group : selectedGroup.id }" ng-model="selectedThing">
For example, this will display only secondList items which have a group property equal to selectedGroup.id.
You can use a custom filter if your filtering is more complicated, or a filter function in your controller (triggered with ng-change) if you don't plan to re-use this filter anywhere else :
$scope.filterSecondList = function () {
$scope.secondList = $scope.secondList.filter(function (item) {
return item.group === $scope.selectedGroup.id;
});
};

Knockout arrayFirst with MVC List

My MVC view model contains a number of properties including a list of objects.
This model is bound to a knockout model as follows:
viewModel = new DynamicModelLoading(#Html.Raw(Model.ToJson()));
ko.applyBindings(viewModel);
When looking at the knockout model I can see that my list has been successfully created as an array.
The problem I'm having is that whenever I use arrayFirst on the array it always returns null. I can create an observablearray in javascript and arrayFirst works fine.
var match = ko.utils.arrayFirst(viewModel.activeList(), function (item) {
return 2 === item.id;
});
Is there something I'm doing wrong to be able to apply arrayFirst on an array that has been bound from my MVC model?
Ok, I've finally sussed it. The mapper is creating the observable array as an array of objects full of observable properties. Therefore simply needed to state:
return 2 === item.id();
rather than return
2 === item.id;

Restangular how to update item in collection

I am currently fiddeling around with restangular and ui-router.
I do resolve a collection of items in my router which makes it available to the underlying controllers. I have a list of todos and i want to edit a todo. So i load a view where i can edit the item.
I get the model by $scope.todo = todos.get(id) I make a change to it and then i do $scope.todo.save() which updates the model on the server. But now i have the old item still in the collection of todos.
I want my collection to reflect the changes in the single item. I could delete the item from the collection and reinsert it afterwards, but this seems a little bit too complicated. Is there no easy way to update a model within a collection?
Update: Adding some Code
Note: The todos property gets resolved if the state is called.
If i edit a single todo i resolve it by
resolve : {
todo : function($stateParams, todos) {
return todos.get($stateParams.id);
}
}
I do some changes and then i call todo.save(). No changes will happen on the collection this way. I tried to do a todos.patch(todo) but that actually did a request to weird url and i guess it is intended to patch the whole collection (?)
I am sure there is a way to change a model within a collection, but i dont know how
After trying some stuff i ended up with replacing the item inside the collection. I created a little helper to lodash which i want to show here:
var replaceItemById = function(list, element) {
var index = _.indexOf(list, _.find(list, { id : element.id }));
list.splice(index, 1 , element);
return list;
};
_.mixin({'replaceItemById' : replaceItemById});
When i want to update a model inside a collection i do step by step:
Fetch the collection
Get a single item from the collection and edit it
Call save on the item
//The server returns the updated model
todo.save().then(function(editedTodo) {
_.replaceItemById(todos, editedTodo);
$state.go('todos.index');
});
This way i do not need to fetch the collection again (even if in most cases this is what you would do) and it is up to date after updating a single item.

SignaR + AngularJS Array property change is not modifying UI

I am trying to create a chat application and wanted to display availability of users on UI.
I am using SignalR and AngularJS.
But UI is not updating after I update members online property like below,
self.hubProxy.client.isAvailable = function (loggedinuser, isOnline) {
$scope.$apply(function () {
self.allMembers[loggedinuser].isOnline = isOnline;
});
I am using ng-repeat="member in allMembers" to bind with all members and using its isOnline property to apply a class.
Assuming your method gets called, and assuming that loggedinuser is the name of the user, then your problem might be with ngRepeat, because you should be enumerating by key:
ng-repeat="(key, value) in allMembers"
Check this: How can I iterate over the keys, value in ng-repeat in angular

Linking MVC In AngularJS

I have a basic application in AngularJS. The model contains a number of items and associated tags of those items. What I'm trying to achieve is the ability to filter the items displayed so that only those with one or more active tags are displayed, however I'm not having a lot of luck with figuring out how to manipulate the model from the view.
The JS is available at http://jsfiddle.net/Qxbka/2 . This contains the state I have managed to reach so far, but I have two problems. First off, the directive attempts to call a method toggleTag() in the controller:
template: "<button class='btn' ng-repeat='datum in data' ng-click='toggleTag(datum.id)'>{{datum.name}}</button>"
but the method is not called. Second, I'm not sure how to alter the output section's ng-repeat so that it only shows items with one or more active tags.
Any pointers on what I'm doing wrong and how to get this working would be much appreciated.
Update
I updated the method in the directive to pass the data items directly, i.e.
template: "<button class='btn' ng-repeat='datum in data' ng-click='toggle(data, datum.id)'>{{datum.name}}</button>"
and also created a toggle() method in the directive. By doing this I can manipulate data and it is reflected in the state HTML, however I would appreciate any feedback as to if this is the correct way to do this (it doesn't feel quite right to me).
Still stuck on how to re-evaluate the output when a tag's value is updated.
You can use a filter (docs) on the ng-repeat:
<li ng-repeat="item in items | filter:tagfilter">...</li>
The argument to the filter expression can be many things, including a function on the scope that will get called once for each element in the array. If it returns true, the element will show up, if it returns false, it won't.
One way you could do this is to set up a selectedTags array on your scope, which you populate by watching the tags array:
$scope.$watch('tags', function() {
$scope.selectedTags = $scope.tags.reduce(function(selected, tag) {
if (tag._active) selected.push(tag.name);
return selected;
}, []);
}, true);
The extra true in there at the end makes angular compare the elements by equality vs reference (which we want, because we need it to watch the _active attribute on each tag.
Next you can set up a filter function:
$scope.tagfilter = function(item) {
// If no tags are selected, show all the items.
if ($scope.selectedTags.length === 0) return true;
return intersects($scope.selectedTags, item.tags);
}
With a quick and dirty helper function intersects that returns the intersection of two arrays:
function intersects(a, b) {
var i = 0, len = a.length, inboth = [];
for (i; i < len; i++) {
if (b.indexOf(a[i]) !== -1) inboth.push(a[i]);
}
return inboth.length > 0;
}
I forked your fiddle here to show this in action.
One small issue with the way you've gone about this is items have an array of tag "names" and not ids. So this example just works with arrays of tag names (I had to edit some of the initial data to make it consistent).

Resources