Angularjs data is null from async service in scope - angularjs

thanks in advance for read my post.
I have a service, like this:
angular.module('myApp.services')
.factory('myService', function($http, $q) {
var myService = {
getId: function() {
var defer = $q.defer();
$http.get('http://xxxxxxxxxxxxxxxxxx/id/')
.success( function(data) {
defer.resolve(data);
})
.error( function(data) {
defer.reject(data);
});
return defer.promise;
},
begin : function(jsonBegin) {
var defer = $q.defer();
$http.post('http://xxxxxxxxxxxxxxxxxxxxxxx/begin/?format=json', jsonBegin)
.success( function(data) {
defer.resolve(data);
})
.error( function(data) {
defer.reject(data);
});
return defer.promise;
}
};
return myService;
});
A parent controller (works fine):
angular.module('myApp.controllers')
.controller('controllerA', function($scope, myService) {
myService.getId()
.then(function(data) {
$scope.bID = data.bID;
});
});
And child controller:
angular.module('myApp.controllers')
.controller('controllerB', function($scope) {
console.log($scope.$parent.bID);
});
The console.log value for bID is undefined, do u know why? I am setting that variable in the parent controller trough myService service. I guess my problem is due to the asynchronous call but I'm not sure.
Thanks.

You could do something like this:
angular.module('myApp.controllers')
.controller('controllerA', function($scope, myService) {
$scope.bID = myService.getId()
.then(function(data) {
return data.bID;
});
});
angular.module('myApp.controllers')
.controller('controllerB', function($scope) {
$scope.$parent.bID.then(function(bID) { console.log(bID); });
});
The child scope will wait for the parent to resolve bID. And when parent bID is already resolved, it will immediately return the value in the child scope promise. It's a little messy looking though.
The alternative is to have the myService request resolve before the controller loads. Check this out:
http://www.codelord.net/2015/06/02/angularjs-pitfalls-using-ui-routers-resolve/
Then you can set the parent scope to the resolved value and access it as a normal property in the child scope.

The problem is that the console.log code is excuted when the controller is initialized, in that time the response do't arrive with the results on the parent controller.
Try to put this code on your test controller.
angular.module('myApp.controllers')
.controller('controllerB', function($scope, $timeout) {
// waith 1second for the server response on the parent controller
$timeout(function(){
console.log($scope.$parent.bID);
}, 1000);
});
FYI. This work console.log($scope.bID) check scope inheritance.

Related

Unit test an angular controller and service which uses a promise?

I cannot get the test result to pass I'm using a very basic implementation to understand testing deeper.
I have a factory which returns a promise, accessed from my controller. I want to test that the call succeeds and assigns the response to the repos var. Following is the code:
'use strict';
angular.module('app')
.factory('searchServ', function ($timeout, $q, $http) {
return {
fetch: function(user) {
var deferred = $q.defer();
$timeout(function(){
$http({method: 'GET', url: 'https://api.github.com/users/' + user + '/repos'}).then(function(repos) {
deferred.resolve(repos.data);
}, function(reason){
deferred.reject(reason.status);
console.log(reason);
});
}, 30);
return deferred.promise;
}
};
})
.controller('MainCtrl', function ($scope, searchServ) {
$scope.results = function(user) {
$scope.message = '';
searchServ.fetch(user).then(function (repos) {
if(repos.length){
$scope.message = '';
$scope.repos = repos;
}
else{
$scope.message = 'not found'
}
}, function (){
$scope.message = 'not found';
});
};
});
//Test
'use strict';
describe('MainCtrl', function () {
var scope, searchServ, controller, deferred, repos = [{name: 'test'}];
// load the controller's module
beforeEach(module('app'));
beforeEach(inject(function($controller, $rootScope, $q) {
searchServ = {
fetch: function () {
deferred = $q.defer();
return deferred.promise;
}
};
spyOn(searchServ, 'fetch').andCallThrough();
scope = $rootScope.$new();
controller = $controller('MainCtrl', {
$scope: scope,
fetchGithub: fetchGithub
});
}));
it('should test', function () {
expect(scope.test).toEqual('ha');
});
it('should bind to scope', function () {
scope.results();
scope.$digest();
expect(scope.message).toEqual('');
//expect(scope.repos).not.toBe(undefined);
});
});
Running the test gives me the following error :
TypeError: undefined is not a function (evaluating 'spyOn(searchServ, 'fetch').andCallThrough()') in test/spec/controllers/main.js (line 15)
Any idea how I can test this such that it tests the scope binding as well as the async call?
There are a lot of issues with your code.
I've created this Plunkr for the purpose. index.js is the file with your code and test cases. I've edited most of the part according to the conventions and best-practices.
There are a few pointers I wanted to give you:
Since $http returns a promise, you should use that, instead of resolving the promise and creating another promise from your method. Not sure why is timeout used. So I removed $q and $timeout from searchServ's dependencies.
I did the same in the test case by removing the deferred variable that you used.
You should be using angular-mocks.js to mock your services and other dependencies instead of defining a service inside your test case(The way you have did.)
You should create separate describe blocks for testing different parts of your code(a controller in this case).
Hope this helps!

