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.
Related
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.
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.
In AngularJS, a Controller itself is an object defined by a Javascript constructor function and this constructor function is a callback to .controller method.
var myapp=angular.module('scopeExample', []);
myapp.controller('MyController',function MyController($scope){
$scope.name="john";
this.num=700;
});
In the above example, MyController is the constructor function which creates the Controller object with one property (num). I have three queries upon that:
Actually, what is the use of the Controller object in that case?
Does it have some more properties not visible and is it accessible from outside Angular?
How it is interconnected to its scope which in turn is another object?
I came upon the following queries because of the controller as syntax which creates a controller object that is a property of controller's scope and therefore easily accessible, e.g.
<div ng-app="scopeExample" ng-controller="MyController as ctrl">
<input id="box" ng-model="ctrl.num"> equals {{ ctrl.num }}
</div>
<script>
angular.module('scopeExample', [])
.controller('MyController', [function () {
this.num=12;
}]);
</script>
var x=angular.element('#box').scope().ctrl; //x is the controller object itself
1.a. What is the use of the Controller object in that case?
There is nothing special about this example, angular is an MVC framework(or any other buzz word you wish to use that describes almost the same thing), the controller's responsibility is to response to view events, update the model accordingly and execute business logic tasks (you can choose where to actually implement the logic, wheres in the controller itself or use services).
Of course that in this example the controller is pretty useless, because you have no logic, and only 2 proprieties.
1.b. Specking of ctrl-as syntax, in your example you injected 'scope' into the controller and added property ($scope.name), when you're using controller as it is recommended for you to avoid using scope unless you are obligated to do so. (e.g. $watch, parent...)
2.a. Does it have some more properties not visible?
No it doesn't have any invisible properties, you can check it easily by your self with the following code:
.controller('MyController', function () {
window.DoesThisControllerHaveInvisibleProps = this;
});
2.b. is it accessible from outside Angular?
I'm not sure that I fully understood what you've meant with "outside Angular", if so here is an example that the controller obj can be accessible from "outside":
class MyController {
static foo() {
console.log('hello!');
}
}
myapp.controller('MyController', MyController);
// maybe somewhere else in that module
MyController.foo();
3.How it is interconnected to its scope which in turn is another object?
As you said, when using controller as syntax angular is initializing the controller and put it on the $scope so it will be accessible in the template.
$scope is just an unnecessary glow and you should avoid using it. my way of seeing it is like it was angular implantation details, when migrating to ng-2 you will see that there is no more scope.
If you're interested in more detailed info about how exactly $scope and controllers in angular are working I suggest you have a look at
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.
In Angular, when registering a directive to a module, does the directive factory function get invoked using new or just with simple function call?
eg.
var MyDirective = function() {
return {
link: function() { ... }
};
}
module('myMod', []).directive('myDirective', MyDirective);
Does MyDirective get called internally as:
... = MyDirective();
or as
... = new MyDirective();
The Angular Guide on providers states:
Earlier we mentioned that we also have special purpose objects that
are (...) are Controller, Directive, Filter and Animation.
The instructions for the injector to create these special objects
(with the exception of the Controller objects) use the Factory recipe
behind the scenes.
This fact be clearly seen in compile.js source code. And because we know that factory recipe in Angular simply invokes the function (with its dependencies, via $injector.invoke(fn)) so the correct answer to your question is ... = MyDirective();
It is invoked using an $injector, i.e. $injector.invoke(MyDirective), so that dependencies can be resolved and injected. Internally, $injector.invoke call MyDirective(), without the new, and pass in the dependencies as arguments.