Triggering function on ngclick with AngularJS - javascript

I have a function that makes a $http call to an external API and then populates some results within an ng-repeat array.
Right now the function gets triggered on every element on the ng-repeat, which creates a whole lot of server calls. I'd like for the function to only make the call once an element from the ng-repeat is clicked upon.
I've tried with ng-click, but i'd say i'm missing something.
The $http query that i'm trying to call on click is the second one:
function ImageCtrl($scope, $http) {
$scope.image = 'img/record-default.png';
$http.get('http://ws.audioscrobbler.com/2.0/?method=album.getinfo&api_key=e8aefa857fc74255570c1ee62b01cdba&artist=' + $scope.artist.name + '&album=' + $scope.release.title + '&format=json').
success(function (data4) {
$scope.image = data4.album.image[2]['#text'];
}
)
function getVersions ($scope, $http){
$http.get('http://api.discogs.com/masters/' + $scope.release.id + '/versions').
success(function (data5) {
$scope.versions = data5.versions;
});
}
}
And the relevant html:
<div class="col-md-3" ng-controller="ImageCtrl" ng-repeat="release in releases | filter:album | filter:year | filter:{ role: \'main\' }" >
<div class="release" ng-click="getVersions()"> \
<img class="img-responsive" ng-src="{{image}}" /> {{release.title}}
<ul ng-controller="ImageCtrl">
<li ng-repeat="version in versions">{{version.format}}</li>
</ul>
</div>
</div>
And a working Plunker. Function in question is line 60 on script.js

So I ended up taking what you have shown and doing some refactoring.
I moved getVersions to the prototype, and use it to append versions to a release object instead of the $scope.
function ImageCtrl($scope, fakeService) {
var _this = this;
this.fakeService = fakeService;
this.$scope = $scope;
fakeService.getReleases()
.then(function (releases) {
$scope.releases = releases;
});
this.$scope.getVersions = function(release){
_this.getVersions(release);
};
}
ImageCtrl.$inject = ['$scope', 'fakeService'];
ImageCtrl.prototype.getVersions = function (release) {
this.fakeService.getVersions(release.id)
.then(function (versions) {
release.versions = versions;
});
};
The markup isn't terribly different, but you can see where I pass the actual release object into the getVersions function in the click event. This way it always acts directly on the object bound to that particular row.
<div class="row" ng-controller="ImageCtrl">
<div class="col-md-3" ng-repeat="release in releases">
<div class="release" ng-click="getVersions(release)">
<h1>{{release.title}}</h1>
<img class="img-responsive" height="100" width="100" ng-src="{{release.image}}" />
<ul>
<li ng-repeat="version in release.versions">{{version.format}}</li>
</ul>
</div>
</div>
</div>
And here is a working demo showing the whole thing in action: http://jsfiddle.net/jwcarroll/k6mkt/
I'm using a fake service here to mimic calling a web service in order to get the data. I highly recommend wrapping up your calls to $http in order to encapsulate data access in your controller.

Related

Angular: Updating $scope with new data causes old data to remain when using ng-repeat

