I am new to JavaScript and AngularJS. There are couple things with JS that astounds me. For example I am trying to create a modal service and I found teh following example online. I wish to understand what is happening internally in the specific line where the modal is opened.
$scope.checkout = function (cartObj) {
var modalInstance = $modal.open({
templateUrl : 'assets/menu/directives/payment-processing-modal.tmpl.html',
controller : ["$scope", "$modalInstance", "cartObj", function($scope, $modalInstance, cartObj) {
$scope.submit = function () {
console.log("Submit");
//DB CALL HERE (with promise)
return "well";
};
$scope.cancel = function () {
console.log("Cancel");
$modalInstance.dismiss('cancel');
return "not so well";
};
}],
resolve : { // This fires up before controller loads and templates rendered
cartObj : function() {
return cartObj;
}
}
});
In the Line :
var modalInstance = $modal.open({
What I understand is the the open method is called in the $modal service with a bunch of configurations set up.
No within the controller I have my cartObj that is sent by the consuming view which I will then use to make certain CRUD operations.
My question is :
I want the consuming view to know if there was a success or a failure of teh crud operation and also to return the data. How can I implement this with a call back at the consuming view? This is getting confusing because the "Submit" logic is within the Modals's controller.
1. When I return something from here, I am unable to access it on the consuming end. How do return from within submit call?
2. How do I handle success and error based on this setup? Do I handle success and failure at modal service and return just data? Or is there a way I can do it gracefully at consuming end, like:
modalService.checkout(cartObj).success(function(response){
//success handler
}).error(function(response)){
//failure handler
}
As per your code, you are using AngularUI
So, I will just complete your code, which is a way to solve your problem
$scope.checkout = function(cartObj) {
var modalInstance = $modal.open({
templateUrl: 'assets/menu/directives/payment-processing-modal.tmpl.html',
controller: ["$scope", "$uibModalInstance", "cartObj", function($scope, $uibModalInstance, cartObj) {
$scope.submit = function() {
console.log("Submit");
//DB CALL HERE (with promise)
DB_CALL.then(function(success) {
// It resolve and pass to success fuction of the result promise
$uibModalInstance.close({ success: success });
}, function(err) {
// It rejects the result promise
$uibModalInstance.dismiss({ error: err });
});
return "well";
};
$scope.cancel = function() {
console.log("Cancel");
// It rejects the result promise
$uibModalInstance.dismiss({ error: 'cancel' });
return "not so well";
};
}],
resolve: { // This fires up before controller loads and templates rendered
cartObj: function() {
return cartObj;
}
}
}).result;
}
first of all use $uibModalInstance instead modalInstance. Read this
Now what I have done is binded modalInstance with the "result" method provided by $modal.open(), which eventually returns a Promise,
So now you can resolve the promise like this
modalIntance.then(function(success) {
/* Your success code goes here*/
}, function(err) {
/* Your failure code goes here*/
});
Hope it helps.
I figured out an old school way to approach this issue. I did not have to use promise at all. From the consumer end I just return two functions in params like so at the consuming end :
service.method(param, function(result){
//success handler
}, function(err){
//error handler
});
in the modal service, the signature changes as follows:
service.method(params, succesFunc, errorFunc){
//modal code
}
Now I will just call successFunc or errorFunc call backs when I need to based on whether the DB call was a success or not and pass the data or error message respectively in the function's param. Like:
$scope.submit = function() {
console.log("Submit");
//DB CALL HERE (with promise)
DB_CALL.then(function(success) {
// It resolve and pass to success fuction of the result promise
successFunc(success);
}, function(err) {
// It rejects the result promise
errorFunc(err);
});
return "well";
};
Hope this helps someone in this kind of use case.
Related
Please forgive me if my Title doesn't exactly match with the issue.
I have a custom Angular directive to generate kendo grids dynamically.
I am making an Ajax call to get the configuration from server.
Now issue is, directive first gets loaded and then the ajax call gets completed because of which it is throwing error in my directive.
Please let me know if there is any workaround for this.
My directive:
gridApp.directive('grid', function () {
return {
restrict: "EA",
scope: true,
template: '<div kendo-grid="mainGrid" options="gridOptions"></div>',
controller: function ($scope, $element, $attrs, gridService) {
var gridConfig = gridService.getGridConfig(); //this is undefined. However, when I check in console it gets loaded once the directive is executed.
//removed for breveity
}
};
});
My Service:
angularApp.factory('gridService', gridService);
function gridService($http, $q) {
var getGridConfig = function (gridId) {
var deferred = $q.defer();
$http.get('/Base/GetGridConfiguration?GridId=' + gridId)
.then(function (response) {
deferred.resolve(response.data);
});
return deferred.promise;
}
return {
getGridConfig: getGridConfig
};
}
promises are async. you need to use .then
gridService.getGridConfig()
.then(function(data) {
////
// code dependent on server response
////
});
RFTD link
Chnage your service to return deferred object rather than data.
return $http.get('/Base/GetGridConfiguration?GridId=' + gridId)
resolve the promise inside the directive.
var gridConfig, _self = this;
gridService.getGridConfig().then((response) => {
_self.gridConfig = response.data;
});
I am testing an angular service using karma/jasmine and one of my service functions is as follows. I need to get coverage to 100%, but can't seem to figure out how to test both success and error cases..
function getAccount(accountId) {
var defer = $q.defer(), myService;
myService = Restangular.all('Some/Url/Path');
myService.get('', {}, {
'header-info': 'bla'
})
.then(function onSuccess(response) {
defer.resolve(response);
}, function onError() {
someMethodCall();
});
return defer.promise;
}
In my corresponding .spec test file, I have:
it('should succeed in getting account', function() {
httpBackend.whenGET('Some/Url/Path').respond(200, mockResponse);
var promise = myServices.getAccount('account123');
promise.then(function(response) {
expect(response).toEqual(mockResponse);
});
it('should error out in getting account', function() {
httpBackend.whenGET('Some/Url/Path').respond(500, '');
var promise = myServices.getAccount('account123');
promise.then(function() {
expect(someMethodCall).toHaveBeenCalled();
});
Right now, both cases "pass", but I'm not getting the branch coverage for the onError case. Something seems fishy about the onSuccess case passing too.
Basically I am asking what is the correct syntax and way of writing the test cases such that I can hit both success and on error cases when I make a 200 and a 500 call to my API
Since you don't have any calls to $http in your service, I would recommend mocking Restangular instead of using httpBackend. This way your test doesn't have to know anything about the implementation details of Restangular, other than what it returns, just like your service.
Mock example:
var Restangular = {
all: function() {
return {
get: function() {
restangularDeferred = $q.defer();
return restangularDeferred.promise;
}
};
}
};
Now you can easily either resolve or reject restangularDeferred depending on what you want to test.
Set up your module to use the mock:
module('myApp', function($provide) {
$provide.value('Restangular', Restangular);
});
Example test of success case:
it('success', function() {
// If you want you can still spy on the mock
spyOn(Restangular, 'all').and.callThrough();
var mockResponse = {};
var promise = myServices.getAccount('account123');
promise.then(function(response) {
expect(response).toEqual(mockResponse);
expect(Restangular.all).toHaveBeenCalledWith('Some/Url/Path');
});
restangularDeferred.resolve(mockResponse);
// Trigger the digest loop and resolution of promise callbacks
$rootScope.$digest();
});
Example test of error case:
it('error', function() {
spyOn(anotherService, 'someMethodCall');
var mockResponse = {};
myServices.getAccount('acount123');
restangularDeferred.reject(mockResponse);
$rootScope.$digest();
expect(anotherService.someMethodCall).toHaveBeenCalled();
});
Note that I moved someMethodCall into anotherService in the example.
Demo: http://plnkr.co/edit/4JprZPvbN0bYSXFobgmu?p=preview
I've checked in How do I verify jQuery AJAX events with Jasmine? and How to test an Angular controller with a function that makes an AJAX call without mocking the AJAX call? but for some reason they don't necessarily come out with a format that works for me.
My issue is that I'm unit testing a function that makes a call to a function that fires off an AJAX request. I want to test the outer function without firing off the AJAX request (or halt that request) so I don't get a bunch of faulty data shot into the data server.
Here is my outer function (which calls the function containing the AJAX):
vm.clickSubmit = function () {
vm.loading = true; // starts the loading spinner
vm.updateData(); // makes current data current in the data model
vm.inputData = { // build the data to be submitted
...
};
vm.submit(); // calls the AJAX function
};
Here is my AJAX function:
vm.submit = function () {
var d = $.ajax({
type: "POST"
, data: angular.toJson(vm.inputData)
, url: "http://submit_the_data"
, contentType: "application/json"
, xhrFields: { withCredentials: true }
, crossDomain: true
})
.success(function (resp) {
DataService.ticketNumber = resp; // returns ticket# for the data model
})
.error(function (error) {
DataService.ticketNumber = DataService.submitError;
});
d.then(function (d) {
vm.loading = false; // stops the loading spinner
DataService.tickets = []; // empty's the array to be filled anew
$location.path('/submitted'); // success splash html
$scope.$apply();
});
};
I've written all the tests that will read and verify the values in the inputData object, but I'm not sure how to surround the call to clickSubmit() so nothing is actually submitted to the server. I've gotten to this point in my unit testing:
'use strict';
describe('Controller: HomeController', function () {
beforeEach(module('tickets'));
var controller, scope, $location, DataService;
var tests = 0;
beforeEach(inject(function ($rootScope, $controller, _$location_, _DataService_) {
$location = _$location_;
DataService = _DataService_;
scope = $rootScope.$new();
controller = $controller('HomeController', {
$scope: scope
});
}));
afterEach(function () {
tests += 1;
});
describe('clickSubmit should verify data and submit new ticket', function () {
beforeEach(function () {
jasmine.Ajax.install();
controller.loading = false;
... // inputData fields filled in with test data
});
afterEach(function () {
jasmine.Ajax.uninstall();
});
it('should start the spinner when called', function () {
controller.clickSubmit();
expect(controller.loading).toBeTruthy();
});
// other it('') tests
});
it('should have tests', function () {
expect(tests).toBeGreaterThan(0);
});
});
So what should go after the expect on the loading spinner to cancel the call to vm.submit() in the actual code?
Thanks,
-C§
I suggest mocking out the call rather than actually calling it. The granularity is up to you, you can either stub the ajax call, or stub the whole submit function.
Here is how you can stub the submit function:
spyOn(controller, 'submit').and.callFake(function() {
DataService.ticketNumber = somevalue;
});
Place that code prior to the actually call to the controller which caller.clickSubmit().
You can then follow up with expectations on the spy, such as:
expect(controller.submit).toHaveBeenCalled()
Or any of the other expectations related to spyOn.
Here are the jasmine docs: http://jasmine.github.io/2.0/introduction.html
Look down in the 'spies' area.
If you want to mock up the ajax call, you will have to mock a promise like this:
spyOn($, 'ajax').and.callFake(function() {
var deferred = $q.defer();
deferred.resolve(someResponse);
return deferred.promise;
});
Also, in order to get the code waiting on the promise to resolve, after the submit call has been made, you need to run a $scope.$digest() so that angular can handle the promise resolution. Then you can check your expectations on code that depended on the resolution or rejection of the promise.
Please forgive me if this is a simply problem for an angular guru, i am fairly new to services.
Below is a snippet of my controller where i have attempted make a service request to call out data from my JSON file "jobs.json".
I am not receiving an data when i load my web page neither i am seeing the JSON file in inspector element.
I assume there's something incorrect in my below code. Does anyone what the issue is?
Click here if you need to play about with the code
"use strict";
var app = angular.module("tickrApp", []);
app.service("tickrService", function ($http, $q){
var deferred = $q.defer();
$http.get('app/data/items.json').then(function (data){
deferred.resolve(data);
});
this.getItems = function () {
return deferred.promise;
}
})
.controller('tickCtrl', function($scope, tickrService) {
var promise = tickrService.getItems();
promise.then(function (data){
$scope.items= getData;
console.log($scope.items);
});
In your Plunkr, you had a few errors, such as the <script> tags around the wrong way (you need to have Angular first, so your code can then use angular.module). You also had the wrong attribute of ng-app-data instead of data-ng-app.
The key problem was with the JS code, the first parameter to the success handler for the $http.get() call is an object with a data property, which is the actual data returned. So you should resolve your promise with that property instead.
Then in the controller, like Michael P. said, getData is undefined, you should use the data parameter passed in.
app.service("tickrService", function($http, $q) {
var deferred = $q.defer();
$http.get('jobs.json').then(function(response) {
deferred.resolve(response.data);
});
this.getjobs = function() {
return deferred.promise;
}
})
.controller('tickCtrl', function($scope, tickrService) {
var promise = tickrService.getjobs();
promise.then(function(data) {
$scope.jobs = data;
console.log($scope.jobs);
});
});
See forked Plunkr.
In the success handler of your getItems function, you are storing getData, which is undefined. You want to store data instead.
Therefore, in the controller, your call to getItems() should be as follows
tickrService.getItems().then(function (data) {
$scope.items = data;
});
Also, you want to make the $http call in getItems. Like that :
this.getItems = function () {
var deferred = $q.defer();
$http.get('app/data/items.json').then(function (data) {
deferred.resolve(data);
});
return deferred.promise;
}
However, you can avoid the above boilerplate code around the promises, because $http.get returns itself a promise. Your service and controller could be much more concise and less polluted by boilerplate code.
The service could be as simple as :
app.service("tickrService", function ($http) {
this.getItems = function () {
return $http.get('app/data/items.json');
}
});
And the controller could be shortened to:
app.controller('tickCtrl', function ($scope, tickrService) {
tickrService.getItems().then(function (response) {
$scope.items = response.data;
})
});
Please note that the response resolved by $http is an object that contains (link to doc) :
data – The response body transformed with the transform functions.
status – HTTP status code of the response.
headers – {function([headerName])} – Header getter function.
config – The configuration object that was used to generate the request.
statusText – HTTP status text of the response.
Therefore in the success handler of getItems we are storing response.data, which is the response body, and not the whole response object.
I am trying to build a factory to act as a staging area for my database models, as well as an api to perform basic CRUD calls. I want to be able to access data by storing it in a service or a factory, and keep api methods along with it so I can perform actions like these in the controller.
$scope.folders = Folders.data(); // for a factory
$scope.folders = Folders.data; // for a Service
Folders.create({name: "My Stuff, $oid: { 5fwewe033333 }, user_id: CurrentUser.id"});
Currently I am using the Folder factory like this in the controller.
Folders.foldersData().success( function(data, status) {
$scope.folder = data;
})
.error( function(data,status) {
Flash.warning("There was a problem fetching your data");
});
I know I can just have a promise resolved in the controller, but with the size of the project I'm working on, I like the idea of accessing the Folders model in a service, with out having to make a server call to sync the data every time I make a change.
angular.module('cmsApp')
.factory('Folders', function($http, $q){
var folders = {};
var messageWarn = "Upload Retrival Failed.";
return {
get: function(){
var defered = $q.defer();
$http.get('/folders').success( function ( data, status ) {
defered.resolve(data);
})
.error( function ( data, status ) {
defered.reject();
Flash.warning(message_warn);
});
defered.promise.then( function (promise)
folders = promise;
});
},
data: function (){
return folders;
},
}
});
My problem is that I can't keep the folders object to persist after I call Folders.get(). It always comes back after I call Folders.data() as an empty object.
Is there a way to keep this data stored in the Factory as a up-to-date representation of the Folder model that is not dependent on hitting the server every time?
Running angular 1.2.3, on a Rails 4 API.
You can store the promise in the service as an object on the service. I forked the expanded demo above to demonstrate http://plnkr.co/edit/2HqQAiD33myyfVP4DWg3?p=preview
As with the previous examples, the http call is only made once but this time the promise is added to the folders item on the service object which gets created by the factory.
app.factory('myService', function($http, $q) {
return {
myObject: '',
get: function() {
// Create the deffered object
var deferred = $q.defer();
if(!this.myObject) {
// Request has not been made, so make it
$http.get('my-file.json').then(function(resp) {
console.log('Making the call!');
deferred.resolve(resp.data);
});
// Add the promise to myObject
this.myObject = deferred.promise;
}
// Return the myObject stored on the service
return this.myObject;
}
};
});
In this example, the service essentially IS the data. The first time the service is injected, a promise is created and the call goes out. The service is actually the promise of that data, but when the call comes back, the promise is resolved with the data. So, the second, third, etc time the service is injected, the call isn't made again - the factory has already done its job and returned the service (the promise in this example).
Live demo (click).
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope, myService) {
myService.then(function(data) {
$scope.data = data
})
});
app.factory('myService', function($http, $q) {
//this runs the first time the service is injected
//this creates the service
var promise = $http.get('my-file.json').then(function(resp) {
return resp.data;
});
return promise;
});
Here's an expanded demo in which I use the data twice. Note the console log - the call is only ever made once. In this demo, I made the service into an object with a get method that returns the promise. I just wanted to demonstrate another way this technique could be implemented.
app.factory('myService', function($http, $q) {
console.log('Making the call!');
var promise = $http.get('my-file.json').then(function(resp) {
return resp.data;
});
var myService = {
get: function() {
return promise;
}
};
return myService;
});