Sharing Data between controllers : Service or Value? - angularjs

To share data between controllers, most of the Stack overflow Answers suggest to use services. Mostly when I share data between controllers, it is my application model(Data) and it changes in each controller as per application logic. So, should it not be an angular value instead of angular service?
For example, take the following service,
app.factory('Employee',function($http){
function Employee(){
this.data = {};
}
Employee.prototype.load = function(){
//XHR call which invokes employee details and assigns it here
$http.get(url).then(
function(response){
this.data = response.data;
}
);
}
return new Employee();
});
With this service in hand, I would not be able to inject my Employee model during ui-router's resolve(as services cannot be injected into config blocks). But if I create the same using value, I would be able to inject it during stateRouting itself. Could you please give me why value is not preferred to create models/share data between controllers over service?

First, values can't be injected into config blocks either. But that's irrelevant, since resolve functions are not called during the config phase, but at runtime, every time you navigate to the enclosing route.
Values can't be injected at all, so I don't really see how you would have access to $http when defining your value.
Finally, you can access your service in a resolve function, simply by injecting it into the function:
resolve: {
employee: function(Employee) {
return Employee.load();
}
}
But that would not make much sense, since your load() method doesn't return anything. What it should do is returning a promise of employee:
Employee.prototype.load = function(){
return $http.get(url).then(
function(response) {
return response.data;
}
);
};
But that has nothing to do with sharing data between controllers. What it allows doing is waiting for the employee data to be available and injecting it in the controller before switching to the employee view.

Related

Return value from service once $resource promise is resolved

