I am having difficulty binding nested array using ng-Repeat. Is there a way I can bind complex array object to view.
Scope in directive holds object.
(directive controller is as - controllerAs: 'srCtrl')
var vm = this;
vm.resultCategories = [[{"id":3,"name":"name3","desc":"2","categoryId":1,"categoryName":"cat 1"},
{"id":1,"name":"name1","desc":"2","categoryId":1,"categoryName":"cat 1"},
{"id":2,"name":"name2","desc":"2","categoryId":1,"categoryName":"cat 1"}],
[{"id":4,"name":"name4","desc":"name1","categoryId":2,"categoryName":"cat 2"},
{"id":2,"name":"name2","desc":"2","categoryId":2,"categoryName":"cat 2"}]];
Binding directive view
<ul>
<li ng-repeat="category in srCtrl.resultCategories">
<h3>{{category.categoryName}}</h3>
</li>
</ul>
Any help is appreciated. Many thanks in advance.
The best solution is to concat all the arrays using a reduce function and a concat function before you serve it up to a ui. You can either do this through a filter or just within the controller upon receiving the data:
vm.resultCategories = vm.resultCategories.reduce(function(last,next){
return last.concat(next);
})
Note there are ways to add nested ng-repeat, but this is not advised for a variety of reasons:
you are using list items, it would mean you need to create divs to put them together (this could affect your css and overall structure)
confusing as hell!
room for potential error
edit:
Another way you achieve this(with less lines) is to use:
vm.resultCategories = new Array(vm.resultCategories.join());
Note: Although this method is shorter, it actually takes longer to run since the cpu is creating a 'new' array each time.
Related
I'd like to implement an infinite scroll, but without call a load more function everytime.
Let's think I've download an array of 1000 items from Parse. I'd like to show 10 items and then add more and more items to my list using ng-repeat from a local array.
I think the best solution is to implement a directive, but maybe there is something already done...
Anyone?
Sure you could do it as a directive, but regardless, you are going to need some sort of loadMore function. To put in other words, you are going to need to detect when the user has scrolled to a particular position, and perform some updating function.
While your own directive might be better at encapsulating your specific requirement, you could achieve what you want with any other current infinite scrolling plugin. Simply keep two versions of your array in your model. One is the data, while the other is the data that the user sees. If your arrays are of objects, then both should be able to work on the same items.
For example:
var data = [...];
var position = 0;
var pageSize = 10;
$scope.viewable = [];
$scope.loadMore = function(){
$scope.viewable = $scope.viewable.concat(data.slice(position, position + pageSize));
position += pageSize;
};
Update:
If this is something you need to implement often, but don't want to implement your own scrolling related directive, you could encapsulate the idea above in a factory that manages multiversion arrays. You would still have to hook the loadMore, and setup the multiversion, but your controller and view code would look like the following:
// the data you loaded somewhere
var data = [...];
$scope.specialArray = PageableArrayFactory.create(data);
// then in your html
ng-repeat="item in specialArray.viewItems"
// where you put your infinite scroll
infinite-scroll="specialArray.loadMore()"
PageableArrayFactory just needs to be a factory that takes in your big data array, and keeps track of a viewable copy array like my initial example. It shouldn't be too hard to implement, and can then be reused with a single line of code in any controller after you load your data.
Another example:
You could also build a custom filter on $index (or use ng-show/if), so that ng-repeat only shows the items that you want. You will still need to hook loadMore() though if you want to use existing infinite scroller code, which means you will still need something my factory example.
Matt's solution is perfectly fine. Especially considering you've mentioned you want to use ng-repeat and you want to paginate your result.
It doesn't seem to be a huge effort to implement what he's suggesting. The other option is to use collection-repeat, especially for performances.
The implementation is very simple:
<ion-content>
<ion-item collection-repeat="item in items">
{{item}}
</ion-item>
</ion-content>
and you don't have to be worried about loading all the items together as the framework will be responsible to load chunks of information for you.
The only drawback - as far as I am aware - is you cannot control the number of items you want to display.
When I update my $scope like so in a controller
$scope.item = "Hello";
Then the whole DOM for item seems to be removed and then added again. This seems fine, but if I have a list of items and do
$scope.items = Resource.query();
To update all the items then all of the DOM for items is removed and then re-added, this looks a broken and clumsy - is there anyway around this removing and then adding of the DOM elements when $scope is updated?
This issue is further exasperated if $scope.items and its children are used inside several ng-repeat statements as all of those ng-repeat sections are removed and then re-added.
EDIT
I have read this and feel that this is the issue https://www.binpress.com/tutorial/speeding-up-angular-js-with-simple-optimizations/135
That I have so much "stuff" going on the $digest is just slow. I am working on an example but in the mean time imagine this try of data
{
{
id: 1,
name: "name1",
something: {
id: 10,
name: "something10"
else: {
id: 15,
name: "else15"
}
}
}
}
But there are 20 such objects all with nested objects - this appears to be the issue. That there are so many objects being parsed and bound to the DOM that the $watchers are just taking a long time to go over everything.
EDIT 2
I made this demo, perhaps I am using resource wrong? http://plnkr.co/edit/QOickL0Dyi8jmuvG9mzN
But the items are replaced every 5 seconds, on replace they all disappear and then reappear. This is the issue I am having.
This is a common problem when using ng-repeat. In order to track the objects the directive itself by default adds additional property to every object in the array ($$hashkey). And whenever new object is added or removed from the array the same is happening for it's associating DOM element. When the array is replaced with new array (ex. returned from the server) as you mentioned all the DOM elements representing objects from the previous array are removed and replaced with new DOM elements for the new objects. This is simply because the new objects in the array does not have the $$hashkey property even though they may be semantically the same.
A solution for this common problem came in Angular 1.2 version with the track by clause. You can find more detailed explanation about it in this post.
In your case the objects in the array have an 'id' property which it's a good candidate for tracking them if you can guarantee it's uniqueness. So using ng-repeat like this will improve it's performance.
<div ng-repeat="item in items track by item.id">
<!-- tpl -->
</div>
If you'll take a look at $resource documentation, you'll see that the correct way of manipulation with data - to use callback function
Instead of
$scope.items = Resource.query();
Try
Resource.query(function(data) {
$scope.items = data;
});
Did you try ngCloak directive? https://docs.angularjs.org/api/ng/directive/ngCloak
I really dislike how angular-ui is documented. Sometimes they really don't explain a lot. This is the documentation to sortable-ui: https://github.com/angular-ui/ui-sortable
First, I cannot pass in options.
$scope.sortableOptions = {
cursor:"move"
};
I also changed "move" to "pointer" or "crosshair". Nothing happens.
Second, I need to update the backend by the new order of which the user has sorted. I am not a great javascripter at all (more of a back end developer). The only order-related js function I can find is indexof(). Then it gets very complicated because I need to iterate through all elements and find the new order since the user has rearranged all the elements.
Is there an easier way to get the current order of the list whenever the sortable directive is updated?
I created a demo on plunker (since it allows me to add extra libraries)
http://plnkr.co/edit/uNErHgKL3ohNyFhgFpag?p=preview
Again the cursor part is not working, and I have no idea how to get the order of these items.
I see there are methods on the Sortable UI page...I'm new to angularJS. I just couldn't figure out how to call these methods within AngularJS code.
Seralize method/toArray might not be a good idea..The actual data I'm dealing with does not look like ["one", "two", "three"]. It's more like:
[{"id":"5","article_name":"New Article
Title","article_order":"1","article_author":"Author","article_body":"Start typing your
article here!","is_visible":"1","created_date":"2013-10-27
05:37:38","edit_date":null,"magazineID":"7"},
{"id":"13","article_name":"New Article
Title","article_order":"2","article_author":"Author","article_body":"Start typing your
article here!","is_visible":"1","created_date":"2013-10-27
05:45:10","edit_date":null,"magazineID":"7"}]
If you guys look into this data stream..there is one attribute called article_order. This is the attribute (database column) I am trying to modify...
Read the jQuery UI Sortable docs. There are lots of events you can bind to and methods for serializing the sorted elements. Within the event callbacks you want to use you can make ajax calls to server with updated data
This angular module is simply a wrapper for jQuery UI Sortable.
Create a demo in jsfiddle or plunker that shows the problems you are having
If you use my new sortable Angular.js directive, you would do it like this:
$scope.items = [{"id":"5","article_name":"New Article
Title","article_order":"1","article_author":"Author","article_body":"Start typing
your article here!","is_visible":"1","created_date":"2013-10-27
05:37:38","edit_date":null,"magazineID":"7"},
{"id":"13","article_name":"New Article
Title","article_order":"2","article_author":"Author","article_body":"Start typing
your article here!","is_visible":"1","created_date":"2013-10-27
05:45:10","edit_date":null,"magazineID":"7"}];
$scope.onChange(fromIdx, toIdx) {
$scope.items[fromIdx].article_order = toIdx;
$scope.items[toIdx].article_order = fromIdx;
// OR
// var temp = $scope.items[fromIdx].article_order;
// $scope.items[fromIdx].article_order = $scope.items[toIdx].article_order;
// $scope.items[toIdx].article_order = temp;
}
HTML:
<ul ng-sortable="items"
ng-sortable-on-change="onChange">
<li ng-repeat="item in items" class="sortable-element" ng-style="{backgroundColor: item.color}">
{{item.name}}, {{item.profession}}
</li>
</ul>
See demo + documentation here:
https://github.com/schartier/angular-sortable
https://github.com/schartier/angular-sortable-demo
I guess I got into an issue similar to you. If we subscribe the update callback we don't get the latest order of items. But using the stop event handler helped me. While using angular ui sortable, we get the model updated with the latest order in the stop event handler. You can post this data to backend or wherever you want to store..Hope this helps...:)
You can refer the jquery ui sortable documentation for stop here
http://api.jqueryui.com/sortable/#event-stop
I have an array of items bound to <li> elements in a <ul> with AngularJS. I want to be able to click "remove item" next to each of them and have the item removed.
This answer on StackOverflow allows us to do exactly that, but because the name of the array which the elements are being deleted from is hardcoded it is not usable across lists.
You can see an example here on JSfiddle set up, if you try clicking "remove" next to a Game, then the student is removed, not the game.
Passing this back from the button gives me access to the Angular $scope at that point, but I don't know how to cleanly remove that item from the parent array.
I could have the button defined with ng-click="remove('games',this)" and have the function look like this:
$scope.remove = function (arrayName, scope) {
scope.$parent[arrayName].splice(scope.$index,1);
}
(Like this JSFiddle) but naming the parent array while I'm inside it seems like a very good way to break functionality when I edit my code in a year.
Any ideas?
I did not get why you were trying to pass this .. You almost never need to deal with this in angular. ( And I think that is one of its strengths! ).
Here is a fiddle that solves the problem in a slightly different way.
http://jsfiddle.net/WJ226/5/
The controller is now simplified to
function VariousThingsCtrl($scope) {
$scope.students = students;
$scope.games = games;
$scope.remove = function (arrayName,$index) {
$scope[arrayName].splice($index,1);
}
}
Instead of passing the whole scope, why not just pass the $index ? Since you are already in the scope where the arrays are located, it should be pretty easy from then.
When I try to append my backbone view to two different places at the same time in my success method only the second appending works. Do you know why?
$(content).prepend(this.$el.append(this.template({ data: data })));
$(chat_window).prepend(this.$el.append(this.template({ data: data })));
Each DOM node can have exactly 0 or 1 parent nodes, never more than 1. If you append a node somewhere, it gets removed from it's current parent and then appended to the new parent. What you need here is 2 distinct view instances each with it's own element.
el corresponds to one html element that a backbone view generates. Into that html element you can append more html weather it be another backbone view or a rendered template.
Hence in your case if the el is attached twice it finally stays where it was attached last to the dom tree. If you want attach in multiple places then I guess you should instantiate the backboneview twice.
In my opinion, the view you're talking about should not know about its distant parents or cousins but rather should trigger an event "I have new content" and then the interested views can act upon this the way they want.
That being said there is a difference between a view and its html representation(s), you could design your app so that you get 2 places in the html where you put ".new-content-holder" and pass this selector as the el of your view upon creation. Then the 2 places will be updated at the same time without you explicitly programming it. I sometimes use this technique for example when I want a paginator for a long list to be displayed over and under the list.
Some html :
<div class="content">
<p>Recent comments</>
<ul class="new-content-holder"></ul>
</div>
<div class="chat-room">
Live feed
<ul class="new-content-holder">
<li>a chat message</li>
<li>another chat message</li>
</ul>
</div>
And a view
....
var MessageView = Backbone.View.extend({
template: _.template('<li class="chat-message"><%= message %></li>'),
prependData: function(data){
this.$el.prepend(this.template(data))
},
onMessage: function(message) {
this.prependData({message: message.data})
}
});
....
//And in a super controller of sorts :
var messageView = new MessageView(el: '.new-message-holder')
Again, this is not a very good separation of concerns...but I hope that helps.
I agree with #Peter Lyons, You can not inject the same node into two elements. Ultimately, the node will move to new element. One of the ways is to get HTML from the element you want to inject and inject the same HTML twice. Since html is a string and not a dom element. You can add it as many times and inside as many elements.
Try this one:
var html = this.$el.append(this.template({ data: data })).html();
$(content).prepend(html);
$(chat_window).prepend(html);
I hope you are not using id's on elements inside your template.
PS: I don't know your use case exactly.