Wait till images are loaded inside angularjs factory - javascript

I've created a small feature alowing users to search for movie titles. This does a JSON requests from tmdb.org which returns things like titles, dates and url's posters.
The controller:
angular.module('movieSeat')
.factory('moviesearchFactory', ['$http', '$q', '$rootScope', function ($http, $q, $rootScope) {
var factory = {};
function httpPromise(url) {
var deferred = $q.defer();
$http({
method: 'JSONP',
url: url
})
.success(function (data) {
deferred.resolve(data.results);
})
.error(function () {
deferred.reject();
});
return deferred.promise;
}
factory.getMovies = function (searchquery) {
return httpPromise('http://api.themoviedb.org/3/' + 'search/movie?api_key=a8f7039633f2065942cd8a28d7cadad4' + '&query=' + encodeURIComponent(searchquery) + '&callback=JSON_CALLBACK')
}
return factory;
}]);
The factory:
angular.module('movieSeat')
.controller('moviesearchCtrl', ['$scope', 'moviesearchFactory', function ($scope, moviesearchFactory) {
$scope.createList = function (searchquery) {
$scope.loading = true;
moviesearchFactory.getMovies(searchquery)
.then(function (response) {
$scope.movies = response;
})
.finally(function () {
$scope.loading = false;
});
}
}]);
The template:
<div ng-controller="moviesearchCtrl" id="movieSearch">
<div class="spinner" ng-show="loading">Loading</div>
<input ng-model="searchquery" ng-change="createList(searchquery)" ng-model-options="{ debounce: 500 }" />
{{ search }}
<ul>
<li ng-if="movie.poster_path" ng-repeat="movie in movies | orderBy:'-release_date'">
<span class="title">{{ movie.title }}</span>
<span class="release_date">{{ movie.release_date }}</span>
<img ng-src="https://image.tmdb.org/t/p/w300_and_h450_bestv2/{{ movie.poster_path }}" class="poster"/>
</li>
</ul>
</div>
The problem with this feature is that the spinner class only waits for the requested data. But just loading some JSON doesn't take long, it's downloading the images from the api in the browser that takes a while.
This causes 2 things. First of the spinner is removed before the images are rendered in the browser and because the images are all loaded async it causes a waterfall effect.
The easiest way to resolve this problem would to delay the .then call in the controller until the images are downloaded for the user and then go into the .finally call.
But I can't find a way to create something like that. Any tips?

Try this and let me know:
The idea is to use a directive to emit a render finished event:
dashboard.directive('onFinishRender', function ($timeout) {
return {
restrict: 'A',
link: function (scope, element, attr) {
if (scope.$last === true) {
$timeout(function () {
scope.$emit(attr.onFinishRender);
});
}
}
}
});
In the controller keep the event listener that wait for the image load promises:
$scope.$on('dataLoaded', function(ngRepeatFinishedEvent) {
// your code to check whether images has loaded
var promises = [];
var imageList = $('#my_tenants img');
for(var i = 0; i < imageList.length; i++) {
promises.push(imageList[i].on('load', function() {}););
}
$q.all(promises).then(function(){
// all images finished loading now
$scope.loading = false;
});
});
and in the html side:
<div id = "my_tenants">
<div ng-repeat="tenant in tenants" on-finish-render="dataLoaded">
// more divs
</div>
</div>

Related

How do I return promise to satisfy the directive?

I have the following code and I have tried just bout everything to return a promise that would work with the directive. I have even tried returning the response data and return $q.when(data) and nothing. Ive tried reading on promises and this one is a bit different then Ive read. Something I'm missing?
myApp.controller('smsCtrl', function($scope){
$scope.sendSMS = function(){
let sms = {'number': $scope.number ,'message': $scope.message};
serviceNameHere.sendSMS(sms).then(function(response){
$scope.smsSuccessMsg = "Message Sent Successfully";
}, function(response){
$scope.smsErrorMsg = response.data['message'];
});
};
})
myApp.directive('onClickDisable', function() {
return {
scope: {
onClickDisable: '&'
},
link: function(scope, element, attrs) {
element.bind('click', function() {
element.prop('disabled',true);
scope.onClickDisable().finally(function() {
element.prop('disabled',false);
})
});
}
};
});
The following html
<div ng-controller="smsCtrl">
<-- field inputs here --></-->
<button type="button" class="btn btn-primary" on-click-disable="sendSMS()">SEND</button>
</div>
JSfiddle Example
There is no need to have a special directive for this. Simply use ng-disabled and ng-click:
<div ng-controller="smsCtrl">
<!-- field inputs here -->
<button ng-click="sendSMS()" ng-disabled="pendingSMS">
SEND
</button>
</div>
In the controller:
myApp.controller('smsCtrl', function($scope, serviceNameHere){
$scope.sendSMS = function(){
let sms = {'number': $scope.number ,'message': $scope.message};
$scope.pendingSMS = true;
serviceNameHere.sendSMS(sms).then(function(response){
$scope.smsSuccessMsg = "Message Sent Successfully";
}, function(response){
$scope.smsErrorMsg = response.data['message'];
}).finally(function() {
$scope.pendingSMS = false;
});
};
})
When the SMS message starts, the controller sets the pendingSMS flag to disable the Send button. When the service completes, the flag is reset and the button is re-enabled.

