Changes in $http data from 1 controller to affect another in AngularJS? - angularjs

So if my data is coming from a json $http, how do i make it so that if i click vote on one controller, it will sort the order by votes on another controller?
What I have so far isnt working correctly and it seems like the shared data does catches any changes between controller... take it look
app.controller('voteController', function ($scope, musicData) {
musicData.getMusic().then(function(data){
$scope.music = data.data;
});
$scope.upVote = function(music) {
music.likes++;
};
$scope.downVote = function(music) {
music.dislikes--;
};
});
// controller for top 5 charts
app.controller('topFiveController', function ($scope, musicData) {
musicData.getMusic().then(function(data){
$scope.music = data.data;
});
$scope.sortorder = '-likes';
});
// factory to share data between controllers
app.factory('musicData', function($http) {
var factory = {};
factory.getMusic = function() {
return $http.get('app/data/music.json');
};
return factory;
});
and this is what I have in the mark up
<div id="topMusic" ng-controller="topFiveController" ng-model="sortorder">
<h3 class="title"><i class="fa fa-hand-o-right"></i> Top 5 titles</h3>
<ul>
<li ng-repeat="music in music.music | orderBy:sortorder | limitTo: 5">
{{music.artist}} - "{{music.title}}"
</li>
</ul>
</div>
<div id="voteMusic" ng-controller="voteController">
<h1>Vote here</h1>
<ul>
<li class="likeMe" ng-click="upVote(music)">
<i class= "fa fa-thumbs-up" ></i> like - {{music.likes}}</li>
<li class="dislikeMe" ng-click="downVote(musics)">
<i class="fa fa-thumbs-down"></i>dislikes -
{{music.dislikes}}</li>
</ul>
</div>
this is how the json file looks like
{"music": [
{
"artist": "Artst 1",
"title": "title one",
"likes": 10,
"dislikes": 1
},
{
"artist": "Artst 2",
"title": "title 2",
"likes": 5,
"dislikes": 1
},
{
"artist": "Artst 3",
"title": "title 3",
"likes": 3,
"dislikes": 1
},
{
"artist": "Artst 4",
"title": "title 4",
"likes": 2,
"dislikes": 1
},
{
"artist": "Artst 5",
"title": "title 5",
"likes": 1,
"dislikes": 1
}
]
}

From the looks of it, you will actually need to use $rootScope.$broadcast instead of $scope.$emit, as sma suggested. As you have your markup, voteController is not actually a child of topFiveController. In your voteController, you will need to inject $rootScope as a dependency, and then use:
$rootScope.$broadcast('vote');
and in topFiveController:
$scope.$on('vote', function () {
// sort
});
If you want to update the music object in topFiveController to show the new vote without having to get from the music service again, you can also do this in your voteController:
$rootScope.$broadcast('vote', $scope.music);
then do this in your topFiveController:
$scope.$on('vote', function(music) {
$scope.music = music;
// sort
});

You can emit an event from your voteController:
$scope.$emit("vote");
and then listen for that event in your topFiveController:
$scope.$on("vote", function () {
// sort
});
I am using $emit because that will send events up the $scope inheritance tree. According to your markup, it looks like voteController is a child scope of topFiveController. If they are sibling scopes or if the topFiveController is a child scope of voteController, then use $scope.$broadcast instead of $emit. $broadcast sends events down the inheritance tree.

Related

Access images from local JSON file on the web page in angularjs

Where am i going wrong?
I want to access images from datahl.json file on the web page but unable to access them.Please check the codes and help me.
If possible please refer the solution to plunker editor.
index.html
<div class="col-sm-4" ng-repeat = "hbl in hbls">
<div class="thumbnail" ng-repeat = "h in hbl.data_list"
style="width:100%;">
<img ng-src="{{h.img}}" alt="" style="height:50vh;">
<div class="caption">
<p><strong>{{h.name}}</strong></p>
</div>
</div>
</div>
app.js This is my js codes
var app = angular.module('hostellApp', []);
app.controller('hostellController', function($scope, hblsFactory){
$scope.hbls;
hblsFactory.getHbls().then(function(response){
$scope.hbls = response.data;
});
$scope.sayHello = function(){
console.log("Hello");
}
});
app.factory('hblsFactory', function($http){
function getHbls(){
return $http.get('datahl.json');
}
return {
getHbls: getHbls
}
});
datahl.json This is my local JSON file
`
{
"view_type": 5,
"title": "Hostels By Locality",
"position": 5,
"data_list":
[
{
"img": "https://s3.us-east-2.amazonaws.com/images-city-
teens/IMG_20180526_1139570.8091572935412125.jpg",
"name": "Mahavir Nagar"
},
{
"img": "https://graph.facebook.com/1666751513414902/picture?
type=large",
"name": null
},
{
"img": "https://s3.us-east-2.amazonaws.com/images-city-
teens/cropped1148015742983667713.jpg",
"name": "New Rajiv Gandhi"
},
{
"img": "https://s3.us-east-2.amazonaws.com/images-city-
teens/cropped998427941.jpg",
"name": "Jawahar Nagar"
}
]
}`
The data you get from your JSON file is not an array but an object. This means that you only need one ng-repeat.
Change your first ng-repeat to ng-repeat="h in hbls.data_list"(added an 's' to hbl) and delete your second ng-repeat.
Working plunker: https://plnkr.co/edit/OOd1M1dNFjSB7uaqZZB6?p=preview

