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

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

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.

Angular - Passing data - Services vs URL parameters vs $scope

I'm new to Angular and I guess this question may already have a fair few comments however I'd like to know what experienced Angular developers find the best practice on how to handle:
Passing data between pages (assuming each page has its own controller).
So the 3 ways I can see:
1 - In the URL parameters (I don't prefer this - doesnt give great flexibility, also in my opinion doesn't look so good)
2 - Create a service (e.g. as described here AngularJS - Passing data between pages)
3 - Using parent '$scope'
Thanks.
Most of the time you will want to use FACTORY because it has:
ability to use other services (have dependencies)
service initialization
delayed/lazy initialization
Factory example (Fiddle)
<div ng-app="myApp">
<div ng-controller="FirstCtrl">
<input type="text" ng-model="Data.FirstName"><!-- Input entered here -->
<br>Input is : <strong>{{Data.FirstName}}</strong><!-- Successfully updates here -->
</div>
<hr>
<div ng-controller="SecondCtrl">
Input should also be here: {{Data.FirstName}}<!-- How do I automatically updated it here? -->
</div>
</div>
var myApp = angular.module('myApp', []);
myApp.factory('Data', function(){
return { FirstName: '' };
});
myApp.controller('FirstCtrl', function( $scope, Data ){
$scope.Data = Data;
});
myApp.controller('SecondCtrl', function( $scope, Data ){
$scope.Data = Data;
});
Using $broadcast
You can also use $broadcast to pass data from the high tier of your controller to the end.(Example: jsFiddle)
No 2 is the most preferable way as I always use that in my projects
2 - Create a service (e.g. as described here AngularJS - Passing data between pages)
This is the most Angular-ish way and as you've pointed out, is recommended by their docs.
But there are a few things to ponder, much of which is dependent on a) your development experience and b) what development paradigms you're used to. As a bit of background, I came from a very Java-ish background (I was a Flex/Actionscript developer for nearly 10 years). Many of the typical Java-esque development paradigms do not manifest themselves clearly in Angular. Angular seems to me to come from a more Ruby-like paradigm. This is exemplified in many of their APIs and usage recommendations.
One thing I've found out is that rolling your own solution to bypass some of the things that may be distasteful to your development proclivities is often more work in the long run. I can give you about 6-7 examples (in the comments if you wish) in which my inexperience in using Angular and my hardheadedness in being an experienced developer ended up with me finally refactoring to be more Angular.
So to recap, in the case of construct-to-construct communication, I'd go with option #2. I'd elect for an event bus as a secondary option only if the recommended option doesn't suit your needs.
It depends on what the data you are passing is for.
If there is some data that determines that state of your controller and should be linkable by users, e.g. you have a page that displays a blog article and it needs an ID to get the article info from a database, then that id should be a url parameter e.g. /blog/:id.
If you need some data that belongs to the parent controller, e.g. you have a blog article page and a child part of that page is an author panel that needs to get some information from the blog object itself, then you could access that via the parent $scope, however you should note that this tightly couples the author panel to the blog article controller, and it is almost always a better idea to create a directive and pass the blog object in to the directive isolate scope.
If you have some global data, e.g. a logged in user has a language setting which needs to be accessed by all pages, then you should use a service.
If you need some data that is to do with an event, e.g. a user clicks something and a directive somewhere else needs to know about it, then you can use $emit or $broadcast

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

How can I call a service from a template in AngularJS?

I have a service that returns a json object that it makes, for brevity lets say it looks like this:
.service ('levelService', function () {
// service to manage levels.
return {
levels : [{name:'Base', href:'base'},{name:'Level 1', href:'level1'},{name:'level2', href:'level2'}]
};
})
I think that is fine, but I want to use it now, in a template. Currently I have something like this:
<ul class="dropdown-menu" ng-init="levels = [{name:'Base', href:'base'},{name:'Level 1', href:'level1'},{name:'level2', href:'level2'}];">
<li ng-repeat="level in levels">
<a ng-href="#/modeling/level/{{level.href}}">{{level.name}}</a></li>
</ul>
How can I get the ng-init to now use the service? I feel like the right thing to do, is add the service to the controller, but this is outside of any controller. Do i need to create a new controller for this space, or can i directly reference the service?
Yes, it would be best practice to create a controller.
The idea behind the MVC app architecture is that you don't tightly couple your objects/classes together. Injecting a service into a controller, then subsequently your controller adding levels to $scope means that your HTML doesn't have to worry about where it gets the data from.
Also, using ng-init in that way is arguably fine for knocking up a very quick prototype, but that approach shouldn't be used in production code (as your model's data itself is tightly coupled to your view's HTML).
Tip: It might be a good idea to use a controller for the parent container of your dropdown-menu (ie. the page/section) and then use a directive for your dropdown-menu itself. Think of a directive as a view component.
In general, you might find the video tutorials at egghead.io helpful.
put the service in a controller..
Then you can call service in your template..
app.controller('yourCtrl', function($scope, levelService) {
$scope.levelService= levelService;
});

Resources