ReferenceError: lecturerFac is not defined

When I load the html page, my controller retrieves data from an API end point regarding a course. The page gets populate with the data about the course. But at the same time I want to populate part of the page with data about the lecturer of the course (their image, name , description etc ...). I pass the lecturer name to the method using the ng-init directive but I get a
ReferenceError: lecturerFac is not defined.
I am not sure but I believe the issue is the way I am calling the getLecturer() function using the ng-init directive.
What I want to happen when the page loads is have the Lecturer's details displayed on the page along with the course details.
courses.html
<div class="container" ng-controller="CoursesDetailsCtrl">
<div class="row" >
<div class="col-4" ng-model="getLecturer(courses.lecturer)">
<div>
<h3>{{lecturer.name}}</h3>
<<div>
<img class="img-circle" ng-src="{{lecturer.picture}}" alt="" />
</div>
<p>{{lecturer.description}}</p> -->
</div>
</div>
<div class="col-8">
<div class="myContainer" >
<h2>{{courses.name}}</h2>
<div class="thumbnail">
<img ng-src="{{courses.picture}}" alt="" />
</div>
<div>
<p>{{courses.description}}</p>
</div>
</div>
</div>
</div>
</div>
CoursesDetailsCtrl
todoApp.controller('CoursesDetailsCtrl', ['coursesFac','lecturerFac','$scope','$stateParams', function CoursesCtrl(coursesFac, lecturerFac, $scope, $stateParams){
$scope.getLecturer = function(name){
lecturerFac.getLecturerByName(name)
.then(function (response) {
$scope.lecturer = response.data;
console.log($scope.lecturer);
}, function (error) {
$scope.status = 'Unable to load lecturer data: ' + error.message;
console.log($scope.status);
});
};
}]);
lecturerFac
todoApp.factory('lecturerFac', ['$http', function($http) {
var urlBase = '/api/lecturer';
var coursesFac = {};
lecturerFac.getLecturer = function () {
return $http.get(urlBase);
};
lecturerFac.getLecturerByName = function (name) {
return $http.get(urlBase + '/' + name);
};
return lecturerFac;
}]);
todoApp.factory('lecturerFac', ['$http', function($http) {
var urlBase = '/api/lecturer';
var coursesFac = {};
var service = {};
service.getLecturer = function () {
return $http.get(urlBase);
};
service.getLecturerByName = function (name) {
return $http.get(urlBase + '/' + name);
};
return service;
}]);
i Think the cause of this error is the lecturerFac variable is not initialize in the factory. Create an empty object call lecturerFac in the factory and return it.
todoApp.factory('lecturerFac', ['$http', function($http) {
var urlBase = '/api/lecturer';
var coursesFac = {};
var lecturerFac= {};/////////////////////
lecturerFac.getLecturer = function() {
return $http.get(urlBase);
};
lecturerFac.getLecturerByName = function(name) {
return $http.get(urlBase + '/' + name);
};
return lecturerFac;
}]);
Also avoid calling functions inside the ng-model. When you bind something with ng-model it must be available for both reading and writing - e.g. a property/field on an object. use ng init instead

AngularJs - Execute Function in ng-repeat

