ng-repeat gives me only one result - javascript

I'm using AngularJs to read the iTunes API. I want to get all the results in <entry> but so far I only get the first result:
<div class="container" ng-controller="appController">
<div class="item" ng-repeat="chart in charts">
<h2>{{chart.entry[$index]['im:name'].label}}</h2>
</div>
The result is im:name from entry[0]; when I replace $index with a number then I get the result as well, so clearly everything is loaded just fine.
Controller:
app.controller('appController', ['$scope', 'charts', function($scope, charts) {
charts.success(function(data) {
$scope.charts = data;
});
}]);
Loading JSON file:
app.factory('charts', ['$http', function($http) {
return $http.get('https://itunes.apple.com/us/rss/topsongs/limit=100/json')
.success(function(data) {
return data;
})
.error(function(data) {
return data;
});
}]);

I think you want to repeat over charts.feed.entry instead.
<div class="item" ng-repeat="entry in charts.feed.entry">
<h2>{{entry['im:name'].label}}</h2>
</div>
Edit: An explanation as to why this is charts.feed.entry and not charts.entry. From the question:
<div class="item" ng-repeat="chart in charts">
This looped through charts, which because it was an object and not an array, meant looping through its properties. There was only one, so there was one iteration, which assigned the single property, feed, to chart. Therefore:
chart.entry[$index]['im:name'].label
was equivalent to:
charts.feed.entry[$index]['im:name'].label

Based on your JSON structure, you're trying to loop over charts but it's not an array, it's just an object with an array called entry that has all the entries. So you should change your
<div class="item" ng-repeat="chart in charts">
<h2>{{chart.entry[$index]['im:name'].label}}</h2>
</div>
to
<div class="item" ng-repeat="entry in charts.feed.entry">
<h2>{{entry ['im:name'].label}}</h2>
</div>

Try changing it to
<div class="container" ng-controller="appController">
<div class="item" ng-repeat="chart in charts">
<div class="item" ng-repeat="entry in chart.entry">
<h2>{{entry['im:name'].label}}</h2>
</div>
</div>

Related

Show HTML elements in JSON with Angular js

I am new in angular js, I am trying to format a json data with angular js.
Here is my json data
[
{"field_add_link": "Home"},
{"field_add_link": "About Us"}
]
here is my conroller
var myApp = angular.module('myApp', ['ngSanitize']);
myApp.controller('myController',function($scope, $http) {
$http.get('http://localhost/drupal3/menu')
.then(function(response) {
$scope.links = response;
});
});
and finally here is how i am fetching the json data with angular
<div class="col-md-8 content" ng-controller ="myController">
<div class="col-md-12" ng-repeat="link in links" ng-bind-html="links">
<p>{{ link.field_add_link }}</p>
</div>
</div>
no output was shown after this, giving an error Error: [$sce:unsafe] Attempting to use an unsafe value in a safe context. in the browser's console
but when I used ng-bind-html="links[0].field_add_link instead of ng-bind-html="links" and <p>{{ link[0].field_add_link }}</p> instead of <p>{{ link.field_add_link }}</p>
then i get Home as output 'without interpreting the tag'
Pls, how do I go about this?
First of all you dont need the ng-bind-html attribute on your div with ng-repeat. It should be on your p tag. Second, you need to bind link (the current element of the ng-repeat-loop) not links (the array). Although you need to look into the error [$sce:unsafe]. HTMl can only be bind to an element-body if it is trusted. For that you need to have, for example, a filter.
myApp.filter ('to_trusted', ['$sce', function ($sce) {
return function (text) {
return $sce.trustAsHtml (text);
};
}]);
<div class="col-md-8 content" ng-controller ="myController">
<div class="col-md-12" ng-repeat="link in links">
<p ng-bind-html="link.field_add_link | to_trusted"></p>
</div>
</div>
You just need to move your ng-bind-html from div to <p> like following code.
<p ng-bind-html="link.field_add_link"></p>
var myApp = angular.module('myApp', ['ngSanitize']);
myApp.controller('myController', function($scope, $http) {
var data = [{"field_add_link": "Home"},
{"field_add_link": "About Us"}];
$scope.links = data;
//For demo, removed the http call
});
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.5/angular.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.5/angular-sanitize.js"></script>
</head>
<body ng-app="myApp">
<div class="col-md-8 content" ng-controller="myController">
<div class="col-md-12" ng-repeat="link in links">
<p ng-bind-html="link.field_add_link"></p>
</div>
</div>
</body>

