AngularJS: How a Directive without a Controller can work - angularjs

just reading a write up from this link http://weblogs.asp.net/dwahlin/creating-custom-angularjs-directives-part-6-using-controllers
it was hard me like novice in ng to understand their code sample. just tell me a example where people would write directive without controller ?
their code
(function() {
var app = angular.module('directivesModule');
app.directive('isolateScopeWithController', function () {
var controller = ['$scope', function ($scope) {
function init() {
$scope.items = angular.copy($scope.datasource);
}
init();
$scope.addItem = function () {
$scope.add();
//Add new customer to directive scope
$scope.items.push({
name: 'New Directive Controller Item'
});
};
}],
template = '<button ng-click="addItem()">Add Item</button><ul>' +
'<li ng-repeat="item in items">{{ ::item.name }}</li></ul>';
return {
restrict: 'EA', //Default in 1.3+
scope: {
datasource: '=',
add: '&',
},
controller: controller,
template: template
};
});
}());
Directive usage:
Attribute: <div isolate-scope-with-controller datasource="customers" add="addCustomer()"></div>
Element: <isolate-scope-with-controller datasource="customers" add="addCustomer()"></isolate-scope-with-controller>
How we can pass customer data directly to directive. basically we have model in controller and populate model and then pass that model data to directive via isolated scope or directive use controller scope. I am very confused the how above code can work, please help me to understand. thanks

The scenario that is being considered implies that the directive will be used in a part of the application, that already has a declared controller, the scope of which contains the properties datasource and add. In turn, new controllers will be instantiated for each of the instances of the directive and will have their own isolate scope.
In reality, it is much more common to create directives that do not have a controller, but rather use the link function. These directives can either rely on the parent controller, sometimes perform DOM manipulation, bind to JS events or simply serve as means to encapsulate part of your application.
You can find a good example of a directive that does not create its own controller here. It is taken from the Angular docs. You will find that it does not even belong to a parent scope in this case meaning that no controller is involved. In reality, an element like this would most probably report to a parent controller, which would then do something with the position.
You can read more about directives, the link function, and how directives work with controllers here.

Related

Accessing data out of parent controller with Controller as

I am working through the book Learning Angular Js by Brad Dayley. The book uses $scope in its examples. I am pushing myself to use controllerAs. In chapter seven the book focuses on creating custom directives.
I created a simple one similar to the example provided. Inside I am setting transclude to true. I am using the link function to append a footer to the parent div. Inside of the footer tag the author of the book calls scope.$parent.title In the example the title value comes from the parent controller.
.directive('myBox', function() {
return {
transclude: true,
restrict: 'E',
scope: {title: '#', bwidth: '#bwidth'},
template: "<div><span class='titleBar'>{{title}}"+ "</span> <div ng-transclude></div></div>",
link:function(scope, elem, attr, controller, transclude) {
console.log('scope', scope.$parent)
console.log('controller', controller);
elem.append('<span class="footer">'+ scope.$parent.title + '</span>');
elem.css('border', '2px ridge black');
elem.css('display', 'block');
elem.css('width', scope.bwidth);
}
}
})
Inside of the book the controller uses $scope, I wanted to use controller as and am using vm to equal this. Here is my function for the controller. The vm.title is supposed to be the value on the footer.
I am getting undefined from my console when I check the value.
function FunCtrl() {
var vm = this;
vm.title = "myApplication";
}
Here is a plunker of what I am trying
http://plnkr.co/edit/uUeKrTwLOfkcGpkTU1Uz?p=preview
When you use ng-controller syntax, it's just calling the members of the controller like "start()" in the scope.
<input ng-click="start()" type="button" value="Button"/>
But when you use controllerAs syntax, an instance of the controller is created and assigned to the reference variable that you have provided - "fun". So whatever you have defined in the controller is accessible only by using the reference. - "fun.start()" in the scope.
<input ng-click="fun.start()" type="button" value="Button"/>
Therefore you have to do the same when accessing the parent scope members if you have used controllerAs syntax like shown below
scope.$parent.fun.title
Demo
Note
The controller you are outputting in console is the one that belongs to the directive which you have not defined.

In a custom directive, what functionality does `controller` have over `link`?