I have such working code:
Service (factory?):
myApp.factory('MyService', ['$q','$resource', 'MyResource',
function ($q, $resource, MyResource) {
return {
getRoles: function () {
return MyResource.getRoles(); //MyResource makes API calls using $resource
} } });
Controller:
$scope.Roles = MyService.getRoles();
$scope.Roles.$promise.then(function () { $scope.RolesCount = $scope.Roles.length; });
What I'd like to do is to create such function in MyService that will return number of roles once getRoles is resolved so I can use it in controller like this:
$scope.RolesCount = MyService.getRolesCount();
and then in html
{{RolesCount}}
Unfortunately, I have no idea how to do this since my getRolesCount() method needs to return something so I can't use $promise.then() inside MyService. I'll try to update my question's title once I come up with something more accurate.
If server response needs to be transformed in a service, then then should be moved there. However, it's not practical or possible if then is supposed to unwrap a promise and assign a value to scope, like in the code above.
$resource returns an object that is filled on promise resolution. The way a resource is supposed to be used in view is:
{{Roles.length}}
When an object is updated, it will be updated in view, too. Assigning the value to another variable is inefficient.
It's impossible to do something like
$scope.RolesCount = MyService.getRolesCount();
because getRolesCount is asynchronous, and the value it resolves with is scalar. It is possible to flatten it with `async..await, (TypeScript or ES2017), but will require additional measures to synchronize control flow with AngularJS digests, as explained in this answer.

In AngularJS, is a factory or service providing the exact same thing, but just differ in how the service object is created?

I think I had the misconception before that an AngularJS factory is for object creation or functions, while an AngularJS service is for HTTP requests.
Another question talked about this, but it didn't clarify some of the most fundamental concepts.
It looks like their end result is exactly the same -- to get a what is called a "service object". We can usually use factory() or service() interchangeably, and it just matter how the service object is created.
In the case of a factory, the object is returned as is:
angular.module("myApp", [])
.factory("myService", function() {
return { // the awesome service object
foo: 123,
doSomething: function() { }
};
});
In the case of a service, AngularJS will use that function as a contructor function, with a new keyword, to create the service object and return to the user, when the user uses myService.doSomething() or myService.foo in the user's code.
angular.module("myApp", [])
.service("myService", function() {
// I am the awesome service object when AngularJS uses
// the keyword "new" on the above anonymous function.
// "this" will point to myself.
this.foo = 123;
this.doSomething = function() { };
});
So an AngularJS factory or service can actually both do exactly the same thing (such as fetching data from the server), but they just differ how the service object is created.
If we just use the usual, simple JavaScript object literal, we can just use factory(), but the only case we would use service() instead is that, if we want to create a base class, and then use inheritance to create subclasses, and now we have these constructor functions, and to use constructors, we have to use service().
Is this concept correct for AngularJS?

When to Use service and When to factory in AngularJS?

I have gone through lots of document and also refereed stack overflow post regarding this issue.
I am still having confusion When to use Service and Factory.
Can any one Explain using Real World Example when to use what ?
Common thing about service & factory is they are singleton objects,
there instance gets created once per application.
Basically angular service does return an object by using new keyword, whether factory does return an object which you have created.
Service
app.service('myService', function(){
this.myMethod = function(){
return 'something';
};
})
In above service we added one method myMethod which can be available to the the component whoever inject the service. Service does gives access to all of its properties which are assigned to its this context.
Factory
app.factory('myService', function(){
//here you can do some initial work before creating an service object.
//which is very advantageous part of service.
var configuredVariable = 'configured';
return {
myMethod : function(){
return 'something'+ configuredVariable;
};
}
})
You could have controller over object before creating it, you could do some configuration kind of setting before returning an object from a service.
More detailed answer here
Summary
When to Use service and When to factory in agularJS?
Used service whenever you don't won't to return an configurable object, If you want to configure the object you should go for an factory.

AngularJS - When to service what

So, over the last month I've been diving pretty hard into AngularJS and my controllers got extremely big. I decided to learn about factories and services so I could separate some of my logic...which leads me to my question.
I see a lot of examples online where people are using ajax calls to the server from their controllers. Is this a good practice? The more I study AngularJS and compare it to what I already know about software design, I see the controller's job pass data into the view. Much like a controller in MVC, where business logic shouldn't exist. Is that a safe assumption?
Also -- If I were to move my ajax calls over to a factory or service, would the functionality be the same? Something like below?
Service
app.service('orderService', ['$http', function ($http) {
var orderdata = {};
// Gets orders from WebAPI
this.getRecentOrders = function () {
$http.get('/api/orders').success(function (data) {
this.orderdata = data;
});
return this.orderdata;
};
}]);
Controller
$scope.recentOrders = orderService.getRecentOrders();
edit
I had to modify the code in the controller slightly from the answer to get it to work correctly:
orderService.getRecentOrders().then(function(result) {
$scope.recentOrders = result.data;
});
Services should return an object with methods. Also, the method you invoke should return the promise as it's an async call, so the return this.orderdata will always be undefined (or at least the first time):
app.service('orderService', ['$http', function ($http) {
return {
getRecentOrders: function () {
return $http.get('/api/orders');
}
};
}]);
No with this, you can chain to that promise in order to get the data in your controller:
orderService.getRecentOrders().then(function(data) {
$scope.recentOrders = data;
});
Now as per the question itself, basically, controllers should be "dumb", what does this mean? it should not have any heavy logic within. It should only be the layer that gets data from the services, and use them to show them in the $scope. Everything related to heavy logic (data processing and such) should be apart from the controller. That's why angular provides you with Filters, Services and Directives.

How to provide initial data to Angular's controller/$scope?

There seems to be no way to provide data to an Angular controller other than through attributes in the DOM handled by directives (of which ngInit is a handy example).
I'd like to provide other "constructor" data, e.g. objects with functions to my
$scope.
Background: We have an existing dashboard-style single page application,
where each widget manages a <div>, and widget-instance-specific data
is provided as an object along with support functions, etc.. This object data
doesn't fit nicely into DOM attributes or ngInit calls.
I can't really come up with a better way to it than to have a global hash, and use an instance-specific unique key. Before calling angular.bootstrap(domElement, ['myApp']), we set up all "constructor" parameters in this global hash under the key and then use
<div ng-init='readInitialValuesFromHash("uniqueKey")'>...</div>
where readInitialValuesFromHash gets all its data from
globalHash["uniqueKey"] and stores what it needs it in $scope (possibly
just the "uniqueKey").
(What seems like an alternative is to use a directive and jQuery.data(), but jQuery.data uses a global hash behind the scenes)
Of course I can hide the global data in a function, but fundamentally still use
a singleton/global variable. This "global hash and pass key as param to ng init
trick" just seems like such a hack...
Am I missing something? Is there a better way, given that the
widget-instance-specific data is actually more complicated than suitable for
inserting in the DOM via directives/attributes due to the legacy dashboard
framework?
Are there dangers when putting complicated objects in the $scope as long as they aren't referenced by directives, {{}} or $scope.$watch() calls?
Angular's front page says:
Add as much or as little of AngularJS to an existing page as you like
So in light of that, how should I proceed?
EDIT: Comments have asked to make my question more clear. As an example of a non-trivial constructor parameter, assume I want to give this myObj to the controller, prototypical inheritance, function and all:
var proto = {
p1: "p1",
pf: function() {
return "proto"
}
};
function MyObj(ost) {
this.ost = ost;
}
MyObj.prototype=proto;
var myObj = new MyObj("OST");
So I have myObj, and I have a string:
<div ng-app='myApp' ng-controller="MyCtrl">....</div>
I put the string in the DOM, and call angular.bootstrap().
How to I get the real myObj object into MyCtrl's $scope for this <div>, not a serialized/deserialized version/copy of it?
Services is what you are looking for.
You can create your own services and then specify them as dependencies to your components (controllers, directives, filters, services), so Angular's dependency injection will take care of the rest.
Points to keep in mind:
Services are application singletons. This means that there is only one instance of a given service per injector. Since Angular is lethally allergic to global state, it is possible to create multiple injectors, each with its own instance of a given service, but that is rarely needed, except in tests where this property is crucially important.
Services are instantiated lazily. This means that a service will be created only when it is needed for instantiation of a service or an application component that depends on it. In other words, Angular won't instantiate services unless they are requested directly or indirectly by the application.
Services (which are injectable through DI) are strongly preferred to global state (what isn't), because they are much more testable (e.g. easily mocked etc) and "safer" (e.g. against accidental conflicts).
Relevant links:
Understanding Angular Services
Managing Service Dependencies
Creating Angular Services
Injecting Services into Controllers
Testing Angular Services
About Angular Dependency Injection
Example:
Depending on your exact requirements, it might be better to create one service to hold all configuration data or create one service per widget. In the latter case, it would probably be a good idea to include all services in a module of their own and specify it as a dependency of your main module.
var services = angular.module('myApp.services', []);
services.factory('widget01Srv', function () {
var service = {};
service.config = {...};
/* Other widget01-specific data could go here,
* e.g. functionality (but not presentation-related stuff) */
service.doSomeSuperCoolStuff = function (someValue) {
/* Send `someValue` to the server, receive data, process data */
return somePrettyInterestingStuff;
}
...
return service;
}
services.factory('widget02Srv', function () {...}
...
var app = angular.module('myApp', ['myApp.services']);
app.directive('widget01', function ('widget01Srv') {
return function postLink(scope, elem, attrs) {
attrs.$set(someKey, widget01Srv.config.someKey);
elem.bind('click', function () {
widget01Srv.doSomeSuperCoolStuff(elem.val());
});
...
};
});
ExpertSystem's answer gave me the hint that I needed. A separate controller instance for each widget. Note how the constructorParameter (==myObj) gets inserted into the controller.
function creatWidgetInstance(name) {
....
var controllerName = name + 'Ctrl';
// myObj comes from the original question
var constructorParameter = myObj;
widgetApp.controller(controllerName, function($scope) {
$scope.string = constructorParameter;
});
....
newWidget = jQuery(...);
newWidget.attr('ng-controller', controllerName);
angular.bootstrap(newWidget[0], ['widgetApp']);
....
}
See it working in a plunker
Perhaps a more beautiful solution is with a separate service too, as in:
function creatWidgetInstance(name) {
....
var controllerName = name + 'Ctrl';
var serviceName = name + 'Service';
// myObj comes from the original question
var constructorParameter = myObj;
widgetApp.factory(serviceName, function () {
return { savedConstructorParameter: constructorParameter };
});
widgetApp.controller(controllerName,
[ '$scope', serviceName, function($scope, service) {
$scope.string = service.savedConstructorParameter;
}
]
);
....
newWidget = jQuery(...);
newWidget.attr('ng-controller', controllerName);
angular.bootstrap(newWidget[0], ['widgetApp']);
....
}
See this in a working Plunker
The answer to the question requires backtracking a few assumptions. I thought that the only way to setup $scopewas to do it on a controller. And so the question revolves around how to "provide data to an Angular controller other than through attributes in the DOM handled by directives".
That was misguided.
Instead, one can do:
var scope = $rootScope.$new();
// Actually setting scope.string=scope makes for a horrible example,
// but it follows the terminology from the rest of the post.
scope.string = myObj;
var element = $compile(jQuery('#widgetTemplate').html())(scope);
jQuery('#widgets').append(element);
See this Plunker for a working example.

Resources