Angular.js multiple rest calls scope issue - javascript

i'm a biginner when it comes to Angular.js, and i have a problem with $scope not getting additional value from one of two $resource rest calls. Here's my code:
controller: function ($scope, $modalInstance, $route) {
$scope.server = {}
$scope.submit = function () {
//AddNewServer is a $resource service
AddNewServer.post($.param({
'name': $scope.server.name,
'ip': $scope.server.ip,
'port': $scope.server.port
}));
//ServerStats is a $resource service
ServerStats.postServerStats(function success(data) {
$scope.server.bytesIn = data.returnValue.bytesIn
}, function err(err) {
console.log("Error: " + err)
})
$modalInstance.dismiss('cancel');
$route.reload()
//BELLOW LOG RETURNS Object {name: "asd", ip: "asd", port: 2} NO bytesIn
console.log($scope.server)
}
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
$route.reload()
};
}
Question is how do i add bytesIn from my other service call into my server object? I'm sure it a pretty obvious thing but i'm still in learning phase. Thanks in advance.

Your postServerStats() call is asynchronous, so it's likely that your success function isn't being called before the console.log($scope.server) statement.
Put console.log($scope.server) in your success function, after you assign $scope.server.bytesIn.
Perhaps you mean to do more work in your postServerStats() callback?
Or better yet, look into angular promises

Related

angular 1.5 access data in http.get without scope

I am using Angular 1.5.
I can't access my data, from the http.get, out the http.get.
Let me explain:
I have my component:
(function(){
'use strict';
class myComponent {
constructor(
$http,
$scope)
{
var self = this;
self.test="this is a test";
$http.get(MYAPI).then(function(response){
self.MYDATA = response.data;
console.log(self.MYDATA)
});
console.log(self.test)
console.log(self.MYDATA)
}
}
angular.module('myApp')
.component('myApp.test', {
templateUrl: 'myTemplate.html',
controller: myComponent,
controllerAs:'vm',
});
})();
The console.Log give me:
this is a test --> for the test
undefined --> out the http.get
Object {id: 1…} --> in the http.get
So I can't access to my data out the http.get and this is what I want.
$http.get is an asynchronous call meaning the program execution doesn't wait for the data to be fetched from server .
Thus by the time console.log(self.MYDATA) located outside of $http.get() is executed , the data has not been fetched from server that is why you get a undefined error.
To solve this problem or to handle asynchronous calls , you could do something like this :
var promise = $http.get(MYAPI);
then access data in the following manner with the help of callbacks :
promise.then(
function successCallBack(response)
{
self.MYDATA = response.data;
console.log(self.MYDATA)
}
).then(
function errorCallBack(response)
{
console.log(response);
}
);
Here is a nice article on callbacks that could help you !

Creating a function around angular $http requests