I'm having a strange issue where I replace the values in $rootScope.data.vehicles with new data but the old data points on my view remain for about one second alongside the new data.
For instance, right after I replace the $rootScope.data.vehicles array with a new array my view will display the 3 new values first followed by the 3 old values. View is using ng-repeat on $rootScope.data.vehicles to display tiles.
About one second later the 3 old values are no longer displayed in the view.
Each time the interval fires I get new values followed by old values, even if the values have not changed.
My controller and factory look like so:
(function () {
var vehiclesInjectParams = ['$location', 'dataService', '$rootScope', 'VehiclesRefreshService'];
var VehiclesController = function ($location, dataService, $rootScope, VehiclesRefreshService) {
var vm = this;
if ($rootScope.data == undefined)
$rootScope.data = {};
$rootScope.data.vehicles = [];
function init() {
dataService.getVehicles()
.then(function (data) {
$rootScope.data.vehicles = data.results;
}, function (error) {
var thisError = error.data.message;
});
VehiclesRefreshService.getValues();
};
init();
};
VehiclesController.$inject = vehiclesInjectParams;
var vehiclesRefreshInjectParams = ['$interval', '$rootScope', '$q', 'dataService'];
var VehiclesRefreshService = function ($interval, $rootScope, $q, dataService) {
var factory = {};
factory.getValues = function () {
var interval = $interval(function () {
dataService.getVehicles()
.then(function (data) {
$rootScope.data.vehicles = data.results;
}, function (error) {
var thisError = error.data.message;
});
}, 10000);
};
return factory;
};
VehiclesRefreshService.$inject = vehiclesRefreshInjectParams;
angular.module('teleAiDiagnostics').controller('VehiclesController', VehiclesController).factory('VehiclesRefreshService', VehiclesRefreshService);
}());
First I load the array then I start an $interval timer to refresh the values every 10 seconds. As soon as the dataService returns the new list of values and puts them in $rootScope.data.vehicles I notice the six tiles instead of 3. One second after that I'm left with only the 3 new tiles.
My view looks like so:
<header>
<h1>Vehicles</h1>
</header>
<article class="icon-gallery flexslider">
<section>
<figure ng-repeat="vehicle in $parent.data.vehicles" class="gallery-item svg">
<a class="nextpage" ng-href="#!/vehicle/{{vehicle.vehicleID}}">
<img src="img/machine/01.svg" alt="">
<span class="green-machine-code">{{vehicle.id}}</span>
</a>
<ul>
<li>{{vehicle.id}}</li>
<li>{{vehicle.ip}}</li>
<li>Online: {{vehicle.online}}</li>
<li>Status: {{vehicle.status}}</li>
<li>AllClear: {{vehicle.allClear}}</li>
</ul>
</figure>
</section>
</article>
Any ideas as to how to get my tiles to refresh seamlessly? All help is greatly appreciated.
Avoid track by $index when there is a unique property identifier to work with.
<figure ng-repeat="vehicle in $parent.data.vehicles track by ̲v̲e̲h̲i̲c̲l̲e̲.̲i̲d̲ ̶$̶i̶n̶d̶e̶x̶" class="gallery-item svg">
<a class="nextpage" ng-href="#!/vehicle/{{vehicle.vehicleID}}">
<img src="img/machine/01.svg" alt="">
<span class="green-machine-code">{{vehicle.id}}</span>
</a>
<ul>
<li>{{vehicle.id}}</li>
<li>{{vehicle.ip}}</li>
<li>Online: {{vehicle.online}}</li>
<li>Status: {{vehicle.status}}</li>
<li>AllClear: {{vehicle.allClear}}</li>
</ul>
</figure>
From the Docs:
If you are working with objects that have a unique identifier property, you should track by this identifier instead of the object instance. Should you reload your data later, ngRepeat will not have to rebuild the DOM elements for items it has already rendered, even if the JavaScript objects in the collection have been substituted for new ones. For large collections, this significantly improves rendering performance.
— AngularJS ng-repeat Directive API Reference - Tracking
Mike Feltman in the comments above resolved the issue. It was as simple as adding 'track by $index' to the ng-repeat tag in the view. See here:
<header>
<h1>Vehicles</h1>
</header>
<article class="icon-gallery flexslider">
<section>
<figure ng-repeat="vehicle in $parent.data.vehicles track by $index" class="gallery-item svg">
<a class="nextpage" ng-href="#!/vehicle/{{vehicle.vehicleID}}">
<img src="img/machine/01.svg" alt="">
<span class="green-machine-code">{{vehicle.id}}</span>
</a>
<ul>
<li>{{vehicle.id}}</li>
<li>{{vehicle.ip}}</li>
<li>Online: {{vehicle.online}}</li>
<li>Status: {{vehicle.status}}</li>
<li>AllClear: {{vehicle.allClear}}</li>
</ul>
</figure>
</section>
</article>
Hopefully this post will help someone in the future who is experiencing the same type of problem.
IIRC, internally Angular sees that the vehicles array has changed and immediately renders it, then cleans up. Adding track by $index to the ng-repeat should basically force rendering to start at the first item each time.
I do agree with the other post regarding the use of $rootScope. If you're trying to make the vehicles available globally rather than storing them in rootScope, store them in your data service and inject it whereever necessary. It will be a much cleaner approach.