Angularjs data binding between controller and service

I'm not able to get the data binding between controller and service working.
I have a controller and a factory which makes an HTTP call. I would like to be able to call the factory method from other services and see the controller attributes get updated. I tried different options but none of them seem to be working. Any ideas would be greatly appreciated.
Please see the code here:
http://plnkr.co/edit/d3c16z?p=preview
Here is the javascript code.
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
});
app.controller('EventDetailCtrl', ['$http', 'EventDetailSvc', '$scope',
function ($http, EventDetailSvc, $scope) {
this.event = EventDetailSvc.event;
EventDetailSvc.getEvent();
console.log(self.event);
$scope.$watch(angular.bind(this, function () {
console.log('under watch');
console.log(this.event);
return this.event;
}), function (newVal, oldVal) {
console.log('under watch2');
console.log(newVal);
this.event = newVal;
});
}])
.factory('EventDetailSvc', ['$http', function ($http) {
var event = {};
var factory = {};
factory.getEvent = function() {
$http.get('http://ip.jsontest.com')
.then(function (response) {
this.event = response.data;
console.log('http successful');
console.log(this.event);
return this.event;
}, function (errResponse) {
console.error("error while retrieving event");
})
};
factory.event = event;
return factory;
}]);
It seems to me that you have nested the event object inside of a factory object. You should be returning event directly instead wrapping it with factory. As it stands now you would need to call EventDetailSvc.factory.event to access your object.

Load views after services in angularjs

