Maximum call stack size exceeded in angularJS, when I try use | filter:searchText - angularjs

I try to filter data using |filter in the ng-repeat directive.
ng-repeat="item in transactions |filter:searchText"
transactions is array with data, which looks like this:
{
$$hashKey: "object:666",
amount: -50,
card: "3158",
catId: 0,
dateTime: {month: 2, value: "2015-02-23T14:00:00"}
details: "blabla",
id: 2830,
}
searchText - is text which inputted by user.
It has to filter data when user inputs text, but it doesn't work because it throws exception(Maximum call stack size exceeded). It normally works if definitely to write what kind of fields you want to use to filter data.
ng-repeat="item in transactions |filter:{amount:searchText}"

I had this same problem with a very simple filter, but I was filtering on an array of objects that had a circular reference. I wasn't making any function calls on the filter - it was just the built-in filter on a simple string.
I had two lists of objects with a many-to-many relationship, and once loading them from a $resouce I had set them each up with a property that was an array of all the objects in the other list with which they had a relationship. I had a list of jobs that tracked its own list of tasks, and a list of tasks with its own sublist of jobs.
for (let job of service.jobs)
{
job.tasks = service.tasks.filter(t => t.jobid == job.id);
}
for (let task of service.tasks)
{
task.jobs = service.jobs.filter(j => j.taskid == task.id);
}
(The relationship was a little more complex that this, but this was the heart of it).
I was trying to save cycles by associating them up front just once rather than every digest through the page, but it created a circular loop that only caused a problem once I tried to filter them. I probably could have limited it to filter on the non-"task" fields, but I actually do want to look through them as well. I changed it so that the view just calls the function to pull the related objects inline in the template.
<div ng-repeat="job in service.jobs | filter:jobFilterText"> <-- BOOM
<div ng-repeat="task in service.getTasksForJob(job.id)">
{{ task.name }}
</div>
</div>
The other option I considered was creating a shallow copy of the objects to put in their sub-arrays without the reference back to the original object types, but until I hit a performance problem with the inline binding I prefer its simplicity.

Related

What is best for performance with array?

I have a component that displays a list. From an observable, I can received added item or the new array. What is the best on performance perspective?
myArray=[];
$myArray.subsscribe(d=>this.myArray=d)
myArray=[];
$myArrayJustNewItem.subsscribe(d=>this.myArray.push(d))
<span *ngFor='item of myArray'>{{item}}</span>
What I understood, is whatever solution, the view will be re-render entirely? Or should I use index?
There are two places which may be used in a different way
You don't have to .subscribe() you can always use async pipe. In this case you avoid creating a new variable and direct subscription to an Observable, which obviously a better solution.
myArray$: Observable<any[]>; // or any other type
<div *ngFor="let item of (myArray$ | async)">{{ item }}</div>
If you want to add new item to an existing array, which renders inside *ngFor, you should do it like this: (actually create a new Array instance with the same or updated content).
Pushing a new item to an existing array, probably should be done on a service or even on a server side, so your component always gets a new array instance and won't need to manually add it, so re-render only happens when new data comes to Observable.
myArray: Array<any> = [1,2,3,4];
addItemToArray(item: number): void {
this.myArray = [...this.myArray, item];
}
<div *ngFor="let item of myArray">{{ item }}</div>
UPDATE: added descriptions and advices for a performance perspective

angularJS target specific json node

Im working with a webservice and the returning JSON brings back some meta data that I use to define the layout of forms.
However, the JSON is continually evolving by the developers so currently where I target:
ng-repeat="cat in metaData[1].AcceptedValues"
To draw all the form structure...
Means that at some stage metaData Array element 1 may no longer hold the structure for this current form, it may be 2, 3 etc. JSON developer said he has now added additional data to identify each of the nodes under the value NAME
Under array element 1 I can now see Name : "ProductData" - and similarly under the other nodes different unique identifiers for Name
So basically I need to know how I can adjust my condition above to search for the metaData array element that contains the value Name = "ProductData" (or the value for the form I am rendering) and then any changes in array position is irrelevant.
Thanks
You can create a method to find the specified object:
$scope.getProductData = function() {
angular.forEach($scopem.metaData, function(item){
if(item.Name === "ProductData"){
return item.AcceptedValues;
}
});
return [];
};
Then use it in your markup:
ng-repeat="cat in getProductData()"

How do I keep results filtered with new results added dynamically?

