I would like to ask/discuss wether this is good or bad practise - what are the pros and cons of making a service call insde a controller as clean and short as possible. In other words: not having a callback anymore but make use of the Angular binding principles of Angular.
Take a look at a Plnkr I forked:
http://plnkr.co/edit/zNwy8tNKG6DxAzBAclKY
I would like to achieve what is commented out on line 42 of the Plnkr > $scope.data = DataService.getData(3);.
app.factory('DataService', function($q, $http) {
var cache = {};
var service= {
data:{src:''},
getData: function(id, callback) {
var deffered = $q.defer();
if (cache[id]) {
service.data.src = 'cache';
deffered.resolve(cache[id])
} else {
$http.get('data.json').then(function(res) {
service.data.src = 'ajax';
cache[id] = res.data;
cache[id].dataSource = service.data.src;
deffered.resolve(cache[id])
})
}
return deffered.promise.then(callback);
}
}
return service
})
app.controller('MainCtrl', function($scope, DataService) {
DataService.getData(3, function(result) {
$scope.data = result;
});
//$scope.data = DataService.getData(3);
});
My best practice with regards to services requesting data and returning promises is:
return a promise (in DataService, return deferred.promise)
in the controller, call DataService.getData(3).then(, )
So I would not pass a callback to a service function that uses a promise.
The more difficult question is what should the service function do, and what should the then(function(data) {...}) do. Here are a few guidelines:
Anything that is shared (data / repetitive functionality), implement in the service
Anything related to binding data / functions to UI elements, implement in the controller
Keep your controllers as simple as possible - they should just link between UI elements and the model.
Any model logic, processing, format parsing, etc - implement in the service
I added this part after reading the comments:
If you need to do some processing (like checking a cached result, parsing the result, etc.), then the proper place to do this is in the service.
So I would modify your code as follows:
var service= {
data:{src:''},
getData: function(id) {
var deffered = $q.defer();
if (cache[id]) {
service.data.src = 'cache';
deffered.resolve(cache[id]);
return deferred;
} else {
return $http.get('data.json').then(function(res) {
service.data.src = 'ajax';
cache[id] = res.data;
cache[id].dataSource = service.data.src;
return cache[id]; // this will resolve to the next ".then(..)"
});
}
}
}
AfaIk this is not possible - see this Thread
You can 'automatically' resolve the promise by using angular-route. The resolved promises will be injected into your controller.
You can do like this plunker
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script>
document.write('<base href="' + document.location + '" />');
</script>
<link href="style.css" rel="stylesheet" />
<script data-semver="1.2.4" src="http://code.angularjs.org/1.2.4/angular.js" data-require="angular.js#1.2.x"></script>
<script src="app.js"></script>
<script>
app.factory('DataService', function($q, $http) {
var cache = {};
var service= {
data:{src:''},
getData: function(id, callback) {
var deffered = $q.defer();
if (cache[id]) {
service.data.src = 'cache';
deffered.resolve(cache[id])
} else {
$http.get('data.json').then(function(res) {
service.data.src = 'ajax';
cache[id] = res.data;
cache[id].dataSource = service.data.src;
deffered.resolve(cache[id])
})
}
return deffered.promise;
}
}
return service
})
app.controller('MainCtrl', function($scope, DataService) {
DataService.getData(3).then(function (data) {
$scope.data = data;
});
});
</script>
</head>
<body ng-controller="MainCtrl">
<div>Source: {{data.dataSource}}</div>
<pre>{{data}}</pre>
</body>
</html>
Related
I wanna use multiple ( in this case, 2 ) $http.gets in my service !
As you know the simple form of using $http.get is this :
app.factory('MyService', function ($http, $q) {
return {
getData: function() {
return $http.get('myfile.json')
.then(function(response) {
return response.data;
});
}
};
});
Now I wanna use 2 files ( 2 $http.gets ) and compare them to each other ( with some for loops and etc that I can ... ) !
What can I do now ? :(
use $q.all.
Add $q to controller's dependencies, exemple
$scope.req1 = $http.get('myfile.json');
$scope.req2 = $http.get('myfile2.json');
$q.all([$scope.req1, $scope.req2]).then(function(data) {
// data is array of your files
if ( JSON.stringify(data[0]) === JSON.stringify(data[1])){
console.log('is equal');
}
});
It is an extension of Hajji Tarik's solution. I was able to derive from your comments that you were still not clear with what to code in where. So I have developed a sample application which will assist you for the same.
//--app.module.js--//
angular.module('notesApp', []);
//--app.service.js--//
angular.module('notesApp')
.factory('notesFactory', ['$http',
function($http) {
var notesService = {};
notesService.getData = function(url, method) {
return $http({
url: url,
method: method
});
}
return notesService;
}
]);
//--app.controller.js--//
angular.module('notesApp')
.controller('MainController', ['$scope', '$http', '$log', '$q', 'notesFactory',
function($scope, $http, $log, $q, notesFactory) {
$scope.data = {};
var data1 = notesFactory.getData('http://localhost:3000/api/notes/1', 'GET');
var data2 = notesFactory.getData('http://localhost:3000/api/notes/2', 'GET');
var combinedData = $q.all({
firstResponse: data1,
secondResponse: data2
});
combinedData.then(function(response) {
$log.log(response.firstResponse.data);
$log.log(response.secondResponse.data);
//Write your comparison code here for comparing json results.
}, function(error) {
$scope.data = error;
});
}
]);
<html ng-app='notesApp'>
<head>
<title>
Notes Application
</title>
</head>
<body>
<div ng-controller='MainController'>
</div>
<script src='https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js'></script>
<script src='app.module.js'></script>
<script src='app.controller.js'></script>
<script src='app.service.js'></script>
</body>
</html>
I want to chain 2 resource calls together but keep it invisible from the consuming application/controller.
For example: resource "Person" has multiple "Role" records, however I want to keep "Person" and "Role" as separate resources on the server.
So the application calls the "Person" resource for a list of people but before they are returned the resource's transformReponse method calls the "Roles" resource for each person and adds the roles as an array. Therefore the application simply asks for people and gets all people with associated roles.
I've tried to simplify the scenario in the example code listed below. Here the application calls 1 resource which then calls the 2nd, however the data from the 2nd resource is resolved after the call to the initial resource in the controller returns.
Any ideas how to do this would be much appreciated.
angular.module('services', ['ngResource'])
.factory("someService2", function ($resource) {
return $resource(
'/', {}, {
get: {
method: 'GET',
transformResponse: function(data, headers){
//MESS WITH THE DATA
data = {};
data.coolThing = 'BOOM-SHAKA-LAKA-V2';
return data;
}
}
}
);
});
angular.module('services')
.factory("someService", function ($q, $resource, someService2) {
return $resource(
'/', {}, {
get: {
method: 'GET',
transformResponse: function(data, headers){
data.title1 = "Resource1";
var defer = $q.defer();
// Call 2nd resource
someService2.get(function(d){
data.title2 = d.coolThing;
defer.resolve(data);
});
return defer.promise;
}
}
}
);
});
var app = angular.module('myApp', ['ngResource', 'services']);
app.controller('MainController', ['$scope', 'someService', function ($scope, svc) {
$scope.title1 = 'Transform Test';
$scope.title2 = 'Transform Test2';
var promise = svc.get().$promise.then(function(data){
$scope.title1 = data.title1;
$scope.title2 = data.title2;
});
}]);
The HTML is very simple:
<html>
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script type="text/javascript" src="http://code.angularjs.org/1.2.0-rc.2/angular-resource.js" ></script>
<script type="text/javascript" src="JScript.js"></script>
</head>
<body>
<div ng-app="myApp">
<div ng-controller="MainController">
<h1>{{title1}}</h1>
<h1>{{title2}}</h1>
</div>
<div>
</body>
</html>
I have finally come to the conclusion that using the TransformResponse function of the Resource will not work as required as it looks like the resource does some external wrapping of the data further up the promise pipeline. ie. the TransformResponse happens before the promise is resolved internally.
My final solution was to simply abstract the resource call in my own object and chain my promises here. This still allows me to abstract the nested calls from the controller which was my main requirement.
Service2:
angular.module('services', ['ngResource'])
.factory("someService2", function ($resource) {
return $resource(
'/', {}, {
get: {
method: 'GET',
transformResponse: function(data, headers){
//MESS WITH THE DATA
data = {};
data.coolThing = 'BOOM-SHAKA-LAKA-V2';
return data;
}
}
}
);
});
Service1, which encapsulates the nested calls:
angular.module('services')
.factory("someService", function ($q, $resource, someService2) {
var r = $resource(
'/', {}, {
get: {
method: 'GET',
transformResponse: function(data, headers){
//MESS WITH THE DATA
data = {};
data.title1 = 'BOOM-SHAKA-LAKA-V1';
return data;
}
}
}
);
var svc = {
get: function(){
var data;
var defer = $q.defer();
r.get().$promise.then(
function(x){
data = x;
someService2.get().$promise.then(
function(y){
data.title2 = y.coolThing;
defer.resolve(data);
}
);
}
);
return defer.promise;
}
};
return svc;
});
Controller:
var app = angular.module('myApp', ['ngResource', 'services']);
app.controller('MainController', ['$scope', 'someService', function ($scope, svc) {
$scope.title1 = 'Transform Test';
$scope.title2 = 'Transform Test2';
svc.get().then(function(data){
$scope.title1 = data.title1;
$scope.title2 = data.title2;
});
}]);
HTML:
<html>
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script type="text/javascript" src="http://code.angularjs.org/1.2.0-rc.2/angular-resource.js" ></script>
<script type="text/javascript" src="http://localhost/Angular/NestedResources/JS.js"></script>
</head>
<body>
<div ng-app="myApp">
<div ng-controller="MainController">
<h1>{{title1}}</h1>
<h1>{{title2}}</h1>
<pre>
{{data}}
</pre>
</div>
<div>
</body>
</html>
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
};
});
}
};
});
Inside an AngularJS controller I do a $http.post() request and update $scope with new data which works fine.
But in case of an error, I want to change the template of the same controller to another one. I haven't figured out how to change the template of a controller.
Here is some pseudo code inside the controller:
// this is the controller
var self = this
$http
.post('/something', $scope.something)
.success(function(data) {
$scope.result = data
})
.error(function(err) {
$scope.error = err
var statusCode = err.statusCode
// for example on a 403, I want the template 'errors/403.html' to
// be rendered
self.template = $templateCache.get('errors/' + statusCode + '.html')
})
Any ideas how this is possible? I do not want to put '/errors/403' to the routing because it is not idempotent.
Thanks for any suggestions. I am a bit desperate here ...
try like
$location.path('/login');
first of all you should setup the route provider and use then use $location service to change the URL.
configure route provider :
var app = angular.module("myApp", ["ngRoute"]);
app.config(function ($routeProvider) {
$routeProvider.when("/error", { controller: "", templateUrl: "/app/error.html" });
// setup some more routes here as above
$routeProvider.otherwise({ redirectTo: "/login" });
And in JS file -
var self = this
$http
.post('/something', $scope.something)
.success(function(data) {
$scope.result = data
})
.error(function(err) {
$scope.error = err
var statusCode = err.statusCode
// for example on a 403, I want the template 'errors/403.html' to
// be rendered
$location.path("/error");
})
Don't forget to add the angular-route.js file.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
</head>
<body data-ng-app="myApp">
<div data-ng-view=""></div>
<script src="Scripts/angular-route.js"></script>
</body>
</html>
angular.module('myApp', ['myApp.filters', 'myApp.services', 'myApp.directives'], function ($routeProvider, $locationProvider, $httpProvider) {
var interceptor = ['$rootScope', '$q', function (scope, $q) {
function success(response) {
return response;
}
function error(response) {
var status = response.status;
if (status == 401) {
window.location = "./index.html";
return;
}
// otherwise
return $q.reject(response);
}
return function (promise) {
return promise.then(success, error);
}
}];
$httpProvider.responseInterceptors.push(interceptor);
There's no problem with populating a service (factory actually) with asynchronous data. However, what is the proper way of updating data in a service?
The problem that I run into is that all async data is access with .then() method, basically a promise resolve. Now, how would I put something into a service, and update related views?
The service I'm using:
function ($q) {
var _data = null;
return {
query: function (expire) {
var defer = $q.defer();
if (_data) {
defer.resolve(response.data);
} else {
$http.get('/path').then(function (response) {
defer.resolve(response.data);
});
}
return defer.promise;
}
,
byId: function(id) {
var defer = $q.defer();
this.query().then(function(data){
angular.forEach(data, function(item) {
if (item.id == id) {
return defer.resolve(item);
}
});
return defer.reject('id not found');
});
return defer.promise;
}
,
add: function(item) {
...
}
};
}
What would be good implementation of add method? Note, that I'm working with Angular >1.2
I've posted a few examples to show ways to get data from your service into your controllers and thereby allow the data to be bound in the views.
http://plnkr.co/edit/ABQsAxz1bNi34ehmPRsF?p=preview
The HTML
<!DOCTYPE html> <html>
<head>
<script data-require="angular.js#*" data-semver="1.2.4" src="http://code.angularjs.org/1.2.3/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script> </head>
<body ng-app="myApp">
<div ng-controller="MyCtrl">
{{sharedData.label}}
<br>
<input type="text" ng-model="sharedData.label"/>
</div>
<div ng-controller="MyCtrl2">
<input type="text" ng-model="sharedData.label"/>
<button ng-click="updateValue()">test</button>
</div>
<div ng-controller="MyCtrl3">
<input type="text" ng-model="sharedData.label"/>
<button ng-click="updateValue()">test</button>
</div>
<div ng-controller="MyCtrl4">
<input type="text" ng-model="sharedData.label"/>
</div>
</body>
</html>
The JS
angular.module("myApp", []).service("MyService", function($q) {
var serviceDef = {};
//It's important that you use an object or an array here a string or other
//primitive type can't be updated with angular.copy and changes to those
//primitives can't be watched.
serviceDef.someServiceData = {
label: 'aValue'
};
serviceDef.doSomething = function() {
var deferred = $q.defer();
angular.copy({
label: 'an updated value'
}, serviceDef.someServiceData);
deferred.resolve(serviceDef.someServiceData);
return deferred.promise;
}
return serviceDef;
}).controller("MyCtrl", function($scope, MyService) {
//Using a data object from the service that has it's properties updated async
$scope.sharedData = MyService.someServiceData;
}).controller("MyCtrl2", function($scope, MyService) {
//Same as above just has a function to modify the value as well
$scope.sharedData = MyService.someServiceData;
$scope.updateValue = function() {
MyService.doSomething();
}
}).controller("MyCtrl3", function($scope, MyService) {
//Shows using a watch to see if the service data has changed during a digest
//if so updates the local scope
$scope.$watch(function(){ return MyService.someServiceData }, function(newVal){
$scope.sharedData = newVal;
})
$scope.updateValue = function() {
MyService.doSomething();
}
}).controller("MyCtrl4", function($scope, MyService) {
//This option relies on the promise returned from the service to update the local
//scope, also since the properties of the object are being updated not the object
//itself this still stays "in sync" with the other controllers and service since
//really they are all referring to the same object.
MyService.doSomething().then(function(newVal) {
$scope.sharedData = newVal;
});
});
Regarding the add method in the service you'd want it to do something similar to a get, just create a deferred that you return the promise from and then do your async business (http request). For your byId function you may want to use a cached version (save the data that comes back from the query call in a property of the service). This way the query doesn't need to be executed every time if that's not necessary.