Parsing JSON inside ng-repeat - angularjs

Is it possible to parse JSON inside an ng-repeat?
I tried both JSON.parse and angular.fromJson.
<div ng-repeat='x in JSON.parse(element.info)'>
{{x.key}}
</div>
It can be checked here: http://jsfiddle.net/y4qje54o/1/

AngularJS handles all ng-repeat with a $watch module nested and hard coded inside the core. If you try to write an uncorrect ng-repeat it will cause a log error spam, this is due to the "reloading" of the ng-repeat loop.
If you put a JSON parse inside a loop it will cause a huge performance breakdown. From controller you will do it once.
Notice that if the view were generating the data once, then performance will be equal. But you will lose MVC pattern. Take care about it.

Why you don't want to do like this ?
var ngApp = angular.module('ngApp',[]);
var ngCtrl = ngApp.controller('ngCtrl',['$scope', function($scope){
$scope.elements = [];
$scope.element = {};
$scope.element.title = 'first element';
//this comes from DB like this(as string)
$scope.element.info = JSON.parse('[{"key":"value1"},{"key":"value2"}]');
$scope.elements.push($scope.element);
}]);

Check it here: https://jsfiddle.net/y4qje54o/4/
Same results, but I did it using split, slice & indexOf instead of JSON.parse.
Code:
<div ng-repeat="x in temp=angular.equals(temp,element.info.slice(1, -1).split(','))?temp:element.info.slice(1,-1).split(',')">
{{x.slice(x.indexOf(':')+2, -2)}}
</div>

Related

Using ng-repeat with the Pokeapi

I'm trying to get the data for two random pokémon from the Pokéapi. I can grab the data fine, but my angular $scope variables are overwriting the first pokémon with the second pokémon, instead of storing both:
$http.get(endPoint)
.then(function(res){
$scope.pokemonName = res.data.name; // only one 'pokemonName' in $scope
});
You can visually see the pokémon being loaded and displayed incorrectly (two identical pokémon instead of two different ones) in this Plunker example.
I could manually declare two separate $scope variables, eg:
$scope.pokemonNameOne;
$scope.pokemonNameTwo;
But I want to use ng-repeat in the HTML markup, like this:
<div class='pokemon' ng-repeat="pokemon in pokemonPair">
<h2 class='name'>{{ pokemonName }}</h2>
<img src='{{ pokemonImage }}' />
<!-- etc -->
</div>
I feel like what I should be doing, is looping through the required data from the API, then pushing it into a new array of my own creation, and then using my new array in combination with ng-repeat in order to display the two random pokémon. However, I'm unsure as to whether this is an overly convoluted solution. Should I really be re-creating the API data in my own array? It feels like that would be reinventing the wheel. However, I'm not sure how else to solve this problem besides 'Don't use ng-repeat', which isn't a route I want to go down if I can help it, as I'm trying to learn Angular.
Should I be pushing the API data into an array of my own making in order to display it correctly, or is there a smarter way to use ng-repeat?
You are replacing the existing variables, instead of pushing it to an array. Do something like this:
function getStats(pokemonIndex){
var pokemon = $scope.pokemonPair[pokemonIndex];
$http.get(pokemon.endPoint)
.then(function(res){
pokemon.pokemonName = res.data.name;
pokemon.pokemonExperience = res.data.base_experience;
pokemon.pokemonImage = res.data.sprites.front_default;
pokemon.pokemonStats = res.data.stats;
console.log("pokemonName = " + pokemon.pokemonName);
});
}
and call it like this:
function populateStats(){
for (var i = 0; i < $scope.pokemonPair.length; i++){
getStats(i);
}
}
See this edited plunker
Also notice that I have changed the pokemonPair property to be an array of objects, to be able to add the properties to it and use the ng-repeat.
Here is a working plunker:
https://plnkr.co/edit/c5seK6wr8rQjMAj5GZA9?p=preview
Just push the response from the api into an array and databind everything from there!
function getStats(endPoint){
$http.get(endPoint)
.then(function(res){
$scope.pokemons.push(res.data);
console.log("pokemonName = " + $scope.pokemonName);
});
}
You have to read about Arrays in JavaScript. Your question doesn't actually related to Angular itself.
$scope.pokemonNames = [];
$http.get(endPoint)
.then(function(res){
var pokemon = {};
pokemon.name = res.data.name;
pokemon.attribute = res.data.attribute;
$scope.pokemonNames.push(pokemon); // then iterate over pokemonNames array in ng-repeat.
});

Initializing an array at return of an ajax request

