Synchronous call - javascript

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)
}

Related

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

AngularJS Factory adds $promise and $resolve to object but not sure how to resolve it correctly?

I have a Factory in AngularJS, which serves as the bridge between my app and my back-end. When I try to add a DELETE function to my factory, it creates a weird URL which includes $promise and $resolved elements.
This is my AngularJS code. Basically: I am not sure where the $promise and $resolve statements are coming from (they should come from the getById statement right?)
This is the error:
DELETE http://localhost:8080/rest/speaker?$promise=%7B%7D&$resolved=false 415 (Unsupported Media Type)
1/ Controller
$scope.delete = function(id) {
var speaker = SpeakerService.getById(id);
SpeakerService.delete(speakerOrEmpty, function () {
$log.log('saved at: ' + new Date());
$route.reload();
});
};
2/ Service
this.getById = function (id, callback) {
return SpeakerResource.get({speakerId: id}, callback);
};
this.delete = function (data, callback) {
SpeakerResource.delete(data);
}
3/ Factory/Resource
{
'delete': { method: 'DELETE' }
})
What I have tried is to update my getById statement to this, but without success as I am still seeing the $resolved and $promise items in my DELETE request, although now they are set to TRUE.
DELETE http://localhost:8080/rest/speaker?$promise=%7B%7D&$resolved=true&id=5419e8…5c1cde009%22,%22name%22:%22x%22%7D&organizationId=541a3eee772fb8f5c1cde009 415 (Unsupported Media Type)
$scope.delete = function(id) {
var speakerOrEmpty = SpeakerService.getById(id, function (data) {
speakerOrEmpty = data;
SpeakerService.delete(data, function () {
$log.log('saved at: ' + new Date());
$route.reload();
});
});
};
Thank you so much for any help. It is much appreciated.

Extend a function stored in a variable

I have a large JavaScript project that makes several Ajax web service calls. The code to handle the web service calls comes from a shared external file.
To separate the web service from calling code, there is a global object to reference to the calling function like so
var doRemote ={};
$(document).ready(function(){
doRemote =getRemoteEndpoint('https://someplace.org/MyWebService.aspx');
}
A simplified version of the getRemoteEndpoint, which is in a file shared by several other pages in addition to the one I'm working on is as follows:
function getRemoteEndpoint(url) {
return function(methodName, options) {
var extension = {
url: url + '/' + methodName,
data: {},
async: true
};
var combined = $.extend({}, extension, options);
combined.data = JSON.stringify(combined.data);
return $.ajax( combined );
};
}
I invoke the web service calls by the following code
doRemote('WebServiceMethodName',
{
success: function(data) {
alert('Web Service Returned' + data);
},
error: function(req, stat, err) {
alert('Error');
}
});
I have the need to execute a function before executing the getRemoteEndpoint call in only the page I'm working on. Instead of calling the function before each of the 30 web service calls, I'd like to add a line of code to the function. I've tried to replace the doRemote assignment with the following.
doRemote =function() {
DoTask();
return getRemoteEndpoint('https://someplace.org/MyWebService.aspx');
};
DoTask is a named function in the program I'm working on. While it throws no errors, none of the Ajax calls work.
I tried to use the JQuery.extend function, but it didn't work either.
What am I doing wrong?
You have to actually call it to assign the result of getRemoteEndpoint to doRemote:
doRemote = (function() {
DoTask();
return getRemoteEndpoint('https://someplace.org/MyWebService.aspx');
})();
Update:
doRemote = (function() {
var oldDoRemote = getRemoteEndpoint('https://someplace.org/MyWebService.aspx');
return function(a1, a2) {
DoTask();
oldDoRemote(a1, a2);
}
})();

AngularJS ng-click and callback function

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);
}
});
}

Fake Ajax request

In order to check the submission of an ajax request, when a user submit a form, I implemented the following test using jasmine (1).
The test is successfully passed but looking at javascript console I get the following error 500 (Internal Server Error).
Since I don't care the server response, my questions are:
1) Should I or should I not care about this error.
2) Will it be better to fake the ajax request to avoid Internal Server Error? if yes, how in my context?
(1)
define([
'backendController'
], function (backendController) {
// some code
describe('When button handler fired', function () {
beforeEach(function () {
spyOn(backendController, 'submitRequest1').andCallThrough();
this.view = new MyView({
el: $('<div><form><input type="submit" value="Submit" /></form></div>')
});
this.view.$el.find('form').submit();
});
it('backendController.submitRequest1 should be called', function () {
expect(backendController.submitRequest1).toHaveBeenCalled();
});
});
});
(2)
// backendController object
return {
submitRequest1: function (message) {
return $.ajax({
url: 'someUrl',
type: 'POST',
dataType: 'json',
data: {
message: message
}
}).done(function () {
// some code
});
}
}
(3)
// submitForm in MyView
submitForm: function (event) {
event.preventDefault();
var formElement = event.currentTarget;
if (formElement.checkValidity && !formElement.checkValidity()) {
this.onError();
} else {
backendController. submitRequest1('some data').done(this.onSuccess).fail(this.onError);
}
}
Your test says "backendController.submitRequest1 should be called" so you only care about the method to be called. So, just don't andCallThrough() and you'll be free of troubles.
Update
Maybe you have to preventDefault() in the submit event so:
spyOn(backendController, 'submitRequest1').andCallFake(function(e) { e.preventDefault(); });
Update 2
After checking the rest of your code you have added to the question I still suggest to stop your code execution as sooner as possible. Just until the test in arriving, and this is still until backendController.submitRequest1() is called.
In this case is not enough with a simple mock in the method due the return of this method is used subsequently and your mock should respect this:
// code no tested
var fakeFunction = function(){
this.done = function(){ return this };
this.fail = function(){ return this };
return this;
}
spyOn(backendController, 'submitRequest1').andCallFake( fakeFunction );
When things start to be so complicate to test is a symptom that maybe they can be organized better.

Categories