Which things i can put in service in angular Js - javascript

I have read many articles but still could not find how to put stuff in services.
Currently this is my Service
angular
.module('users')
.factory('objectiveService', objectiveService);
objectiveService.$inject = ['$http', 'Restangular'];
function objectiveService($http, Restangular) {
return {
getObjectives: getObjectives,
getSingleObjective: getSingleObjective
};
function getObjectives(pid) {
var pr = Restangular
.all('api')
.all('users')
.one('subjects', pid)
.all('objectives');
return pr;
}
function getSingleObjective(oid) {
var pr = Restangular
.all('api')
.all('users')
.one('objectives', oid);
return pr
}
}
This is the controller:
var _vm = this;
this.objPromise = objectiveService.getObjectives(44);
function getData() {
var promise = _vm.objPromise;
promise
.getList(filters)
.then(function(result) {
$scope.gridData = result;
});
}
function remove(id) {
if (confirm('Are you sure you want to delete this!')) {
objectiveService.getSingleObjective(id).remove().then(function() {
$scope.getData();
});
}
}
// initial call
$scope.getData();
In this code i basically see no use to define service because I still has to use then() in the controller to assign data to Grid.
Also I can't use then() in service because there I don't have $scope to update the data.
People say to do all stuff in service but I am not able to figure out how.
Ideally I want to put all functions like remove(object_id) in service
IS it possible that I can just do objectiveService.remove(id) in controller.
But then I have to call $scope.getData() after deleting which I can't do in service

The reason to use services is not to avoid the use of .then. In your controller you would use .then like so:
objectiveService.getObjectives(filters)
.then(function(result) {
$scope.gridData = result;
});
.then is required as the call to get data occurs asynchronously.
Some reasons taken from John Papa's style guide:
The controller's responsibility is for the presentation and gathering of information for the view. It should not care how it gets the data, just that it knows who to ask for it.
This makes it easier to test (mock or real) the data calls when testing a controller that uses a data service.
Data service implementation may have very specific code to handle the data repository. This may include headers, how to talk to the data, or other services such as $http. Separating the logic into a data service encapsulates this logic in a single place hiding the implementation from the outside consumers (perhaps a controller), also making it easier to change the implementation.
If you put the code in the controller it also means it's not reusable. You can't use it in more than one controller or you can't you it in another service.

Related

Patterns for AngularJS to update view in controller during recursive HTTP GET

