Can I create a singleton angularjs controller? - angularjs

For debugging and developing I use an area outside of the ng-view showing what I just want to see. It works fine1, but I had to create both a service and a controller for it. My problem is that dividing the work between them is pretty confusing, so I'd like to get rid of one of them.
I don't mind putting the DebugCtrl in the $rootScope or letting the DebugSvc control a $scope (it's hacky, but it's just for hacking, so what?), but for the former, I'd need to make the controller to a singleton and concerning the latter, I have no idea how to get the $scope.
I'm aware about controllers not being singletons. They can't be, as there may be many independent areas controlled by different instances of the same controller. But in my case, I know I need only a single instance. Given the comments, I guess, I need both, but I also need a clear criteria how to divide the work between them.
1 Of course I use the debugger and logging to console, too, but such a debugging playground complements them nicely. It also has buttons for filling forms by debug data, periodical css reloading, etc.

app.factory('controllerSingleton', function () {
return { ... };
});
app.controller('SomeController', function (controllerSingleton) {
return controllerSingleton;
});
While the controller is used with controllerAs syntax in this manner, controller instance is a singleton:
<p ng-controller="SomeController as some"><input ng-model="some.value"></p>
<p ng-controller="SomeController as awesome">{{ awesome.value }}</p>
Obviously, this won't work when scope properties are assigned directly to $scope object, which is unique for each ng-controller directive, and this behaviour shouldn't be changed.

Related

Storing $scopes in a factory

I'm adding features to a Web application that I did not write myself. I feel that a swipe feature cannot be added.
I saw somewhere "$scope cannot be accessed in a factory", but I somehow managed to store all scopes created by an ng-repeat in a factory. Since JavaScript passes by value I'm probably not "storing the scope" but still if I set the scopes ng-class within the factory I can clearly see the difference. I thought it was a good idea since scope traversing is a pain. Did I strike gold or am I wasting speed and space on storing the massive data which is scope?
HTML
main.html
<div ng-controller="someCtrlWhichWasDeclaredALongTimeAgo">
.
.
.
<div class="items-container"
ui-sortable="sortableOptions"
ng-model="selectedSession.items">
<div ng-repeat="item in selectedSession.items"
ng-controller="itemEditorCtrl"
ng-class="{'item-last':$last}">
<div ng-if="item._value !== undefined"
class="item inline-comment">
<span class="handle"></span>
</div>
<div ng-if="item._value === undefined"
class="item"
ng-include="'/views/item-editor.html'"></div>
</div>
</div>
<div class="stopper"></div>
</div>
</div>
item-editor.html
<div item-editor
swipe-function="swipeMe"
ng-swipe-left="swipeLeft(item)"
ng-swipe-right="swipeRight(item)">
...
</div>
This will render a list with my magic code. If an item in list is clicked it will expand to full screen. You should be able to swipe between the items from there.
I don't think I have to post more than the HTML to explain the problem. I did manage to make the swipe happen with $rootScope.$broadcast but what if I have hundred items in my list? Will it not grow slower with more and more items receiving message and processing them? I'd rather just load all of the scopes into one factory on selection of this session and then get rid of it when a session is deselected. It might need some time to start but it doesn't take 5 minutes to swipe.
What you're doing is totally fine. You can pass scope objects around your application at will. Just keep in mind what they represent. The $scope object is your interface with the view. Most of the time your services are going to have little to do with the view and should ideally be used to share data between controllers and directives. That said, there are several examples where it makes total sense for a service to indirectly affect a view. For example, we wrote a spinner service that allows spinner directives to register themselves with the service.
The spinner directive injects the spinnerService into it, calls the spinnerService.register function and passes in its isolate scope so the service can keep track of it and use it to toggle the spinner on and off. Then our devs can inject the service wherever they need it and hide/show loading spinners or groups of spinners when they need to.
What they mean by "$scope cannot be accessed in a factory" is that you cannot inject $scope inside a factory. $scope is tied to a DOM element (the element the controller or isolate directive was invoked on), so injecting a fresh $scope into a factory doesn't make much sense and is in fact impossible. You can't do app.factory('myFactory', function ($scope) { ... }); but you can definitely call your factory/service from a controller or directive and pass the scope object from there into a method you provide on your service.
There is nothing wrong with tracking scopes within services as long as you know what you're doing. People will tell you that you can't pass your scope into a service because it's an anti-pattern, but then they'll suggest putting something like this in your directive/controller and then passing it to the service:
var api = {
hideSpinner: function () {
$scope.show = false;
},
showSpinner: function () {
$scope.show = true;
}
};
Sure, you're not directly passing your $scope object to the service, but if the argument is that your scope object could get stuck in memory and never garbage collected, this solution doesn't help either. The API object you build and pass around takes its JavaScript scope chain with it, keeping in memory all the variables within the scope that it was defined, including the $scope of your controller/directive. It has to, or else those API methods wouldn't work. Your $scope is a sort of API for that component and it's fine to use it that way.
A better argument for doing something like the above example is that perhaps your $scope object contains information or functions that you don't need/want to share with the service. Making a little API object with just the things you want to expose to the service makes perfect sense there. Then your service only has access to the things you want it to have access to and can still affect the controller/directive $scope, but only in the ways outlined by the API object you passed in.
Please stay away from storing $scope objects in any type of global store. This includes $rootScope and Angular Services.
Since services in Angular are singleton, if any service is tracking $scope object (in any service variable) then the $scope may never get release, and there are chances of memory leaks.
Also the concept of scope is tightly tied to view and should not be used outside that context.
Maybe you can share some code around your implementation so we can suggest better solution.
"$scope cannot be accessed in a factory"
You're almost correct. You can't inject the $scope service into another service. There's nothing stopping you from creating a service that stores data, and putting $scope in as that data.
THIS IS A BAD IDEA. Services are used to share data between components. $scope is used to communicate with the view. Think of this as bananas and toasters. Both have purposes, no one will fault you for using one or the other, but using a service to store $scope is like stuffing a banana in a toaster.
Each component using $scope should only be concerned with its own scope - and no other (there are very rare exceptions but you shouldn't use them unless you know what you're doing).
Instead, put data in services and then put the service's data onto the scope:
.controller('ExampleController', function($scope, myService) {
$scope.names = myService.names;
});
Even better, you can look into using controllerAs syntax