How to call multiple angularjs service calls from within nested ng-repeat

I am making a simple sports goods shopping app in AngularJs.
I am in a situation where I have three nested ng-repeats.
First loop: Get the brand name. I have written angularjs service that calls the rest endpoint to fetch the lists of brands (Adidas, Yonex, Stiga, etc). I am calling this service as soon as the page(controller) gets loaded.
Second loop: For each brand, I want to display the category of products they are offering. Inside this loop, I want to execute a function/service that will take the brand name as input and get all the categories for the brand. For this, I also have an angularjs service that calls the rest endpoint to fetch the list of categories for a given brand name.
Third loop: For each brand and category, I want to display the products in that category. Inside this loop, I want to execute a function that will take the brand name and category as input and get all the products in that category. I an angularjs service call which will call the rest endpoint to fetch the products given the brand name and category.
Sample data set:
Adidas
-----T-Shirts
----------V-Neck
----------RoundNeck
-----Shoes
----------Sports Shoes
----------LifeStyle Shoes
Yonex
-----Badminton Racquet
----------Cabonex
----------Nanospeed
-----Shuttlecocks
----------Plastic
----------Feather
Stiga
-----Paddle
----------Procarbon
----------Semi-carbon
-----Ping Pong Balls
----------Light Weight
----------Heavy Weight
Please note that because of some constraints I cannot have a domain object on the REST side to mimic the data structure shown above.
I want to display the above data in a tree-like fashion (something on the same lines as shown above possibly with expand/collapse options).
Below are the code snippets.
CONTROLLER:
(function () {
'use strict';
angular.module('SportsShoppingApp.controllers').controller('sportsController', ['sportsService', '$scope', function (sportsService, $scope) {
$scope.brands = [];
$scope.categories = [];
$scope.products = {};
$scope.getBrands = function () {
sportsService.getBrands()
.then(loadBrands, serviceError);
};
var loadBrands = function(response) {
$scope.brands= response.data;
};
$scope.getCategories = function(brand) {
sportsService.getCategories(brand)
.then(loadCategories, serviceError);
};
var loadCategories = function (response) {
$scope.categories = response.data;
};
$scope.getProducts = function(brand, category) {
sportsService.getProducts(brand, category)
.then(loadProducts, serviceError);
};
var loadProducts = function (response) {
$scope.products = response.data;
};
var serviceError = function (errorMsg) {
console.log(errorMsg);
};
$scope.getBrands();
}]);
}());
HTML:
<div class="well">
<div class="row">
<div id="sportsHeader" class="col-md-3">
<div ng-repeat="brand in brands.data">
<div class="row">
<div class="col-md-9">{{brand}}</div>
</div>
<div ng-repeat="category in categories.data" ng-init="getCategories(brand)">
<div class="row">
<div class="col-md-9">{{category}}</div>
</div>
<div ng-repeat="product in products.data" ng-init="getProducts(brand, category)">
<div class="row">
<div class="col-md-9">{{product}}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
When I use the above HTML, only the brand names are displayed on the UI. The categories and their corresponding products are not displayed. I know that there is some overlapping that is happening. I am not sure if I am doing it the right way. I might be completely wrong with my approach. I am new to AngularJS. I want to know how to loop in nested ng-repeat so that each ng-repeat could call an angularjs service and also I want to display the data in the tree fashion as shown above. Can someone help me here?
I think that the ng-inits have to be placed on separate tags to the ng-repeats:
<div class="well">
<div class="row">
<div id="sportsHeader" class="col-md-3">
<div ng-repeat="brand in brands.data">
<div class="row">
<div class="col-md-9">{{brand}}</div>
</div>
<div ng-init="getCategories(brand)">
<div ng-repeat="category in categories.data">
<div class="row">
<div class="col-md-9">{{category}}</div>
</div>
<div ng-init="getProducts(brand, category)">
<div ng-repeat="product in products.data">
<div class="row">
<div class="col-md-9">{{product}}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
You might have to juggle your bootstrap classes around also, moving ng-init is only to fix the angular part.
Move the ng-init directives outside of the ng-repeat to which they provide data.
<div class="well">
<div class="row">
<div id="sportsHeader" class="col-md-3">
<!-- MOVE init of categories here -->
<div ng-repeat="brand in brands.data" ng-init="getCategories(brand)">
<div class="row">
<div class="col-md-9">{{brand}}</div>
</div>
<!-- MOVE init of products here -->
<div ng-repeat="category in categories.data" ng-init="getProducts(brand, category)">
<div class="row">
<div class="col-md-9">{{category}}</div>
</div>
<div ng-repeat="product in products.data">
<div class="row">
<div class="col-md-9">{{product}}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
The ng-init directive has a priority of 450; the ng-repeat, priority 1000. This means that when they are on the same element ng-init executes after the ng-repeat directive. The ng-repeat for categories.data won't execute its ng-init until it has a category. Thus its ng-init can't be used to populate the categories array.
Quick question. Is my approach correct ?
The approach works but it violates the Zen of Angular and the principles of an MV* Model View Whatever framework.
The model is the Single Source of Truth
Because the view is just a projection of the model, the controller is completely separated from the view and unaware of it. This makes testing a snap because it is easy to test your controller in isolation without the view and the related DOM/browser dependency.
--AngularJS Developer Guide -- Data-Binding
Having the ng-repeat and ng-init directives build the model creates a dependency that makes testing and debugging difficult. (As witnessed by this question.)
My advice is to learn how to build the model by chaining promises and using $q.all.

