AngularJS directive not properly receiving link passed in attribute - angularjs

I've got an AngularJS directive that is not placing a string (intended to be a relative path to an image) inside one of the attributes in an HTML element and I'm at a loss as to why.
My item looks like the following:
item : {
name: 'Test Name',
link: 'Assets/logo.png'
}
If I step through the javascript, I'm correctly receiving the link from the webservice, so that's not the problem as my Angular controller properly shows the link in the $scope.
The following is what I have in the template for that controller that I'm having the problem with:
<my-directive name="{{item.name}}" link="{{item.link}}"></my-directive>
Here's the javascript for my directive:
angular.module('myModule').directive('myDirective', function() {
return {
restrict: 'E',
replace: true,
templateUrl: '/RelativePathToTemplateFile.html',
scope: {},
link: function($scope, element, attr, model) {
$scope.name = attr.name;
$scope.link = attr.link;
}
}
})
When I look at the rendered HTML, I have the following:
<div name="Test Name" link></div>
What's going on? How can I pass this link in properly?

Directive scope binding technique can resolve this issue. Try to use "#" to bind the directive property to the evaluated DOM attribute.
HTML
<div ng-controller="myCtrl">
<my-directive my-name="{{item.name}}" my-link="{{item.link}}"></my-directive>
</div>
Javascript
angular.module("myApp",[])
.controller("myCtrl",function($scope){
$scope.item = {
name:"Test Name",
link:"Assets/logo.png"
};
})
.directive("myDirective",function(){
return {
restrict: "E",
template: '<div name="{{myName}}" link="{{myLink}}">{{myName}}</div>',
replace: true,
scope:{
myName:"#",
myLink:"#"
}
};
});
Here is a jsFiddle DEMO, you could refer to it.

From the documentation:
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.
so it's "attrs", not "attr"

try:
<myDirective name="item.name" link="item.link"></myDirective>
It will be better :)

Related

Angular js directive and restrict option