I'm using an infinite scroller package which loads results dynamically. I have a set of filters which display the results I want depending on what filter is selected.
The results will not be returned to the front end in any sort of filtered order. What I want to achieve is get the unfiltered results being returned into filtered state that I may already have selected.
For example one filter is a star rating and lets say that I have 3 and 4 star selected in my filter. How would I get newly loaded results to be returned within that filter.
I suppose I could send the filters back to the backend to specifically request what I'm looking for but I'm looking for an angular solution.
Here's the ng-repeat that iterates over each hotel I want any newly loaded results to be subjected to any filters already selected:
<div ng-repeat="hotel in (filteredHotels = (hotelResults | hotelRatingsFilter:ratings)) | startFrom:(currentPage - 1)*10 | limitTo:10 " class="hotel-results-container">
<ul>
<li>{{hotel.starRating}}</li>
</ul>
</div>
This is the code that communicates with the backend, as far as I can tell (I didn't write it)
.constant('HOTEL_SEARCH_CONSTANTS',{
httpSettings:
{
url:'/hotel/hotelsearch/search',
method:'POST'
}
})
.service('hotelSearchService', ['CachedObservable','$http','HOTEL_SEARCH_CONSTANTS','HOTEL_CONSTANTS','baseSearchSettings','loadingScreen',
function(CachedObservable,$http,HOTEL_SEARCH_CONSTANTS,HOTEL_CONSTANTS,baseSearchSettings,loadingScreen){
CachedObservable.call(this)
var _this = this,
p
this.search = function(searchParameters){
var config = HOTEL_SEARCH_CONSTANTS.httpSettings
var currentSearch = angular.extend(searchParameters,baseSearchSettings)
config.data = {
basicSearch: baseSearchSettings,
hotelSearch: searchParameters
}
_this.update(p=$http(config), angular.extend(angular.copy(searchParameters),baseSearchSettings) )
//Alert subscribers with both the promise and the search parameters
//used
loadingScreen.newSearch.update(p,HOTEL_CONSTANTS.loadingTemplates.loadingMessageTemplate)
//Trigger a loading screen and specify the template to use
}
}])
Thank you
I found this article that says that angular will iterate over the array if there is any change to the data. Looks like its safe to keep adding to the data array and any filters already being used will be applied by angular to the updated data array.
Step 6 says:
"A watch is added on the array, which triggers step 1 again if the array undergoes any change"
ng-repeat in angular

How to implement pagination in Angular when collection size is changing

I am trying to use angular-UI pagination for a table, where I am splicing the array where the page intersects the array. However I am not clear how to handle the fact that the array of objects is being added to and deleted to from within the table itself.
Also, I am sorting through a filter by the name property of an object in my ng-repeat. I expect what's is item[0] in my model array, does not correlate with the first item being displayed in the table via ng-repeat...I could be wrong.
I am just looking for advice on how to implement paging when the ng-repeat sorts the list, and the collection size is changing all the time.
Regards
I
you can try to use multiple filters at once for example:
ng-repeat="item in items | myFilter | limitTo:pageNumber*perPage | limitTo: -perPage"
This allow you to use your filter first on each collection/model change, then it show last "perPage" records dependig of "pageNumber".
Please see that demo : http://plnkr.co/edit/3nqRHUySsJZwpKQiMMTm?p=preview
just set watch to yourCollection.length
ie:
$scope.$watch('yourCollection.length', function(){
$scope.bigTotalItems = $scope.yourCollection.length;
}
)

How can I improve the efficiency of an ng-repeat that includes a filter and a reorder?

I have a grid on my page that is populated like this:
<tr data-ng-repeat="row in grid.view = (grid.data | filter:isProblemInRange | orderBy:option.problemOrderBy[option.sProblemOrderBy].key:option.sProblemSortDirection) track by row.problemId">
The grid can have up to 500 rows. Once the data is retrieved it's possible for a user to filter the rows which is where the isProblemInRange filter is used. It's also possible for the user to reorder the rows. As can be expected this all takes time.
Is there anything that I could do to make this more efficient?
Could I take all of this code out of my ng-repeat and put in the part that creates the grid.view in my controller?
Note that the isProblemInRange is dynamic. As a user types into a select box then after debouncing the numbers entered are used in the filter to restrict the rows that appear on the screen.
One more thing. The data in the grid changes only when the user clicks on a row and it opens a modal. After a user clicks save on the modal then one single row of the grid.data is changed. Is there something I could do to stop ng-repeat from watching every field on every row and have it just respond after my modal has done a save which is the only time the grid data numbers change.
Every filter is callable fom JavaScript code directly. See http://docs.angularjs.org/api/ng.filter:filter for an example of the usage of the "filter" filter.
So you could indeed call these filters directly from your controller each time the data or the user-defined order and criteria change, which would avoid the unnecessary watches.
var filteredData = $filter('filter')($scope.grid.data, $scope.isProblemInRange);
var orderedFilteredData = $filter('orderBy')(filteredData,
$scope.option.problemOrderBy[option.sProblemOrderBy].key,
$scope.option.sProblemSortDirection);
$scope.orderedFilteredData = orderedFilteredData;
And then in your view, you just need to iterate on the already filtered and ordered data:
<tr data-ng-repeat="row in orderedFilteredData">

Resources