Trying to learn angular I got the following situation.
My application returns product objects from a complex and slow database using ASP.NET Web Api. Because of the slow speed i wanna page the objects returned from the server. I created a viewmodel containing an array of products, the current page and the last page of the products.
For displaying a paging widget I wanna fill my $scope object with an array of pagenumbers for use with the ng-repeat directive.
I tried filling an array using a function:
(in pseudo)
$scope.pages = function(){
var pageNumberArray = [];
pagecounter = 1;
while (pagecounter <= maxPage){
pageNumberArray.push(pageCounter);
pageCounter++;
}
};
with in my view an ng-repeat using this function:
<ul>
<li ng-repeat="pageNr in pages()">{{pageNr}}</li>
</ul>
It seems to work for displaying but my developer tools console shows errors which seem legit: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!
How can I initialize the $scope array (field) without calling a $scope function from the view.
p.s. the array should reinitialize when an ajax request returns a new page (not all page numbers are shown (5 beneath, 5 above the current page).
I think the two-way binding is disturbed by using a function for your ng-repeat.
I would say $scope.pages should be a variable in your scope. If you later update this variable, your view will be automatically updated by the grace of two-way binding.
So something like this:
in controller:
$scope.pages = []
for (var i; i < maxPage; i += 1) {
$scope.pages.push(i);
}
$http.get(...).success(data){
// update $scope.pages
}
In view:
<div ng-repeat="page in pages">{{page}}</div>

ngRepeat seems to use a random order instead of order given

I have a json containing objects originating from my Symfony controller.
I need to loop over the objects like so
<li data-ng-repeat="course in courses"></li>
In my app controller I do this
$scope.courses = {{ courses | serialize('json', serialization_context().setGroups(['identification', 'courseListing', 'portalOverview'])) | raw }};
The order is okay when I check the scope variable 'courses' with the ng-inspect browser plugin.
Screenshot : http://i.imgur.com/pKy00uo.png
But still the loop seems kinda random. The last object keeps getting placed after the 2nd child.
Any ideas? Need more info, just ask. Thanks!
Okay, so it seems Angular doesn't handle objects too well when given to ng-repeat.
I transformed my object of objects to an array of objects and the objects were rendered in the correct order.
var arrCourses = [];
$.each($scope.courses, function(index, obj){
arrCourses.push(obj);
});
$scope.courses = arrCourses;

$rootScope:infdig error when paginating filtered data

I'm using ng-paginator-plz to paginate some data in my app. I also want to pass this data through some filters before displaying, so I'm doing:
<paginator class="paginator" data="filteredUsers = (users | filter:search)" page-size="20" export-paged-data-to="paginatedUsers"></paginator>
Then to display the data I ng-repeat through paginatedUsers. This works if I remove the filtering, but with the filter in place I get a [$rootScope:infdig] error.
Searching for this error lead me to answers like this: $rootScope:infdig error caused by filter? - which suggest the issue is because the filter is being evaluated multiple times. How can I fix this?
Edit: I've added the following to my controller JS file:
$scope.filter = function(){
$scope.filteredUsers = $filter('search')($scope.users);
};
$scope.$watch('search', function() {
$scope.filter();
});
I also added $filter to the top where depdendencies are passed in. But this gives me a $injector:unpr error when the page first loads and whenever the filter is used. If I change the filter line to $scope.filteredUsers = $scope.users; the error goes (but obviously it doesn't do any filtering), so I guess there's something wrong with my code there.
You can filter it manually in controller in the moments you think it's necessary to refilter data, and store filtered data in separate $scope variable.
With loDash for shortness.
$scope.filter = function(){
$scope.filteredUsers = _.filter(users, function(user){return user.name.indexOf($scope.search) !== -1;});
// or any kind of angular filters..
}
$scope.$watch('search', _.debounce($scope.filter, 100));
<paginator data="filteredUsers" ....></paginator>

AngularJS InfDig error (infinite loop) with ng-repeat function that returns array of objects

Here's my code:
<h1 ng-repeat="item in func()">something</h1>
$scope.func = function(){
return [{"property" : "value1"},{"property": "value2"}];
}
In Angular.js v. 1.1.1 there's no mistake. In Angular.JS v 1.2.1 I get an infDig mistake.
Fiddle of v.1.1.1
Fiddle of v.1.2.1
Could you explain this situation? Thanks a lot.
As of AngularJS 1.2: The "track by" expression was added to ng-repeat and more appropriately addresses this issue as demonstrated
in the following code.
<h1 ng-repeat="item in func() track by $index">something</h1>
$scope.func = function(){
return [{"property" : "value1"},{"property": "value2"}];
}
The following article helps understand the expression in more detail and why it is so useful, particularly when dealing with $$haskey Using Track-By With ngRepeat In AngularJS 1.2 by Ben Nadal.
The problem is that you're creating a new array each time, so it's something new that angular needs to track. As far as I can tell, ng-repeat runs, then immediately checks its collection again to see if anything changed in that cycle. Because the function returns a new array, that is perceived as a change.
Check this out: http://jsfiddle.net/kL5YZ/.
If you look in the console.log and click the button, you will see that the $$hashKey property of the objects is being changed each time ng-repeat runs.
The change occurs starting at version 1.1.4, but the changelog doesn't give any clues as to why the behavior is different. The new behavior does make more sense to me.
Here's a great post I found explaining the current behavior in depth: How to Loop through items returned by a function with ng-repeat?
If you make sure to return the same object/array each time, you won't have the error. You could have the function cache anything it creates based on the arguments and always return the same array/object when those arguments are passed in. So, myFunc('foo') will always return the same array, not a new one that looks the same. See the notes in my code below. Live demo (click).
<div ng-repeat="foo in foos">
<div ng-repeat="bar in barFunc(foo)">{{bar.text}}</div>
<div ng-repeat="bar in barFunc('test')">{{bar.text}}</div>
</div>
JavaScript:
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope, myService) {
$scope.foos = [
'a','b','c'
];
//I put this into a service to avoid cluttering the controller
$scope.barFunc = myService.getObj;
});
app.factory('myService', function() {
/*
* anything created will be stored in this cache object,
* based on the arguments passed to `getObj`.
* If you need multiple arguments, you could form them into a string,
* and use that as the cache key
* since there's only one argument here, I'll just use that
*/
var cache = {};
var myService = {
getObj : function(val) {
//if we haven't created an array with this argument before
if (!cache[val]) {
//create one and store it in the cache with that argument as the key
cache[val] = [
{text:val}
];
}
//return the cached array
return cache[val];
}
};
return myService;
});

Resources