Angular JS === comparison not working

I am trying to compare the value passed from the url to a controller to a field in a json file.
galleryItem.html
<div class="filter-box">
<ul class="filter list-inline text-center" ng-repeat="gal in ParentData">
<li></li>
</ul>
</div>
<div class="container-fluid">
<div class="row">
<div class="portfolio-box" ng-repeat="x in data">
<div class="col-sm-4">
<div class="item-img-wrap">
<img ng-src={{x.url}} class="img-responsive" alt="">
<div class="item-img-overlay">
<a href={{x.url}} class="show-image">
<span></span>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
The updated controller:
controllers.controller('GalleryViewCtrl', function GalleryViewCtrl($scope, $http, $stateParams) {
$scope.pageName = '';
$scope.Description = '';
$scope.GalleryID = $stateParams.id;
$http.get('/data/galleryItems.json')
.then(function (response) { $scope.ParentData = response.data.galleries });
$http.get('/data/galleryItemImages.json')
.then(function (response) {
$scope.data = response.data.images.galleryIdentifier === $stateParams.id;
});
});
I verified the correct value is being passed in to the controller, the values are static and so is the data being passed from the json file. I placed an if statement to check for null as suggested as well. I removed it temporarily to reduce what I'm working with.
If I remove the === $stateParams.id i get all of the images returned and displayed correctly.
If I replace $stateParams.id with a value that I know is in the list (4 or '4') i do not get anything returned. I also tried the value for the last item in the list.
There are no errors (loading scripts, reading json etc.) and all of the values are correct when I'm debugging.
I am still new to this and there is so much documentation with different solutions it all gets very confusing. If anyone has any ideas they would be greatly appreciated.
You are loading data to the $scope.data when the ajax call returns some data. I am assuming your view code is calling the galleryFiltered even before that. May be try to add a null check before returning the value from the method.
$scope.galleryFiltered = function () {
if($scope.data!=null)
{
return $scope.data.galleryIdentifier === $scope.GalleryID;
}
return false;
};
Remember that $http service returns a promise so your $scope.data will be undefined (or holding current state) until $http.get('/data/galleryItemImages.json') will return a success callback function and assign new value to $scope.data from response.
If you'll run $scope.galleryFiltered() before promise gets resolved you will have $scope.data == undefined or whatever data is stored on $scope.data at the time or $scope.galleryFiltered() execution.

binding to controller object in Angular

