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()'/>
Related
I have a scenario in an Angular 1.x project where I need to watch a controller form within a directive, to perform a form $dirty check. As soon as the form on a page is dirty, I need to set a flag in an injected service.
Here is the general directive code:
var directiveObject = {
restrict: 'A',
require: '^form',
link: linkerFn,
scope: {
ngConfirm: '&unsavedCallback'
}
};
return directiveObject;
function linkerFn(scope, element, attrs, formCtrl) {
...
scope.$watch('formCtrl.$dirty', function(oldVal, newVal) {
console.log('form property is being watched');
}, true);
...
}
The above only enters the watch during initialization so I've tried other approaches with the same result:
watching scope.$parent[formName].$dirty (in this case I pass formName in attrs and set it to a local var formName = attrs.formName)
watching element.controller()[formName] (same result as the above)
I've looked at other SO posts regarding the issue and tried the listed solutions. It seems like it should work but somehow the form reference (form property references) are out of scope within the directive and therefore not being watched.
Any advice would be appreciated.
Thank you.
I don't know why that watch isn't working, but as an alternative to passing in the entire form, you could simply pass the $dirty flag itself to the directive. That is:
.directive('formWatcher', function() {
restrict: 'A',
scope: {
ngConfirm: '&unsavedCallback', // <-- not sure what you're doing with this
isDirty: '='
},
link: function(scope, element, attrs) {
scope.watch('isDirty', function(newValue, oldValue) {
console.log('was: ', oldValue);
console.log('is: ', newValue);
});
}
})
Using the directive:
<form name="theForm" form-watcher is-dirty="theForm.$dirty">
[...]
</form>
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";
}
Let us say I have a custom directive like this applied on a div:
<div my-custom-component></div>
In my controller, I want to define a function like this:
$scope.onAfterMyCustomComponentInit(){
// Do something only once after the component is inited.
}
How do I get the above method in my controller called?
You could pass you controller function to the directive from attribute & as soon as directive link function gets called you can fire the function which you have passed from the attribute. Before passing it from attribute you need to use & with scope variable name inside isolated scope of directive scope: { callback: '&' }
Markup
<div my-custom-component callback="myFunction()"></div>
Directive
app.directive('myCustomComponent', function(){
return {
scope: {
callback: '&'
},
restrict: 'A',
link: function(scope, element, attrs){
//directive has initalized
scope.callback(); //call controller method.
}
}
})
I have an angular directive (with isolate scope) set up like this
<div ng="$last" somedirective datajson=mydata myprop="{{ mydata.myspecialprop }}"></div>
(which actually gets rendered multiple times because it's inside an ng-repeat.)
Following the instructions of this SO answer, I tried to observe myprop for changes in the directive, however, the code inside the $scope.watch never runs even when the property changes (on a click event). I also tried scope.$watch(attrs.myprop, function(a,b){...)and it never ran either. How do I watch for the change in myprop
myapp.directive('somedirective', function () {
return {
restrict: 'AE',
scope: {
datajson: '=',
myprop: '#'
},
link: function (scope, elem, attrs) {
scope.$watch(scope.myprop, function (a, b) {
if (a != b) {
console.log("doesn't get called when a not equal b");
} else {
}
});
}
};
}
Update: the click event that changes the property is handle in the controller and I'm guessing this isn't reflected back in the isolate scope directive so that $watch is never getting triggered. Is there a way to handle that?
When you use an interpolation binding (#) you cannot use scope.$watch, which is reserved for two-way bindings (=) or internal scope properties.
Instead, you need to use attrs.$observe which does a similar job:
link: function(scope, elem, attrs){
attrs.$observe('myprop', function(newVal, oldVal) {
// For debug purposes
console.log('new', newVal, 'old', oldVal);
if (newVal != oldVal){
console.log("doesn't get called when newVal not equal oldVal");
} else {
// ...
}
});
}
Also, everytime myprop change, the newVal will be different from its oldVal, so it is a bit weird that you skip that case which is the only one which will happen.
NOTE: you also forgot doublequotes for the datajson two-way binding: datajson="mydata"
Intead of passing a string, try it as variable
scope: {
datajson: '=',
myprop: '=' //change to equal so you can watch it (it has more sense i think)
}
then change html, removing braces from mydata.myspecialprop
<div ng="$last" somedirective datajson="mydata" myprop="mydata.myspecialprop"></div>
I have an attribute directive that I use to send the value of an input field to my server (my-auto-save), and that works fine. Now I want to add to that directive the capability of validating my value and only in case of success it sends the value to the server.
For this I thought about adding another attribute to my tag like so:
<input type="text"
my-auto-save="saveHandler(field, value)"
my-auto-save-validations="validateNumeric(value)" />
My auto save directive is like this:
myMod.directive("myAutoSave",
function () {
return {
restrict: "A",
scope: {
saveHandler: "&myAutoSave"
},
require: "ngModel",
link: function (scope, elm, attr) {
var fieldName = "test";
var newValue = "new value test";
scope.saveHandler({fieldChanged: fieldName, newValue: newValue});
}
};
}
);
Now, before calling the scope.saveHandler how can I call my validation function passing the newValue value to it?
I know how to access it like attr.myAutoSaveValidations but I don't know how to correctly call the function passed like that...
You can add more stuff to your scope:
scope: {
saveHandler: "&myAutoSave",
validationFunction: "&myAutoSaveValidations"
},