Splitting up the directive's controller into smaller parts - angularjs

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 .

Related

Pure Controller objects

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

Communicate between two same directive having isolated scope

I am novice to angularjs and i have a custom directive which has an isolated scope. I am having problem communicating between two instance of same directive .
How can i do that ? Your suggestion is highly appreciated .
<div date-control="cal1" ng-model="mydate" calendar-properties="calendarProperties1"></div>
<div date-control="cal2" ng-model="mydate2" calendar-properties="calendarProperties2"></div>
What i want to do is open both date-control at once having 'to' and 'from' attribute in calendar properties using some data sharing or any thing ? you can check this plnkr sample.
directives are just the result of a function call
angular.directive('myDir',function(){
var common;
return directiveObject;
});
directiveObject being the any of the variantes you use to create your directive.The main thing is that right before returning you can declare common(class if you will) variables and methods that can be checked for changes or invoked so you could do something like
app.directive('myDir',function(){
var bus={
value1:0
};
function increase(){
bus.value1++;
}
return {
scope:{},
template:"<div><h1>{{counter}}</h1><button ng-click="increase()">add</button></div>"
controller:function($scope){
$scope.bus=bus;
$scope.increase = increase
$scope.$watch('bus',function(){
//something here
})
}
};
});
a sample of this can be found here. this can be shaped in many ways
http://plnkr.co/edit/d9dpIYCAjOaOBNjoI80u?p=preview
some other methods can be used like emitting and broadcasting events or even using services, but i like the simplicity of this method.
You can either store this information directly in a Service, or you can have the directives communicate with each other. In this case, I'd have them communicate.
One option that would work is to use .$broadcast to send a message from the $rootScope and then use .$on to receive that message.
Example: inside your "From" directive
// Once "From" date is selected
$rootScope.$broadcast('from:set', date);
Then, inside your "To" directive
scope.$on('from:set', function (event, data) {
console.log(data); // Will log "From" date
});
There are a lot of different ways you can do this that will work (e.g. you could put them both on the $rootScope or you could have them .$emit upward instead of .$broadcast downward), so use whatever you feel makes the most sense in your case.
There is a great blog post on the subject here: http://toddmotto.com/all-about-angulars-emit-broadcast-on-publish-subscribing/
You are basically trying to share data across different $scope. You will get many suggestions, about storing in $rootScope, $broadcast, but these solutions are not optimal as they do the job but with an overhead.
Create a service, and store the properties "to" and "from" into that service, then inject that service into your directive..
http://plnkr.co/edit/xJrBneiU8RU92czHAZ43?p=preview
.service("MyService", function(){
var from = 'Cal1';
var to = 'Cal1';
return {from:from, to:to};
})
.controller('DatepickerDemoCtrl', function ($scope, MyService) {
$scope.calendarProperties1 = {
format: "yyyy-MM-dd",
label: "Enter the Date From",
from: MyService.from,
opened:false
}
$scope.calendarProperties2 = {
format: "yyyy/MM/dd",
label: "Enter the Date To",
to: MyService.to,
opened: false
}
})
In this example, you will be able to see how we can use Service variables inside directives ... Using this solution will solve your problem
**Edit, I have edited your plunker code, to modify the calendar properties object to use data from the service. Now both calendar properties object will get data from the same service variable.. I hope this might solve your problem
I want to thank you all for answering and providing some suggestion.If i want to communicate through one directive with another directive of same type i will retrieve the scope of the directive to call or share/get the data of another directive by using .scope() method . There may be other way but i am just a novice of angular and i am using this way to access the methods and data between directives .

controllers are created using a factory function?

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.

Angular controller repeating the same if statement

All of the controllers in my app share a dependency on a data provider type service I created. This service consists of functions to retrieve various bits of data, and almost all of these methods allow for an optional parameter. The ability to enter in this optional parameter is role based. The problem is my controllers are now full of code similar to:
// Initializing controller.
dataservice.getRole().then(function(role) { $scope.isAdmin = role.isAdmin; });
// After a button press or some other event.
if($scope.isAdmin) {
dataservice.getData($scope.param1, $scope.param2, $scope.optionalText);
} else {
dataservice.getData($scope.param1, $scope.param2);
}
It seems a code smell to me that I have to keep repeating this code throughout the controllers, but I can't think of a way to construct my controllers where I don't have to.
That's how I've realized that: Controller Inheritance.
Basically:
Create your service (factory) with your Base Controller logic and
return the Constructor (example)
Use $injector.invoke in your derived Class (to implement your
base controller) in order to enrich the actual controller $scope
(example)
There's no an official "angular way" to implement this features, but I hope the angular team will work on it

angular js how can I use a collection in all of my scopes

I have a collection that I need to access in all of my nested scopes. Inside my directive templates, inside my directives in ng-repeat... n levels deep. I don't want to have to say $scope.$parent.$parent.$parent....$parent.MyList.
I've tried using $rootScope, but clearly I lack the understanding of how this works. I pass it into my directive during the declaration like so:
$rootScope.MyList = ["list": 1];
...
...
MyApp.directive('mydirective', ['$rootScope', function ($rootScope) {
return {
restrict: 'A',
replace: false,
link: function (scope, rootScope) {
}
}
}])
The rootScope does not contain MyList. Is there something I'm doing wrong, or a better way to do it? I've thought of using a Factory or Service, but I don't know how to set that up and we all know how crappy the documentation is for Angular, so searching is very frustrating.
Can you provide a plnkr or jsfiddle reproducing this behavior? I can get the controller to communicate with service using $rootScope.
http://plnkr.co/edit/IEhOde
However, you'd probably want to create a service which would cache the value that you want and inject it in your directive and retrieve the value.
First of all $rootScope.MyList = ["list": 1] has syntax error, should be $rootScope.MyList = ["list", 1] or $rootScope.MyList = [{"list": 1}].
Anything you assign to $rootScope is accessible within child $scopes (except isolate scopes) due to prototype inheritance.
Generally if you want to share something within whole app, you can assign it to $rootScope as you did or you can create top level AppCtrl which will have something like $scope.MyList=... and all nested scopes will have access to it. Second option is bit better when it comes to testability as you can inject service to AppCtrl which provides MyList data. You can see this pattern in action here AngularJS GlobalCtrl vs $rootScope vs Service.
A service/provider is the recommended approach.
For just a list of data you can use the value provider:
app.value('listService', {
MyList : {"list": 1}
});
Inject it where you need it, and you can include it in the relevant scopes as such:
app.controller('Ctrl', function($scope,listService) {
$scope.list = listService.MyList;
});
A fiddle of that: http://jsfiddle.net/qKcQL/
Injecting it where you need is good for documentation and maintenance. But if you really need this on the global $rootScope then this will accomplish that:
app.controller('Ctrl', function($scope,listService,$rootScope) {
$rootScope.list = listService.MyList;
});
$rootScope fiddle: http://jsfiddle.net/qKcQL/1/

Resources