Angular $scope not available outside my function - javascript

Somewhat new to Angular and javascript. I have the following controller written use a factory service to access a local JSON file. Most of this code is derived (or completely taken) from this post by Dan Wahlin. I am unable to access the $scope.books variable outside of the function and cannot figure out why. The console.log inside the function gives me the object I am looking for, but the one outside returns undefined. What am I doing wrong here? Thanks.
app.controller('FormController', ['$scope', 'tocFactory', function ($scope, tocFactory) {
$scope.books;
getBooks();
function getBooks() {
tocFactory.getBooks().
success(function(data, status, headers, config) {
$scope.books = data;
console.log($scope.books);
}).
error(function(data, status, headers, config) {
// log error
})
}
console.log($scope.books);
}]);

Because you are making an ajax and then executing the next line of code. Which doesn't mean you have guarantee that your ajax response is ready on execution of next line of code.
You could always gets its response value inside the success function of $http which gets called when ajax call executed successfully.
Read here How asynchronous call works?

actually this isn't an issue of scope, but timing. The getBooks function is executed asynchronously, so when your console log happens it most likely will not have been bound to anything. You could test this easily with interval to see how this is happening:
app.controller('FormController', ['$scope', 'tocFactory', function($scope, tocFactory) {
$scope.books;
getBooks();
function getBooks() {
tocFactory.getBooks()
.success(function(data, status, headers, config) {
$scope.books = data;
console.log($scope.books);
})
.error(function(data, status, headers, config) {
// log error
})
}
setInterval(function(){
console.log($scope.books);
}, 1000);
}]);

You can use $q service to handle asynchronous code with promises :
app.controller('FormController', ['$scope', '$q', 'tocFactory', function ($scope, $q, tocFactory)
{
var getBooks = function()
{
var deferred = $q.defer();
tocFactory.getBooks().
success( function(data, status, headers, config)
{
$scope.books = data;
deferred.resolve();
} ).
error( function(data, status, headers, config)
{
deferred.reject();
} );
return deferred.promise;
};
getBooks().then( function(res)
{
console.log($scope.books); // success : your data
}, function(res)
{
console.log($scope.books); // error : undefined
} );
console.log($scope.books); // undefined
} ] );
I haven't tested this code but it should work and show you promises principle.
More about $q service : https://docs.angularjs.org/api/ng/service/$q

Related

Test an Angular service by expecting a function call: Jasmine createSpy

