I'm fairly new to Ionic and Angular and I've been reading that working with the dependency injection design pattern is recommended. Now I'm fairly new to DI as well so this is a double whammy for me.
If I have a controller for example:
app.controller('myController', ['$scope', '$localstorage', 'myService'
function($scope, $localstorage, myService) {
// Calls function in service
myService.concatStrings();
}
And I have a service:
app.service('myService', ['$localstorage', function ($localstorage) {
// Reads strings from local storage and concatenates them
function concatStrings () {
// ...
}
}
And say I can read the two strings from local storage like this:
var string1 = $localstorage.getStringOne();
var string2 = $localstorage.getStringTwo();
Where is the recommended place where I get these strings? Do I get them in my controller from $localstorage and pass them to the service, or do I not pass anything to the service and obtain the strings from $localstorage in the service?
I'm of the opinion that I actually read the strings from $localstorage in the controller and then pass it to the service. This way I can easily write unit tests for my services... But I'm not sure :\
Services are best used as reusable blocks of self contained code. I would think a concatStrings() method should be given strings to concatenate. If the method is to be concatStringsFromLocalStorage(), then perhaps it should be given some identifier to retrieve the strings from the storage, like perhaps a key or list of keys. The code which then uses localStorage (the service) can know about it, while the calling code (the controller) doesn't have to dirty itself with that knowledge.
The more you can easily reuse your code the more mileage you can get from it. Angular's DI, when used correctly, typically makes writing good tests with separate concerns quite easy.
you can read the strings from service's only it is recommended because we are going to inject the service as dependency to controller but not vice-versa and you can use $window.localStorage for storing the string values
Related
I am beginning my development in angular and I don't know much. What I'm trying to do is that I am trying to pass a fairly large collection of data from one controller to another. This is how I managed to do it.
angular.module("myApp").controller("controllerName", function($rootScope, $scope, *...other stuff...*)
{ /* code */ }
Later there is one specific method which is accessed from outside, and I copy the collection like this:
$rootScope.selectedItems = angular.copy($scope.selected.items);
(This is an array of 5k strings)
Which is then catched in another controller. Other developers said it is unsafe to pass this through $rootScope but after the data is passed and copied into local controller, I use this to get rid of the collection in rootScope
delete $rootScope.selectedItems;
Is this a safe way to do this? It works perfectly, and nothing seems dangerous to me
As a general rule, don't use $rootScope to pass data. By using it you make your module dependent on unspecified functionality which may not be a dependency of your module. It's a structural issue which will create problems later.
Instead, use a service:
angular.module("myApp").service("myAppService", function () {
this.selectedItems = [];
});
angular.module("myApp").controller("controllerName1", function(myAppService, $scope) {
$scope.selectedItems = myAppService.selectedItems;
});
angular.module("myApp").controller("controllerName2", function(myAppService, $scope) {
$scope.selectedItems = myAppService.selectedItems;
});
It's also recommended that all your logic goes into services (and factories/providers where appropriate). Controllers should be used only to expose service functionality, unless a necessary special case can be proven. This makes the logic in services easier to unit test.
There are many service are available you should go with broadcast
Here is example for $broadcast service
https://toddmotto.com/all-about-angulars-emit-broadcast-on-publish-subscribing/
What is better when creating a controller in AngularJs:
1-
angular.module('myApp')
.controller('MyController',['$scope','dependencies','myService', function($scope, dependencies, myService ) {
}]);
2-
angular.module('myApp')
.controller('MyController', function($scope, dependencies, myService ) {
});
Performance wise, the first is technically better, although that cost is probably negligible. If you do it the second way, angular literally calls the .toString() function on your controller function, and then parses the string to determine the dependencies. So you can either declare them yourself, or angular will parse your function as a string to determine them.
As a developer, I find it easier to read and write the second version. Just be aware that you have to change things to the first form if you plan on uglifying/minifying your code. Some tools will do this for you automatically (ngAnnotate, for example), so that you can write it the 2nd way, but have it deployed the first way.
With this method you have the advantage of function hoisting , function name for debugging , ability to mutate $inject;
also easier to reason about & debugging.
function myController(){
}
myController.$inject = ['$scope','dependencies','myService'];
angular.module('myApp' ,myController);
So, this isn't the typical question of HOW to do it. I know it can be done with a service or a factory but I was wondering if someone could share what the advantages/disadvantages would be of just creating a basic service, injecting it into each controller, and then extending the service with functions from each controller. Something similar to the following example..
app.service('HelperService', function() {
return {};
});
app.controller('Controller1', function($scope, HelperService) {
$scope.somefunc = function() {
//do stuff here
};
HelperService.somefunc = $scope.somefunc;
});
app.controller('Controller2', function($scope, HelperService) {
HelperService.somefunc();
});
This works, and works well. I feel a bit stupid for asking this but it just seems like I'm missing something here as to why this isn't used or recommended?
It may work, but its a bad idea.
Controller2 HelperService.somefunc() won't exist until Controller1 has been instantiated. So theres an implicit dependency of Controller2 on Controller1
the code on HelperService isn't in one place where it can be understood together
if you are doing some sort of data manipulation in that function, it really should be operating on data encapsulated by the HelperService.
The service is a singleton and it will be instantiated once calling new on the function itself -- the function you pass in is essentially a constructor. This will create the empty object you are returning for use everywhere, but if you want to return an object in such a way it makes more sense to use .factory, but this is not a big deal.
At any rate, you can consider your code to conceptually do this:
var HelperService = function () {}
var helperService = new HelperService;
function Controller1() {
helperService.someFunc = function () {}
}
function Controller2() {
helperService.someFunc();
}
I would consider this a dangerous thing to do for a couple of reasons:
Controller1 must be instantiated before Controller2 or else somefunc won't be available to Controller2. Ideally the controllers would have no knowledge of each other.
You are coupling Controller/ViewModel (since you're using scope) with service level logic, but these should be decoupled. HelperService shouldn't know about the controllers either. Instead, you should be injecting a service that has an API that the controllers expect to use. This doesn't always have to be HelperService, it just has to look like HelperService to the controllers and its API shouldn't change.
Without knowing specifics about what you're trying to do, it's hard to advise. In general you may rethink what you want to do, but you can extend functionality of services with other services. Consider services to be in their own layer.
The code below represents a situation where the same code pattern repeats in every controller which handles data from the server. After a long research and irc talk at #angularjs I still cannot figure how to abstract that code, inline comments explain the situations:
myApp.controller("TodoCtrl", function($scope, Restangular,
CalendarService, $filter){
var all_todos = [];
$scope.todos = [];
Restangular.all("vtodo/").getList().then(function(data){
all_todos = data;
$scope.todos = $filter("calendaractive")(all_todos);
});
//I can see myself repeating this line in every
//controller dealing with data which somehow relates
//and is possibly filtered by CalendarService:
$scope.activeData = CalendarService.activeData;
//also this line, which triggers refiltering when
//CalendarService is repeating within other controllers
$scope.$watch("activeData", function(){
$scope.todos = $filter("calendaractive")(all_todos);
}, true);
});
//example. another controller, different data, same relation with calendar?
myApp.controller("DiaryCtrl", function($scope, Restangular,
CalendarService, $filter){
//this all_object and object seems repetitive,
//isn't there another way to do it? so I can keep it DRY?
var all_todos = [];
$scope.todos = [];
Restangular.all("diary/").getList().then(function(data){
all_diaries = data;
$scope.diaries = $filter("calendaractive")(all_diaries);
});
$scope.activeData = CalendarService.activeData;
$scope.$watch("activeData", function(){
$scope.todos = $filter("calendaractive")(all_diaries);
}, true);
});
DRY should be followed purposefully, not zealously. Your code is fine, the controllers are doing what they are supposed to be doing: connecting different pieces of the app. That said, you can easily combine repeated code in a factory method that returns a function reference.
For example,
myApp.factory('calendarScopeDecorator', function(CalendarService, Restangular, $filter) {
return function($scope, section) {
$scope.todos = [];
$scope.activeData = CalendarService.activeData;
Restangular.all(section+"/").getList().then(function(data){
$scope.all_data = data;
$scope.filtered_data = $filter("calendaractive")(data);
});
$scope.$watch("activeData", function(){
$scope.todos = $filter("calendaractive")($scope.all_data);
}, true);
}
});
And then incorporate this into your controller:
myApp.controller("DiaryCtrl", function($scope, calendarScopeDecorator){
calendarScopeDecorator($scope, 'diary');
});
I wouldn't do this kind of thing with a watcher and local reference like in this controller. Instead, I would use $on() / $emit() to establish a pub-sub pattern from the service out to the controllers that care about its updates. This is an under-used pattern IMO that provides a more "DRY" mechanism. It's also extremely efficient - often more so than a watcher, because it doesn't need to run a digest to know something has changed. In network-based services you almost always know this for certain, and you don't need to go from knowing it for certain to implying it in other locations. This would let you avoid the cost of Angular's deep inspection of objects:
$rootScope.$on('calendarDiariesUpdated', function() {
// Update your $scope.todos here.
}, true);
In your service:
// When you have a situation where you know the data has been updated:
$rootScope.$emit('calendarDiariesUpdated');
Note that emit/on are more efficient than using broadcast, which will go through all nested scopes. You can also pass data from the service to listening controllers this way.
This is a really important technique that does a few things:
You no longer need to take a local reference to activeData, since you aren't actually using it (it's DRY).
This is more efficient in most/many cases than a watcher. Angular doesn't need to work out that you need to be told of an update - you know you do. This is also kind of a DRY principle - why use a framework tool to do something you don't actually need? It's an extra step to put the data somewhere and then wait for Angular to digest it and say "whoah, you need to know about this."
You may even be able to reduce your injections. There's no need to take CalendarService because that service can pass a reference to the array right in its notification. That's nice because you don't need to refactor this later if you change the storage model within the service (one of the things DRY advocates also advocate is abstracting these things).
You do need to take $rootScope so you can register the watcher, but there's nothing in pub-sub concepts that violate DRY. It's very common and accepted (and most important: it performs very well). This isn't the same thing as a raw global variable, which is what scares people off from using $rootScope in the first place (and often rightly so).
If you want to be "Super DRY" you can re-factor the call to $filter into a single method that does the filtering, and call it both from your REST promise resolution and from the calendar-update notification. That actually adds a few lines of code... but doesn't REPEAT any. :) That's probably a good idea in principle since that particular line is something you're likely to maintain (it takes that static "calendaractive" parameter...)
It looks like the code in the 2 controllers is identical except for the path to which the API call is made: "vtodo/" or "diary/".
One way to achieve something closer to DRY-ness is to pass the API path as an option to the controller as an attribute. So, assuming we call the controller ApiController, this can be used as
<div ng-controller="ApiController" api-controller-path="vtodo/">
<!-- Todo template -->
</div>
and
<div ng-controller="ApiController" api-controller-path="diary/">
<!-- Diary template -->
</div>
Which is then accessible in the controller by the injected $attrs parameter:
myApp.controller("ApiController", function($scope, $attrs, Restangular, CalendarService, $filter) {
// "vtodo/" or "diary/"
var apiPath = $attrs.apiControllerPath;
As a caution I would beware of over-architecting, not everything needs to be factored out, and there is an argument that you are just following a design pattern rather than copy+pasting code. However, I have used the above method of passing options to a controller for a similar situation myself.
If you are worried about making multiple calls to the same resource CalendarService, I'd recommend finding a way to cache the result, such as defining a variable in that service or using Angular's $cacheFactory.
Otherwise, I don't see anything wrong with your patterns.
I've been reading through the tutorial, dev guide, and practicing on my own, but I'm having trouble piecing everything together in my mind with regard to dependency injection.
Question: Within the first code snippet in the linked page below, why is the name of the "service" located in front of $inject and why is the parameter of the service used here again? Or better yet what concepts am I lacking in understanding? I'd like to be able to piece it all together in my head step by step, but I'm still trying to understand how exactly even the globally defined "services/functions" can be written this way.
http://docs.angularjs.org/guide/dev_guide.services.understanding_services
So in that code snippet is injecting the $location service into MyController. So MyController depends on $location so it declares the dependency and its owns the dependency declaration.
Here is the code commented:
// declaring a Controller function
var MyController = function($location) { ... };
// $location service is required by MyController
MyController.$inject = ['$location'];
// Then register the Controller in the module.
// The module is the container that performs DI on the objects within it.
myModule.controller('MyController', MyController);
Typically though you'd do the following to declare the Controller and it's dependencies in one shot that's cleaner. The dependencies are strings at the front of the array before the final function which is the controller being registered. Here is the simpler definition:
myModule.controller('MyController', ['$scope', '$location', function($scope, $location) {
$scope.someFunction = function() {
// do something with $location service in here
};
}]);
Keep in mind this:
"...even the globally defined "services/functions"
The whole point of DI is to not define things globally because global definitions create coupling that make it hard to reuse major portions of your system (ie you can't break apart the system without instantiating the whole thing). Dependency Injection separates the dependency (ie MyController depends on/uses $location service) from where it finds that reference. Beginner developers, and some dense senior devs quite frankly, typically just define things globally and that's how everything gets a reference to their dependencies. DI allows code to simply declare its dependencies so its dependencies can be given to the code by an external entity instead of the code assuming where to get it from. Often this is called the Hollywood Principle - Don't call us we'll call you.
It looks like you're lacking a strong understanding of Dependency Injection in AngularJS.
Once you define a service, it then needs to be injected inside to the controller that is going to use it. The two code samples on that page show the two different methods of injecting the service into the controller.
I'd suggest you look at the docs: AngularJS: Dependency Injection