How do I implement a filter in AngularJS?

How do I filter an array by a property that exists in another array?
I have this two arrays in my control:
this.items = [{ "Id": 1, "TypeId": 1, "name": "Michael" },
{ "Id": 2, "TypeId": 2, "name": "Helga" },
{ "Id": 3, "TypeId": 1, "name": "Max" },
{ "Id": 4, "TypeId": 2, "name": "Ann" }];
this.filterBy = [{ "Id": 1, "gender": "Male" },
{ "Id": 2, "gender": "female" }];
And here is my ng-repeat in view template:
<tr ng-repeat="item in list.items">
<td>
<div class="container-fluid">
<div class="row">
<div class="text-center">{{ item.name }}</div>
</div>
</div>
</td>
</tr>
Here is the generated view:
At some point I want to filter items array by gender to show in the view above only females or only males.
I tried to implement filter, like this:
(function () {
"use strict";
angular.module("sensorsData").filter('filterByGender', filterByGender);
function filterByGender() {
var result = [];
return function (items, filterBy) {
if (!items)
return;
if (!filterBy)
return;
angular.forEach(items, function (item) {
angular.forEach(filterBy, function (f) {
if (item.TypeId == f.Id) {
result.push(item);
}
})
});
return result;
}
};
})();
And here how I use it in view template:
<tr ng-repeat="item in list.items | filterByGender:list.filterBy">
<td>
<div class="container-fluid">
<div class="row">
<div class="text-center">{{ item.name }}</div>
</div>
</div>
</td>
</tr>
The desired view in case if content of filterBy array like that:
this.filterBy = [{ "Id": 1, "gender": "Male" }];
The view Should display only items rows with TypeId = 1.
Like this:
But the filter above is not working.
And in the debugger I get this errors:
Error: [ngRepeat:dupes] Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: r in list.arr | filterByAlert:list.filterBy, Duplicate key: object:8, Duplicate value: {"Id":1,"TypeId":1,"name":"Michael"}
http://errors.angularjs.org/1.5.3/ngRepeat/dupes?p0=r%20in%20list.arr%20%7C…%3A8&p2=%7B%22Id%22%3A1%2C%22TypeId%22%3A1%2C%22name%22%3A%22Michael%22%7D
at angular.js:68
at ngRepeatAction (angular.js:28799)
at $watchCollectionAction (angular.js:16734)
at Scope.$digest (angular.js:16869)
at Scope.$apply (angular.js:17133)
at done (angular.js:11454)
at completeRequest (angular.js:11652)
at XMLHttpRequest.requestLoaded (angular.js:11593)
Why is the filter not working? And how can I create a filter to show items by gender?
And this:
angular.js:13424 Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!
Watchers fired in the last 5 iterations: []
http://errors.angularjs.org/1.5.3/$rootScope/infdig?p0=10&p1=%5B%5D
at angular.js:68
at Scope.$digest (angular.js:16907)
at Scope.$apply (angular.js:17133)
at done (angular.js:11454)
at completeRequest (angular.js:11652)
at XMLHttpRequest.requestLoaded (angular.js:11593)(anonymous function) # angular.js:13424
angular.js:17136 Uncaught Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!
Watchers fired in the last 5 iterations: []
http://errors.angularjs.org/1.5.3/$rootScope/infdig?p0=10&p1=%5B%5D
And this:
Uncaught Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!
Watchers fired in the last 5 iterations: []
Why is the filter not working? And how can I create a filter to show items by gender?
The problem is that you are declaring the result array in the wrong place.
function filterByGender() {
var result = [];
return function (items, filterBy) {
...
return result;
}
};
The filterByGender function here is what creates the filter function, while the anonymous inner function starting on line 3 is the actual filter function. Angular calls filterByGender only once, to build the filter, then calls the second function each time the inputs change. In your current code, the result array is always the same (i.e. only one exist in the lifetime of the application) and every time the filter is invoked, the items get pushed again into it. The filter then returns this array full of duplicates, and you get errors from Angular.
What you want instead is to get new results each time the inputs change, so results should be declared and initialized in the anonymous function, not filterByGender. Try this:
function filterByGender() {
return function (items, filterBy) {
var result = [];
...
return result;
}
};
You could use some very complicated filtering-with-functions-solution, but honoustly the easiest way in my opinion is to create a merged list of items that contains exactly what your view needs (i.e. a view model). This way, you can simply use filter on the ng-repeat to filter on gender.
Here's a code snippet:
var app = angular.module('myapp', []);
app.controller('myctrl', function($scope) {
var items = [{ "Id": 1, "TypeId": 1, "name": "Michael" },
{ "Id": 2, "TypeId": 2, "name": "Helga" },
{ "Id": 3, "TypeId": 1, "name": "Max" },
{ "Id": 4, "TypeId": 2, "name": "Ann" }];
$scope.types = [{ "Id": 1, "gender": "Male" },
{ "Id": 2, "gender": "female" }];
$scope.filterBy = $scope.types[0];
var constructViewModel = function(items, types) {
$scope.mergedItems = [];
angular.forEach(items, function(i){
var mergedItem = angular.copy(i);
var type = types.filter(function(t){return t.Id == i.TypeId})[0];
mergedItem.gender = type.gender;
$scope.mergedItems.push(mergedItem);
});
}
constructViewModel(items, $scope.types);
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myapp" ng-controller="myctrl">
<span>Filter by gender: </span><select ng-model="filterBy" ng-options="t as t.gender for t in types"></select>
<div ng-repeat="item in mergedItems | filter: { gender: filterBy.gender } :true">
<td>
<div class="container-fluid">
<div class="row">
<div class="text-center">{{ item.name }} {{item.gender}}</div>
</div>
</div>
</td>
</div>
</div>

Only show content associated with user

I have a service that does a request,
.factory('movieService', ['$http', function($http) {
return {
loadMovies: function() {
return $http.get('/movies_users.json');
}
};
}])
This is the JSON output and is the result of 2 tables being joined. A user table and a movie table. As you can see the users are associated with 1 or more movies.
[
{"id":1,
"email":"peter#peter.nl",
"movies":[
{
"id":4,
"title":"Creed",
movie_id":"312221"
},
{
"id":5,
"title":"Star Wars: Episode VII - The Force Awakens",
"movie_id":"140607"
}
]
},
{"id":2,
"email":"jan#jan.com",
"movies":[
{
"id":4,
"title":"Creed",
movie_id":"312221"
}
]
}
]
I then have this function in my controller,
movieService.loadMovies().then(function(response) {
$scope.movies = response.data;
});
This stores the data from the service into the movie scope.
If I do,
"ng-repeat" => "movie in movies"
The ng-repeat shows all the movies added by all the users. How would I only show the movies associated with the current user in a view?
This seems to be what you want - let me know if it helps.
Note that you have an extra " on movie_id" that is certainly not helping
angular.module('myApp', [])
.controller('ctrl', function($scope) {
$scope.users = [
{"id":1,
"email":"peter#peter.nl",
"movies":[
{
"id":4,
"title":"Creed",
movie_id:"312221"
},
{
"id":5,
"title":"Star Wars: Episode VII - The Force Awakens",
"movie_id":"140607"
}
]
},
{"id":2,
"email":"jan#jan.com",
"movies":[
{
"id":4,
"title":"Creed",
movie_id:"312221"
}
]
}
];
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="ctrl">
<div ng-repeat="user in users">
{{user.email}}
<div ng-repeat="movie in user.movies">
{{movie.title}}
</div>
</div>
<h2>Movies for user 2</h2>
<div ng-repeat="movie in users[1].movies">
{{movie.title}}
</div>
</div>
Found in a quick google search:
How do I filter an array with AngularJS and use a property of the filtered object as the ng-model attribute?
<div ng-repeat="movie in movies | filter {email:'user#email.com'}">
Edit: it looks like you actually have a list of users, so you could do a filter on an ngrepeat on users, then you'd have a nested ngrepeat on movies (unfiltered because they're attached to a user). But depending on how your app actually needs to work you could just filter the data when it comes back and throw out the users you don't need. Which introduces the question, do you have access to the server side code? If so you could filter there and not return data that is associated with a different user in the first place

JSON Angular Array pulling data from 3rd Loop

I am struggling to pull the data from the 3rd loop (Names) in the array below.
Any idea what i am doing wrong?
sample.json
{
"otc": [
{
"name": "Tums",
"language": [
{
"title": "English",
"names": [
{"name": "Tums"},
{"name": "Pepto"}
]
},
{
"title": "China"
},
{
"title": "Germany"
}
,
{
"title": "Mexico"
}
,
{
"title": "India"
}
,
{
"title": "United Kingdom"
}
]
},
{
"name": "Dramamine",
"language": [
{
"title": "title2album1"
},
{
"title": "title2album2"
},
{
"title": "title2album3"
}
]
}
]
}
And this is my index.html
<body ng-app="list">
<div ng-controller="ListCtrl">
<ul>
<li ng-repeat-start="meds in otc">
<strong> {{meds.name}}</strong> //This displays just fine
</li>
<li ng-repeat="lang in meds.language"><em>{{lang.title}}</em></li> //This displays just fine
<li ng-repeat-end ng-repeat="drugs in lang.names">{{drugs.name}}</li> //This doesnt display
</ul>
</div>
<script>
angular.module('list', []);
function ListCtrl($scope, $http) {
$http({method: 'GET', url: 'sample.json'}).success(function(data) {
$scope.otc = [];
angular.forEach(data.otc, function(value, key) {
$scope.otc.push(value);
});
$scope.isVisible = function(name){
return true;
};
});
}
</script>
It looks like your JSON data is a bit incomplete, since you're missing the names key in all of the language objects except for the English one.
Beyond that, your html/angular-view code is a bit off. The ng-repeat's should be nested inside of one-another. This is done by having the opening/closing html tags that contain the ng-repeat directive completely surround the inner <li> tags.
It's a bit hard to tell, but if you want nested lists, you need to add <ul> tags like in my example below.
I believe your index html should look something like:
<ul>
<li ng-repeat="meds in otc">
<strong> {{meds.name}}</strong> //This displays just fine
<ul>
<li ng-repeat="lang in meds.language">
<em>{{lang.title}}</em>
<ul>
<li ng-repeat="drugs in lang.names">{{drugs.name}}</li>
</ul>
</li>
</ul>
</li>
</ul>
The reason why the li ng-repeat tags should be nested is because ng-repeat creates its own isolate scope. Which means that inside of an ng-repeat tag, it only has access to the data made available by the ng-repeat="data in datar" part.
So having:
<li ng-repeat="lang in meds.language"><em>{{lang.title}}</em></li> //This displays just fine
<li ng-repeat="drugs in lang.names">{{drugs.name}}</li> //This doesnt display
as sibling elements, and not the second as a child of the other, the 2nd ng-repeat list does not know what the var lang is. So the drugs in lang.names fails.
Btw, this snippet assumes that the output you want is:
Tums
English
Tums
Pepto
China
Germany
etc
Dramamine
title2album1
title2album2
etc
If you wanted the output as a flat list, you can use the following CSS
ul ul {
padding: 0;
margin: 0;
list-style-type: disc;
}

How to control a specific attribute of DOM created using ng-repeat in angularjs?

Question:
Is there a proper way of changing the value of an attribute of dom from on to off without doing it in the controller and how can i change it because my code below change all the state even though I only clicked one element?
JSON:
sections = {"data": [{
"label": "somelabel 1",
},{
"label": "somelabel 2",
}]};
HTML:
<ul ng-repeat="data in sections.data">
<a href="" ng-click="do_this ()" data-state="{{! state }}" >{{! data.label }}</a>
</ul>
JS:
var module = angular.module('sidebar_module', []);
module.controller('Menu2Ctrl', ['$scope',
$scope.state = 'off';
$scope.do_this = function(){
if ($scope.state == 'off') {
dosomething();
$scope.state = 'on';
} else {
dosomething();
$scope.state = 'on';
}
}
]);
The correct way is to have all your javascript in JS files, which essentially means controllers, services etc. Not in HTML.
You have one $scope.state variable, and use that to be display the status of all sections. So the result you get is what exactly you wrote in your program :).
If you want each section to maintain their states separately, you can include the state property in sections object as follows,
sections = {"data": [{
"label": "somelabel 1", state:'on'
},{
"label": "somelabel 2", state:'off'
}]};
In the do_this(), take the $index and set the state of the section,
$scope.do_this = function(index){
var section = sections.data[index];
if (section.state == 'off') {
dosomething();
section.state = 'on';
} else {
dosomethingElse();
section.state = 'on';
}
}
In your html you will need to pass the index for do_this
<a href="" ng-click="do_this($index)" data-state="{{!state}}" >{{!data.label}}</a>
Info on ngRepeat and $index

Resources