i was reading a write up on directive from this url https://docs.angularjs.org/guide/directive
The restrict option is typically set to:
'A' - only matches attribute name
'E' - only matches element name
'C' - only matches class name
'M' - only matches comment
<div ng-controller="Controller">
<my-customer></my-customer>
</div>
angular.module('docsRestrictDirective', [])
.controller('Controller', ['$scope', function($scope) {
$scope.customer = {
name: 'Naomi',
address: '1600 Amphitheatre'
};
}])
.directive('myCustomer', function() {
return {
restrict: 'E',
templateUrl: 'my-customer.html'
};
});
template html file
Name: {{customer.name}} Address: {{customer.address}}
please help me to understand what is the meaning of restrict: 'E', ?
i am looking for a example where restrict will be A or C
please show me the usage of restrict: 'A' and C
also tell me how could i pass multiple argument to directives ?
thanks
let say you have a directive 'myDirective'
if in restrict you have only C you can only use it as classes like this :
<div class="my-directive"></div>
If it's A it's as attribute :
<div my-directive></div>
If it's E it's as element
<my-directive></my-directive>
TO pass argument you generaly se attributes :
<div my-directive my-first-argument="toto" my-second-argument="titi"></div>
To get the value you have to way :
use the attr provided in link function
use the scope with one way or two way binding.
Personnaly i prefer the attribute approach when it comme to directive, element after and class in last. I have already bootstrap based on classes i don't want to clash with it.
please help me to understand what is the meaning of restrict: 'E', ?
The 'E' restriction will match by element name:
<my-customer></my-customer>
i am looking for a example where restrict will be A or C
please show me the usage of restrict: 'A' and C
The 'A' restriction will match by an element attribute:
<div my-customer=""></div>
The 'C' restriction will match by a class:
<div class="my-customer"></div>
also tell me how could i pass multiple argument to directives ?
It depends on your requirements, but one simple way is using an isolated scope:
.directive('myCustomer', function() {
return {
restrict: 'E',
scope: {
arg1: "#",
arg2: "#"
},
templateUrl: 'my-customer.html'
};
});
<my-customer arg1="first" arg2="second"></my-customer>
This SO answer gives a pretty good explanation of this process.
restrict is key to be used in DDO (Directive Definition Object) to inform what it Would be in view
for Example in DDO (Directive Definition Object)
Directive with restrict:E (Element)
Directive
app.directive('my-directive',function(){
return {
restrict: 'E', //means HTML Element
...
};
});
so directive in View for restrict:'E' as Element in HTML element as below
view
<myDirective></myDirective>
Directive with restrict:A (Attributes)
Directive
app.directive('my-directive',function(){
return {
restrict: 'A', //means HTML attribute
...
};
})
so directive in View for restrict:'A' as attribute in HTML element as below
view
<div myDirective></div>
Directive with restrict:'C' (class)
Directive
app.directive('my-directive',function(){
return {
restrict: 'C', //means HTML attribute
...
};
})
so directive in View for restrict:'C' as class in HTML element as below
view
<div class="myDirective"></div>
you can use Isolate scope (doesn't inherit from its parents scope) or you can pass variables to attributes which depends on your implementation both can used in directive's link and controller
for isolate scope DDO will be as
<my-directive variable1="hello" variable2="world" variable3="call()"></my-directive>
app.directive('myDirective',function(){
restrict:E,
scope:{
variable1:'#variable1',
variable2:'=varialbe2',
variable3:'&variable3'
}
})
Or you can also pass data through attributes and access them using attrs in Directive's link Function and $attrs DI in Directives Controller

Issue with dom manipulation inside Directive link function - Angularjs

I set up a directive as follows:
.directive('ogTakeATour', function() {
return {
restrict: 'E',
replace: true,
templateUrl: '../scripts/directives/TakeATourTemplate.html',
scope: {
content: '#',
uid: '#'
},
link: function(scope) {
angular.element(scope.uid).css("top","250px");
}
};
});
Directive template looks like this:
<div id="{{uid}}" class="tourContainer">
{{content}}
</div>
And this is how I call my directive:
<og-take-a-tour content="Content goes here" uid="menuTour"></og-take-a-tour>
However for some reasons this does not apply the css to the applicable div.
angular.element(scope.uid).css("top","250px");
Why is this? Could it be that the directive does not know what the id of my element is at the time the link function is running? How would I get around this if that is the case?
Angular's jQlite does not support search by id or CSS selector. So change your code like this:
angular.element(document.querySelector('#' + scope.uid)).css("top", "250px");

Assign random ngModel to directive

I'm trying to assign a random string to ngModel. But I can't even seem to assign a regular string to it. In the code below, I'm trying to change the ngModel to "new", but in chrome, it's still showing me that the ngModel is "placeholder". What am I doing wrong?
app.directive("page", function(){
return {
restrict: 'E',
replace: true,
template: '<div ng-model="placeholder"></div>',
controller: function ($scope, $element) {
$scope.placeholder = "new";
}
}});
The plain div tag was just an example. What I actually have is a content editable div that I've bound to a textarea with a contenteditable directive. I made a button to allow me to add as many of these directives as I want, but when I add this directive, I'd like a new ng-model for each one, because that's what I'm using to save the content of each content editable div to a file.
This is the full example in case it might help someone else. I put the addPage directive to a button, which, when clicked, appends a new content editable div (which I'm calling a page). There is one more directive that I didn't include (contenteditable) because I got it from the bottom of the docs over here
app.directive("addPage", function($compile){
return function(scope, element, attrs){
element.bind("click", function(){ angular.element(document.getElementById('container')).append($compile('<page></page>')(scope));
});
};
});
app.directive("page", function(){
return {
restrict: 'E',
replace: true,
// template: '<div class="page" contenteditable strip-br="true" ng-model="chapter"></div>',
template: function (elem, attr) {
var test="chapter.two"; //in reality, I will generate a random string to keep these divs unique
return '<div class="page" contenteditable strip-br="true" ng-model=' + test +'></div>'
}
}});
In my controller, I have this object that is used to store all the ng-model content
$scope.chapter = {};
I think you are approaching this a bit backwards. Starting with the model, you'd need an array to keep the values from all of the inputs / contenteditables.
.controller("MainCtrl", function($scope){
$scope.data = [{v: ""}]; // first element
$scope.addContentEditable = function(){
$scope.data.push({v: ""});
};
})
Then, you can bind to each element of that array easily, and add elements at will:
<button ng-click="addContentEditable()">Add</button>
<div ng-repeat="item in data" ng-model="item.v" contenteditable></div>
I'm not sure exactly how the page directive needs to be used here, but one way or another, the approach is the same as above.
If you just want to pass a string in you could use attributes and template(fn)
HTML
<div page="new">
Directive
app.directive("page", function () {
return {
restrict: 'E',
replace: true,
template: function (elem, attr) {
return '<div ng-model="' + attr.page + '"></div>';
}
}
});

Triggering a function with ngClick within ngTransclude

I have an unordered list loaded with four items from an array while using ngRepeat. The anchor tag in the list item has a function in the ngClick attribute that fires up a message. The function call works well when used like this:
<ul>
<li ng-repeat="n in supsNames">
<a ng-click="myAlert(n.name)">{{n.name}}</a>
</li>
</ul>
I created a simple directive for inserting unordered lists with list items. The list is loaded just fine but the same functioned I previously mentioned does not fire up. The code is as follows:
<div list items="supsNames">
<a ng-click="myAlert({{item.name}})">{{item.name}}</a>
</div>
Here is my javascript and angularjs code:
var app = angular.module('myapp', []);
app.controller('myCtrl', function($scope) {
$scope.title = 'ngClick within ngTransclude';
$scope.supsNames = [
{"name" : "Superman"},
{"name" : "Batman"},
{"name" : "Aquaman"},
{"name" : "Flash"}
];
$scope.myAlert = function(name) {
alert('Hello ' + name + '!');
};
});
app.directive('list', function() {
return {
restrict: 'A',
scope: {
items: '='
},
templateUrl: 'list.html',
transclude: true,
link: function(scope, element, attrs, controller) {
console.log(scope);
}
};
});
I also have a plnkr in case you want to see what I tried to do:
http://plnkr.co/edit/ycaAUMggKZEsWaYjeSO9?p=preview
Thanks for any help.
I got the plunkr working. I'm not sure if its exactly what you're looking for. I copied the main code changes below.
Here's the plunkr:
http://plnkr.co/edit/GEiGBIMywkjWAaDMKFNq?p=preview
The modified directive looks like this now:
app.directive('list', function() {
return {
restrict: 'A',
scope: {
items: '=',
ctrlFn: '&' //this function is defined on controller
},
templateUrl: 'list.html',
transclude: true,
link: function(scope, element, attrs, controller) {
//directive fn that calls controller defined function
scope.dirFn = function(param) {
if(scope.ctrlFn && typeof scope.ctrlFn == 'function') { //make sure its a defined function
scope.ctrlFn( {'name': param} ); //not sure why param has to be passed this way
}
}
}
};
});
And here's how it's called in the html file that's bound to your controller:
<div list items="supsNames" ctrl-fn="myAlert(name)">
<a ng-click="dirFn(item.name)">{{item.name}}</a>
</div>
I think what was happening before is that you were trying to use a function defined in your controller within the isolated scope of the directive, so it wasn't working--that function was undefined in the directive. So what I did was added another parameter to the directive that accepts method binding (I think that's what its called) with the '&'.
So basically you pass your controller method to the directive, and that method gets invoked however you want by the directive defined method I creatively named "dirFn". I don't know if this is the best way per se, but I've used it in an existing project with good results ;)
you need to pass the function to the directive
scope: {
items: '=', 'myAlert': '='
},
The ng-repeat inside the template of the directive insert a new scope and it require to call transclude funcion manually to work. I suggest remove ng-repeat and make the transclusion manually passing a copy of the controller scope and setting the item on each copy:
for(var i=0,len=scope.items.length;i<len;i++){
var item=scope.items[i];
var itemScope=scope.$parent.$new();
$transcludeFn(itemScope, function (clone,scope) {
// be sure elements are inserted
// into html before linking
scope.item=item;
element.after(clone);
});
};
I edit the pluker and I hope that could be helpfull: http://plnkr.co/edit/97ueb8SFj3Ljyvx1a8U1?p=preview
For more info about transclusion see: Transclusion: $transcludeFn