I have a controller that calls a service function, by passing in 2 functions as parameters (to be called dependent on the outcome of the service function), like so:
Controller code:
onSuccess(data){ ... }
onError(data){ ... }
service.post(url, query, onSuccess, onError);
Service code:
var service = function($log, $http, $location, $interval) {
...
var post = function(url, query, onSuccess, onError){
$http.post(url, query)
.success(function(data, status, headers, config){
onSuccess(data);
})
.error(function(data, status, headers, config){
onError(data);
});
};
...
});
What I'm trying to do is test that the query passed into the post function is correct and so the onSuccess function located within the controller code is called. I've tried to test this like so:
Test code:
describe("post", inject(function (service) {
beforeEach(module('myApp'));
beforeEach(function() {
var url = "http://..."
var query = {
...
}
var mockSuccess = jasmine.createSpy('Success function');
var mockError = jasmine.createSpy('Error function');
service.post(url, query, mockSuccess, mockError);
}
it('Should call success function following call to http.post', function () {
expect(mockSuccess).toHaveBeenCalled();
});
))};
Although I'm getting the error:
TypeError: null is not an object (evaluating 'currentSpec.$modules')
So I'm not sure how to properly test whether my query is successful in returning data through the http request?
Thanks in advance.

Get Angular $http return value into another oject

Would like to get the return object from an HTTP request into another scope object so I can use it in HTML with ng-repeat directive. How can I get the returned object into a variable?
Call HTTP Function
Get Data
Store response
Make scope variable the value of the HTTP response
JS:
angular.module('myApp').controller('ItemController',[
'$scope', '$http', '$location',
function($scope, $http, $location){
var currentLocation = $location.path();
$scope.getItem = function(){
$http.get('path/to/json/file.json').then(
function(response){
$scope.results = response.data;
console.log('item controller, item:', $scope.results);
});
};
$scope.item = //MAKE THIS VALUE OF THE RETURN OBJECT FROM getItem
console.log('scope.item', $scope.item);
}
]);
JSON
[
{
"first_name": "Mega",
"last_name": "Man",
"image": "http://placehold.it/75x75"
}
]
HTML
Would like to be able to just have
<p>{{item.first_name}}</p>
<p>{{item.last_name}}</p>
<img ng-src="{{item.image}}"/>
Updated JS Call
$scope.getItem = function(){
$http.get('path/to/json/file.json')
.success(function(data){
$scope.results=data;
console.log('$scope.results: ',$scope.results);
}).error(function(data){
console.log(data);
}).then(
function(){
$scope.item = $scope.results;
console.log('$scope.item: ', $scope.results);
});
};
You can do it in different ways one way is to use success and error callback functions like:
$http.get('path/to/json/file.json')
.success(function (data, status, header, config) {
$scope.results =data;
console.log('item controller, item:', $scope.results);
})
.error(function (data, status, header, config) {
console.log(data);
});
Another option is using then promise function like:
$http({method: 'GET', url: 'path/to/json/file.json').
then(function(response) {
$scope.results = response.data;
console.log('item controller, item:', $scope.results);
}, function(response) {
console.log(response);
});
The issue that was happening what getting callbacks to work correctly. Replaced the .then() method with .success() and .error(). Since the object that was getting called was an array it could not be parsed out in the HTML to get object properties. The other part that was needed was in the success callback to get the first object of the array with $scope.results=data[0]
$scope.getItem = function(){
$http.get('path/to/json/file.json')
.success(function(data){
$scope.results=data[0];
console.log('$scope.results: ',$scope.results);
}).error(function(data){
console.log(data);
});
};

How can I replace $resource with $http in AngularJS?

My application has the following $resource calls. From what I can see this could be replaced with $http.
$resource('/api/:et/', { et: $scope.data.entityType })
.save(data, newSuccess, error)
.$promise.finally(last);
$resource('/api/:et/:id', { et: $scope.data.entityType })
.delete({ id: entityId }, deleteSuccess, error)
.$promise.finally(last);
$resource('/api/:et/:id', { et: $scope.data.entityType }, { update: { method: 'PUT' } })
.update({ id: entityId }, data, editSuccess, error)
.$promise.finally(last);
I have checked the $http documentation but I cannot see how to add the calls to the xxxSuccess, error functions and how to do the .$promise.finally(last).
Can someone explain how I could replicate this functionality using $http ?
$http is for general purpose AJAX. In most cases this is what you'll be using. With $http you're going to be making GET, POST, DELETE type calls manually and processing the objects they return on your own.
$resource wraps $http for use in RESTful web API scenarios.
Syntax
$http({
method : 'GET',
url : '/someUrl',
param : { paramKey : paramValue}, // optional
headers : 'someHeaders' // optional
}).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.
});
$http Documentation - https://docs.angularjs.org/#!/api/ng/service/$http
Maintaing Promise in $http
app.factory('myService', function($http) {
var myService = {
async: function() {
// $http returns a promise, which has a then function, which also returns a promise
var promise = $http.get('/someUrl').then(function (response) {
// The then function here is an opportunity to modify the response
console.log(response);
// The return value gets picked up by the then in the controller.
return response.data;
});
// Return the promise to the controller
return promise;
}
};
return myService;
});
app.controller('MainCtrl', function( myService,$scope) {
// Call the async method and then do stuff with what is returned inside our own then function
myService.async().then(function(d) {
$scope.data = d;
});
});
Take a look at this article Angular Promises, It will definitely benifit you in acheving such scenerios.
$resource is a further abstracted version of $http. If you are already using $response you may find it's not useful to change your logic to use $http. That said -
https://docs.angularjs.org/api/ng/service/$http
General usage
The $http service is a function which takes a single argument — a configuration object — that is used to generate an HTTP request and returns a promise with two $http specific methods: success and error.
$http({method: 'GET', url: '/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.
});

AngularJs $scope doesn't update after a GET request on a factory

