ng-controller="invoiceController as invoice" create new invoiceController constructor and assigned the same to scope behind the scene, which the similar thing $scope injection does in controller function parameter
am i right for the above point.
if yes then how scope is related to this.
Please help.
-- AngularJS Developer Guide - Conceptual Overview
"as" tells what "this" is supposed to point to. This way, you can create more than one instance of the controller in different scopes, without getting them confused thanks to their different names.
It allows you to use your controller as a class or prototype, where you expose class/prototype methods and properties to your templates rather than your controller adding methods and properties to the scope object.
So with ES2015 or ES5 you could do either:
export class SomeController {
someProperty = true;
someMethod() {
return 'foo';
}
}
Or
function SomeController() {}
SomeController.prototype.someProperty = true;
SomeController.prototype.someMethod = function() { return 'foo'; }
Now if you provide one of these to your template as SomeController as ctrl you will then be able to access these as ctrl.someProperty and ctrl.someMethod(). The ctrl instance of your controller is added to the $scope for you.
Another benefit is memory footprint. Monkey patching functions onto $scope is wasteful. Class and prototypes allow the same method implementation to be shared, while keeping each instance separate. This adds up for components with many instances such as a list-item, for example.
Related
I have a page that consists of several panels that are very similar in their structure. They use one controller each. But due to their similarity i reuse the controller function like so:
function panelController($scope) {
...
}
angular.module('myApp.controllers')
.controller('panel1Controller', panelController);
angular.module('myApp.controllers')
.controller('panel2Controller', panelController);
The result is that panel1 and panel2 have their own distinct scopes but will look the same in terms of what the view can bind to.
Now however I am at a point where I want to use the same pattern for panel3 but with a slight extension. That is, I have something I want to include only in the $scope for panel3 only. So ideally I would like to be able to do something like this:
function panel3ControllerExtension($scope) {
$scope.panel3Field = "I must only live in panel3";
}
angular.module('myApp.controllers')
.controller('panel3Controller', panelController, panel3ControllerExtension);
But that's not possible. Are there any good patterns for this out there?
Edit:
The similar panels are similar only in what they expect the $scope to contain. Specifically the expect the scope to contain a customer object. So e.g. panel1 binds to $scope.customer.name and panel2 to $scope.customer.phone. ...So since they look different and behave different I don't think making a directive of them are the way to go. Correct me if I'm wrong.
Controllers in Angular are used effectively as constructors. So the rules for "inheritance" in Javascript apply to them. Some methods for extension:
apply/call the "base" function:
function panel3Controller($scope) {
// add the functionality of `panelController` to this scope
// in OO terms this can be thought of as calling the `super` constructor
panelController.call(this, $scope);
// add panel3 specific functionality
$scope.panel3SpecificThing = "...";
}
// just register the new controller
angular.module('myApp.controllers')
.controller('panel3Controller', panel3Controller);
This method will probably get you what you want with the minimum modifications to your code.
Use JS inheritance: Make the controller a JS "class" and let the child controller prototypically inherit from it. You may also want to use this in conjuction with the controller as syntax:
function PanelController($scope) {
this.$scope = $scope;
this.something = '...';
}
PanelController.prototype.someMethod = function() {
...
}
function Panel3Controller($scope) {
PanelController.call(this, $scope);
this.somethingElse = '...';
}
Panel3Controller.prototype = new PanelController();
Panel3Controller.prototype.constructor = Panel3Controller;
Panel3Controller.prototype.panel3SpecificMehod = function() {
...
};
If you are using ES2015, the above can be simplified:
class PanelController {
constructor($scope) {
...
}
...
}
class Panel3Controller extends PanelController {
constructor($scope) {
super($scope);
...
}
...
}
Again, you just register the new controller alone:
angular.module('myApp.controllers')
.controller('panel3Controller', Panel3Controller);
If the properties and methods are placed in the controller, as shown here, use the controller as syntax, i.e. in the HTML:
<div ng-controller="panel3Controller as p3ctrl">
<span>{{ p3ctrl.somethingElse }}</span>
Having a module system in place makes this pattern really useful.
Depending on the exact functionality of the controllers and, as pointed out in a comment, you may be able to extract the functionality of the controller(s) in one or more services. Then the controllers will be thin wrappers for these services. Again whether this is a good idea or not depends on the exact functionality of the controller(s).
As for directives: they are always the way to go :) And you can reuse your code as the controller of the directive instead of using it with ng-controller. You can even use two directives with different templates (the customer.name and customer.phone binding for example) and the same controller.
Pls refer - http://jsfiddle.net/36qp9ekL/629/
I am using a factory method so that i can hide the implementation logic, private(pvt) variables and all the pros that factories are meant for.
If you run the code and see the console, I am able to view pvtvar1 and pvtvar2. How do I actually hide the pvt variables, implementation details.
app.controller("mycontroller", function($scope, myfactory) {
console.log(myfactory);
});
If you could tell the advantages of factories over services, would be helpful.
app.factory("myfactory", function() {
var pvtvar1= 3;
var pvtvar2 = 4;
return {
a:function(){
return pvtvar2 + pvtvar1;}
}
});
You are declaring the object and the function on the same scope of the variables. That's why they are shown. Here I declare the function on the object's scope. Take a look at this for more details http://toddmotto.com/mastering-the-module-pattern/
As you know, there are no privates in javascript. You are using closure to create inaccessible variables and functions. This is as good as you'l get. The console may show you (depending on the browser) properties that are defined via closure, but the important thing is, these properties aren't accessible in your code.
Advantages of a service over a factory? You can create an injectable service from a js object instead of a function so you can create services that inherit functionality. Not terribly useful since the injectable model is a replacement for inheritance in many ways but it's still an option.
I exclusively write services instead of factories but I'm using typescript in my projects (which has privates ;))
If a parent controller has a service injected, do the controller's children inherit the service implicitly or do they have to have the service injected explicitly?
You need to inject the service explicitly. The child controller inherits the scope, but of course the service is not on the scope object (unless you put it there)
see this jsfiddle to prove it: http://jsfiddle.net/HB7LU/11596/
var myApp = angular.module('myApp',[]);
myApp.factory('serviceId', function() {
var a = 1;
return {
a: a
};
});
myApp.controller('Ctrl',['$scope', 'serviceId' ,function($scope, serviceId) {
$scope.name = 'Superhero' + serviceId.a;
}])
myApp.controller('ChildCtrl',['$scope',function($scope) {
// the next line throws an undefined error because of serviceId
$scope.name = 'Superhero' + serviceId.a;
}])
If you do prototypical inheritance then you can have a field from the parent controller as something that can be used within the derived controller. In order to make angular js controllers inheritable you have to do a few things. Hopefully the following explanation will be more clear.
Prototypical inheritance in JavaScript
So to start off with lets define how we are going to prototypically inherit from an object. In javascript prototypical inheritance can be done as follows:
function Foo(some_param)
{
this.some_param = some_param
}
function Bar(some_param, some_specialized_param)
{
/**
* Call the super class constructor
*/
Foo.call(this, some_param);
this.some_specialized_param = some_specialized_param;
}
/**
* Now that you have defined the constructor, you need to
* define the prototypical inheritance
*/
Bar.prototype = Object.create(Foo.prototype);
/**
* Now you have to set the constructor
*/
Bar.prototype.constructor = Bar;
So using this example you can create angularjs controllers. There is one problem however. If for example The class Bar is in a different module or directory, you need to import the definition. You could use some other tool like RequireJS to load the module/controller/class definition in the file where Bar is defined and then inherit as follows. You could however also use angularjs constructs as follows:
Controller inheritance in AngularJS
So you could use two angularjs constructs to define a controller's instantiation point (angular.module(..).constructor) and the definition of a controller (angular.module(..).factory). AngularJS factories can be used for controller's class definition.
angular.module('some_module')
.factory('BaseCtrlClass', ['aService', function(aService)
{
function BaseCtrl($scope)
{
/**
* Assign a reference to "aService"
* which is injected whithin this factory
*/
this.aService = aService;
this.$scope = $scope;
}
return BaseCtrl;
}]);
Now you can define a controller definition so that you can instantiate it using ngController like:
angular.module('some_module')
.controller('BaseCtrl', [$scope, 'BaseCtrlClass', function($scope, BaseCtrlClass)
{
return new BaseCtrlClass($scope);
}]);
Notice that in the factory I have defined it as BaseCtrlClass because that is the class definition. In the controller construct I have defined BaseCtrl which is something that you can use externally as mentioned using ngController. The BaseCtrlClass name should be used internally in derived controllers as we will see below. So as I mentioned the problem comes when you are trying to import the definition of BaseCtrlClass in another file/module ideally a file defining a DerivedCtrl class. Lets define the DerivedCtrl Class as follows:
angular.module('a_different_module')
.factory('DerivedCtrlClass', ['BaseCtrlClass', function(BaseCtrlClass)
{
function DerivedCtrl($scope, aDifferentService)
{
BaseCtrlClass.call(this, $scope);
this.aDifferentService = aDifferentService //Specialized resource
}
DerivedCtrl.prototype = Object.create(BaseCtrlClass.prototype);
DerivedCtrl.prototype.constructor = DerivedCtrl;
return DerivedCtrl;
}])
.controller('DerivedCtrl', ['$scope', 'aDiferentService', 'DerivedCtrlClass', function($scope, aDifferentService, DerivedCtrlClass)
{
return new DerivedCtrlClass($scope, aDifferentService);
}]);
So here you can see that I have defined DerivedCtrl in a factory called DerivedCtrlClass. This essentially defines the controller class definition. So any other module or class trying to inherit from this can utilise this factory. In the constructor I have called the BaseCtrlClass's constructor passing "this" and "$scope" as arguments as it requires. Later on I defined the prototype constructions and default constructor similar to the javascript example I mentioned earlier. For convenience as well I have defined a controller construct which can be used with ngController as well.
The main point that I want to highlight is that an instance of DerivedCtrl can now use
this.aService
which is something that is defined in BaseCtrl. Basically anything that is defined within the scope of BaseCtrl and DerivedCtrl can now be used within an instance of DerivedCtrl. This works quite well in html when you are trying to define a DerivedCtrl you can just do
<div ng-controller="DerivedCtrl">
......
</div>
Scoped inheritance
If you want to however have scoped inheritance you can use something like this:
<div ng-controller="BaseCtrl">
<div ng-controller="DerivedCtrl">
...
</div>
</div>
The problem with this is that you need to communicate fields using $scope. In the constructor for BaseCtrl you need to do the following:
$scope.aService = aService
And then from DerivedCtrl you can use $scope.aService. This doesn't work that well because it breaks encapsulation. For maintainability you might want to not export this service and make it pseudo protected only to be accessed by classes that inherit from BaseCtrl. In that case the solution I provided works great, at least I have been using that way.
Hope this helps.
I am new to angularJS and going through angular docs.I came across this line controllers are created using a factory function
I tried to find what that means and found what is factory and service and providers but that does not fit here.
How controllers are created using a factory function?
Please explain what is the meaning of factory in this context.
The key quote you are missing from the previous section you are referring to is this:
"First, there is a new JavaScript file that contains a so-called "controller". More exactly, the file contains a constructor function that creates the actual controller instance. "
If this were an actual angular factory, it would make more sense. But, controllers are instances just like Angular factories, services, and providers.
A factory, is actually a Javascript design pattern, maybe reading here it will make more sense.
For the controller to work, the instance must exist for the two-way binding to be able to take place. So basically, an instance of the controller is created. The angular controller page explains it well with:
"When a Controller is attached to the DOM via the ng-controller directive, Angular will instantiate a new Controller object, using the specified Controller's constructor function. A new child scope will be available as an injectable parameter to the Controller's constructor function as $scope." Here's the link.
In the event of controllers though, you would most likely store items on the $scope and not 'this'. So they separate controllers from factories this way as they do not return an accessible instance of themselves and instead bind their properties to the view through $scope or 'this'.
TO BE CLEAR, I'm not saying that they are referring to Angular factories. I believe the reason for this phrasing is tied to the same wording for the service factory function:
"Application developers are free to define their own services by registering the service's name and service factory function, with an Angular module.
The service factory function generates the single object or function that represents the service to the rest of the application. The object or function returned by the service is injected into any component (controller, service, filter or directive) that specifies a dependency on the service."
They give this example:
var myModule = angular.module('myModule', []);
myModule.factory('serviceId', function() {
var shinyNewServiceInstance;
// factory function body that constructs shinyNewServiceInstance
return shinyNewServiceInstance;
});
So when you see them say created from a factory function, they are just saying using the following pattern:
.controller('PersonCtrl', [
'PersonFactory'
function(PersonFactory) {
this.name = 'Tom';
console.log(PersonFactory.name); //would print 'Tom'
}]);
.factory("PersonFactory", [
function () {
return {
name: 'Tom'
};
}]);
I hope that helps, or maybe someone could be more concise in my explanation and edit this description.
What this means is that you provide AngularJS with a function that it can execute as many times as it needs to produce instances of your controller. So to take the example from the page you linked to:
angular.module('invoice3', ['finance3'])
.controller('InvoiceController', ['currencyConverter', function(currencyConverter) {
this.qty = 1;
this.cost = 2;
this.inCurr = 'EUR';
this.currencies = currencyConverter.currencies;
this.total = function total(outCurr) {
return currencyConverter.convert(this.qty * this.cost, this.inCurr, outCurr);
};
this.pay = function pay() {
window.alert("Thanks!");
};
}]);
That function that starts on line 2 with function(currencyConverter) { is the factory function.
Whenever the page has a location that uses an InvoiceController, AngularJS will (essentially) do the following with that factory function, passing in any dependencies that it has:
var currencyConverter = ...; // obtain a currency converter from the factory
var theController = new thatFactoryFunction(currencyConverter);
and then it will use the value that is returned as your controller. It will do this separately for each InvoiceController indicagted the page, creating a separate instance for each one.
(I stress that the code above is purely an illustration of what AngularJS is doing and not an actual representation of the code that it uses.)
The creation of a controller instance is interesting. One would expect that it is created with new InvoiceController(...), and it's also suggested by the sentence More exactly, the file contains a constructor function that creates the actual controller instance, but that's not the case. Actually it's created like this:
instance = Object.create(controllerPrototype);
and later the constructor function is called as function:
return fn.apply(self, args); //self == instance
To be honest, we can only guess what the author meant by factory function. It could be the fact that controllers are not created by new. Maybe the the constructor function is therefore referred to as factory or it's the internal factory function. It could also just be bad wording or even a mistake.
So I've made this Plunker that works well as a demo: http://plnkr.co/edit/Zm9d6zHhrnqDlnJsSZ1I?p=preview . It's a simple pagination with two attribute arguments that holds the model-state and some config-state. I want to end up with a directive factory like this (or something explaining how to reason differently):
angular.module('mnPagination').factory(function(model, config) {
return {
model: model,
config: config
}
})
My issue with the current plunker is that the app layer and the directive layer doesn't look alike. Since I only have one app I can use factories as singleton data providers. That's really good!
But inside the directive, I can't use any kind of provider since it will be a singleton. The scope is a new object for every declared instance so that's the only place I can put any stateful code that should be contained in the directive.
Are directives supposed to be stateless?
Another more meta question: Am I the only one freaking out about this?
It's my second SO attempt, and noone on facebook or at work are really that into MVVM/MVC or directives with isolated scope.
I'm Leya, come be my Luke!
So the way I solved this was by creating a factory inside an angular factory.
angular.module('mnPagination').factory('mnPaginationFactory', function() {
var factory = function (items, config) {
...
}
return factory
}
Now I get a new object for each directive by calling the factory from the controller which has the items and config objects on the scope.
Plunker here: http://plnkr.co/edit/DPTZUjeMihsva5nJ3IVx?p=preview .