AngularJS ng-click and callback function - javascript

On my web application, there are two kinds of users: guests & logged. The main page loads the same content for each.
My goal :
When a registered user clicks the link, 2 ajax requests ($http) retrieve
the data of another page and load them in a model.
If the user is a guest, another model appears saying that he has to register.
My link :
<h4 ng-click="guestAction($event, showOne($event,card.id));">click me</h4>
GuestAction :
$scope.guestAction = function($event, callbackB) {
$http.get('/guest/is-guest/').success(function(data) {
console.log("isGuest retrieved : " + data);
if (data == 1)
{
alert('guest spotted !');
return false;
}
else
{
alert('user');
console.log(callbackB);
eval('$scope.'+callbackB);
}
});
}
This way, if a guest is spotted, we return false and stop the execution. If it's a regular user, we execute the function showOne. As I want to do 2 asynchronous requests one after the other, I chose to use the callback trick.
The problem is that showOne() is executed directly when ng-click is launched. I tried to pass showOne() as a string, and eval() the string in GuestAction, but the parameters become undefined...
Any idea how to solve this problem? I want to use a generic method which fires a function only if the user is logged.

I would recommend using a service and promises, see this AngularJS $q
You don't have to use a service for $http requests but that is just my preference, it makes your controller a lot cleaner
Here is the service with the promise:
app.factory('myService', function ($http, $q) {
var service = {};
service.guestAction = function () {
var deferred = $q.defer();
$http.get('/guest/is-guest/').success(function(data) {
console.log("isGuest retrieved : " + data);
if (data == 1) {
deferred.resolve(true);
} else {
deferred.resolve(false);
}
}).error(function (data) {
deferred.reject('Error checking server.');
});
return deferred.promise;
};
return service;
});
And then in our controller we would call it something like so:
app.controller('myController', function ($scope, myService) {
$scope.guestAction = function($event, card) {
myService.guestAction().then(function (data) {
if (data) {
alert('guest spotted !');
} else {
alert('user');
// Then run your showOne
// If this is also async I would use another promise
$scope.showOne($event, card.id);
}
}, function (error) {
console.error('ERROR: ' + error);
})
};
});
Now obviously you may have to change things here and there to get it working for your needs but what promises do is allow you to execute code and once the promise is returned then continue, I believe something like this is what you are looking for.

You have to pass functions as parameters without the parenthesis and pass in the parameters separately:
<h4 ng-click="guestAction($event,card.id, showOne);">click me</h4>
and
$scope.guestAction = function($event,id, callbackB) {
$http.get('/guest/is-guest/').success(function(data) {
console.log("isGuest retrieved : " + data);
if (data == 1)
{
alert('guest spotted !');
return false;
}
else
{
alert('user');
callbackB($event,id);
}
});
}

Related

Angular not updating Service variable