How to avoid a large number of dependencies in Angularjs

I have an Angular application. Its working good, but as my application is getting bigger I'm worried about the large number of dependencies that I have to inject in each controller.
for example
app.controller('viewapps',[
'$scope','Appfactory','Menu','$timeout','filterFilter','Notice', '$routeParams',
function($scope,Appfactory,Menu,$timeout,filterFilter,Notice,$routeParams) {
//controller code..
}])
I am sure that the list of dependencies are going to increase in future. Am I doing something wrong here? Is this the right approach? What is the best way to effectively handle this?
It's hard to be specific without an exact use case, or seeing the exact code in your controller, but it looks like your controller might be doing too much (or might end up doing too much as you add things later). 3 things you can do:
Delegate more of the logic to service(s) that are injected in.
Separate out into different controllers, so each only has (just about) 1 responsibility.
Separate out into directives, each with their own controllers and templates, and allow options to be passed in, and output given out, via attributes and the scope option of the directive. This is often my preferred option, as you end up building a suite of reusable components, each with a mini-API.
It is fine for directives to be used like this, at least in my opinion. They aren't just for handling raw Javascript events, or accessing the DOM directly.
I've been playing with the idea of bundling services based on controllers.
So in your example you'd refactor your; AppFactory, Menu, filterFilter and Notice services into a single service e.g. ViewAppsServices.
Then you'd use your services like ViewAppsServices.AppFactory.yourFunction().
As I see it that way you can at least shift your injections into another file cleaning your controller up a bit.
I think readability would suffer a bit since another developer would then have to look at bundles rather than the controller itself.
Here's a JSFiddle I put together to demonstrate how it would work; this is how I'd imagine yours would work.
.service('ViewAppsServices', ['AppFactory', 'Menu', 'filterFilter', 'Notice',
function (AppFactory, Menu, filterFilter, Notice) {
return {
AppFactory: AppFactory,
Menu: Menu,
filterFilter: filterFilter,
Notice: Notice
};
} ])
Try to move as much logic as possible to services, even just make controller methods act as "routing - passing through" methods . After time you will see it very usefull if you will want to use similar methods in other controllers/directives. Anyway, 7 injections is in my opinion not much :)
(edit: see the comment of Matt Way below)
Also, a tip - in newer versions of Angular you don't have to write this array, just:
app.controller('viewapps', function($scope,Appfactory,Menu, $timeout,filterFilter,Notice,$routeParams){
//controller code..
}])
My approach is to use $injector, when there are lots of dependencies:
app.controller('viewapps', ['$scope','$injector',function($scope,$injector){
var Appfactory = $injector.get('Appfactory');
var Menu = $injector.get('Menu');
//etc...
}]);
The advantages:
Code can be minified and obfuscated safely
You don't need to count the index of the dependency, when you declare dependency as a function's parameter

AngularJS - Why use "Controller as vm"?