First project in AngularJS and I started creating my services (factories) that I made modular like this
angular.module('app.services.public', [])
.factory('publicService', ['$http', function publicService($http) {
var results = {};
results.contact = function (name, email, message){
return $http.get();
};
return results;
}]);
That I then call in my main angular app by including it. When I call it, I need to listen for success or error
publicService.contact().success(callback).error(callback)
My question is, I'm going to be doing a lot of API requests through these services and seems to be bad code to listen to the error everytime since 90% of the time it will do the same thing.
How can I create a wrapper around the $http.get or around all factory calls?
So something like
apiCall = function (url, data, successCallback, errorCallback){
$http.get(url,data).success(function(){
successCallback()
}).error(function(){
if(errorCallback()){ errorCallback(); return; }
// or display general error message
})
}
I would recommend against converting promise-based into callback-based APIs. Angular adopted promises and it best to stay with them.
Also, stay away from $http-specific .success/.error and use promise .then/.catch APIs.
How wide do you need to cast your net to handle $http errors?
1) Say, it only applies to your publicService service, then you can "handle" it at the each function:
.factory("publicService", function($http, $q){
function handleError(){
// invokes error handlers
}
return {
onError: function(cb){
// register error handlers
},
doSomethingA: function(){
return $http.get("some/url/A")
.then(function(response){
return response.data;
})
.catch(function(error){
handleError(error);
return $q.reject(error); // still "rethrow" the error
}
},
doSomethingB: function(){
// similar to above
},
// etc...
};
})
Then you could separate request from error handling:
.controller("MainCtrl", function($scope, publicService){
publicService.onError(function(error){
$scope.showError = true; // or something like that
})
})
.controller("FunctionACtrl", function($scope, publicService){
publicService.doSomethingA()
.then(function(data){
$scope.data = data;
});
})
2) Of course, the above, would only apply to request made via publicService. If you want to catch all $http errors, you could implement an $http interceptors. I won't go into detail - there is enough info in documentation and elsewhere - but it would could work like below:
.factory("ErrorService", function(){
return {
onError: function(cb){
// register error handlers
},
broadcastError: function(error){
// invoke error handlers
}
};
})
Then in interceptor, use ErrorService as a dependency:
'responseError': function(rejection) {
ErrorService.broadcastError(rejection);
return $q.reject(rejection);
}
Then you could handle the errors globally:
.controller("MainCtrl", function($scope, ErrorService){
ErrorService.onError(function(error){
$scope.showError = true; // or something like that
})
})
You have the right idea. You can do it easily with a Factory.
myApp.factory(APIService, function(publicService, $http) {
return {
// create methods in here
...
contact: function(cb) {
$http.get(url,data).success(cb).error(function(err){
console.error('oh no!', err);
});
}
};
});
Then you can use it in your controllers.
APIService.contact(function(data){
console.log('response from the api!', data);
});
You can even move your error handler to its own factory as well.
I would suggest an implementation using angular's $q service.
angular.module('app.services.public', [])
.factory('publicService', ['$http', '$q', function publicService($http, $q) {
var results = {};
results.contact = function (name, email, message){
return $q.when($http.get());
};
return results;
}]);
Or rather than use the $q.when(...) method you can use $q.deferred like so:
angular.module('app.services.public', [])
.factory('publicService', ['$http', '$q', function publicService($http, $q) {
var deferred = $q.deferred();
var results = {};
results.contact = function (name, email, message){
$http.get().success(function(data){
deferred.resolve({
// assumes data retried from http request has a title and price attribute
title: data.title,
cost: data.price});
}).error(function(data){
deferred.reject(data);
});
};
return deferred.promise;
}]);

Using factory to expose a simple API