Pass form to directive

I want to encapsulate my form fields in a directive so I can simply do this:
<div ng-form='myForm'>
<my-input name='Email' type='email' label='Email Address' placeholder="Enter email" ng-model='model.email' required='false'></my-input>
</div>
How do I access the myForm in my directive so I can do validation checks, e.g. myForm.Email.$valid?
To access the FormController in a directive:
require: '^form',
Then it will be available as the 4th argument to your link function:
link: function(scope, element, attrs, formCtrl) {
console.log(formCtrl);
}
fiddle
You may only need access to the NgModelController though:
require: 'ngModel',
link: function(scope, element, attrs, ngModelCtrl) {
console.log(ngModelCtrl);
}
fiddle
If you need access to both:
require: ['^form','ngModel'],
link: function(scope, element, attrs, ctrls) {
console.log(ctrls);
}
fiddle
Here a complete example (styled using Bootstrap 3.1)
It contains a form with several inputs (name, email, age, and country).
Name, email and age are directives. Country is a "regular" input.
For each input is displayed an help message when the user does not enter a correct value.
The form contains a save button which is disabled if the form contains at least one error.
<!-- index.html -->
<body ng-controller="AppCtrl">
<script>
var app = angular.module('app', []);
app.controller('AppCtrl', function($scope) {
$scope.person = {};
});
</script>
<script src="inputName.js"></script>
<script src="InputNameCtrl.js"></script>
<!-- ... -->
<form name="myForm" class="form-horizontal" novalidate>
<div class="form-group">
<input-name ng-model='person.name' required></input-name>
</div>
<!-- ... -->
<div class="form-group">
<div class="col-sm-offset-2 col-sm-4">
<button class="btn btn-primary" ng-disabled="myForm.$invalid">
<span class="glyphicon glyphicon-cloud-upload"></span> Save
</button>
</div>
</div>
</form>
Person: <pre>{{person | json}}</pre>
Form $error: <pre>{{myForm.$error | json}}</pre>
<p>Is the form valid?: {{myForm.$valid}}</p>
<p>Is name valid?: {{myForm.name.$valid}}</p>
</body>
// inputName.js
app.directive('inputName', function() {
return {
restrict: 'E',
templateUrl: 'input-name.html',
replace: false,
controller: 'InputNameCtrl',
require: ['^form', 'ngModel'],
// See Isolating the Scope of a Directive http://docs.angularjs.org/guide/directive#isolating-the-scope-of-a-directive
scope: {},
link: function(scope, element, attrs, ctrls) {
scope.form = ctrls[0];
var ngModel = ctrls[1];
if (attrs.required !== undefined) {
// If attribute required exists
// ng-required takes a boolean
scope.required = true;
}
scope.$watch('name', function() {
ngModel.$setViewValue(scope.name);
});
}
};
});
// inputNameCtrl
app.controller('InputNameCtrl', ['$scope', function($scope) {
}]);
Edit 2: I'll leave my answer, as it might be helpful for other reasons, but the other answer from Mark Rajcok is what I originally wanted to do, but failed to get to work. Apparently the parent controller here would be form, not ngForm.
You can pass it in using an attribute on your directive, although that will get rather verbose.
Example
Here's a working, simplified jsFiddle.
Code
HTML:
<div ng-form="myForm">
<my-input form="myForm"></my-input>
</div>
Essential parts of the directive:
app.directive('myInput', function() {
return {
scope: {
form: '='
},
link: function(scope, element, attrs) {
console.log(scope.form);
}
};
});
What's happening
We've asked Angular to bind the scope value named in the form attribute to our isolated scope, by using an '='.
Doing it this way decouples the actual form from the input directive.
Note: I tried using require: "^ngForm", but the ngForm directive does not define a controller, and cannot be used in that manner (which is too bad).
All that being said, I think this is a very verbose and messy way to handle this. You might be better off adding a new directive to the form element, and use require to access that item. I'll see if I can put something together.
Edit: Using a parent directive
OK, here's the best I could figure out using a parent directive, I'll explain more in a second:
Working jsFiddle using parent directive
HTML:
<div ng-app="myApp">
<div ng-form="theForm">
<my-form form="theForm">
<my-input></my-input>
</my-form>
</div>
</div>
JS (partial):
app.directive('myForm', function() {
return {
restrict: 'E',
scope: {
form: '='
},
controller: ['$scope', function($scope) {
this.getForm = function() {
return $scope.form;
}
}]
}
});
app.directive('myInput', function() {
return {
require: '^myForm',
link: function(scope, element, attrs, myForm) {
console.log(myForm.getForm());
}
};
});
This stores the form in the parent directive scope (myForm), and allows child directives to access it by requiring the parent form (require: '^myForm'), and accessing the directive's controller in the linking function (myForm.getForm()).
Benefits:
You only need to identify the form in one place
You can use your parent controller to house common code
Negatives:
You need an extra node
You need to put the form name in twice
What I'd prefer
I was trying to get it to work using an attribute on the form element. If this worked, you'd only have to add the directive to the same element as ngForm.
However, I was getting some weird behavior with the scope, where the myFormName variable would be visible within $scope, but would be undefined when I tried to access it. That one has me confused.
Starting with AngularJS 1.5.0, there is much cleaner solution for this (as opposed to using the link function directly). If you want to access a form's FormController in your subcomponent's directive controller, you can simply slap the require attribute on the directive, like so:
return {
restrict : 'EA',
require : {
form : '^'
},
controller : MyDirectiveController,
controllerAs : 'vm',
bindToController : true,
...
};
Next, you'll be able to access it in your template or directive controller like you would any other scope variable, e.g.:
function MyDirectiveController() {
var vm = this;
console.log('Is the form valid? - %s', vm.form.$valid);
}
Note that for this to work, you also need to have the bindToController: true attribute set on your directive. See the documentation for $compile and this question for more information.
Relevant parts from the documentation:
require
Require another directive and inject its controller as the fourth argument to the linking function. The require property can be a string, an array or an object:
If the require property is an object and bindToController is truthy, then the required controllers are bound to the controller using the keys of the require property. If the name of the required controller is the same as the local name (the key), the name can be omitted. For example, {parentDir: '^parentDir'} is equivalent to {parentDir: '^'}.
Made your 'What I'd prefer' fiddle work!
For some reason you could see the "$scope.ngForm" string in a console.log, but logging it directly didn't work, resulting in undefined.
However, you can get it if you pass attributes to the controller function.
app.directive('myForm', function() {
return {
restrict: 'A',
controller: ['$scope','$element','$attrs', function($scope,$element,$attrs) {
this.getForm = function() {
return $scope[$attrs['ngForm']];
}
}]
}
});
http://jsfiddle.net/vZ6MD/20/

Resources