This entire weekend, I was in much distress, not understanding why a parent controller's function was not being recognized by a child controller.
I soon realized that having my controller as vm was the cause:
<div data-ng-controller="ParentCtrl as vm">
<div data-ng-controller="Child1 as vm"></div>
<div data-ng-controller="Child2 as vm"></div>
</div>
Sure, it all seems obvious now that neither child1 nor 2 will see functions in ParentCtrl, and if instead I had used the prior pattern of working without vm, and instead had $scope, all would be well.
So my question is "What does it benefit anyone for using the "vm" method, and if it is superior to not using it, how can one call function calls in the ParentCtrl short of emit?
Thank You
One advantage of using the controller as syntax is that it allows you to define your controllers as a simple javascript constructor function with properties and functions exposed directly from the instantiated object rather than the $scope.
For example:
function MyController() {
var ctl = this;
ctl.message = 'You have not clicked anything yet.';
ctl.onClick = function() {
ctl.message = 'You clicked something.';
};
return ctl;
}
...
myModule.controller('MyController', MyController);
...
<div ng-controller="MyController as vm">
<span>{{vm.message}}</span>
<button ng-click="vm.onClick()">Click Me</button>
</div>
Notice that we are able to use a controller that is plain old javascript without even being tied to angular. For scenarios where you need additional dependencies such as $scope or other services, you can still easily pass them in to your constructor, but this pattern encourages less clutter directly on your $scope and also solves the problem of variable hiding when variables are set directly on the scope.
Ultimately it really comes down to a matter of preference, but for me I really like not having to define everything directly on the scope and to treat my controllers as any old javascript object as much as possible.
Here's an excellent article on the usage of controller as:
http://www.johnpapa.net/angularjss-controller-as-and-the-vm-variable/
I used to use controller as vm syntax, but lately I've been moving away from it. I'm finding that when I build complex pages with nested directives using scope isolation, the traditional $scope approach is far easier to work with.
Your question is one I wondered about for a while. The only real value I can see is that when you use nested controllers in a page, you can get semantic reference to each of the scopes so that your markup becomes a little easier to read. So, for instance:
<div ng-controller="CustomersCtrl as customers">
<div ng-controller="OrdersCtrl as orders">
<p>Showing{{customers.model.length}} customers with a total of {{orders.model.length}} orders</p>
</div>
</div>
Other than this, I don't really see the value and if you prefer nesting with directives as I do, the value is quickly nullified imho.
What you are experiencing with this example is not specifically due to the use of the controller as syntax; instead, it is due to your nested objects hiding the parent due to naming.
The controller as option is highly useful when working extensively with other languages that compile to JavaScript such as CoffeeScript or TypeScript. It also allows you to create much more lightweight controllers which can be interchanged with non-angular components, due to not needing the $scope injection. It is simply an alternate syntax, but you can still use $scope if you wish.
The real question here is why people who have written examples of the controller as syntax have decided to use the "as vm". Technically, the syntax is designed to provide an MVVM style coding experience, and so using "as vm" may make sense, but this exposes the issue you are seeing. Your parent controller is an object named vm, and your child object is also named vm, so the child object is hiding the parent object from view. If, instead, you name your objects differently, your child object will have no issue accessing the parent object, and it will actually be very clear in code which object the property you are working with is coming from.
I think one of the main advantages is that it automatically makes sure you'll end up with a . in your bindings. Because the rule of thumb in Angular is that if you don't have a . in your bindings you might shoot yourself in the foot.
Consider you have this:
<input ng-model="foo" />
This might not work as intended. On the other hand, if you use the SomeController as vm syntax you would automatically end up with this:
<input ng-model="vm.foo" />
This will save you from the potential trouble with the data binding not working as expected.
The reasons behind that lay in the way prototypal inheritance works in JavaScript and it's been described in very much detail here: What are the nuances of scope prototypal / prototypical inheritance in AngularJS?
As I understand, the main reason to use "controller as vm" syntax is that controllers in angularjs actually serve as models(encapsulate data and provide behaviours), or view models(expose data to html).
In most cases it worked well, but one main fallback I've encountered is that it's difficult to access data in parent controller from child controller, and possible solution(Get parent controller in child controller which all use 'controller as vm' notation) is not elegant.

Do native (non-$scope, etc) members of angularjs controller objects do anything?