In my angular app I have a view, a controller and a service.
The service load resources ex:service load persons and initialize value with the result.
I want to load my view after my service finish his function to load his resources.
var myApp = angular.module('myApp',[]);
myApp.controller('PersonsCtrl', ($scope, Persons) {
$scope.persons = Persons.data;
});
myApp.factory('Persons', {
data: [[function that load resources => take time]]
});
So I want to load my controller when my service finish his initialization.
Some ideas?
Assuming you have a route provider, here's a basic example. When the promise is resolved, "personData" will be injected into your controller. There's not much info about what your service does, so I had to give something very generic.
myApp.config(['$routeProvider', function($routeProvider) {
$routeProvider
.when('/persons', {
controller: 'PersonsCtrl',
templateUrl: 'persons.html',
resolve: {
personData: ['Persons', function(Persons) {
return Persons.getData();
}]
}
});
}]);
myApp.controller('PersonsCtrl', ($scope, personData) {
$scope.persons = personData;
});
myApp.factory('Persons', {
getData: function() {//function that returns a promise (like from $http or $q)};
});
Maybe try using promises, example below
var myApp = angular.module('myApp',[]);
myApp.controller('PersonsCtrl', ($scope, Persons) {
$scope.persons = Persons.getData().then(function(response){
//do whatever you want with response
});
});
myApp.factory('Persons', function ($http, $q) {
return {
getData: function () {
var def = $q.defer();
$http.get('url').
success(function (response) {
def.resolve(response);
})
return def.promise();
}
}
});

How can I get a service to access server data via an $http call?

I make an $http call inside a service that is supposed to get data from my server. For some reason I can't get my service to work - nothing happens. I know the server code works because if I place the $http call inside a function within my controller, then it gets the server data as expected. Here is the code I have so far:
app.service('myService', function($q,$compile,$http) {
this.getData = function() {
var deferred = $q.defer();
var promise = $http.get('myfile.php').success(function (data) {
var response = $compile(data)($scope);
deferred.resolve(response);
});
return deferred.promise;
};
});
Also, I know the code that uses this service works because if I do something like the following,
app.service('myService', function() {
this.getData = function() {
return 'TEST';
};
});
then I will see the word "TEST" show up in the div that utilizes this service. I feel like I'm very close, but there is something I am missing. Any ideas on what I'm doing wrong?
UPDATE:
controller: function($scope, $http, $rootScope, myService){
var scope = $rootScope;
scope.viewMyData = function() {
var element = myService.getData();
$('#myDiv').html(element);
}
}
HTML:
<div ng-click="viewMyData()">Click Here</div>
<div id="myDiv"></div>
If I strip the code in myService and simply return TEST (as above), then I will see "TEST" show up in id="myDiv". But for some reason the $http call isn't being triggered.
#tymeJV is right, but here's my attempt to spell out the example better. $http returns a promise interface that allows you to chain callbacks to be executed when the $http response returns. So, in this case, calling myService.getData() can't return the result immediately (it's off getting the data from the server), so you need to give it a function to execute when the server finally responds. So, with promises, you simply attach your callback using the thePromise.then(myCallbackFunc).
Service
app.service('myService', function($q,$compile,$http) {
this.getData = function() {
var promise = $http.get('myfile.php');
promise = promise.then(function (response) {
return response.data;
});
return promise;
};
});
Controller
controller: function($scope, $rootScope, myService){
var scope = $rootScope;
scope.viewMyData = function() {
myService.getData().then(function(data) {
$('#myDiv').html(element);
});
}
}
Use .then in the controller to continue the promise pattern:
myService.getData().then(function(data) {
$('#myDiv').html(data);
});

Angular ui-router get asynchronous data with resolve

I want to display a form with data corresponding to the edited item. I use ui-router for routing. I defined a state:
myapp.config(function($stateProvider) {
$stateProvider.
.state('layout.propertyedit', {
url: "/properties/:propertyId",
views : {
"contentView#": {
templateUrl : 'partials/content2.html',
controller: 'PropertyController'
}
}
});
In PropertyController, I want to set $scope.property with data coming from the following call (Google Cloud Endpoints):
gapi.client.realestate.get(propertyId).execute(function(resp) {
console.log(resp);
});
I don't know if I can use resolve because the data are returned asynchronously. I tried
resolve: {
propertyData: function() {
return gapi.client.realestate.get(propertyId).execute(function(resp) {
console.log(resp);
});
}
}
First issue, the propertyId is undefined. How do you get the propertyId from the url: "/properties/:propertyId"?
Basically I want to set $scope.property in PropertyController to the resp object returned by the async call.
EDIT:
myapp.controller('PropertyController', function($scope, , $stateParams, $q) {
$scope.property = {};
$scope.create = function(property) {
}
$scope.update = function(property) {
}
function loadData() {
var deferred = $q.defer();
gapi.client.realestate.get({'id': '11'}).execute(function(resp) {
deferred.resolve(resp);
});
$scope.property = deferred.promise;
}
});
You need to read the docs for resolve. Resolve functions are injectable, and you can use $stateParams to get the correct value from your routes, like so:
resolve: {
propertyData: function($stateParams, $q) {
// The gapi.client.realestate object should really be wrapped in an
// injectable service for testability...
var deferred = $q.defer();
gapi.client.realestate.get($stateParams.propertyId).execute(function(r) {
deferred.resolve(r);
});
return deferred.promise;
}
}
Finally, the values for resolve functions are injectable in your controller once resolved:
myapp.controller('PropertyController', function($scope, propertyData) {
$scope.property = propertyData;
});
I think your controller function needs $stateParams parameter from which you can get your propertyId. Then you can use $q parameter and create promise to set $scope.property with something like this:
var deferred = $q.defer();
gapi.client.realestate.get(propertyId).execute(function(resp) {
deferred.resolve(resp);
});
$scope.property=deferred.promise;
Here is description of using promises for handling async calls.
Try this easy way to use resolve in proper way
State code:
.state('yourstate', {
url: '/demo/action/:id',
templateUrl: './view/demo.html',
resolve:{
actionData: function(actionData, $q, $stateParams, $http){
return actionData.actionDataJson($stateParams.id);
}
},
controller: "DemoController",
controllerAs : "DemoCtrl"
})
In the above code I am sending parameter data which I am sending in the url,For examples if i send like this /demo/action/5
this number 5 will go to actionData service that service retrieve some json data based on id.Finally that data will store into actionData You can use that in your controller directly by using that name
Following code return some JSON data based on id which iam passing at state level
(function retriveDemoJsonData(){
angular.module('yourModuleName').factory('actionData', function ($q, $http) {
var data={};
data.actionDataJson = function(id){
//The original business logic will apply based on URL Param ID
var defObj = $q.defer();
$http.get('demodata.json')
.then(function(res){
defObj.resolve(res.data[0]);
});
return defObj.promise;
}
return data;
});
})();
How about this:
function PropertyController($scope, $stateParams) {
gapi.client.realestate.get($stateParams.propertyId).execute(function(resp) {
$scope.property = resp;
});
}

Resources