Angular.js: Data binding not working with directives controller option - angularjs

Why is angular's data binding not working when I specify a controller in the directives controller option? $scope.emailInvalid.text normally should get mapped to type.text but in my case, nothing get's displayed.
JS:
.directive('alert', function () {
return {
template: '<div>{{type.text}}</div>',
restrict: 'E',
scope: {
type: '='
},
controller: function ($scope) {
$scope.emailInvalid = {
text: 'Text Alert Two'
};
}
};
});
HTML:
<alert type="emailInvalid"></alert>
When I define a separate controller and pass it with ng-controller in the HTML, everything works like expected.
Here is a plunker.

Since you want to display type.text you need to define
$scope.type = {
text: 'Text Alert Two'
};
in your directive controller. By doing so, you don't event have to pass the object to the directive.
PLUNKR

OK, I found a solution:
The problem was that angular fails at mapping $scope.emailInvalid to $scope.type. What I have done in my example with <alert type="emailInvalid"></alert>, is passing an object emailInvalid to the directive. Angular is looking for this object in the scopes model of where I used the directive. Obviously this object doesn't exist and angular can't map anything.
The part I was missing is, that the controller I defined with the directives controller option is defined in a different scope than the controller I used with ng-controller.
To work around this problem I'm now passing a string instead of an object and use switch/case to map the alert type.
plunker

Related

AngularJS: How a Directive without a Controller can work

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.

Accessing ng-repeat scope on a custom directive

I'm having a go at a directive which will dynamically load a template based on a scope value passed into it.
I am using ng-repeat on my directive, and am using the iterated item as an attribute property:
<my-form-field ng-repeat="field in customFields" field="field">
In my directive I have the following code to set the template being used.
(function() {
'use strict';
angular
.module('app')
.directive('myFormField', myFormField);
function myFormField() {
var directive = {
restrict: 'E',
scope: {
field: '='
},
link: function(scope){
scope.getContentUrl = function() {
return 'app/modules/form_components/form_field/' + scope.field.type + '-template.html';
}
},
template: '<div ng-include="getContentUrl()"></div>'
};
return directive;
}
})();
Whilst the above works (which I found from other posts), I wonder if there is a better way.
For example I have seen examples of calling a function on the templateUrl config option instead, and then in that function access the scope attributes being passed in. When I tried this way, my field attribute was a literal 'field' string value (they are objects in my customFields array), so I think at that point the scope variables had not yet been evaluated.
With this current solution I am using, all of my templates get wrapped in an extra div since I am using ng-include, so I am just trying to make the rendered markup more succinct.
Any suggestions\advice is appreciated.
Thanks

How to include data/scope from controller in a dynamically added directive?

I'm trying to figure out how to include scope with a directive that I add to the dom on a click event in a controller.
Step 1. On a click event, I call a function in my controller that adds a directive like this
$scope.addMyDirective = function(e, instanceOfAnObjectPassedInClickEvent){
$(e.currentTarget).append($compile("<my-directive mydata='instanceOfAnObjectPassedInClickEvent'/>")($scope));
}
//I'm trying to take the `instanceOfAnObjectPassedInClickEvent` and make it available in the directive through `mydata`
The above, part of which I got from this SO answer, successfully adds the directive (and the directive has a template that gets added to the dom), however, inside the directive, I'm not able to access any of the scope data mydata it says it's undefined.
My directive
app.directive('myDirective', function(){
return {
restrict: 'AE',
scope: {
mydata: '='
//also doesn't work if I do mydata: '#'
},
template: '<div class="blah">yippee</div>',
link: function(scope,elem,attrs) {
console.log(scope) //inspecting scope shows that mydata is undefined
}
}
}
Update
I changed the name of datafromclickedscope in the OP to make it more clear. In the controller action addMyDirective (see above) instanceOfAnObjectPassedInClickEvent is an instance of an object passed into the controller method on a click event that I try to pass into the directive as mydata='instanceOfAnObjectPassedInClickEvent'. However, even if I change = to # in the directive and I try to access scope.mydata in the link function of the directive, it just shows a string like this "instanceOfAnObjectPassedInClickEvent", not the actual object data that is available to me in my method that handles the click event
When you use mydata='instanceOfAnObjectPassedInClickEvent' in a template you need instanceOfAnObjectPassedInClickEvent to defined in $scope. So before compiling you should assign a variable in $scope. I will rename this variable in code below, so that same names would not confuse you and it would be clear that a formal parameter of a function cannot be visible in a template.
$scope.addMyDirective = function(e, instanceOfAnObjectPassedInClickEvent){
$scope.myEvent = instanceOfAnObjectPassedInClickEvent;
$(e.currentTarget).append($compile("<my-directive mydata='myEvent'/>")($scope));
}
EDIT: slightly adapted jsfiddle not using JQuery no manipulate DOM

Function arguments in directive with templateUrl

I created a custom directive with an isolated scope that binds to a function from the enclosing controller and with references to a templateUrl. Here's what my code looks like:
the html
<div ng-controller='MyCtrl as my'>
<custom-directive data='my.data' on-search="my.find(param1, param2)"></custom-directive>
</div>
the directive
app.directive('customDirective', function() {
return {
restrict : 'E',
scope : {
data : '=data'
search : '&onSearch',
},
templateUrl : 'customDirective.html'
};
});
the template
<div>
<input ng-model='data.id'>
<a ng-click='find(param1, param2)'></a>
</div>
The arguments received by function find is also stored in data. The controller data binds to the directive but not the function. My log inside the function won't even show.
It seems there are different ways to do it as I have seen in many examples (see below) but none seems to work in my case.
Example 1: pass a mapping of parameter and values in the template
<div>
<input ng-model='data.id'>
<a ng-click='find.({param1: data.value1, param2: data.value2})'></a>
</div>
Example 2: put a link in the directive
app.directive('customDirective', function() {
return {
restrict : 'E',
scope : {
data : '=data'
search : '&onSearch',
},
templateUrl : 'customDirective.html',
link : function(scope, elem, attr) {
scope.retrieve({param1: scope.data.value1,
param2: scope.data.value2});
}
};
});
Example 3 : use scope.$apply(), $parse in link but haven't tried this
Could someone show me how to do it and also explain to me the link part (I don't understand that part) and if you're feeling generous, show the working alternatives as shown by the examples. Thanks
You don't have to passe params for your function just the reference so in your html
<custom-directive data='my.data' on-search="my.find"></custom-directive>
and your template directive directly call
<div>
<input ng-model='data.id'>
<a ng-click='find(data.value1, data.value2)'></a>
</div>
I also suggest you to use $scope and not the controller. So in your controller define
$scope.data = {
id: 1,
value1: "value1",
value2: "value2"
}
$scope.find = function (param1, param2) {
//Your logic
}
And in your template put directly
<custom-directive data='data' on-search="find"></custom-directive>
I hope this answer to your question
About link this text from angular js doc is pretty clear I think
Directives that want to modify the DOM typically use the link option.
link takes a function with the following signature, function
link(scope, element, attrs) { ... } where:
scope is an Angular scope object. element is the jqLite-wrapped
element that this directive matches.
attrs is a hash object with key-value pairs of normalized attribute names and their
corresponding attribute values.
In our link function, we want to update the
displayed time once a second, or whenever a user changes the time
formatting string that our directive binds to. We will use the
$interval service to call a handler on a regular basis. This is easier
than using $timeout but also works better with end-to-end testing,
where we want to ensure that all $timeouts have completed before
completing the test. We also want to remove the $interval if the
directive is deleted so we don't introduce a memory leak.

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.

Resources