Learning angularjs, the curve is steep but I can see that it's going somewhere.
Given the following:
var app = angular.module('theApp',[]);
app.controller('Controller1', function($scope) {
var self=this;
$scope.thing = "hi.";
this.thang = "yo.";
$scope.doIt=function(){
return self.thang;
};
});
thing and doIt() are exposed through $scope, but thang is not:
<body ng-app="theApp">
<div ng-controller="Controller1">
<div>{{thing}}</div>
<div>{{thang}}</div>
<div>{{doIt()}}</div>
</div>
</body>
Is there any way to bind or otherwise expose thang? Or is this. completely pointless inside an ngController?
No problem if it is, just trying to get it straight in my head. I'm getting the idea that and ngController only communicates through $scope (and other ng service objects), they aren't independently accessible to other code; and I see that as a basically good thing, within anjularjs.
But it means you have to go all-in with angularjs; legacy/external code has to be wrapped in angularjs services and stuff in order to communicate across an ngController, would that be a fair statement?
I'm using angularjs 1.2.13, does the behavior change across versions?
Is there any way to bind or otherwise expose thang?
It depends how you want it to be exposed. If you want it to be available in the view; you'll have to put it into the $scope.
If you want to share the value with other controllers, you'll probably want to use a service or to store the value.
Or is this. completely pointless inside an ngController?
I consider variables, or methods, not in the $scope to be similar to a protected variable [or method]. Depending what your building their can be uses for that. I would not consider it pointless.
But it means you have to go all-in with angularjs; legacy/external
code has to be wrapped in angularjs services and stuff in order to
communicate across an ngController, would that be a fair statement?
No, it doesn't mean that. For example, I once wrote an app with a login form, and needed to hash the password before calling a remote service. I just Googled and found a JAvaScript hash library; then imported it into the html page (AKA Used the script tag), and was able to access the hash function from within the controller without doing any other work. This is due to the nature of JavaScript and how browsers work.
However, by doing this I added an external dependency into my controller which would make it difficult to write tests against. If I had wrapped the library in an Angular service, then passed it into the controller I would have built a semi-self-documenting API for the controller that could be tested with known dependencies.
So, you don't have to wrap external services in AngularJS. But, you may want to. As long as you understand the trade offs your making, you can make good decisions for your use case. (Just like any 'best practice').
I'm using angularjs 1.2.13, does the behavior change across versions?
All versions of Angular I've used have the same "Controller / Scope / Dependency Injection" approach.
The ng-controller tag can put the controller on the scope. These are equivalent:
<body ng-app="theApp">
<div ng-controller="Controller1 as c">
<div>{{thing}}</div>
<div>{{c.thang}}</div>
<div>{{doIt()}}</div>
</div>
</body>
And:
app.controller('Controller1', function($scope) {
var self=this;
$scope.thing = "hi.";
$scope.c = this;
this.thang = "yo.";
$scope.doIt=function(){
return self.thang;
};
});
Personally I think putting the name in the tag usually makes more sense as the template reads better.
Remember you can also access controllers which aren't on $scope from the link function of a directive. When you do that you refer to the name you gave the controller when you registered it, Controller1 in this case. See the require attribute here: http://docs.angularjs.org/guide/directive

AngularJS: Using services in directives

This is an angularjs app. I have a service that handles the loading of content (ajax). While the service is getting the content, a number of things throughout the app hide, later showing again (depending on the content returned). They might have the same scope, different scope, whatever. They just need to hide while content is loading, and then show when it's done. Pretty normal stuff.
Right now, I have separate controllers watching a "loading" property of the service and using regular angular directives (ng-show, ng-hide, etc.) to show/hide. But this feels like overkill. I'd prefer to write a custom "loading" directive that injects the loading service and does the watching and showing/hiding.
My question is: Is what I want to do "bad"? The controller way, I end up boilerplating a bunch of code, maybe up to like 5 or 6 times, or even more as the app grows. The custom directive way, I write it once and use an attribute where I need it. Yeah - there's a dependency on that service, but that just doesn't feel like the end of the world that some people have made me start to think I should think it is.
For what it's worth, I feel like I've heard "separation of concerns" so many times I've become paralyzed by it. It leads me to overthink everything because I want to do things the right way, but it sure doesn't feel like I'm being very productive.
If I understood correctly, you have a bunch elements that should hidden when a particular service is loading data, and then be displayed again when the data is loaded, right?
In that case, events might be a good solution:
they can be global to the appliciation (which i think is what you are aksing for).
they allow for avoiding direct coupling between elements (also one of your concerns).
So, in your service, just broadcast events when stuff happens:
$rootScope.$broadcast('loading-data');
axajStuffOrWhatever(function() {
$rootScope.$broadcast('data-loaded');
});
Then, wrap the show/hide behaviour in a directive that will listen to those events.
.directive('hideWhileLoadingData', function() {
return {
link: function(scope, el, attrs) {
scope.$on('loading-data', function() {
el.css('display', 'none');
});
scope.$on('data-ready', function() {
el.css('display', 'block');
});
}
};
});
Use the directive:
<div hide-while-loading-data>something</div>
The advantage of using events here, is that later on, they could be originated by a different service, or by multiple services, and the directive will not be affected by that as long as the events are the same.
For more complex behaviour, you could also parametrize the events and the directive, so different elements will react to different kind of stuff.
I've made an example of this.
In my opinion all scopes which depend on this service should be children of one parent scope. If you have the parent scope responsible for talking with the service then any directive of any scope can access it via $parent on the $scope.

Resources