I'm new to angular, trying to bind an an element's content into the controller's Scope to be able to use it within another function:
here is the scenario am working around:
I want the content of the <span> element {{y.facetName}} in
<span ng-model="columnFacetname">{{y.facetName}}</span>
to be sent to the controller an be put in the object $scope.columnFacetname in the controller
Here is a snippet of what I'm working on:
<div ng-repeat="y in x.facetArr|limitTo: limit track by $index ">
<div class="list_items panel-body ">
<button class="ButtonforAccordion" ng-click="ListClicktnColumnFilterfunc(); onServerSideButtonItemsRequested(ListClicktnColumnFilter, myOrderBy)">
<span>{{$index+1}}</span>
<span ng-model="columnFacetname">{{y.facetName}}</span>
<span>{{y.facetValue}}</span>
</button>
</div>
</div>
angular.module('mainModule').controller('MainCtrl', function($scope, $http) {
$scope.columnFacetname = "";
$scope.ListClicktnColumnFilter = "";
$scope.ListClicktnColumnFilterfunc = function() {
$scope.ListClicktnColumnFilter = "\":\'" + $scope.columnFacetname + "\'";
};
}
the problem is that the $scope.ListClicktnColumnFilter doesn't show the $scope.columnFacetname within it, meaning that the $scope.columnFacetname is not well-binded.
In your ng-click instead of calling two different function
ng-click="ListClicktnColumnFilterfunc(); onServerSideButtonItemsRequested(ListClicktnColumnFilter, myOrderBy)"
you can declare like this
ng-click="columnFacetname = y.facetName; onServerSideButtonItemsRequested(columnFacetname , myOrderBy)"
You are trying to pass that model to another function by assigning it to ListClicktnColumnFilter in your controller
By doing in this way, you can achieve the same thing.
I have done one plunker with sample array,
http://embed.plnkr.co/YIwRLWXEOeK8NmYmT6VK/preview
Hope this helps!

Logic problems with AngularJS and multiple $http queries

I have an app that will make 2 $http queries to an external API and get 2 different JSON responses. These responses will populate a ng-repeat, headers, etc.
My problem is that I want to include a 3rd query, dependent on the first two.
Like so:
I get artist JSON and release JSON, and I use artist.name and release.title to populate the URL of the third $http query.
So far I've managed to get the two first queries, and once the results they are displaying in the ng-repeat, with ng-click I launch the 3rd query and populate an img ng-src.
Buuut, my problem is that I want the img ng-src to be populated automatically without ng-click, so the function that triggers the 3rd query has to get launched right after the 2 first queries. And also, in my working version right now, the img that I fetch with ng-click, will populate all items in ng-repeat. Meaning that every item should get their own image, and right now they don't.
I've created a working Plunker, if you search for a music artist and click on a result and then on an album, you'll see what I mean.
Basically, I think I'm missing a piece of logic that will put everything together and in proper trigger order.
Any thoughts?
My JS:
angular.module('myApp', ['ngResource'])
function Ctrl($scope, $http) {
var search = function(name) {
if (name) {
$http.get('http://api.discogs.com/database/search?type=artist&q='+ name +'&page=1&per_page=5').
success(function(data3) {
$scope.clicked = false;
$scope.results = data3.results;
});
}
$scope.reset = function () {
$scope.sliding = false;
$scope.name = undefined;
}
}
$scope.$watch('name', search, true);
$scope.getDetails = function (id) {
$http.get('http://api.discogs.com/artists/' + id).
success(function(data) {
$scope.artist = data;
});
$http.get('http://api.discogs.com/artists/' + id + '/releases?page=1&per_page=100').
success(function(data2) {
$scope.releases = data2.releases;
});
$scope.clicked = true;
$scope.sliding = true;
$scope.getImages = function (title, name) {
$http.get('http://ws.audioscrobbler.com/2.0/?method=album.getinfo&api_key=e8aefa857fc74255570c1ee62b01cdba&artist=' + name + '&album='+ title +'&format=json').
success(function(data4) {
$scope.images = data4;
});
}
}
};
My directive:
angular.module('myApp', ['ngResource'])
.directive('artistData', function() {
return{
restrict: 'E',
template: '<div class="col-md-8 col-md-offset-2"> \
<h1 ng-show="artist.name" class="artist-name">{{artist.name}}</h1> \
<div class="header-border" ng-show="artist.name"></div> \
<input ng-show="artist.name" class="form-control" ng-model="album" /> \
<div class="col-md-3" ng-click="getImages(release.title, artist.name)" ng-repeat="release in releases | filter:album | filter:{ role: \'main\' }"><div class="release">{{release.title}}<img class="img-responsive" ng-src="{{images.album.image[2][\'#text\']}}" /></div></div> \
</div>',
replace: true
};
})
And my HTML:
<div class="container">
<div class="row" ng-controller="Ctrl">
<div class="col-md-8 col-md-offset-2">
<div class="intro">
<div class="intro-text" ng-class="{'slide':sliding}">
<h1>Howdy stranger!</h1>
<h3>Use the form below to search for an artist and start building your record collection!</h3>
</div>
<input type="text" ng-model="name" class="form-control input-lg" ng-class="{'slide':sliding}" ng-focus="reset()" placeholder="Artist name"/>
</div>
<ul ng-hide="clicked" class="search-results">
<li ng-repeat="result in results" ng-click="getDetails(result.id)">{{result.title}}</li>
</ul>
</div>
<artist-data></artist-data>
</div>
</div>
I would use "Chaining Promises" in this case.
In basic words you call new async task on response of previous.
You can read this POST that might help you

AngularJS - Not able to update model programatically

When I update my model through method, model does not update. Below is my AngularJS code:
var app = angular.module('MyApp',[]);
app.controller('businessCtrl', function($scope, Business) {
$scope.businesses = Business.query(); // getting data using REST
$scope.currentBusinessIndex = 0;
$scope.changeBusinessIndex = function (indx) {
if (indx) {
$scope.currentBusinessIndex = indx;
} else {
$scope.currentBusinessIndex = 0;
}
};
});
Below is the HTML:
<ul class="signups">
<li ng-controller="businessCtrl" ng-repeat="business in businesses">
<div class="user pull-left" ng-click="changeBusinessIndex($index)">
<img ng-src="http://localhost:8081{{business.logo.uri}}" alt="business logo" >
</div>
<div class="info" ng-click="changeBusinessIndex($index)">
<h6 ng-cloak>{{business.name}}</h6>
<small ng-cloak>{{business.streetAddress + ',' + business.city + ',' + business.country}}</small>
</div>
</li>
</ul>
<div id="business-image" class="main-container" ng-controller="businessCtrl">
<img ng-src="http://localhost:8081{{businesses[currentBusinessIndex].logo.uri}}" alt="Business Logo">
</div>
Problem:
In changeBusinessIndex() method, when I modify currentBusinessIndex, it does not modify and as a result image in "business-image" div is not updating.
You are creating two different instances of businessCtrl, one for the li (let's call it businessCtrl1) and one for the business-image div (let's call that businessCtrl2). When you are calling changeBusinessIndex() in the li-instance it will update currentBusinessIndex for businessCtrl1, but not for the other instance.
Set ng-controller="businessCtrl" on an element that wraps both the li and the business-image divs to get a single instance of the controller, something like:
<div ng-controller="businessCtrl>
<li ng-repeat ... etc ></li>
<div id="business-image" ... ></div>
</div>
First of all it seems strange that you call twice ng-controller="businessCtrl" (with the same name). But if you interesting to use two different controllers I would store currentBusinessIndex into the Service and use it as storage. Since service is singleton, you can register the service to both controllers and by this way your logic should work.
HTML
<div ng-controller="MyCtrl_1">
<button ng-click="showMe();">press me</button>
<pre>{{myStorage.getIndex()}}</pre>
</div>
<div ng-controller="MyCtrl_2">
<pre>{{myStorage.getIndex()}}</pre>
</div>
JS
var myApp = angular.module('myApp', []);
function MyCtrl_1($scope, myStorage) {
$scope.myStorage = myStorage;
$scope.showMe = function(){
myStorage.setIndex(3);
}
}
function MyCtrl_2($scope, myStorage) {
$scope.myStorage = myStorage;
}
myApp.factory('myStorage', function () {
var currentBusinessIndex = 0;
return {
getIndex: function () {
return currentBusinessIndex;
},
setIndex: function (index) {
currentBusinessIndex = index;
}
}
});
Demo Fiddle

Categories

Resources