I have a simple Angular directive that adds a CSS class to an element when being clicked:
.directive("addClass", function () {
return {
scope: {
name: "=addClass"
},
link: function (scope, element, attributes) {
element.on("click", function () {
element.addClass(scope.name);
console.log("Element activated");
});
element.on("mouseleave", function () {
element.removeClass(scope.name);
console.log("Element deactivated");
});
}
}
});
I'm using it on my links:
...
But when I click my link my event handlers execute, although scope.name is undefined. I could read attribute value using attributes, but that beats the purpose of assigning attribute values to scope properties as described in Angular Directives API.
What am I doing wrong?
Replace =addClass with #addClass, or, in case you don't need an isolate scope, just read the class name right out of the attribute object:
element.on("click", function() {
element.addClass(attributes.addClass);
...
});
The reason = doesn't work in your case is that it expects a property so a two-way binding can be established and you're providing a static string (I'm assuming you are since class-name isn't a valid property name).
try ..., note the single-quoted 'class-name'
Related
I have a directive myEditable that toggle a <div> with an <input type=text> to allow inline edition :
<my-editable value="vm.contact.name"></my-editable>
I was happy with it until I read some articles that say that $scope.$apply should not be used. I'm using it when the user save his changes to update the model (vm.contact.name in my case) :
function save() {
scope.$apply(function(){
scope.value = editor.find('input').val();
});
toggle();
}
But since it is a bad thing, I would like to pass a callback method to my directive. This callback must be called with the new value when the user save his changes. However, it seems that I cannot add two fields to the directive scope :
return {
restrict: 'EA',
scope: {
value: '=value'/*,
onSave: '&onSave'*/
},
link: function(scope, element, attr) {
// ...
element.find('.save').click(function(){
save();
});
// Declaration of `save` as above.
}
}
If I uncomment onSave then the value is never received and onSave is undefined.
My question is, how can I give a value and a callback method to a directive ?
And, as bonus, how can I pass parameters to the callback ?
Thanks
You can pass 'n' number of fields in directives isolated scope.
If you want to pass a function use &. Keep this in mind if your property name is onSave then in the view use it like this on-save.
Your directive should look like below
app.directive('dir',function(){
return {
restrict: 'EA',
scope: {
onSave: '&'
},
link: function(scope, element, attr) {
// ...
debugger
console.log(scope.onSave)
scope.onSave();
// Declaration of `save` as above.
}
}
})
In the view you can pass the function like below
<dir on-Save='abc()'/>
OR
<dir on-save='abc()'/>
In my controller :
myApp.controller('homeCtrl', function($scope, $rootScope, $state, 'red';
$rootScope.$on('new_story', function(event, data) {
$scope.cardObj = {key:'value'};
});
});
In my HTML :
<div clickmeee ></div>
<div id="feedContainer" card='{{cardObj}}'> </div>
In my directive :
myApp.directive('clickmeee', function($compile, $rootScope) {
return {
restrict: 'A',
scope: {
card: '#'
},
link: function(scope, element, attrs) {
element.bind('click', function() {
scope.$watch('card', function(newVal, oldVal) {
alert(scope.card);
});
});
}
};
});
How do I pass data from controller to this directive. I compile some html and prepend it to the div. All of that is sorted out but I need some data from object I am trying to pass.
Any help??
There are several problems in your code:
you define a scope attribute named 'card', but you use cardObj instead
you use a watch that is completely unnecessary. And worse: you create a new watch every time the element is clicked
you don't define any card attribute on your clickmeee element. Instead, you're placing it on another element, on which the directive is not applied
you're passing the attribute with '#'. That works, but the directive will receive a string, containing the JSONified object, rather than the object itself
you're not showming us where you emit an event that will initialize cardObj in the controller scope
Here is a plunkr showing a working version of your code.
Also, note that using bind('click') is a bad idea. You'd better have a template in your directive and use ng-click in the template, or simply not use a directive at all and just use ng-click directly on the div element.
Bad news. You are doing it wrong all the ways.
Firstly
card='{{cardObj}}' >
this one should be put in the
<div clickmeee ></div>
So you can take it as binded scope variable in your directive registration
Secondly
If you managed to use '#' syntax
card: '#'
it will turn your input to string, not a binded scope. Use '=' instead.
In the end
You dont need to use watch here:
scope.$watch('card', function(newVal, oldVal) {
alert(newVal);
});
since scope.card is binded via '=' connector. Just simple use alert(scope.card). (Need to warn you that alert an object is not a good idea)
I have tried your code here: plunker. Changed a litte bit by using cardObj as string for easier presentation. Does it match your work?
You should watch the card object:
myApp.directive('clickmeee', function() {
return {
restrict: 'A',
scope: {
card: '#'
},
link: function(scope, element, attrs) {
scope.$watch('card', function(value) {
console.log(value);
});
}
};
});
And:
<div clickmeee id="feedContainer" card='{{cardObj}}'> </div>
Whenever the controller changes the cardObj, the directive's watch on card is triggered:
$scope.$apply(function() {
$scope.cardObj = "test";
}
Im wondering if there is a way to pass an argument to a directive?
What I want to do is append a directive from the controller like this:
$scope.title = "title";
$scope.title2 = "title2";
angular.element(document.getElementById('wrapper')).append('<directive_name></directive_name>');
Is it possible to pass an argument at the same time so the content of my directive template could be linked to one scope or another?
here is the directive:
app.directive("directive_name", function(){
return {
restrict:'E',
transclude:true,
template:'<div class="title"><h2>{{title}}</h3></div>',
replace:true
};
})
What if I want to use the same directive but with $scope.title2?
You can pass arguments to your custom directive as you do with the builtin Angular-directives - by specifying an attribute on the directive-element:
angular.element(document.getElementById('wrapper'))
.append('<directive-name title="title2"></directive-name>');
What you need to do is define the scope (including the argument(s)/parameter(s)) in the factory function of your directive. In below example the directive takes a title-parameter. You can then use it, for example in the template, using the regular Angular-way: {{title}}
app.directive('directiveName', function(){
return {
restrict:'E',
scope: {
title: '#'
},
template:'<div class="title"><h2>{{title}}</h2></div>'
};
});
Depending on how/what you want to bind, you have different options:
= is two-way binding
# simply reads the value (one-way binding)
& is used to bind functions
In some cases you may want use an "external" name which differs from the "internal" name. With external I mean the attribute name on the directive-element and with internal I mean the name of the variable which is used within the directive's scope.
For example if we look at above directive, you might not want to specify another, additional attribute for the title, even though you internally want to work with a title-property. Instead you want to use your directive as follows:
<directive-name="title2"></directive-name>
This can be achieved by specifying a name behind the above mentioned option in the scope definition:
scope: {
title: '#directiveName'
}
Please also note following things:
The HTML5-specification says that custom attributes (this is basically what is all over the place in Angular applications) should be prefixed with data-. Angular supports this by stripping the data--prefix from any attributes. So in above example you could specify the attribute on the element (data-title="title2") and internally everything would be the same.
Attributes on elements are always in the form of <div data-my-attribute="..." /> while in code (e.g. properties on scope object) they are in the form of myAttribute. I lost lots of time before I realized this.
For another approach to exchanging/sharing data between different Angular components (controllers, directives), you might want to have a look at services or directive controllers.
You can find more information on the Angular homepage (directives)
Here is how I solved my problem:
Directive
app.directive("directive_name", function(){
return {
restrict: 'E',
transclude: true,
template: function(elem, attr){
return '<div><h2>{{'+attr.scope+'}}</h2></div>';
},
replace: true
};
})
Controller
$scope.building = function(data){
var chart = angular.element(document.createElement('directive_name'));
chart.attr('scope', data);
$compile(chart)($scope);
angular.element(document.getElementById('wrapper')).append(chart);
}
I now can use different scopes through the same directive and append them dynamically.
You can try like below:
app.directive("directive_name", function(){
return {
restrict:'E',
transclude:true,
template:'<div class="title"><h2>{{title}}</h3></div>',
scope:{
accept:"="
},
replace:true
};
})
it sets up a two-way binding between the value of the 'accept' attribute and the parent scope.
And also you can set two way data binding with property: '='
For example, if you want both key and value bound to the local scope you would do:
scope:{
key:'=',
value:'='
},
For more info,
https://docs.angularjs.org/guide/directive
So, if you want to pass an argument from controller to directive, then refer this below fiddle
http://jsfiddle.net/jaimem/y85Ft/7/
Hope it helps..
Controller code
myApp.controller('mainController', ['$scope', '$log', function($scope, $log) {
$scope.person = {
name:"sangeetha PH",
address:"first Block"
}
}]);
Directive Code
myApp.directive('searchResult',function(){
return{
restrict:'AECM',
templateUrl:'directives/search.html',
replace: true,
scope:{
personName:"#",
personAddress:"#"
}
}
});
USAGE
File :directives/search.html
content:
<h1>{{personName}} </h1>
<h2>{{personAddress}}</h2>
the File where we use directive
<search-result person-name="{{person.name}}" person-address="{{person.address}}"></search-result>
<button my-directive="push">Push to Go</button>
app.directive("myDirective", function() {
return {
restrict : "A",
link: function(scope, elm, attrs) {
elm.bind('click', function(event) {
alert("You pressed button: " + event.target.getAttribute('my-directive'));
});
}
};
});
here is what I did
I'm using directive as html attribute and I passed parameter as following in my HTML file. my-directive="push" And from the directive I retrieved it from the Mouse-click event object. event.target.getAttribute('my-directive').
Insert the var msg in the click event with scope.$apply to make the changes to the confirm, based on your controller changes to the variables shown in ng-confirm-click therein.
<button type="button" class="btn" ng-confirm-click="You are about to send {{quantity}} of {{thing}} selected? Confirm with OK" confirmed-click="youraction(id)" aria-describedby="passwordHelpBlock">Send</button>
app.directive('ngConfirmClick', [
function() {
return {
link: function(scope, element, attr) {
var clickAction = attr.confirmedClick;
element.on('click', function(event) {
var msg = attr.ngConfirmClick || "Are you sure? Click OK to confirm.";
if (window.confirm(msg)) {
scope.$apply(clickAction)
}
});
}
};
}
])
my html is taking input in two form, input and contenteditable div . I want to write one directive that handles both, but I cannot find a way to figure out which tag has called the function (because Angular's JQLite doesnt provide a is() or get() function). The following code will be complete if I can figure out to evaluate IS_INPUT_TAG:
function funct() { return {
require: 'ngModel',
link: function(scope, element, attrs, ctrl) {
// view -> model
element.bind('input', function() {
scope.$apply(function() {
if(IS_INPUT_TAG)
ctrl.$setViewValue(element.val());
else
ctrl.$setViewValue(element.text());
scope.watchCallback(element.attr('data-ng-model'));
});
});
// model -> view
ctrl.$render = function() {
if(IS_INPUT_TAG)
element.val(ctrl.$viewValue);
else
element.text(ctrl.$viewValue);
};
}};
}
app.directive('input', funct);
app.directive('contenteditable', funct);
In your directive, you can make use of the element parameter of the linking function to identify the tag on which the directive is applied. You can then use that in your IF condition as follows:
ctrl.$render = function() {
var tagname = element["0"].tagName;
if(tagName === "INPUT")
element.val(ctrl.$viewValue);
else
element.text(ctrl.$viewValue);
};
After, this you can simply attach the directive to the input and the div tags as an attribute to the tags to identify the tag to which the directive is applied.
How do I set the interpolated value in a directive? I can read the correct value from the following code, but I have not been able to set it.
js:
app.directive('ngMyDirective', function () {
return function(scope, element, attrs) {
console.log(scope.$eval(attrs.ngMyDirective));
//set the interpolated attrs.ngMyDirective value somehow!!!
}
});
html:
<div ng-my-directive="myscopevalue"></div>
where myscopevalue is a value on my controller's scope.
Whenever a directive does not use an isolate scope and you specify a scope property using an attribute, and you want to change that property's value, I suggest using $parse. (I think the syntax is nicer than $eval's.)
app.directive('ngMyDirective', function ($parse) {
return function(scope, element, attrs) {
var model = $parse(attrs.ngMyDirective);
console.log(model(scope));
model.assign(scope,'Anton');
console.log(model(scope));
}
});
fiddle
$parse works whether or not the attribute contains a dot.
If you want to set a value on the scope but don't know the name of the property (ahead of time), you can use object[property] syntax:
scope[attrs.myNgDirective] = 'newValue';
If the string in the attribute contains a dot (e.g. myObject.myProperty), this won't work; you can use $eval to do an assignment:
// like calling "myscopevalue = 'newValue'"
scope.$eval(attrs.myNgDirective + " = 'newValue'");
[Update: You should really use $parse instead of $eval. See Mark's answer.]
If you're using an isolate scope, you can use the = annotation:
app.directive('ngMyDirective', function () {
return {
scope: {
theValue: '=ngMyDirective'
},
link: function(scope, element, attrs) {
// will automatically change parent scope value
// associated by the variable name given to `attrs.ngMyDirective`
scope.theValue = 'newValue';
}
}
});
You can see an example of this in this Angular/jQuery color picker JSFiddle example, where assigning to scope.color inside the directive automatically updates the variable passed into the directive on the controller's scope.