I'm trying to display the result of a promise in the view, but I get this exception. Other cases of this exception I've found on Google / SO are caused by mistakes I don't see in my code.
I've verified I am using promises, I am resolving the promise inside of the function passed to $timeout, I am returning the promise from the function getData() and not the function that resolves the promise.
Thanks in advance.
Viewer
<html ng-app="controller" ng-controller="MyController as controller">
<head>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.16/angular.min.js"></script>
<script type="text/javascript" src="controller.js"></script>
<script type="text/javascript" src="services.js"></script>
</head>
<body>
{{data}}
</body>
</html>
Controller (controller.js)
angular.module('controller', ['services'])
.controller('MyController', ['MyService', function(MyService) {
MyService.getData().then(function(data) {
$scope.data = data;
});
}]);
Service (services.js)
angular.module('services', [])
.factory('MyService', function($q, $timeout){
var getData = function getData() {
var deferred = $q.defer;
$timeout(function () {
deferred.resolve('Foo');
}, 5000);
return deferred.promise;
};
return {
getData: getData
};
});
Exception stack trace
TypeError: Cannot read property 'then' of undefined
at new <anonymous> (controller.js:5)
at Object.e [as invoke] (angular.js:4219)
at $get.x.instance (angular.js:8525)
at angular.js:7771
at q (angular.js:334)
at M (angular.js:7770)
at g (angular.js:7149)
at angular.js:7028
at angular.js:1460
You are not getting deferred object properly, instead deferred variable holds the function reference, you missed invocation of defer constructor.
var deferred = $q.defer();
^___Missing this
also note that you could just return $timeout as is, since it returns a promise. In your real case if you are using $http just return $http promise instead of creating a redundant promise object with deferred pattern.
.factory('MyService', function($q, $timeout){
var getData = function getData() {
return $timeout(function () {
return 'Foo' ;
}, 5000);
};
return {
getData: getData
};
Related
I'm trying to change some $rootscope variables from within a controller after a I have received a promise from a service.
The $rootscope variables are used to set the html page title attribute etc.
Below is the code I have, I created a function called changeRootPageNotFound() to change the $rootscope variables. It does not work if it's called in the promise.then function.
app.controller('mainController', ['$routeParams', '$scope', '$rootScope', 'mainService', function ($routeParams, $scope, $rootScope, mainService) {
var mainCtrl = this;
mainCtrl.id = $routeParams.itemId;
var promise = mainService.getData($routeParams.id);
promise.then(function (response)
{
if (response.data.data) {
mainCtrl.data = response.data.data;
} else {
mainCtrl.data = false;
changeRootPageNotFound();
}
});
function changeRootPageNotFound() {
$rootScope.title = "Page Not Found - 404";
$rootScope.titleSuffix = "";
}
// changeRootPageNotFound(); // works here
}]);
How can I change the $rootscope variables after I have received the deferred promise from the service?
Add a .catch method:
promise.then(function (response)
{
//if (response.data.data) {
mainCtrl.data = response.data.data;
//} else {
// mainCtrl.data = false;
// changeRootPageNotFound();
//}
}).catch(function(errorResponse) {
console.log(errorResponse.status);
mainCtrl.data = false;
changeRootPageNotFound();
throw errorResponse;
});
The $http service rejects the promise when the status is outside the range 200-299.
What is the throw errorResponse; for, can it be left out?
If the throw errorResponse is omitted, the rejection handler returns a value of undefined. This will convert the rejected promise to a fulfilled promise that resolves as undefined. If there is no further chaining, it can be left out.
A common cause of problems is programmers being unaware of this and unintentionally converting promises.
instead of .catch you can pass the same function to then as the 2nd argument
One of the subtle differences between .catch and using the 2nd argument of the .then method, is that runtime errors in the .then success handler will not be caught in the rejection handler of the 2nd argument.
According to your snippet your code should have worked. In my plunker its working after the deferred promise also.
// Code goes here
angular.module('Test',[])
.service('Service', function($q){
this.ts = function(){
var deferred = $q.defer();
deferred.resolve("hello")
return deferred.promise;
}
})
.controller('Controller', function(Service, $rootScope){
Service.ts().then(function(response){
$rootScope.title="hello";
changeRootPageNotFound();
});
function changeRootPageNotFound() {
$rootScope.title = "Page Not Found - 404";
$rootScope.titleSuffix = "";
}
});
Here is the html
<!DOCTYPE html>
<html>
<head>
<script data-require="angularjs#1.5.8" data-semver="1.5.8" src="https://opensource.keycdn.com/angularjs/1.5.8/angular.min.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body ng-app="Test">
<div ng-controller="Controller">
<h1>{{title}}</h1>
</div>
</body>
</html>
Please check this Plunker https://plnkr.co/edit/THXDYrWuTqR8UYSJlerB?p=preview
I am trying to figure out, why my test tries to find a provider for my service.
beforeEach(function () {
module('loggingModule', inject(function ($q, _loggingService_) {
var deferred = $q.defer();
var loggingService = _loggingService_;
deferred.resolve('somevalue'); // always resolved, you can do it from your spec
// jasmine 2.0
spyOn(loggingService, 'removeAndGetNext').and.returnValue(deferred.promise);
}));
});
The loggingService is part of the loggingModule and registered as service there.
Calling my test fails with
Unknown provider: loggingServiceProvider <- loggingService
If I inject my service into a controller with constructor injection it works.
Why does the test need a provider?
First setup Angular module with beforeEach(module('loggingModule')) than incject some dependencies beforeEach(inject(function() {})), like that:
angular.module('loggingModule', [])
.service('loggingService', function($q) {
this.methodUnderTest = function(attr) {
return this.removeAndGetNext(attr)
}
this.removeAndGetNext = function() {
// return $q.resolve('foo')
}
})
describe('Module `loggingModule`', function() {
var loggingService
var promise
var $rootScope
beforeEach(module('loggingModule'))
beforeEach(inject(function($q, _loggingService_, _$rootScope_) {
loggingService = _loggingService_
$rootScope = _$rootScope_
promise = $q.defer()
spyOn(loggingService, 'removeAndGetNext').and.returnValue(promise.promise)
}))
it('.methodUnderTest() calls .removeAndGetNext()', function(done) {
var mockArgument = 'some arguments'
var mockResponse = 'some resolved value'
loggingService.methodUnderTest(mockArgument).then(function(r) {
expect(r).toBe(mockResponse)
expect(loggingService.removeAndGetNext).toHaveBeenCalledWith(mockArgument)
done()
})
promise.resolve(mockResponse)
$rootScope.$apply()
});
})
<link href="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine.css" rel="stylesheet" />
<script src="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine-2.0.3-concated.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular-mocks.js"></script>
I am trying to use the $resource for REST API interaction with following code:
Inside Factory:
api.user = $resource(api.baseUrl + '/admin/login', {}, {
login: {method:'POST'}
});
Controller One:
vm.user.$login().$promise.then(function(successResult) {
console.log(successResult);
}, function(errorResult) {
console.log(errorResult);
if(errorResult.status === 404) {
}
});
I get the error : "TypeError: Cannot read property 'then' of undefined" as the $promise is undefined for the resource object.
Question 1: I read on multiple questions that one can use the $promise
property to write the callbacks. What am I doing wrong here that the
$promise property is coming as undefined for login method on user object.
Controller Two:
vm.user.$login(function(successResult) {
console.log(successResult);
}, function(errorResult) {
console.log(errorResult);
if(errorResult.status === 404) {
}
});
This properly processes the success/error handling, however on the successResult object, there are two additional properties of $promise:undefined, $resolved: true, while I was assuming successResponse should be the actual plain object returned by the server, but it looks like an instance of $resource.
Q2: Is there a way to capture the plain object returned by the server
while using the $resource, not having $resource properties?
Any help to resolve the confusion is appreciated.
You can simply call your factory method this way: vm.user.login().$promise.then(function(data) {...
Also you need to use return, as the following: return $resource(api.baseUrl + '/admin/login', {}, {...
I made a snippet, check it:
(function() {
"use strict";
angular.module('app', ['ngResource'])
.controller('mainCtrl', function(userFactory) {
var vm = this;
vm.data = {};
vm.login = function() {
userFactory.login().$promise.then(function(data) {
vm.data = data.status;
}, function(data) {
console.log(data);
});
}
})
.factory('userFactory', function($http, $resource) {
return $resource('http://api.geonames.org/citiesJSON?', {}, {
login: {
method: 'POST'
}
});
})
})();
<!DOCTYPE html>
<html ng-app="app">
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.7/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.7/angular-resource.min.js"></script>
</head>
<body ng-controller="mainCtrl as main">
Response: <pre ng-bind="main.data | json"></pre>
<hr>
<button type="button" ng-click="main.login()">Login</button>
</body>
</html>
I am newbie learning to make back end calls from my angular app's service, I am making the back end call from the angular's Service.
I am calling the function in the service from the controller.
The rest service I provided is not the actual service I am hitting, for some reasons I cannot disclose it. I am sure that the rest service I have is valid and is working, cause I was able to hit it though the controller, which is a bad way of doing it, so this is the reason i want to change the back end call to the service.
Below is my js file. Any help would be appreciated, please feel free to let me know if I am doing this wrong.
angular.module("myApp",[])
.controller("myCont", ['myService', function($http, myService){
var vm = this;
this.myUrl = "some rest service";
console.log("The controller");
vm.getDataInController = function() {
console.log("The function is called");
myService.getData(vm)
.success(function (custs) {
console.log("The data is obtained");
})
.error(function (error) {
console.log("some error occurred");
});
}
}])
.service('myService', ['$http', function ($http) {
this.getData = function (vm) {
console.log("control is in the service");
console.log(vm.myUrl);
$http({
type: 'GET',
url: vm.myUrl
// data is where we have the JSON returned in the form of OBJECT from the gis
}).then(function successCallback(response) {
console.log("the backend call worked");
}), function errorCallback(response) {
console.log("the backend call worked");
}
};
}])
;
My Html file is
<!DOCTYPE html>
<html >
<head lang="en">
<meta charset="UTF-8">
<title></title>
<script src = "angular-min.js"></script>
<script src = "sampleOneScript.js"></script>
</head>
<body ng-app = "myApp" ng-controller = "myCont as main">
{{main.myUrl}}
<br>
<button type = "button" ng-click = main.getDataInController()>Click me </button>
</body>
</html>
The error I got in the console.
TypeError: Cannot read property 'getData' of undefined
at vm.getDataInController (http://localhost:63342/exercise.weokspce/ng-repeat%20example/sampleOneScript.js:15:26)
at fn (eval at (http://localhost:63342/exercise.weokspce/ng-repeat%20example/angular-min.js:212:87), :4:275)
at f (http://localhost:63342/exercise.weokspce/ng-repeat%20example/angular-min.js:252:82)
at m.$eval (http://localhost:63342/exercise.weokspce/ng-repeat%20example/angular-min.js:132:366)
at m.$apply (http://localhost:63342/exercise.weokspce/ng-repeat%20example/angular-min.js:133:60)
at HTMLButtonElement. (http://localhost:63342/exercise.weokspce/ng-repeat%20example/angular-min.js:252:134)
at HTMLButtonElement.Hf.c (http://localhost:63342/exercise.weokspce/ng-repeat%20example/angular-min.js:35:137)
The problem may be that you are not injecting properly all the dependencies for "myCont". Try changing the line:
.controller("myCont", ['myService', function($http, myService){
with:
.controller("myCont", ['$http', 'myService', function($http, myService){
and see if that corrects things
I am trying to get hands in promises. SO i wrote a sample code like below
<!doctype html>
<html ng-app="myApp">
<head>
<meta charset="UTF-8">
<script src="../angularjs.js"></script>
</head>
<body>
<div ng-controller="CartController">
</div>
<script>
var app = angular.module('myApp', []);
app.controller('CartController', function($scope, $q,$http){
$scope.newFun = function()
{
var defered = $q.defer();
$http.get('data.json').success(function(data) {
console.log(data);
defered.resolve(data);
})
.error(function(data, status) {
console.error('Repos error', status, data);
});
return defered.promise;
}
var newdata = $scope.newFun().then(
function(data1)
{
//console.log(data1);
return data1;
});
console.log(newdata);
});
</script>
</body>
</html>
Here i am trying to return the data got from the then function and assign it to a variable. But i am getting a $$ state object, which has a value key which holds the data. Is directly assigning the value is possible or inside the then function i need to use scope object and then access the data??
Many problems with your code.. To start with: you can't return from asynchronous operations, you need to use callbacks for this. In your case since you are using promises use then API of it. Inside of its callback you would assign your data to variable. Angular will do the rest synchronizing scope bindings (by running new digest).
Next problem: don't use $q.defer(), you simply don't need it. This is the most popular anti-pattern.
One more thing: don't make any http requests in controller, this is not the right place for it. Instead move this logic to reusable service.
All together it will look something like this:
var app = angular.module('myApp', []);
app.controller('CartController', function ($scope, data) {
data.get().then(function (data) {
var newdata = data;
});
});
app.factory('data', function($http) {
return {
get: function() {
return $http.get('data.json').then(function (response) {
return response.data;
}, function (err) {
throw {
message: 'Repos error',
status: err.status,
data: err.data
};
});
}
};
});