I'm trying to write a factory which exposes a simple users API. I'm new to AngularJS and I'm a bit confused about factories and how to use them. I've seen other topics but none that are a good match to my use case.
For the sake of simplicity, the only functionality I'd like to achieve is getting all users in an array and then pass them to a controller through the injected factory.
I stored the users in a json file (for now I only want to read that file, without modifying the data)
users.json:
[
{
"id": 1,
"name": "user1",
"email": "a#b.c"
},
{
"id": 2,
"name": "user2",
"email": "b#b.c"
}
]
The factory I'm trying to write should be something like this:
UsersFactory:
app.factory('usersFactory', ['$http', function ($http) {
return {
getAllUsers: function() {
return $http.get('users.json').then(
function(result) {
return result.data;
},
function(error) {
console.log(error);
}
);
}
};
}]);
And finally, the controller call would be like this:
UsersController
app.controller('UsersCtrl', ['$scope', 'usersFactory', function($scope, usersFactory){
usersFactory.getAllUsers().then(function (result) {
$scope.users = result;
});
}]);
I've searched the web and it seems like it is not really a good practice to use factories this way, and if I'd like to achieve some more functionality like adding/removing a new user to/from the data source, or somehow store the array within the factory, that wouldn't be the way to do it. I've seen some places where the use of the factory is something like new UsersFactory().
What would be the correct way to use factories when trying to consume APIs?
Is it possible to initialize the factory with an object containing the $http.get() result and then use it from the controller this way?
var usersFactory = new UsersFactory(); // at this point the factory should already contain the data consumed by the API
usersFactory.someMoreFunctionality();
I don't see anything wrong with your factory. If I understand correctly you want to add functionality. A few small changes would make this possible. Here's what I'd do (note that calling getAllUsers wipes out any changes):
app.factory('usersFactory', ['$http', function ($http) {
var users = [];
return {
getAllUsers: function() {
return $http.get('users.json').then(
function(result) {
users = result.data;
return users;
},
function(error) {
users = [];
console.log(error);
}
);
},
add: function(user) {
users.push(user);
},
remove: function(user) {
for(var i = 0; i < users.length; i++) {
if(users[i].id === user.id) { // use whatever you want to determine equality
users.splice(i, 1);
return;
}
}
}
};
}]);
Typically the add and remove calls would be http requests (but that's not what you're asking for in the question). If the request succeeds you know that your UI can add/remove the user from the view.
I like my API factories to return objects instead of only one endpoint:
app.factory('usersFactory', ['$http', function ($http) {
return {
getAllUsers: getAllUsers,
getUser: getUser,
updateUser: updateUser
};
function getAllUsers() {
return $http.get('users.json');
}
function getUser() {
...
}
function updateUser() {
...
}
}]);
That way if you have any other user-related endpoints you can consume them all in one factory. Also, my preference is to just return the $http promise directory and consume the then() in the controller or where ever you're injecting the factory.
I'm really a fan of route resolve promises. Here is John Papa's example. I will explain afterwards how to apply this to what you're doing:
// route-config.js
angular
.module('app')
.config(config);
function config($routeProvider) {
$routeProvider
.when('/avengers', {
templateUrl: 'avengers.html',
controller: 'Avengers',
controllerAs: 'vm',
resolve: {
moviesPrepService: moviesPrepService
}
});
}
function moviesPrepService(movieService) {
return movieService.getMovies();
}
// avengers.js
angular
.module('app')
.controller('Avengers', Avengers);
Avengers.$inject = ['moviesPrepService'];
function Avengers(moviesPrepService) {
var vm = this;
vm.movies = moviesPrepService.movies;
}
Basically, before your route loads, you get the request data you need (in your case, your "users" JSON.) You have several options from here... You can store all that data in a Users factory (by the way, your factory looks fine), and then in your controller, just call Users.getAll, which can just return the array of users. Or, you can just pass in users from the route resolve promise, much like John Papa does in his example. I can't do it as much justice as the article he wrote, so I would seriously recommend reading it. It is a very elegant approach, IMHO.
I typically use a factory something like this:
.factory('usersFactory', ['$resource',
function($resource){
return $resource('http://someresource.com/users.json', {}, {
query: {
method:'GET',
isArray:true
}
})
}])
Which you could call with:
usersFactory.query();
As this is a promise you can still use the .then method with it too
$http is a promise that means you have to check whether your get call worked or not.
so try to implement this type of architecture in your controller
$http.get('users.json')
.success(function(response) {
// if the call succeed
$scope.users = result;
})
.error(function(){console.log("error");})
.then(function(){
//anything you want to do after the call
});

How do I unit test $scope.broadcast, $scope.$on using Jasmine