Following is the code that makes an http request to MyApp's API for user profile data(like name, photo) to update the navbar.
var app = angular.module('MyApp', ['ng']);
app.controller('navBarController', function($scope, $userProfile) {
$scope.userProfile = $userProfile;
console.log("$userProfile: " + JSON.stringify($userProfile));
setTimeout(function() {
console.log("$userProfile: " + JSON.stringify($userProfile));
}, 3000);
});
app.factory('$userProfile', function($http) {
var profile = null;
$http.
get('/api/v1/me').
success(function(data) {
profile = data;
console.log("profile after get: " + JSON.stringify(profile));
}).
error(function(data, $status) {
if ($status === status.UNAUTHORISED) {
profile = null;
console.log("profile if error: " + JSON.stringify(profile));
}
});
console.log("profile (finally): " + JSON.stringify(profile));
return profile;
});
app.directive('navBar', function() {
return {
controller: 'navBarController',
templateUrl: 'templates/nav_bar.html'
}
});
I am console logging to check for the unexpected results I am getting and the logs are as follows:
profile (finally): null
$userProfile: null
profile after get:
{"photo":"http://localhost:3000/img/1023.jpg","name":"Utkarsh Gupta"}
$userProfile: null
The first two log msgs are obvious as $http.get() is asynchronous so the profile is null as defined at the start of the function. But after the $http.get() function returns successfully, the profile var got updated as shown in the third log msg but the $userProfile service continues to be null.
Looks like your service is not injected into the controller.
Have you tried it that way?
app.controller('navBarController', ["$scope", "$userProfile", function($scope, $userProfile) {}]
Example here:
https://docs.angularjs.org/guide/services
In your service, you have declared var profile = null; and triggering the $http api call then immediately returning the variable profile which is null at the time of returning and later you are updating the variable once the api got response and you are expecting it should be propagated to the controller which is not the case.
As service is singleton in nature, instance will be created once and
never created/updated.
Hence, your code is not a recommended one to use a service. I have updated the code below where service will return a method called load to call the api which is getting triggered from the controller where $scope can be directly assigned with the response data.
var app = angular.module('MyApp', ['ng']);
app.controller('navBarController', function($scope, $userProfile) {
$userProfile.load().
success(function(data) {
$scope.userProfile = data;
console.log("profile after get: " + JSON.stringify($scope.userProfile));
}).
error(function(data, $status) {
if ($status === status.UNAUTHORISED) {
$scope.userProfile = null;
console.log("profile if error: " + JSON.stringify($scope.userProfile));
}
});
});
app.factory('$userProfile', function($http) {
var getProfile = function() {
return $http.
get('/api/v1/me');
};
//console.log("profile (finally): " + JSON.stringify(profile));
return {
load: getProfile
};
});
app.directive('navBar', function() {
return {
controller: 'navBarController',
templateUrl: 'templates/nav_bar.html'
}
});
Note: Also, please don't use $ prefix to service, variable, controller names as this is reserved to AngularJS and may create
conflicts when you use the same name as AngularJS reservered keywords/services.
You need to first fix your services (factory). It needs to return and object. Right now you are just running async code in your service, no way for your controller to use it. Second once you fix your service (look at the code below) you need to create a function to get the user profile. The user profile function needs to return a promise since you are working with async code. Again look at the code below and I hope it helps.
var app = angular.module('MyApp', ['ng']);
app.controller('navBarController', function($scope, $userProfile) {
$userProfile.get().then(function(response){
$scope.userProfile = response;
});
setTimeout(function() {
console.log("$userProfile: " + JSON.stringify($userProfile));
}, 3000);
});
app.factory('$userProfile', function($http) {
var self = {};
self.get = getProfile;
return self;
function getProfile(){
var profile = null;
return $http.get('/api/v1/me')
.success(function(data) {
return data.data;
})
.error(function(data, $status) {
if ($status === status.UNAUTHORISED)
return profile;
});
}
});
Instead of initialising profile as null at the top, you must initialise it as:
var profile = {};
And then tack on to that empty profile object your API returned data like:
$http.
get('/api/v1/me').
success(function(data) {
//like this
profile.data = data;
})
In your code, when the $userProfile-service function finishes it returns the profile simply as null. And even after the $http.get request is complete your $userProfile has the same null because now the updated profile variable is not accessible anymore as it is out of scope. But if you initialize and return profile as an object, you can access it even after the function has returned because objects are passed by reference in javascript. You can then access the content of the profile object in your controller the way you are alreday doing because now $userProfile is the same exact profile object that you declared above and not a null anymore and any update on that profile object anywhere in your entire code will be reflected wherever that object is being accessed.

Angular 1: Defer html rendering after http call

In my Angular controller I have a http call to a REST service that returns data in a database. This data are shown in a table in the partial view.
It happens random that the render of the html view is done before to get the callback with data, so I see a void table.
Here the code in the controller (I use services for some business logic and to implement the http call):
commonServices.find(vm.modelUri, null, vm.filter, function (err, msg, data) {
if (err || !data.length) {
$scope.noResults = true;
return;
}
$scope.docs = data; //docs is bind in the view
return;
});
Here the service for the http call:
function _commonServices(config, $http, $rootScope, $cookies) {
return {
find: function _find(modelUri, id, filter, callback) {
var url = config.servicesUri + '/' + modelUri;
if (id) {
url += '/' + id;
}
if (filter) {
if (typeof filter !== 'string') {
filter = JSON.stringify(filter);
}
url += '?filter=' + filter;
if (document.cookie) {
url += '&' + accessToken;
}
} else {
if (document.cookie) {
url += '?' + accessToken;
}
}
$http.get(url)
.then(function (data) {
//success
return callback(null, null, data.data);
},
function (data) {
//error
var err = true;
return callback(err, data.data.error.message);
});
}
}
}
The find service is used in other controllers and it seems it works good. I would know if it is possible to defer the render of the html table until the data are ready to be shown.
I would suggest the use of Angular's ng-cloak. It is a directive to prevent the html from being displayed until your angular app is finished loading. Check out the documentation here: ng-cloak

When to call $scope.apply() for webservice callbacks

I am currently developing an Angular JS project where in I call a webservice and then in the success callback update a textbox value based on the result.
Now, my webservice gets called a bit late and the success callback takes some time. In the success callback I have updated a textbox value as well as hidden a loading progress dialog.
However the progress dialog is never hidden and textbox is not updated if I don't use $scope.apply().
If the use the same, it gets applied. What is the purpose of $scope.apply().
What are the best practices of using it. Can it be used in situations like mine. I have also tried using $timeout . Even that works but I don't think its a preferred approach.
Here is my code
//the service implementation
app.service('registerService', function ($http, APP_CONFIG, $q, spinnerService) {
this.callService = function (request) {
spinnerService.show('mySpinner')
var deferred = $q.defer();
$http({
method: 'POST',
url: APP_CONFIG.baseUrl + '/register',
data: request
}).success(function (data) {
deferred.resolve(data);
}).error(function () {
deferred.reject('There was an error while making request');
});
return deferred.promise;
};
});
//calling the service from inside the controller
registerService.callService(JSON.stringify(requestData)).then(function (data) {
$scope.$apply(function () {
spinnerService.hide('mySpinner');//hide loading..this works!
});
spinnerService.hide('mySpinner');//hide loading ..this does not if I remove $scope.apply()
}, function () {
//unable to fetch NLS resource
spinnerService.hide('mySpinner');//hide loading
});
}
//spinner service implemtation
app.factory('spinnerService', function () {
var cache = {};
return {
// A private function intended for spinner directives to register themselves with the service.
_register: function (spinnerScope) {
// If no id is passed in, throw an exception.
if (!spinnerScope.id) {
throw new Error("A spinner must have an ID to register with the spinner service.");
}
// Add our spinner directive's scope to the cache.
cache[spinnerScope.id] = spinnerScope;
},
// A private function exposed just in case the user really needs to manually unregister a spinner.
_unregister: function (spinnerId) {
delete cache[spinnerId];
},
// A private function that will remove an entire spinner group if needed.
_unregisterGroup: function (group) {
for (var spinnerId in cache) {
if (cache.hasOwnProperty(spinnerId)) {
if (cache[spinnerId].group === group) {
delete cache[spinnerId];
}
}
}
},
// A private function that will clear out all spinners from the cache.
_unregisterAll: function () {
for (var spinnerId in cache) {
if (cache.hasOwnProperty(pinnerId)) {
delete cache[spinnerId];
}
}
},
// Show the specified spinner.
// If loadingText is specified, replace the loadingText specified on the directive as we show the spinner.
show: function (spinnerId, loadingText) {
$("body").find("#loading").addClass("mydiv");
if (cache.hasOwnProperty(spinnerId)) {
var spinnerScope = cache[spinnerId];
spinnerScope.showSpinner = true;
if (loadingText !== undefined) {
spinnerScope.loadingText = loadingText;
}
}
},
// Hide the specified spinner.
// If doneText is specified, replace the doneText specified on the directive as we hide the spinner.
hide: function (spinnerId, doneText) {
if (cache.hasOwnProperty(spinnerId)) {
var spinnerScope = cache[spinnerId];
$("body").find("#loading").removeClass("mydiv");
spinnerScope.showSpinner = false;
if (doneText !== undefined) {
spinnerScope.doneText = doneText;
}
}
}
};
});