I have been trying AngularJS for a experimental project and I came along with this problem.
In my html I want to display a list of items
Index.html
<h1>Some list</h1>
<div ng-controller="datlist">
<div ng-repeat="item in items">
<div>Item description: {{item.description}}</div>
<div>Item name: {{item.name}}</div>
</div>
</div>
At first I was using a simple controller to get the information and update the view just using this:
controllers.js (original)
function datlist($scope,$http){
$http({method: 'GET', url: 'http://localhost:61686/getdatlist?format=json', headers: {'Access-Control-Allow-Origin': 'localhost:*'}}).
success(function(data, status, headers, config) {
$scope.items=data.itemsToReturn;
console.log(data);
}).
error(function(data, status, headers, config) {
console.log("fail");
});
}
This was working pretty well and I could get the list of items. Whilst, by changing my structure to use a factory to make the same request and bind it to $scope.items it doesn't work. I tried a lot of variations of $watch but I couldn't get it to update $scope.items. I found something about $apply but I really can't understand how to use it.
controllers.js (new one)
var datModule = angular.module('datModule',[]);
datModule.controller('datlist', function ($scope, datfactory){
$scope.items = datfactory.getlist();
$scope.$watch($scope.items, $scope.items = datfactory.getlist());
});
datModule.factory('datfactory', function ($http){
var factory = {};
factory.getlist = function(){
$http({method: 'GET', url: 'http://localhost:61686/getdatlist?format=json', headers: {'Access-Control-Allow-Origin': 'localhost:*'}}).
success(function(data, status, headers, config) {
console.log(data.itemsToReturn); //I get the correct items, all seems ok here
return data.itemsToReturn;
}).
error(function(data, status, headers, config) {
console.log("fail");
});
}
return factory;
});
Any ideas about this will be great.
PS: I found a lot of posts talking about this issue but none of them helped me to get a full solution.
Thanks
Using a watch for that is kinda ugly.
try this:
datModule.factory('datfactory', function ($http, $q){
this.getlist = function(){
return $http.get('http://localhost:61686/getdatlist?format=json',{'Access-Control-Allow-Origin': 'localhost:*'})
.then(function(response) {
console.log(response); //I get the correct items, all seems ok here
return response.data.itemsToReturn;
});
}
return this;
});
datModule.controller('datlist', function ($scope, datfactory){
datfactory.getlist()
.then(function(arrItems){
$scope.items = arrItems;
});
});
This is how you use promises for async matter.
UPDATE (15.01.2015): Now even sleeker!
The issue is nothing to do with the scope digest cycle. You are trying to return from inside a callback directly, which is not asynchronously possible.
I recommend you either use a promise, or return the http promise directly.
var factory = {};
factory.getlist = function(){
return $http({method: 'GET', url: 'http://localhost:61686/getdatlist?format=json', headers: {'Access-Control-Allow-Origin': 'localhost:*'}});
}
return factory;
To return the promise directly, and handle the success/fail at factory.getlist().success()
Alternatively, use your own promise if you want to wrap additional logic around the request.
var datModule = angular.module('datModule',[]);
datModule.controller('datlist', function ($scope, datfactory){
$scope.items = [];
datfactory.getlist().then(function(data) { $scope.items = data });
});
datModule.factory('datfactory', function ($http, $q){
var factory = {};
factory.getlist = function(){
var defer = $q.defer();
$http({method: 'GET', url: 'http://localhost:61686/getdatlist?format=json', headers: {'Access-Control-Allow-Origin': 'localhost:*'}}).
success(function(data) {
// alter data if needed
defer.resolve(data.itemsToReturn);
}).
error(function(data, status, headers, config) {
defer.reject();
});
return defer.promise;
}
return factory;
});
try to initialize $scope.items = []; at controller, before call $http
I hope it helps you.
Well it looks perfect but you can use $apply like this.
datModule.controller('datlist', function ($scope, datfactory){
$scope.$apply(function() {
$scope.items = datfactory.getlist();
});
});
I think another elegant solution to this problem could be - if you are using one of the routing libraries, in my case it is the UI-Router, but could be also ngRoute, is making your controller dependent on the response of the promise, eg. adding a resolve property to the adequate state/route which doesn't let the controller load until the promise is solved and the data is ready, so in your config:
.state('datpage', {
url: '/datpage',
controller: 'DatpageController',
resolve:{
datData: function (datfactory) {
return datDataService.getData("datDataParam");
}]
},
templateUrl: 'views/datpage.html'
})
And inject the datData dependency in your controller, where you can apply it directly to the $scope:
.controller('DatpageController', function ($scope,datData) {
$scope.datPageData = datData; ...

Getting JavaScript error on asynchronous postback scope function

I am getting the following error reported on angular.min.js
0x800a1391 - JavaScript runtime error: 'Error' is undefined
with the following code:
Javascipt:
function Sucess()
{
//close
}
function Save()
{
var e = document.getElementById('FormDiv');
scope = angular.element(e).scope();
scope.Apply(Sucess)
}
My Angular scope function:
function RolesCtrl($scope, $http, $location)
{
$scope.Apply = function (CallBackSucess) {
var userId = getQSP('uid', decode(document.URL));
$http({
method: 'POST', url: 'MultiRole.aspx/Apply',
data: {}
}).
success(function (data, status, headers, config) {
// this callback will be called asynchronously
CallBackSucess();
$scope.f = data.d;
}).
error(function (data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
$scope.name = 'error';
})
}
}
Everything seems to be working fine until CallBackSucess() call is made which throws the error:
0x800a1391 - JavaScript runtime error: 'Error' is undefined
The CallBackSucess argument is passed to the .Apply() method. It is not passed to the .success() method - they are chained methods with separate scopes. Thus, in the .success() callback function, CallbackSucess() is not defined when you try to call it and thus you get an error.
Also, do you really mean to spell Sucess incorrectly?
FYI, I had to format your code like this in order to see what was actually going on:
function RolesCtrl($scope, $http, $location) {
$scope.Apply = function (CallBackSucess) {
var userId = getQSP('uid', decode(document.URL));
$http({
method: 'POST',
url: 'MultiRole.aspx/Apply',
data: {}
}).success(function (data, status, headers, config) {
// this callback will be called asynchronously
CallBackSucess();
$scope.f = data.d;
}).error(function (data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
$scope.name = 'error';
})
}
}

Categories