Directive take other directive's data after deletion

Edit: Thanks to Simon Schüpbach, I was able to resolve the issue by changing the template. See the end for the solution.
Let's preface this by saying that we are beginner to soft-intermediate in Angular.
On one of our project, we are using angularjs 1.4.x and also ng-cart (https://github.com/snapjay/ngCart). It worked great but then we were confronted with a demand from our client that created new weird issues.
We added fsCounter, as a directive, to the cart page so user can add or remove items. This all work great but the users also have the option to delete an item from the cart view. Deletion works as expected BUT it seems to affect the scope to the item that takes it place.
Let me make it clearer :
Let's say we have 2 products in our cart page, it displays something like that
Product_1 {price} {counter} {total} delete_btn
Product_2 {price} {counter} {total} delete_btn
Each fsCounter is its own scope
return {
restrict: "A",
scope: {
value: "=value",
index: "=index"
},
link: //other logic
However when we delete the first item, visually and in the directives, the data seems to shift. So our second row will now inherit the first row's counter.
Directive's data looks like this:
Product_1:
itemId:3,
quantity:2,
{other data}
Product_2:
itemId:8,
quantity:5,
{other data}
But once we delete the first directive (We get the scope, remove the DOM element, destroy the scope) the second directive will now have this data:
Product_2:
itemId:3,
quantity:2,
{other data}
Here is the template code :
<div class="unItem" ng-repeat="item in ngCart.getCart().items track by $index">
<div class="photo"><img src="{[{ item.getImage() }]}" alt=""></div>
<div class="details">
<h3>{[{ item.getName() }]} <span>{[{ item.getPrice() | currency:$}]}</span></h3>
<md-select ng-model="attributes" placeholder="Attribut" class="select-attribut" ng-show="item.hasAttributes()" ng-change="item.updateSelected(attributes)">
<md-option ng-repeat="attr in item.getAttributes()" ng-selected="attr == item.getSelected()" ng-value="attr">{[{ attr }]}</md-option>
</md-select>
</div>
<div class="quantity">
<div fs-counter-dynamic value="itemQuantity"
data-min="1"
data-max="999"
data-step="1"
data-addclass="add-quantity"
data-width="130px"
data-itemid="{[{ item.getId() }]}"
data-editable
ng-model="itemQuantity"
name="quantity" id="quantity-{[{ item.getId() }]}",
index="{[{ item.getId() }]}"
></div>
</div>
<div class="total">Total : {[{ item.getTotal() | currency }]}</div>
<div class="delete"><a ng-click="ngCart.removeItemById(item.getId());"></a></div>
</div>
Is this normal behavior? Is there any way to force the directive to keeps its own data? From what I've understood, each directive has its own scope, so what I think happens is that, when we remove the first one, it keeps the data stored in some kind of array that says "directive 1 data is : " and when we delete the first directive, the second one becomes the first.
So basically, are we doing anything wrong or is there anyway to remap the data?
Hope it was clear enough,
Thanks!
Edit: added html code
Edit2: Answer :
New FsCounter template looks like this:
<div fs-counter-dynamic value="item._quantity"
data-min="1"
data-max="999"
data-step="1"
data-addclass="add-quantity"
data-width="130px"
data-itemid="{[{ item.getId() }]}"
data-editable
ng-model="item._quantity"
name="quantity" id="quantity{[{ item.getId() }]}"
></div>
Do you know ng-repeat, then you don't have such problems
<div ng-repeat="product in products">
<fs-counter index="product.index" value="product.value"></fs-counter>
</div>
and in your controller
$scope.products = [
{index:1, value:"Cola"},
{index:2,,value:"Fanta"}
]
to remove an element you just have to do
$scope.products.splice(0,1);
Edit:
I suggest to save all necessary data inside the item you use inside ng-repeat. Your problem is, that you mix data from array with other data from your $scope. It is possible to $watch changes in your directive, but if you set them with ng-repeat everything is done automatically.
$scope.products = [
{index:1, name:"Cola", price:1.50, image:"your/path.png", attributes:{...}},
{index:2, name:"Fanta", price:1.40, image:"your/path.png"}
]
And then in your html
<div class="unItem" ng-repeat="item in ngCart.products track by $index">
<div class="photo"><img ng-src="item.image" alt=""></div>
<div class="details">
<h3>{{item.name}} <span>{{item.price | currency}}</span></h3>
</div>
<div class="quantity">
<div fs-counter-dynamic value="item.quantity"
data-min="1"
data-max="999"
data-step="1"
data-addclass="add-quantity"
data-width="130px"
data-itemid="item.index"
data-editable
ng-model="item.quantity"
name="quantity" id="{{'quantity-' + $index}}",
index="item.index"
></div>
</div>
<div class="total">Total : {{ item.price * item.quantity | currency }}</div>
<div class="delete"><a ng-click="ngCart.removeItemById(item.index);"></a></div>
</div>

Angular - Error: ngRepeat:dupes Duplicate Key in Repeater

I am working on a simple todo app that adds task to an existing array of json data. However when I try to add more than one task I get this error: Error: [ngRepeat:dupes]. I can't for the life of me figure out what is going wrong. I suspect it might have something to do with the namespace, but after changing names around a few times I still get the same error. If anybody could point me in the right direction I would much appreciate it.
The HTML Code
<div id="taskComplete" ng-app="taskComplete">
<div class="container" ng-controller="taskCtrl">
<div id="taskCompleteHeading" class="row">
<div class="col-xs-12">
<div class="page-header">
<h1 class="text-center">TaskComplete <small>An AgularJs App</small></h1>
</div>
</div>
</div>
<div id="newTaskSubmit">
<input type="text" ng-model="newTask.title">
<input type="text" ng-model="newTask.description">
<button type="button" ng-click="addTask(newTask)">Add Task</button>
</div>
<div class="well">
<pre>{{newTask | json}}</pre>
</div>
<div ng-repeat="task in activeTasks">
<h4>{{task.title}}</h4>
</div>
</div>
</div>
UPDATED SOLUTION
<div ng-repeat="task in activeTasks track by $index">
<h4>{{task.title}}</h4>
</div>
The JAVASCRIPT Code
angular
.module('taskComplete')
.controller('taskCtrl', function($scope, taskFactory) {
$scope.activeTasks;
taskFactory.getTasks().success(function(data) {
$scope.activeTasks = data;
console.log($scope.activeTasks);
}).error(function(error) {
console.log(error);
});
$scope.newTask = {};
$scope.addTask = function(newTask) {
$scope.activeTasks.push(newTask);
}
});
Use this
<div ng-repeat="task in activeTasks track by $index">
<h4>{{task.title}}</h4>
</div>
So angular will track your ng-repeat node
Use track by $index:
ng-repeat="task in activeTasks track by $index"
$scope.addTask = function(newTask) {
$scope.activeTasks.push(newTask);
}
Should become
$scope.addTask = function(newTask) {
$scope.activeTasks.push(newTask);
$scope.newTask = {};
}
The error was caused by the same object being used twice. It is also why changes below were being mirrored by the added task.

Scope variable not updating in view despite $scope.$digest

.controller('PizzaCtrl', ['$scope','$state','$ionicLoading',
function($scope, $state, $ionicLoading) {
$scope.$emit('menu-refresh-request');
$scope.$on('menu-refresh-response', function(event) {
console.log("pizza");
$scope.$broadcast('scroll.refreshComplete');
$scope.items = $scope.$parent.menu.pizze;
console.log($scope.items[1].price);
$ionicLoading.hide();
});
$scope.doRefresh = function() {
$scope.$emit('menu-refresh-request');
};
}])
The data all checks out. The correct item information is logged to the console. However, the ng-repeat="item in items" directive in my view does not update with the pizza items.
I tried using $scope.$apply and $scope.$digest inside the event listener, but the console threw an error saying the digest was already in progress.
Also worth noting that this controller has two sibling controllers that have identical logic to this one, except for different sections of the menu. The console.log("pizza") statement isn't executed until I click into the state.
Is there a clear reason why my view is not updating?
<ion-refresher pulling-text="Updating Menu..." on-refresh="doRefresh()">
<div class="list menu-list">
<a class="item menu-item" ng-repeat="item in items" ui-sref="menu.pizza-detail({ index: $index })">
<div class="row">
<h3 class="row" ng-bind="item.name"></h3>
<div class="row">
<div class="list-price col col-15">
<h4 class="list-value" ng-bind="item.price"></h4>
</div>
<div class="list-description col col-85">
<p ng-bind="item.description"></p>
</div>
</div>
</div>
</a>
</div>
Instead of using $scope.$apply try to use $timeout angular service.
The $timeout does not generate error like „$digest already in progress“ because $timeout tells Angular that after the current cycle, there is a timeout waiting and this way it ensures that there will not any collisions between digest cycles and thus output of $timeout will execute on a new $digest cycle.
.controller('PizzaCtrl', ['$scope','$state','$ionicLoading', '$timeout'
function($scope, $state, $ionicLoading, $timeout) {
$scope.$emit('menu-refresh-request');
$scope.$on('menu-refresh-response', function(event) {
console.log("pizza");
$scope.$broadcast('scroll.refreshComplete');
$timeout(function(){
$scope.items = $scope.$parent.menu.pizze;
});
console.log($scope.items[1].price);
$ionicLoading.hide();
});
$scope.doRefresh = function() {
$scope.$emit('menu-refresh-request');
};
}])
Turns out the solution to this problem is that I needed to add a missing closing tag to the <ion-refresher> tag.
<ion-refresher pulling-text="Updating Menu..." on-refresh="doRefresh()"></ion-refresher>
<div class="list menu-list">
<a class="item menu-item" ng-repeat="item in items" ui-sref="menu.pizza-detail({ index: $index })">
<div class="row">
<h3 class="row" ng-bind="item.name"></h3>
<div class="row">
<div class="list-price col col-15">
<h4 class="list-value" ng-bind="item.price"></h4>
</div>
<div class="list-description col col-85">
<p ng-bind="item.description"></p>
</div>
</div>
</div>
</a>
</div>

Categories