Using AngularJS I have a historicalDataController
angular.module('armsApp').controller('historicalDataController', ($scope, $historicalFactory) => {
$scope.histogram = new Histogram('#histogram');
historicalFactory.getHistorycalData().then((data) => {
$scope.histogram.update(data);
});
});
and historicalFactory
angular.module('armsApp').factory('historicalFactory', ['$q', '$http',
function ($q, $http) {
return {
getHistoricalData() {
const deferred = $q.defer();
function loadAll(page) {
const token = cognito.getToken('id');
// let deferred = $q.defer();
return $http({
// details
})
.then((response) => {
if (response.data.pages > page) {
loadAll(page + 1);
} else {
deferred.resolve(response.data);
}
});
}
loadAll(0);
return deferred.promise;
},
The former manages my D3 histogram element and the latter handles communication to my data server.
From historicalDataController I am initiating a call to a function in historicalFactory which using recursion does serial GETs to a paginated REST endpoint and aggregates the data. In the controller I currently update the D3 histogram when the function returns with the full dataset, which functions OK.
Now I want to incrementally update the histogram with results from each GET but the callbacks are in the factory which has no access to the UI element (and I feel like view-related logic does not belong in there).
What are some good options of patterns to use here?
From factory should I write the incremental data to a variable in controller and broadcast an event to controller? This feels messy somehow.
Could I use some kind of cross-module decorator to wrap the Histogram-update function with the recursive GET function?
You'll want to use the "notification" callback to provide updates on a promise without resolving it. When one of your $http calls resolves, call deferred.notify(). In your controller, you'll want to provide "catch" and "notifiy" callbacks in your .then().
See the full documentation of $q.
https://docs.angularjs.org/api/ng/service/$q

Why move my $http calls into a service?

Currently I have calls like this all over my three controllers:
$scope.getCurrentUser = function () {
$http.post("/Account/CurrentUser", {}, postOptions)
.then(function(data) {
var result = angular.fromJson(data.data);
if (result != null) {
$scope.currentUser = result.id;
}
},
function(data) {
alert("Browser failed to get current user.");
});
};
I see lots of advice to encapsulate the $http calls into an HttpService, or some such, but that it is much better practice to return the promise than return the data. Yet if I return the promise, all but one line in my controller $http call changes, and all the logic of dealing with the response remains in my controllers, e.g:
$scope.getCurrentUser = function() {
RestService.post("/Account/CurrentUser", {}, postOptions)
.then(function(data) {
var result = angular.fromJson(data.data);
if (result != null) {
$scope.currentUser = result.id;
}
},
function(data) {
alert("Browser failed to get current user.");
});
};
I could create a RestService for each server side controller, but that would only end up calling a core service and passing the URL anyway.
There are a few reasons why it is good practice in non-trivial applications.
Using a single generic service and passing in the url and parameters doesn't add so much value as you noticed. Instead you would have one method for each type of fetch that you need to do.
Some benefits of using services:
Re-usability. In a simple app, there might be one data fetch for each controller. But that can soon change. For example, you might have a product list page with getProducts, and a detail page with getProductDetail. But then you want to add a sale page, a category page, or show related products on the detail page. These might all use the original getProducts (with appropriate parameters).
Testing. You want to be able to test the controller, in isolation from an external data source. Baking the data fetch in to the controller doesn't make that easy. With a service, you just mock the service and you can test the controller with stable, known data.
Maintainability. You may decide that with simple services, it's a similar amount of code to just put it all in the controller, even if you're reusing it. What happens if the back-end path changes? Now you need to update it everywhere it's used. What happens if some extra logic is needed to process the data, or you need to get some supplementary data with another call? With a service, you make the change in one place. With it baked in to controllers, you have more work to do.
Code clarity. You want your methods to do clear, specific things. The controller is responsible for the logic around a specific part of the application. Adding in the mechanics of fetching data confuses that. With a simple example the only extra logic you need is to decode the json. That's not bad if your back-end returns exactly the data your controllers need in exactly the right format, but that may not be the case. By splitting the code out, each method can do one thing well. Let the service get data and pass it on to the controller in exactly the right format, then let the controller do it's thing.
A controller carries out presentation logic (it acts as a viewmodel in Angular Model-View-Whatever pattern). Services do business logic (model). It is battle-proven separation of concerns and inherent part of OOP good practices.
Thin controllers and fat services guarantee that app units stay reusable, testable and maintainable.
There's no benefit in replacing $http with RestService if they are the same thing. The proper separation of business and presentation logic is expected to be something like this
$scope.getCurrentUser = function() {
return UserService.getCurrent()
.then(function(user) {
$scope.currentUser = user.id;
})
.catch(function(err) {
alert("Browser failed to get current user.");
throw err;
});
});
It takes care of result conditioning and returns a promise. getCurrentUser passes a promise, so it could be chained if needed (by other controller method or test).
It would make sense to have your service look like this:
app.factory('AccountService', function($http) {
return {
getCurrentUser: function(param1, param2) {
var postOptions = {}; // build the postOptions based on params here
return $http.post("/Account/CurrentUser", {}, postOptions)
.then(function(response) {
// do some common processing here
});
}
};
});
Then calling this method would look this way:
$scope.getCurrentUser = function() {
AccountService.getCurrentUser(param1, param2)
.then(function(currentUser){
// do your stuff here
});
};
Which looks much nicer and lets you avoid the repetition of the backend service url and postOptions variable construction in multiple controllers.
Simple. Write every function as a service so that you can reuse it. As this is an asynchronous call use angular promise to send the data back to controller by wrapping it up within a promise.

Angular Best practice: promise in a Factory or in a Controller?

I have a basic factory in my app that handles API calls. Currently I'm using the form:
.factory('apiFactory', function($http){
var url = 'http://192.168.22.8:8001/api/v1/';
return {
getReports: function() {
return $http.get(url+'reports').then(function(result) {
return result;
});
},
getReport: function(id) {
return $http.get(url+'report/'+id).then(function(result) {
return result;
});
}
}
})
And in my controller I'm handling the promise like so:
.controller('exampleController', function($scope, apiFactory) {
apiFactory.getReports().then(
function(answer) {
if (answer.status==200){
if (answer.data.status == "error"){
// DISPLAY ERROR MESSAGE
console.log(answer.data.msg);
}
} else{
// THROW error
console.log('error: ', answer);
}
},
function(error){
console.log('error: ', answer);
}
);
}
}
})
It seems I could move the promise handling to my Factory instead of doing it in my controller, but I'm not sure if that would have any benefits others than a smaller controller.
Could somebody explain the best practices regarding this pattern?
It is ultimately up to you how much data you want to provide to the caller of the service. If needed, you could definitely return the HTTP response object to the caller, and have them process the response (which, btw, is always HTTP 2xx, if the promise is resolved rather than rejected).
But if you want to isolate the caller from the specifics of how the data got there (maybe it was cached, or supplied via another mechanism), and if you need to post-process the data, then it is advisable to handle the response in the service.
Here's an example:
.factory("apiService", function($http, $q){
var url = 'http://192.168.22.8:8001/api/v1/';
return {
getReports: function() {
return $http.get(url+'reports').then(function(result) {
var data = result.data;
if (data === "something I don't accept"){
return $q.reject("Invalid data");
}
var processedData = processData(data);
return processedData;
})
.catch(function(err){
// for example, "re-throw" to "hide" HTTP specifics
return $q.reject("Data not available");
})
},
// same idea for getReport
}
});
Then the controller wouldn't need to care about the underlying mechanism - all it gets is data or a rejection.
.controller('exampleController', function($scope, apiService) {
apiService.getReports()
.then(function(reports){
$scope.reports = reports; // actual reports data
});
})
Off-topic:
Notice how I changed the name of the service from "apiFactory" to "apiService". I wanted to point that out to remove a possible misconception. Whether you use .factory or .service or .value what you get as an injectable is always a service instance. .factory is just a mechanism of how this service is instantiated, so the name "apiFactory" is a misnomer. The only "factory" here is a function that you register with .factory (which could be anonymous, of course):
.factory("fooSvc", function fooSvcFactory(){
return {
getFoo: function(){...}
}
})
Better to keep all the data fetching inside the factory. This keeps the controller free from state, and it no longer cares how your factory works. If you change how you get data (e.g. not using $http) your controller shouldn't care, as it just calls getReport() and
A good explanation (see "Resolving Model data, no callback arg binding in Controllers"):
http://toddmotto.com/rethinking-angular-js-controllers/
Short answer: Handle promises in Factory.
Why?
Problems you face if you handle promises in Controller:
Let's say you have 5 Controllers that use the same Factory. Now let's say that you want to handle the errors when the promise does not get resolved correctly. So in the first controller, you write an error callback (or the catch(exception) more precisely, as you are dealing with promises), that shows you an alert message with the error. When the promise fails, this controller shows an alert with the error message. So far, so good? Right. But wait! What about the other 4 controllers? You haven't handled the errors in them. So now you end up copying the error handling code from the first controller & pasting it in the rest of the 4 controllers.
Now the fun starts. Imagine that you want to change your logic in the error state. Maybe you want to just log the error in console, or show a toaster message perhaps. So you go to the first controller & update the code. You think you are done? NO!!! There are 4 other controllers that are showing the alert message (remember that you pasted the code from the first controller previously???). So now, you try to update the rest of the controllers by pasting the new error message logic. Think about all the controllers that you need to update, if you have more!!! Tiring, isn't it???
Why to resolve promise in Factory:
Now let's say that you write your error handling logic in your service and all the controllers are using this service. You have written code to show an alert in case of a promise that fails in this service. All controllers will now start showing this alert, in case of a broken promise. In future, if you wish to update your error logic, you simply update this error logic in your service & all your controllers will start using this updated logic automatically, without any more effort on your part. This way, you have saved yourself tons of time.
So think about this:
1 change in Factory vs 1 change in all Controllers (which maybe 1 or more)
I would definitely vouch for 1 change in Factory as that helps reuse my logic with just a simple change and only once. All my controllers will start using the new logic. Controllers are supposed to be slim, lean & clean. Factories & Services are supposed to be reusable. For this reason of reusability, I strongly suggest you handle promise in Factory/Service.

AngularJS pass data from controller to another controller

What I have done.
I retrieve a list of videos from youtube api with json in a controllerA with specific directive. The json contain the list of video and the video itself details.
What I want to do.
When click on a video, I want the video's details display in another ng-view with other controllerB with using the json data I request before.
So my question is
How to pass the data from controllerA to controllerB
Note - $http service is use in controllerA
This is one of the common doubts when starting with AngularJS. By your requirement, I believe your best option is to create a service that retrieves the movie list and then use this service in both controllerA and controllerB.
module.factory('youtube', function() {
var movieListCache;
function getMovies(ignoreCache) {
if (ignoreCache || !movieListCache) {
movieListCache = $http...;
}
return movieListCache;
}
return {
get: getMovies
};
});
Then you just inject this service in both controllers.
module.controller('controllerA', ['youtube', function(youtube) {
youtube.get().then(function doSomethingAfterRetrievingTheMovies() {
});
}]);
module.controller('controllerB', ['youtube', function(youtube) {
youtube.get().then(function doAnotherThingAfterRetrievingTheMovies() {
});
}]);
If you need controllerA to manipulate the info before you use it in B then you could create more methods in the service. Something like this:
module.factory('youtube', function($q) {
var movieListCache,
deferred = $q.defer();
function getMovies(ignoreCache) {
if (ignoreCache || !movieListCache) {
movieListCache = $http...;
}
return movieListCache;
}
function getChangedMovies() {
return deferred.promise;
}
function setChangedMovies(movies) {
deferred.resolve(movies);
}
return {
get: getMovies,
getChanged: getChangedMovies,
setChanged: setChangedMovies
};
});
If you don't know what $q is, take a look at the docs. This is mandatory to handle async operations.
Anyway, there are some other ways of accomplishing this task too:
You could save the videos at $rootScope
If the controllers are father and son the you could use require to retrieve each other controller
IMHO, #1 is a generic solution; I'd use it only if there is no other option. And #2 is useful if you have an intrinsic need to communicate between these controllers, such as configuring or letting one know about the other's existence. There is a example here.
What you want to do is to share stateful singleton information; therefore, a service is the way to go.

angularjs passing variables into controller

I have my angular controller setup like most of the examples shown in the docs such that it is a global function. I assume that the controller class is being called when the angular engine sees the controller tag in the html.
My issue is that i want to pass in a parameter to my controller and i don't know how to do that because I'm not initializing it. I see some answers suggesting the use of ng-init. But my parameter is not a trivial string - it is a complex object that is being loaded by another (non-angular) part of my js. It is also not available right on load but takes a while to come along.
So i need a way to pass this object, when it finally finishes loading, into the controller (or scope) so that the controller can interact with it.
Is this possible?
You can use a service or a factory for this, combined with promises:
You can setup a factory that returns a promise, and create a global function (accessible from 3rd-party JS) to resolve the promise.
Note the $rootScope.$apply() call. Angular won't call the then function of a promise until an $apply cycle. See the $q docs.
app.factory('fromoutside', function($window, $q, $rootScope) {
var deferred = $q.defer();
$window.injectIntoAngularWorld = function(obj) {
deferred.resolve(obj);
$rootScope.$apply();
};
return deferred.promise;
});
And then in your controller, you can ask for the fromoutside service and bind to the data when it arrives:
app.controller('main', function($scope, fromoutside) {
fromoutside.then(function(obj) {
$scope.data = obj;
});
});
And then somewhere outside of Angular:
setTimeout(function() {
window.injectIntoAngularWorld({
A: 1,
B: 2,
C: 3
});
}, 2000);
Here's a fiddle of this.
Personally, I feel this is a little bit cleaner than reaching into an Angular controller via the DOM.
EDIT: Another approach
Mark Rajcok asked in a comment if this could be modified to allow getting data more than once.
Now, getting data more than once could mean incremental updates, or changing the object itself, or other things. But the main things that need to happen are getting the data into the Angular world and then getting the right angular scopes to run their $digests.
In this fiddle, I've shown one way, when you might just be getting updates to an Array from outside of angular.
It uses a similar trick as the promise example above.
Here's the main factory:
app.factory('data', function($window) {
var items = [];
var scopes = [];
$window.addItem = function(item) {
items.push(item);
angular.forEach(scopes, function(scope) {
scope.$digest();
});
};
return {
items: items,
register: function(scope) { scopes.push(scope); }
};
Like the previous example, we attach a function to the $window service (exposing it globally). The new bit is exposing a register function, which controllers that want updates to data should use to register themselves.
When the external JS calls into angular, we just loop over all the registered scopes and run a digest on each to make sure they're updated.
In your non-angular JavaScript, you can get access to the scope associated with a DOM element as follows:
angular.element(someDomElement).scope().someControllerFunction(delayedData);
I assume you can find someDomElement with a jQuery selector or something.

Categories

Resources