My Web-App Should get images from server, show them and and give possibility to vote for Like it or Not.
Votes will be stored on DB.
my Controller :
$scope.beginSearch = function () {
$http
.get("http://example?q=" + $scope.search )
.then(function (response) {
$scope.url = response.data.data;
});
};
<tr ng-repeat="x in url">
<th>
<img src="{{x.images}}"></img>
<div class="well">
<i class="fa fa-thumbs-o-up fa-2x vertical-align" ng-click="vote_up(x.id)"></i>
<i class="fa fa-thumbs-o-down fa-2x vertical-align" ng-click="vote_down(x.id)" ></i>
</div>
</th>
</tr>
I was hoping to use a function in every ng-repeat, which would return
votes for like
{{ return_vote(x.id)}}
But it doesn't work, and as far I see, I should not use functions in html,
if they are not in ng-click functions.
ng-init also doesn't work.
Could anyone provide me help, how could I solve my problem?
Images are on some website, I just get them by using their WEB-API, so they doesn't have API for votes, I need to do it myself.
You can call your function inside brackets {{yourFunction(x.id)}} and add the logic to get the votes inside.
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.url = [{
images: "http://lorempixel.com/g/100/100/",
id: 1
}, {
images: "http://lorempixel.com/100/100/",
id: 2
}]
$scope.getVotes = function(id){
//logic to get number of votes
return id * 5;
}
$scope.vote_up = function(id){
console.log("Vote up id " + id);
}
$scope.vote_down = function(id){
console.log("Vote down id " + id);
}
});
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="myApp" ng-controller="myCtrl">
<div ng-repeat="x in url">
<img src="{{x.images}}" />
<p>{{getVotes(x.id)}} votes</p>
<i class="fa fa-thumbs-o-up fa-2x vertical-align" ng-click="vote_up(x.id)"></i>
<i class="fa fa-thumbs-o-down fa-2x vertical-align" ng-click="vote_down(x.id)"></i>
</div>
</body>
Since you can't have the votes in the API you could alternatively create a service to get all the votes at once and then create some logic to match them to images.
e.g.
$scope.images = [];
var votes = [];
activate();
function activate() {
getImages().then(function () {
getVotes().then(function () {
matchVotesToImages();
//Now we have a property 'votes' in each image object which we can use in ng-repeat
});
});
}
function getVotes() {
var deferred = $q.defer();
$http.get('theUrl').then(success, fail);
function success(res) {
votes = res;
deferred.resolve();
}
function fail(res) {
console.log("Error");
console.log(res);
}
return deferred.promise;
}
function getImages() {
var deferred = $q.defer();
$http.get('theUrl').then(success, fail);
function success(res) {
$scope.images = res;
deferred.resolve();
}
function fail(res) {
console.log("Error");
console.log(res);
}
return deferred.promise;
}
function getIndex(array, property, valueToCompare) {
var i = 0;
var len = array.length;
for (i; i < len; i++) {
if (array[i][property] == valueToCompare) {
return i;
}
}
return -1;
}
function matchVotesToImages() {
var i = 0;
var len = $scope.images.length;
for (i; i < len; i++) {
//We pick need the votes of this specific image so
var indexAtVoteArray = getIndex(votes, 'id', $scope.images[i].id);
if (indexAtVoteArray != -1)
$scope.images.votes = votes[indexAtVoteArray];
else
$scope.images.votes = 0;
}
}
Thanks for answer.
$scope.vote_get = function (id) {
$http({
method: "GET",
url: "vote.php",
data: {
'id': id
},
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
})
.then(function (response) {
return response.data;
});
};
When I used this function to get votes, or return anything
, It does infinite loop.
Maybe I'am trying to do in wrong way, then please give me tips how to do that.
I'am just sure, that I need to send ID of image to .php file, this file will connect to database and return votes for provided ID.
Vote_up and Vote_down functions are same, they just POST data but seem to work ok.
Thanks
So, no one has idea :(?

angular method not firing in controller after service call

I have a angular service that gets the currentuser object that is resolved as a promise. I have a partial that is filled by an object userdetails which is invoked inside a method call, but unfortunately the method call is not firing when called after the promise is resolved.
.controller('AccountCtrl', [
'$scope', 'userService', '$compile', '$http', 'utility', 'cloudService', function ($scope, userService, $compile, $http, utility, cloudService) {
$scope.userdetails = {};
$scope.downloadPageChk = $scope.paymentHistoryPageChk = $scope.manageGroupsPageChk = "hide";
$scope.getUserAttribute = function (param, x) {
return userService.getAttribute(param, x);
};
cloudService.fetchCurrentUser().then(function (newCurrentuser)
{
if (newCurrentuser)
{
$scope.currentUser = newCurrentuser;
$scope.getUserDetails = function()
{
if (userService && userService.isLoggedIn())
{
$scope.userdetails = JSON.parse(JSON.stringify(currentUser));
}
};
if (newCurrentuser == 'member') {
if (newCurrentuser.features.download) $scope.downloadPageChk = "show";
if (newCurrentuser.features.paymenthistory) $scope.paymentHistoryPageChk = "show";
if (newCurrentuser.group.enabled) $scope.manageGroupsPageChk = "show";
}
}
}
])
partial
<div data-ng-controller="AccountCtrl">
<div data-ng-init="getUserDetails()">
<input type="text" class="form-control pull-left" id="FirstName" name="FirstName" data-placeholder-attr="First Name" data-ng-model="userdetails.firstname" required>
<input type="text" class="form-control pull-left" id="LastName" name="LastName" data-placeholder-attr="Last Name" data-ng-model="userdetails.lastname" required>
</div>
</div>
please do let me know what am I missing. I am banging my head for 10 hours now, if I move user details out if that cloudservice.fetchuser().then() it atleast calls the function, not sure what is happening when i put it inside.
Similar plunk created here
http://plnkr.co/edit/pLAB4VpLU7uhlSibHzXP?p=preview
Thanks
If you only want the data to be initializied - you don't need the $scope.getUserDetails function and the data-ng-init="getUserDetails()".
Angular will execute the service fetchCurrentUser function and will populate $scope.userdetails on loading.
cloudService.fetchCurrentUser().then(function (newCurrentuser)
{
if (newCurrentuser)
{
$scope.currentUser = newCurrentuser;
if (userService && userService.isLoggedIn())
{
$scope.userdetails = JSON.parse(JSON.stringify($scope.currentUser));
}
if (newCurrentuser == 'member') {
if (newCurrentuser.features.download) $scope.downloadPageChk = "show";
if (newCurrentuser.features.paymenthistory) $scope.paymentHistoryPageChk = "show";
if (newCurrentuser.group.enabled) $scope.manageGroupsPageChk = "show";
}
}
}
}
See plunker.
EDIT:
If you want the getUserDetails function to still be triggerable after initialization you should put it outside the service method:
$scope.getUserDetails = function(){
if (userService && userService.isLoggedIn()){
$scope.userdetails = JSON.parse(JSON.stringify($scope.currentUser));
}
};
And trigger it however you want (via ng-click="getUserDetails()" from the view or $scope.getUserDetails() from the controller).
You should move the function outside the cloudservice.fetchuser().then() method. The getUserDetails method is not available to the $scope object until you call cloudservice.fetchuser().then(). But you do not do this in your partial.
.controller('AccountCtrl', [
'$scope', 'userService', '$compile', '$http', 'utility', 'cloudService',
function ($scope, userService, $compile, $http, utility, cloudService) {
$scope.userdetails = {};
$scope.getUserDetails = function(service, user) {
if (service && service.isLoggedIn()) {
$scope.userdetails = JSON.parse(JSON.stringify(user));
}
};
$scope.downloadPageChk = $scope.paymentHistoryPageChk = $scope.manageGroupsPageChk = "hide";
$scope.getUserAttribute = function (param, x) {
return userService.getAttribute(param, x);
};
cloudService.fetchCurrentUser().then(function (newCurrentuser) {
if (newCurrentuser) {
$scope.currentUser = newCurrentuser;
$scope.getUserDetails(userService, newCurrentuser);
if (newCurrentuser == 'member') {
if (newCurrentuser.features.download) $scope.downloadPageChk = "show";
if (newCurrentuser.features.paymenthistory) $scope.paymentHistoryPageChk = "show";
if (newCurrentuser.group.enabled) $scope.manageGroupsPageChk = "show";
}
}
});
});
]);
I don't see any use of ng-init directive and $scope.getUserDetails method, angular two way data binding will handle the promises. I have updated the plunker for same. http://plnkr.co/edit/IzjNqpdk0zvnHYyCnKJi?p=preview
You are running ng-init before $scope.getUserDetails has been added to $scope, because that only happens in the callback, and that's why you are having the problems.
i think you may be following some of the basic examples a little too closely. As you are getting data asynchronously you don't need ng-init
You let you controller ask the service to download the data, and when it has arrived you assign it to $scope.getUserDetails and angular will put it on the screen.
You can see the updated plnkr
Let me know if I'm missing something significant

AngularJS - Issue appending HTML to single item in ng-repeat

THE SITUATION:
I am exploring AngularJS by re-building a former project with it. I am using the angularjs-rails gem version 1.2.16. I have a page were I make an API call that returns an array of objects of music events. CONTROLLER:
d2jiveControllers.controller('VenueResultsCtrl', ['$scope','$http','$routeParams',
'$compile', '$sce', 'spotifyFactory', function($scope, $http, $routeParams,
$compile, $sce, spotifyFactory){
"use strict";
var venueId = $routeParams.venueId;
var baseUrl = 'http://api.songkick.com/api/3.0/venues/';
var apiKey = '/calendar.json?apikey=************';
var url = baseUrl + venueId + apiKey + '&jsoncallback=JSON_CALLBACK' ;
var init = function(url){
$http.jsonp(url)
.success(function (data) {
$scope.events = data.resultsPage.results.event;
console.log(data);
}).
error(function(){
console.log('failure');
});
};
$scope.tracks = function(artistName){
var artistTracks = spotifyFactory.getArtistTracks(artistName);
var spotifyIframe = $('spotifyIframe');
$scope.show_tracks = $sce.trustAsHtml("<iframe src='https://embed.spotify.com/?uri=spotify:trackset:Playlist:"+artistTracks.spotifyTracks + "'"+
"&theme=white'width='300' height='300'frameborder='0' allowtransparency='true'></iframe>")
console.log(artistTracks)
};
init(url);
}]);
I list them out using ng-repeat and attach an ng-click to each listing. HTML TEMPLATE:
<div class="eventContainer row" ng-controller="VenueResultsCtrl">
<div ng-repeat="event in events">
<h4>
{{event.displayName}}
</h4>
<p>
Buy Tickets
</p>
<div ng-repeat="artist in event.performance">
<button ng-click="tracks(artist.displayName)">Discover
{{artist.displayName}}<br> -- {{artist.billing}}</button><br><br>
<div ng-bind-html="show_tracks"></div>
</div>
</div>
</div>
On click I want to make another API call to Spotify to get back track IDs that I then place into an iframe. To do this I tried both making the call in a directive and factory:
DIRECTIVE:
d2jive.directive('getSpotifyTracks', [function () {
// <div get-spotify-tracks="artist.displayName"></div>
var spotifyUrl = "http://ws.spotify.com/search/1/track.json?callback=JSON_CALLBACK&q=";
return {
restrict: 'AEC',
scope: {
artistName: '='
},
templateUrl: 'assets/d2jive/templates/artistTracks.html',
controller: ['$scope', '$http', function($scope, $http){
$scope.getTracks = function(artistName){
$http.jsonp(spotifyUrl + encodeURIComponent(artistName))
.success(function (data) {
var trackArray = [];
var tracks = data.tracks.slice(0,9);
for (var track in tracks){
grabbedTrack = tracks[track].href.slice(
14, tracks[track].href.length);
trackArray.push(grabbedTrack);
}
$scope.artistTracks = trackArray;
console.log(data);
});
};
}],
link: function(scope, element, attrs, ctrl){
scope.$watch('artist.displayName', function(displayName){
if (displayName){
scope.getTracks(displayName);
}
})
}
}
}]);
FACTORY::
d2jive.factory('spotifyFactory', ['$http','$q', function($http, $q){
var factory = {}
factory.getArtistTracks = function(artistName){
var tracks = {}
var spotifyUrl = "http://ws.spotify.com/search/1/track.json?q=";
var deferred = $q.defer();
var getTracks = function(artistName){
$http.get(spotifyUrl + encodeURIComponent(artistName))
.success(function (data) {
deferred.resolve(data);
});
return deferred.promise;
};
// tracks.spotifyTrakcs = getTracks(artistName);
var spotifyTracks = getTracks(artistName);
spotifyTracks.then(function(result){
var trackArray = [];
var tracks = result.tracks.slice(0,9);
for (var track in tracks){
grabbedTrack = tracks[track].href.slice(
14, tracks[track].href.length);
trackArray.push(grabbedTrack);
}
tracks.spotifyTracks = trackArray;
});
return tracks;
}
return factory;
}]);
THE PROBLEM:
I can't find a way to append the iframe HTML to a particular item and not each event that is listed. The directive didn't seem to work because it loaded right away and slowed down the app way too much. That is why I went with a Factory to make the API call to Spotify and append the iframe.
THE GOAL:
On ng-click make API call to Spotify, return the track ID's, insert them into the iframe, and then insert that right below the clicked item not below all of the items.
Any help will be much appreciated! Thanks.
Inside the $watch in the link function of your directive, return early on equality between newVal and oldVal parameters:
link: function(scope, element, attrs, ctrl){
scope.$watch('artist.displayName', function(displayName, oldVal){
if (displayName === oldVal) { return }
if (displayName){
scope.getTracks(displayName);
}
})
}
That should prevent getTracks() from being called as soon as the directive links.

Categories

Resources