In trying to get a grasp on creating my own AngularJS directives, I have an example that does everything I need, but realize that in borrowing from various examples, I now can create functionality for the directive's view in both the controller as well as the link.
It seems that I could get rid of the controller all together and just put everything into link, or is there something that I can do with the controller that I can't do with link?
http://jsfiddle.net/edwardtanguay/gxr49h96/6
.directive('itemMenu', function () {
var controller = function ($scope) {
var vm = this;
vm.addItem = function () {
$scope.add();
vm.items.push({
'kind': 'undefined',
'firstName': 'Joe',
'lastName': 'Newton',
'age': Math.floor(Math.random() * 60) + 20
});
};
// DOES THE SAME AS THE FUNCTION DEFINED BELOW IN LINK
// $scope.convertToInternal = function(item) {
// item.internalcode = 'X0000';
// item.kind = 'internal';
// };
};
return {
restrict: 'A',
scope: {
item: '=',
add: '&'
},
controller: controller,
controllerAs: 'vm',
bindToController: true,
template: '<div ng-include="getTemplateUrl()"></div>',
link: function (scope, element, attrs) {
scope.getTemplateUrl = function () {
switch (scope.item.kind) {
case 'external':
return 'itemMenuTemplateExternal';
case 'internal':
return 'itemMenuTemplateInternal';
default:
return 'itemMenuTemplateUndefined';
}
};
scope.convertToInternal = function(item) {
item.internalcode = 'X0000';
item.kind = 'internal';
};
},
};
})
You may find a lot of watery rants about controller vs link, most of them contain the information from $compile service documentation.
Answering the question directly,
controllers from other modules/files can be plugged into the directive via Angular DI with controller: 'Controller'
controller can be injected with dependencies while link has fixed arguments list of and gets by with directive's dependencies
controller kicks in before link, so it can prepare the scope for linking or recompile the element on some condition ASAP
controller function has this, its code appearance complies to other OOP-like ES5 code, and the methods can be easily transferred between other code parts, e.g. service service or third-party code
as the result, controllers are suited to be defined as ES2015 or TS classes.
directive's controller can be required by child directive and provides convenient one-way interaction between those two
controller makes use of bindToController: true + controllerAs: 'vm' and implements $scope.vm recipe (particularly useful to fight JS prototypal inheritance) while keeping this syntax
bindToController object value provides attribute bindings for inherited scope: true scope, no more $attr.$observe
bindToController object value provides further granularity for isolated scope. If there are certain attributes that should be bound to the controller and accessed with require, it can be done now
Which code goes to controller and which to link is more delicate question.
It is conventional to use Angular controllers as view models, as in MVVM (hence the controllerAs: 'vm' convention), so if there is a job for controllers (i.e. binding the services to scope values or setting up scope watchers) give it to them, leave the rest to link.
Since $attrs, $element and $transclude local dependencies should be injected into controller explicitly, the one may consider it a sign to skip them ($scope should be injected too for $scope.$ methods, but we will just ignore this fact).
There are some non-religious concerns about the job that should be done by link and not by controller. required controllers aren't available in controller itself, so this kind of directive interaction takes place in link. Since controller launches at earlier compilation phase than link, bound attribute values won't be interpolated yet, so the code that depends on these scope values goes to link. The same applies to other DOM-related code, it goes to link for a reason.
It is mostly the matter of proper code style and not real necessity. As of the code in the example, all scope stuff is controller-friendly, I don't think that link should be there at all.

AngularJS: Multiple ways to pass function from controller to directive

I am trying to write component-style AngularJS, similar to the practice put forward by this article.
However, I have come to realize there are various ways to pass functions to directives from an associated controller. The directive I'm working on is quite complex and I was passing each function in by binding to the directive in the template, but I now see I could just implicitly inherit the $scope object or reference the Controller object directly.
Here is an example of what I mean:
app.js
var app = angular.module('plunker', [])
app
.controller('myCtrl', function($scope) {
$scope.output = '';
// fn foo is passed into directive as an argument
$scope.foo = function () {
$scope.output = 'foo';
}
// fn inherited from controller
$scope.bar = function () {
$scope.output = 'bar';
}
// fn attached to ctrl object and referenced directly
this.baz = function () {
$scope.output = 'baz';
}
})
.directive('myDirective', function() {
return {
scope: {
output: '=',
foo: '&',
},
templateUrl: 'template.html',
replace: true,
controller: 'myCtrl',
controllerAs: 'ctrl'
};
})
index.html
<body ng-controller="myCtrl">
<my-directive
output="output"
foo="foo()">
</my-directive>
</body>
template.html
<div>
<button ng-click="foo()">Click Foo</button>
<button ng-click="bar()">Click Bar</button>
<button ng-click="ctrl.baz()">Click Baz</button>
<p>You clicked: <span style="color:red">{{output}}</span></p>
</div>
Plunkr: http://plnkr.co/edit/1JzakaxL3D2L6wpPXz3v?p=preview
So there are three functions here and they all work, yet are passed to the directive in different ways. My question is what are the merits of each and which is the best from a code and testability perspective?
You're not really passing anything to the directive, as it's using the same controller as the file containing it...
For instance, if you delete the following:
scope: {
output: '=',
foo: '&',
}
from your directive, everything still works the same. I can't think of a reason to use the same controller for a directive and and the containing application like this. I would never recommend this approach.
If you also remove
controller: 'myCtrl',
controllerAs: 'ctrl'
only foo and bar work. This is because the directive inherits the scope it's contained in. This is only recommended if your directive is pretty simple and tightly coupled to the view using it. Usually this approach is OK when you're just doing some visual modifications that repeat themselves in the page. Just notice that when you change something in the controller, the directive will probably break, and that goes against the encapsulation principle.
Finally, the correct way to pass a function to a directive is indeed using '&' modifier. This lets your directive keep an isolated scope, which means it won't break if some code on the containing controller changes. This makes your directive truly an encapsulated, independent module that you can "drag and drop" anywhere.
Here's a fork of your plunkr.