Synchronous call

I have a web application where my app's front-end uses angular js. I have a method in the angular js controller which is called on-click of a button. And in that method, I call the angular service to send a request to the Java controller. The logic looks like this
var submitted= true;
submitted= sampleService.sampleMethod(sampleParam);
alert(submitted);
if(!submitted){
//some action
}
The service will return true if the request was successful, false if it failed.
The issue that i'm having is that I get the alert before the request is sent (the alert says undefined). So regardless the response, the if condition fails.
Is there a solution for this issue?
edit :
The sample method
this.sampleMethod = function (sampleServiceMethod, obj){
var modalInstance = $modal.open({
templateUrl: 'example.html',
controller: 'anotherController',
resolve: {
modalServiceMethod: function () {
return sampleServiceMethod;
}
}
});
modalInstance.result.then(function (modalServiceMethod) {
modalServiceMethod(obj).then(function(response){
$window.location = response.sampleUrl;
}, function(err) {
$log.error("error occured", err);
});
}, function () {
$log.debug('error on: ' + new Date());
}).finally(function() {
return false;
});
};
Basically if the request is successful, the page will be redirected. But I need the return value of false during failure to make necessary changes.
** Update #1 **
I could help better if you organize and rename the code a bit. Hard to follow with all the names when they don't mean anything.
As a first thing try adding this return statement before the modalInstance::
return modalInstance.result.then(function (modalServiceMethod) {
modalServiceMethod(obj).then(function(response){
$window.location = response.sampleUrl;
}, function(err) {
$log.error("error occured", err);
});
}, function () {
$log.debug('error on: ' + new Date());
}).finally(function() {
return false;
});
** Original answer **
Why do you declare submitted as true and then run run the function on it?
It looks like this function:
sampleService.sampleMethod(sampleParam);
return undefined.
Add the code of that function so we can look into it.
If that function sends a request to the server to fetch data, it will be returned as a promise. in that case your code should be:
sampleService.sampleMethod(sampleParam).then(function(response){
submitted = response.data;
conole.log(submitted)
});
but the fact you get undefined in your alert and not a promise object, indicates you probably missed a "return" statement in your sampleService.sampleMethod(sampleParam) method. that method should look something like this:
function sampleMethod(param) {
return $http.get('url', param)
}