I'm newbie to AngularJs/NodeJs world, so forgive if this is a basic question to some.
So in a nutshell I've two controllers, the first controller $broadcast an 'Id' and the second controller fetches that Id with $on and then passes that Id to an intermediate service, which makes an $http ajax call and returns a single Book object.
How do I unit test $scope.broadcast, $scope.$on using Jasmine
firstCtrl
.controller('firstCtrl', function($scope, ...){
$scope.selectGridRow = function() {
if($scope.selectedRows[0].total !=0)
$scope.$broadcast('id', $scope.selectedRows[0].id);//Just single plain ID
};
});
secondCtrl
.controller('secondCtrl',
function($scope, bookService) {
$scope.$on('id', function(event, id) {
bookService.getBookDetail(id).then(function(d) {
$scope.book = d.book;
});
});
});
expected Json obj
var arr = "book" : [ {
"id" : "1",
"name" : "Tomcat",
"edition" : "9.1"
}
]
Let me know if anyone wants me to post the $http service that's used by the second controller.
expected behavior
So from the top of my head, ideally, I would like to test every possible scenario, but something like below, which can then expend:
expect(scope.book).toEqual(arr);
expect(scope.book).not.toEqual(undefined);
Thanks everyone!
First you should do the broadcast on $rootScope then you can receive on $scope.
Now to the testing. I assume you want to include real request to your API via bookService and $http. This can be mocked but I'll focus on the real call. Let me know if you need the mocked one.
Before the actual test, you will need to do some injections/instantiations:
Initialize your app
Inject $controller, $rootScope, $httpBackend and bookService
Create scopes for firstController and SecondController and store it in a variable
Store bookService and $httpBackend in variables
Instantiate the controllers and store them
Then in the actual test you must tell $httpBackend what to do when it caches request for the books (or books). Construct $httpBackend.whenGET("/api/books/1").passThrough(); will pass request with url "/api/books/1" to the server.
Next your must setup property selectedRows on firstScope so it fulfills the condition in function selectGridRow in your firstCtrl.
Now you can call function selectGridRow to trigger the broadcast and API call. But you must wrap it in runs function so Jasmine recognizes this as an async call and will wait for it to finish. The 'waiting' is defined in waitsFor call. It will wait until it gets a book and it waits max 5000 ms then the test will be marked as failed.
Last step is to check expected result. We don't have to check for undefined anymore as the test would not get to here anyway. The check must be wrapped again runs call so it is executed afters successful 'waitsFor'.
Here is the full code:
describe("Broadcast between controllers", function () {
beforeEach(module('app')); //app initialization
var firstScope;
var secondScope;
var bookService;
var $httpBackend;
var firstController;
var secondController;
beforeEach(inject(function ($controller, $rootScope, _bookService_, _$httpBackend_) {
firstScope = $rootScope.$new();
secondScope = $rootScope.$new();
bookService = _bookService_;
$httpBackend = _$httpBackend_;
firstController = $controller('firstCtrl', { $scope: firstScope });
secondController = $controller('secondCtrl', { $scope: firstScope, bookService: bookService });
}));
it("should work", function () {
$httpBackend.whenGET("/api/books/1").passThrough();
firstScope.selectedRows = [{ id: 1, total: 1000 }];
secondScope.book = null;
runs(function () {
firstScope.selectGridRow();
});
waitsFor(function () {
return secondScope.book != null;
}, "Data not received in expected time", 5000);
runs(function () {
expect(secondScope.book[0].id).toEqual(1);
});
});
});

$scope inside a callback of a resource get call will not work

i am having a problem accessing the $scope. as it seems it is not working for me.
i have a resource:
squashApp.factory('CourtsService', function($resource) {
return $resource('/api/court/:num', {num: '#num'});
});
and my controller does:
squashApp.controller('CourtsController',
function CourtsController($scope, $window, $http, CourtsService) {
CourtsService.get({num:1}, function(data){
$scope.courts = data;
})
});
my server succeeds in returing a data which is in format of javascript object (JSON).
i have checked it. but for some reason the $scope is not updated and after this callback my view is not changing at all.
help please
I created a demo for you and your code looks ok.
However, it is always good to add some logic for the callback when the error occurs like this, so your app will not die silently.
squashApp.controller('CourtsController', function CourtsController($scope, $window, $http, CourtsService) {
CourtsService.get({
num: 1
}, function (data) {
console.log('success');
$scope.courts = data;
}, function (data) { // -> error handling
console.log('failed');
$scope.courts = data;
})
});
Demo
A simple way to access your $scope using angular $resource by using $promise (recommended)
squashApp.controller('CourtsController',
function CourtsController($scope,CourtsService) {
CourtsService.get({num:1}).$promise.then(function(data){
//$scope -- can be accessed here
}, function(failed){
}
});

Categories