How do I assign an attribute to ng-controller in a directive's template in AngularJS?

I have a custom attribute directive (i.e., restrict: "A") and I want to pass two expressions (using {{...}}) into the directive as attributes. I want to pass these attributes into the directive's template, which I use to render two nested div tags -- the outer one containing ng-controller and the inner containing ng-include. The ng-controller will define the controller exclusively used for the template, and the ng-include will render the template's HTML.
An example showing the relevant snippets is below.
HTML:
<div ng-controller="appController">
<custom-directive ctrl="templateController" tmpl="template.html"></custom-directive>
</div>
JS:
function appController($scope) {
// Main application controller
}
function templateController($scope) {
// Controller (separate from main controller) for exclusive use with template
}
app.directive('customDirective', function() {
return {
restrict: 'A',
scope: {
ctrl: '#',
tmpl: '#'
},
// This will work, but not what I want
// Assigning controller explicitly
template: '<div ng-controller="templateController">\
<div ng-include="tmpl"></div>\
</div>'
// This is what I want, but won't work
// Assigning controller via isolate scope variable from attribute
/*template: '<div ng-controller="ctrl">\
<div ng-include="tmpl"></div>\
</div>'*/
};
});
It appears that explicitly assigning the controller works. However, I want to assign the controller via an isolate scope variable that I obtain from an attribute located inside my custom directive in the HTML.
I've fleshed out the above example a little more in the Plunker below, which names the relevant directive contentDisplay (instead of customDirective from above). Let me know in the comments if this example needs more commented clarification:
Plunker
Using an explicit controller assignment (uncommented template code), I achieve the desired functionality. However, when trying to assign the controller via an isolate scope variable (commented template code), it no longer works, throwing an error saying 'ctrl' is not a function, got string.
The reason why I want to vary the controller (instead of just throwing all the controllers into one "master controller" as I've done in the Plunker) is because I want to make my code more organized to maintain readability.
The following ideas may be relevant:
Placing the ng-controller tags inside the template instead of wrapping it around ng-include.
Using one-way binding ('&') to execute functions instead of text binding ('#').
Using a link function instead of / in addition to an isolate scope.
Using an element/class directive instead of attribute directive.
The priority level of ng-controller is lower than that of ng-include.
The order in which the directives are compiled / instantiated may not be correct.
While I'm looking for direct solutions to this issue, I'm also willing to accept workarounds that accomplish the same functionality and are relatively simple.
I don't think you can dynamically write a template key using scope, but you certainly do so within the link function. You can imitate that quite succinctly with a series of built-in Angular functions: $http, $controller, $compile, $templateCache.
Plunker
Relevant code:
link: function( scope, element, attrs )
{
$http.get( scope.tmpl, { cache: $templateCache } )
.then( function( response ) {
templateScope = scope.$new();
templateCtrl = $controller( scope.ctrl, { $scope: templateScope } );
element.html( response.data );
element.children().data('$ngControllerController', templateCtrl);
$compile( element.contents() )( templateScope );
});
}
Inspired strongly by this similar answer.

AngularJS - exposing controller api to directive

How do I make controller functions visible to a directive? Do I attach the methods to the scope, and inject the scope into the directive? Is it a good idea in the first place? I want to manipulate model data from within the UI.
It really dependes on what you want to do.
Since you want to access the controller's scope from the directive, I suggest you declare your directive with it's scope shared with the parent controller by setting it's scope prop to false:
app.directive('directiveName', function() {
scope: false,
link: function(scope) {
// access foo from the controler's scope
scope.foo;
}
});
This is a nice example with how directives can be hooked up to a controller
http://jsfiddle.net/simpulton/GeAAB/
DIRECTIVE
myModule.directive('myComponent', function(mySharedService) {
return {
restrict: 'E',
controller: function($scope, $attrs, mySharedService) {
$scope.$on('handleBroadcast', function() {
$scope.message = 'Directive: ' + mySharedService.message;
});
},
replace: true,
template: '<input>'
};
});
HOOKUP IN CONTROLLER
sharedService.broadcastItem = function() {
$rootScope.$broadcast('handleBroadcast');
};
VIEW
<my-component ng-model="message"></my-component>
Adding to #grion_13 answer, scope:true would also work since it creates a new scope that is child of the parent scope so has access to parent scope data.
But a true reusable directive is one which get it input data using isolated scope. This way as long as your html+ controller can provide the right arguments to the directive isolated scope, you can use the directive in any view.

Resources