Ajax with external parameter Angular

I am new to angular and what I am willing to do is replace a piece of code I wrote in the past in jquery to angularjs.
The goal is to take a string from a span element, split it in two and pass the two strings as parameters in a GET request.
I am trying to learn best coding pratices and improving myself so any comments of any kind are always welcome.
Working Code in jquery:
//Get Song and Artists
setInterval(function () {
var data = $('#songPlaying').text();
var arr = data.split('-');
var artist = arr[0];
var songTitle = arr[1];
//Request Lyrics
$.get('lyricsRequester.php', { "song_author": artist, "song_name" : songTitle},
function(returnedData){
console.log(returnedData);
$('#refreshLyrics').html(returnedData);
});
},10000);
Code in Angular
var app = angular.module("myApp", []);
app.factory('lyricService', function($http) {
return {
getLyrics: function($scope) {
//$scope.songArr = $scope.currentSong.split('-'); <-- **undefined currentSong**
//$scope.artist = $scope.songArr[0];
//$scope.songTitle = $scope.songArr[1];
return
$http.get('/lyricsRequester.php', {
params: {
song_author: $scope.artist,
song_name: $scope.songTitle
}
}).then(function(result) {
return result.data;
});
}
}
});
app.controller('lyricsController', function($scope, lyricService, $interval) {
$interval(function(){
lyricService.getLyrics().then(function(lyrics) {
$scope.lyrics = lyrics; <-- **TypeError: Cannot read property 'then' of undefined**
console.log($scope.lyrics);
});
}, 10000);
});
index.html (just a part)
<div class="col-md-4" ng-controller="lyricsController">{{lyrics}}</div>
<div class="col-md-4"><h3><span id="currentSong" ng-model="currentSong"></span></h3><div>
You need to be careful with your return statement when used in conjunction with newlines, in these lines:
return
$http.get('/lyricsRequester.php',
If you don't, JS will automatically add a semicolon after your return, and the function will return undefined.
Move the $http.get statement to the same line as your return statement.
return $http.get('/lyricsRequester.php', ...
Refer to the following docs:
MDN return statement
Automatic Semicolon Insertion
As for your second issue, you $scope is not really something you inject into your services (like $http). Scopes are available for use in controllers.
You need to refactor your code a bit to make things work.
eg. Your getLyrics function can take a song as a parameter. Then in your controller, you call lyricsService.getLyrics(someSong). Scope access and manipulation are only done in your controller.
app.factory('lyricService', function($http) {
return {
getLyrics: function(song) {
var songArr = song.split('-');
var artist = songArr[0];
var songTitle = songArr[1];
return $http.get('/lyricsRequester.php', {
params: {
song_author: artist,
song_name: songTitle
}
}).then(function(result) {
return result.data;
});
}
}
});
app.controller('lyricsController', function($scope, lyricService) {
$scope.currentSong = 'Judas Priest - A Touch of Evil';
$interval(function(){
lyricService.getLyrics($scope.currentSong).then(function(lyrics) {
$scope.lyrics = lyrics;
console.log($scope.lyrics);
});
}, 10000);
});
You also have some other issues, like using ng-model on your span. ng-model is an angular directive that is used in conjunction with form elements (input, select etc.), not a span as you have. So you might want to change that into an input field.
$http does not use .then, it uses .success and .error. the line that you have where it says then is undefined, should be replaced with a success and error handler instead. Below is a sample from the docs:
// Simple GET request example :
$http.get('/someUrl').
success(function(data, status, headers, config) {
// this callback will be called asynchronously
// when the response is available
}).
error(function(data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
See Link:
https://docs.angularjs.org/api/ng/service/$http

Categories