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

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

Related

Can I create a singleton angularjs controller?

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.

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 can I integrate Angular with an existing JavaScript architecture that makes heavy use of clone?

I'm working on a legacy web application (well, I say legacy, it's from about a year ago and programmed in Knockout and JQuery), and I'd like to program a new interface in Angular, with a view to replacing all other portions of the application with Angular as we go on as this is a very complicated user interface that isn't horrible enough to warrant a complete rewrite.
I would like to integrate Angular code with the existing codebase, and for my first controller I'd like to program a pop-up box which allows the user to modify some data. I created a div with a data-ng-controller of "MyController" and it all works well, but I don't seem to be able to create many of them.
If we have multiple controllers, i.e:
<div data-ng-controller="MyController">
</div>
<div data-ng-controller="MyController">
</div>
Both instances of MyController would have their own scope which is definitely want, and it works totally fine for my app this way. But unfortunately, my system works on cloning a div for every instance of the pop-up box:
<div data-ng-controller="MyController" id="myWindow">
</div>
<script>
function showWindow()
{
var dialog = $("myWindow").clone();
dialog.show();
)
</script>
When showWIndow() is called, the reference to MyController seems to travel along with it, so I don't get two instance of MyController.
I can't seem to find how to create multiple instances of the same controller and attach it though.
For example, I thought this might work:
<div id="myWindow">
</div>
<script>
function showWindow()
{
var dialog = $("myWindow").clone().setupDialog();
dialog.attr("data-ng-controller", "MyController");
dialog.show();
)
</script>
But unfortunately, the breakpoint in MyController never gets hit. I suspected that this might be due to the order of attaching, but reversing the call to attr() and show had no effect:
var dialog = $("myWindow").clone().setupDialog();
dialog.show();
dialog.attr("data-ng-controller", "MyController");
I assume that there's some method in the angular library somewhere that allows me to instantiate a controller and attach it to an element. About an hour of googling has yielded nothing but I can't believe that something like this doesn't exist somewhere.
The HTML must be compiled to make angular directives work.
Usually the compile steps are done by angular behind the scenes (ng-app, bootstrap). Ng-repeat does it for example when creating new templates throug iterations.
But here, angularJS doesn't "know" you have DOM nodes to compile and you must do it manually.
You must learn about compile on angular, here are the docs:
reference api for $compile (the last exemple on this page is the one that helped me the most)
reference guide for compiling on angular
Here's what you might have to do (not sure because I haven't a deep knowledge on this):
create a scope for your duplicated widget
compile the template (where angular look dom markup for directives and expressions)
link the compiled template to the created scope
I think I've found it, but I don't know if this is necessarily the correct way to do it.
I firstly removed the data-ng-app property from my <body> element. I then applied the data-ng-controller programatically and called angular.bootstrap to specify the module I wanted to link to the element:
function showWindow()
{
var dialog = $("myWindow")
.clone()
.attr("data-ng-controller", "MyController")
.setupDialog();
angular.bootstrap(dialog, ["MyModule"]);
dialog.show();
)
It certainly works for me, although I'm not sure if there are side-effects to doing this that won't be apparent until later.

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.

AngularJS : Service vs Inheritance for Access Parameters Outside `ng-view`

Is it more effective to use a Service or rely on Scope Inheritance when updating parameters outside an AngularJS ng-view?
I have an AngularJS page with an ng-view and a common header. When moving between the ng-views I would like to:
update the common header title
select the appropriate navigation menu item.
In order to do this I've read two common solutions. The first is to provide a Service that can pass variables between controllers. A simple and straight-forward solution described in many places, including here: AngularJS: How can I pass variables between controllers?
The second method I've found was to take advantage of Scope Inheritance and wrap my ng-view in a parent controller, like so:
<body ng-controller="MainCtrl">
<!-- stuff -->
<h1>{{ common.pageTitle }}</h1>
<ng-view></ng-view>
<!-- more stuff -->
</body>
Then in my controllers I can do the following:
myApp.controller('MainCtrl', function ($scope)
{
$scope.common = [];
$scope.common.pageTitle = "Set by MainCtrl";
});
myApp.controller('SplashCtrl', function ($scope)
{
$scope.common.pageTitle = "Set by SplashCtrl!"
});
Is there a performance hit between using one over the other? Am I going to discover a hidden "gotcha" if I use one, that I haven't realized yet?
I recommend using services or an even pattern over inheritance. It's more difficult to 'figure out' where things are coming from and what is modifying what unless you have some explicit way of stating that
As far a performance goes, I'm not sure what would be faster, however don't worry about performance until you need to, then look for a solution to your specific performance issue
If you wanted to go the event pattern route then you could have your controller broadcast it did something
$rootScope.broadcast('eventname', { event data });
and consume the message like this in your menu/main controller
$scope.$on('eventname', function(event, data){
// update menu state...
});
The hidden 'gotcha' for me was after going several levels deep in scope inheritance I found that when I started writing my test code I had to instantiate all of the 'parent' controllers and my tests got overly complex very quick

Resources