AngularJS philosophy - controllers as "windows" to services - javascript

Sorry for the vague title;
I've been restructuring some of my AngularJS code, trying to be more "Angular" about it, and I've noticed this pattern popping up quite a bit:
app.service("someService", function(...) {
...
}
app.controller("ControllerForThisSection", function($scope, someService) {
$scope.someService = someService
}
Basically, the controller is mostly there to give the scope a reference to the service so a view can use it, like
<div ng-if="someService.status">
....
</div>
So I have more than a few controllers that do nothing more than depend on certain shared data or services and serve to make references to those services available through the scope.
Is there any disadvantage to using this design? Can I improve my thinking any? Is this the "angular" way to do it?
Thanks for any advice!

This is the "angular way". Shared data should be placed into services, then injected where needed.
I like to think of my Angular apps mainly in terms of models (which are usually stored in services) and views. The controllers are just the glue that allows us to project/extract the parts of our models that a particular UI view needs.
Also, think of services as returning a model API, not a model object (to quote Josh).

Related

Does it make sense to use angular services with es6 modules?

Does it make sense to use angular-services when we use ES6 modules? For example we need a singleton module (userService) in our code and we can do like this:
var app = angular.module('app', []);
app.service('userService', function(){
this.users = ['John', 'James', 'Jake'];
});
app.controller('FooController', ['$scope', 'userService', function($scope, userService){
console.log(userService);
}]);
But we can define the service in separate file:
/* ./user-service.js */
export default users = ['John', 'James', 'Jake'];
, then make the code like this:
var app = angular.module('app', []);
var userService = require('./user-service')
app.controller('FooController', ['$scope', function($scope){
console.log(userService);
}]);
and result will be absolutely the same as with services using. So why use angular services when we can use modules?
Yes! It makes perfect sense.
Services implement a particular responsibility in your application, moving data between the data store and views.
Modules allow you to organize your code and separate sections with different responsibilities.
By putting each service into a module, you make it easier to browse and test your code. It's easy to find all of the code that implements a responsibility.
Source: Difference between service, directive and module =)
From my own personal notes (mostly snippets from the docs, google group posts, and SO posts):
Modules
provide a way to namespace/group services, directives, filters, configuration information and initialization code
help avoid global variables
are used to configure the $injector, allowing the things defined by the module (or the whole module itself) to be injected elsewhere (Dependency Injection stuff)
Angular modules are not related to CommonJS or Require.js. As opposed to AMD or Require.js modules, Angular modules don't try to solve the problem of script load ordering or lazy script fetching. These goals are orthogonal and both module systems can live side by side and fulfill their goals (so the docs claim).
Services
are singletons, so there is only one instance of each service you define. As singletons, they are not affected by scopes, and hence can be accessed by (shared with) multiple views/controllers/directives/other services
You can (and probably should) create custom services when
two or more things need access to the same data (don't use root scope) or you just want to neatly encapsulate your data
you want to encapsulate interactions with, say, a web server (extend $resource or $http in your service)
Built-in services start with '$'.
To use a service, dependency injection is required on the dependent (e.g., on the controller, or another service, or a directive).
Directives (some of the items below say essentially the same thing, but I've found that sometimes a slightly different wording helps a lot)
are responsible for updating the DOM when the state of the model changes
extend HTML vocabulary = teach HTML new tricks. Angular comes with a built in set of directives (e.g., ng-* stuff) which are useful for building web applications but you can add your own such that HTML can be turned into a declarative Domain Specific Language (DSL). E.g., the <tabs> and <pane> elements on the Angular home page demo "Creating Components".
Non-obvious built-in directives (because they don't start with "ng"): a, form, input, script, select, textarea. Under Angular, these all do more than normal!
Directives allow you to "componentize HTML". Directives are often better than ng-include. E.g., when you start writing lots of HTML with mainly data-binding, refactor that HTML into (reusable) directives.
The Angular compiler allows you to attach behavior to any HTML element or attribute and even create new HTML elements or attributes with custom behavior. Angular calls these behavior extensions directives.
When you boil it all down, a directive is just a function which executes when the Angular compiler encounters it in the DOM.
A directive is a behavior or DOM transformation which is triggered by a presence of an attribute, an element name, a class name, or a name in a comment. Directive is a behavior which should be triggered when specific HTML constructs are encountered in the (HTML) compilation process. The directives can be placed in element names, attributes, class names, as well as comments.
Most directives are restricted to attribute only. E.g., DoubleClick only uses custom attribute directives.
see also What is an angularjs directive?
Define and group Angular things (dependency injection stuff) in modules.
Share data and wrap web server interaction in services.
Extend HTML and do DOM manipulation in directives.
And make Controllers as "thin" as possible.

Isolate scopes angularjs, ditching dependency injection, reusable components

I haven't really been fiddling for angularjs's directive for a while and I do not still have a good grasp on it. Before I dive into it, I am thinking react, on how they do their components.
So I have search on how to create reusable components using directives, and found this article:
http://michalostruszka.pl/blog/2015/01/18/angular-directives-di/
But the implementation on his final solution is quite blurry, as I cannot figure out on how to use it correctly.
Let's say I create a title directive:
<epw-title store="epwEventStore">{{vm.title}}</epw-title>
And another directive that uses the same service epwEventStore so that it can update the state
<epw-event-list store="epwEventStore"></epw-event-list>
Where the epw-event-list renders a list and when clicked should change the value of vm.title of the epw-title.
How is this possible?
Update
Q: Are they nested?
A: No, they are siblings.
Don't put Services inside Views
Just to avoid any misunderstanding, if epwEventStore is an AngularJS Service (such as provider, factory, service), it is not meant to be put as attribute value inside your template like that:
<epw-title store="epwEventStore">{{vm.title}}</epw-title>
In a well-designed decoupled architecture, your Views (template) should not "know" anything about your Services. They should only refer to controllers, directives and filters.
Different directives can use the same service
That means you can perfectly have
...
.directive("first", function(myService) {
...
})
.directive("two", function(myService) {
...
})
...
where, e.g., both directive can access and manipulate the same data.
Angular way is declarative
Following Angular's philosophy, if your directive depends on a Service, you declare it in the directive's definition. So whoever reads the code - knows immediately about all dependencies. This makes the code readable.
What the author of the article seems to be suggesting is to use events instead. Which is the publish-subscribe pattern. However, using it inside your directive, especially with $rootScope, makes the directive "leaky" as it is no more encapsulated and can both affect the external state and be affected. And worse - the reader has now manually search all your codebase for whoever is affected by the events emitted from the directive. This pattern has its uses but should be enjoyed with care.

Is there any harm in adding more dependencies in angular js controller or service

Suppose this is my angular controller
app.controller("MyCtrl", function($scope, $modal, $state,) {
});
I am thinking of having one global variable holding most commonly used dependencies like
var all = ['$scope', '$modal', '$state']
and then use all at every place along with some other dependencies if needed
Is there any performace issue having put all dependencies everywhere
Having to inject more code would have a performance hit, but not a major one. I don't recommend defining your dependencies globally like that because dependencies should be very visible. You should know exactly what you're doing with them without having to open another file and check.
If you need to reuse a set of dependencies everywhere, that suggests more that there's probably something wrong with the code. How come different regions of the code base all talk to the same stuff? That suggests duplication of concerns. I don't extend that assertion to just having to inject $scope or $http all the time.
In short, I don't think it's a good idea to manage dependencies like that.
While I don't have deep knowledge of the Angular internals, nor have I bothered to actually measure the performance of what you are asking, I would venture my educated guess as .... no. You're not going to see a performance impact here. The only impact would be on controller instantiation, which only happens once per view. And even then, we're just talking about new-ing up a few objects ... the perf impact ought to be very negligible, and not something I would worry about.
You can't inject using a variable you define within another controller or service. One thing you could do is create a factory and put your dependencies in the $rootScope.
app.factory('root',function($rootScope, $modal, $state){
$rootScope.modal = $modal;
$rootScope.state = $state;
});
You just put $rootScope in all your controllers and you have access to whatever you like. You would only need to inject 'root' in your main controller (if you have one). It seems like fishy architecture to need this shortcut, but that is how I would do it. No perf hit really - non-primitives are reference types.

Angularjs Can I change $scope value of a controller from another controller?

I have an HTML div , like
<div id="loader-container" data-ng-controller="LoaderController" ng-show="shouldShow">
Some html design
</div>
And in my controller
angular.module('core').controller('LoaderController', ['$scope','$location', 'Authentication', '$rootScope',
function($scope,$location, Authentication, $rootScope) {
$scope.shouldShow = true;
}
]);
And now, I want to hide that html div from another controller, That's why I tried to make $scope.shouldShow variable as false from another controller. So how can I make
$scope.shouldShow = false;
from another controller. Is it possible or any alternative way.?
every controller will create a new Scope.
Hence the changing the scope in one controller will not reflect in the scope of other controller.
If you want to share a property across the controllers create that variable on the $rootScope and use it.
In Controller
$scope.$root.shouldShow = true; //or false
In Markup
<div ng-show="$root.shouldShow"></div>
It's not a good idea to manipulate a $scope of a controller from a different controller directly. It would make your controllers very tightly coupled.
One commonly accepted way to communicate between controllers is through messages using the publish-subscribe pattern.
If your controllers are in completely separate components and you just need to notify the other controller, you can use the $broadcast/$emit and $on methods of the scope object to broadcast a message from one controller and listening to a specific message in a different controller. When an action happens that should change the shouldShow variable, you can just broadcast a message and make the other controller listen and act on it.
Root Scope API
Another common way to communicate between controllers is by using angular services, acting as a mediator between controllers.
If your controllers are part of the same component/module, and you need to share state/behavior between those, then using an angular service to encapsulate that logic and expose it would be an OK approach (services are singletons in Angular). That would be pretty simple to implement.
However, not knowing more details about your exact requirements, it's hard to design a proper solution.
Thanks to #Callum Linington for discussing the alternatives in the comments.

AngularJS - Why have more than one controller

What reasons are there to have multiple controllers in an AngularJS application? I've built a few angular apps now and I've never encountered a problem where I thought multiple controllers would make things easier for me.
I'm still a bit of a n00b, have never written a unit test and my code isn't as manageable as it could be so I'm sure it's just ignorance. And I've heard that other people have multiple controllers.
Put another way: how does one know that they should create a new controller?
From what I've seen of it an Angular application should have separate controllers for separate scopes. For instance, almost all applications have user data. You'll want to have this data attached to a user model, inside a user controller:
function UserCtrl ($scope) {
$scope.user = {
name: "bmorrow",
lastLogin: "4/16/2013"
};
}
And the template (our view) will be inside a specific portion of the applications structure. The right side of a navigation bar for example, or on a user details page. We establish where this portion is by assigning it a controller using ng-controller. This creates the scope of said controller and binds the corresponding models to it. The model (our data) is connected to the view (the HTML) via the controller.
Suppose the application has a page for the user's written articles. We can create another controller restricted only to the section of HTML that specifically holds the article content.
function ArticleCtrl ($scope) {
$scope.article = {
title: "Hello World",
body: "Lorem ipsum...."
};
}
In the trivial example above, combining both of the controllers won't do any harm. But once your application begins to grow, logically organizing your controllers/views according to the data it represents will make your code cleaner and easier to understand. Less unneeded complexity will make everything much easier on you. And using one controller for everything is an unneeded complexity.
You can see this illustrated in Basarat's answer as well. You don't necessarily need to use one controller per route, but doing so helps to logically structure the application.
So, to answer your question, you should usually have one controller per category of data. Users, Articles, Fruits, Vegetables, Transactions, and so on.
Read about Angular Controllers and the Model-View-Controller pattern for more information if you haven't already. I hope this helps.
You definitely start to need more controllers when you start to split you application into multiple views.
e.g. When you start to use routes (also called deep linking) you have a template url as well as a controller to go with that template (check out http://docs.angularjs.org/tutorial/step_07) e.g.
angular.module('phonecat', []).
config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/phones', {templateUrl: 'partials/phone-list.html', controller: PhoneListCtrl}).
when('/phones/:phoneId', {templateUrl: 'partials/phone-detail.html', controller: PhoneDetailCtrl}).
otherwise({redirectTo: '/phones'});
}]);
I like to think as controllers as "widgets." One one page of my backend, they can open up the ViewUsers controller (widget), which can open up more UserDetail controllers.
I guess if you're used to OOP, it feels pretty natural to want to keep their scopes separate and encapsulated.

Categories

Resources