Angular directive where one of two attributes are required - angularjs

How do I write a directive that requires either ng-model or k-ng-model? The documentation doesn't cover this :(
app.directive('myDir', function () {
return {
require: 'ngModel || kNgModel',
// omitted
};
});

You need to pass them in as an array of strings.
You can't tell Angular at least one of these needs to be available, so set them as optional and check in the link function if one of both is available. Update your code to:
app.directive('myDir', function () {
return {
require: ['?ngModel', '?kNgModel'],
link: function(scope, element, attrs, controllers){
if(!controllers[0] && !controllers[1]){
throw 'myDir expects either ng-model or k-ng-model to be defined';
}
}
};
});

Related

AngularJS $parser not being called when dynamically adding the directive

So what i'm trying to achieve is to be able to add a directive that contains a parser through another directive.
When directly adding the parser directive on an html element it works completely fine. the parser directive i currently use:
.directive('parseTest', [
function () {
return {
restrict: 'A',
require: ['ngModel'],
link: {
post: function (scope, element, attributes, ctrls) {
var controller = ctrls[0];
controller.$parsers.unshift(function (value) {
var result = value.toLowerCase();
controller.$setViewValue(value);
controller.$render();
return result;
})
}
}
}
}
])
Now when i add this directive through another directive the parser never gets called weirdly enough. The directive that generated the parsetest directive:
.directive('generateTest', ['$compile',
function ($compile) {
return {
restrict: 'A',
compile: function (elem, attrs) {
elem.attr('parse-test', '');
elem.removeAttr('generate-test');
var linkFn = $compile(elem);
return function (scope, element, attr) {
linkFn(scope);
}
}
}
}
])
The following works fine:
<input class="form-control col-sm-6" ng-model="model.parsetest" parse-test/>
The following doesn't work (While the generated result html is the same)
<input class="form-control col-sm-6" ng-model="model.generateTest" generate-test />
So my question is how can i get the parser working when it is in a dynamicly added directive?
Note, i already tried the solution to a similar issue from this question, but that doesn't work for me.
EDIT: Here is a plnkr that demonstrates the issue, both fields have the parse-test directive applied to it that should make the value in the model lowercase, but it only works for the one that is not dynamically added as shown in the console logs.
So I've found the solution, so for anyone that stumbles on the same issue here it is.
The 2 small changes have to made to the directive that generates the directive that contains a parser or formatter.
First of set the priority of the directive to a number higher or equal as 1. Secondly put terminal on true. Those 2 settings seem to resolve the issue.
The problem probably lies in that the default execution of nested directives makes it so that the parser and formatters get inserted slightly to late which is why we need to make sure the directive gets generated first thing before anything else.
This is just an assumption of why it works tho, if anyone else has an explanation it would be great :)
As for the code, the directive that generates another directive should look something like:
directive('generateTest', ['$compile',
function ($compile) {
return {
restrict: 'A',
terminal: true,
priority: 1,
compile: function (elem, attrs) {
attrs.$set('parseTest', '');
attrs.$set('generateTest', undefined);
var linkFn = $compile(elem);
return function (scope, element, attr) {
linkFn(scope);
}
}
}
}
])

AngularJs directive, scope with value and function

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()'/>

Toggle edit and display of the fields in a form

Above all, I have the plnkr at here.
I am trying to create a series of directive that support in-place toggle of text display and edit within a form. As I understand, there is a similar module like xeditable available, but we need to do something different down the road. So I started with an experiment to start with something similar.
First, I create a directive that allows toggling edit/display by setting an attribute editEnabled on the directive called editableForm. The following code does not do anything special other than a line of log message.
function editableForm ($log) {
var directive = {
link: link,
require: ['form'],
restrict: 'A',
scope: {
editEnabled: "&editEnabled"
}
};
return directive;
function link(scope, element, attrs, controller) {
//$log.info('editEnabled: ' + scope.editEnabled());
$log.info('editEnabled: ' + attrs.editEnabled); //this also works
}
} //editableForm
Then I wrote the following directive to override the input tag in html:
//input directive
function input($log) {
var directive = {
link: link,
priority: -1000,
require: ['^?editableForm', '?ngModel'],
restrict: 'E'
};
return directive;
function link(scope, element, attrs, ngModel) {
ngModel.$render = function() {
if (!ngModel.$viewValue || !ngModel.$viewValue) {
return;
}
element.text(ngModel.$viewValue);
};
$log.info('hello from input');
$log.info('input ngModel: ' + attrs.ngModel);
// element.val('Hello');
scope.$apply(function() {
ngModel.$setViewValue('hello');
ngModel.$render();
});
}
} //input
I was trying to show the ngModel value of the input as text in the input directive, however, it doesn't seem to do anything in my testing. Could someone spot where I am doing wrong? I wish to replace each input fields with text/html (e.g. <span>JohnDoe</span> for Username).
My first attempt on input is a proof of concept. If it works, I will keep working on other tags like button, select, etc.
Long shot here... Your requiring both editableForm and ngModel in your input directive. So the fourth parameter of your link function should be an array of controllers in the respective order of the require array, not the ngModel controller as you are expecting.
I didnt go any further in examining your code, but check it out.

angularjs directive - get element bound text content

How do you get the value of the binding based on an angular js directive restrict: 'A'?
<span directiverestrict> {{binding}} </span>
I tried using elem[0].innerText but it returns the exact binding '{{binding}}' not the value of the binding
.directive('directiverestrict',function() {
return {
restrict:'A',
link: function(scope, elem, attr) {
// I want to get the value of the binding enclosed in the elements directive without ngModels
console.log(elem[0].textContent) //----> returns '{{binding}}'
}
};
});
You can use the $interpolate service, eg
.directive('logContent', function($log, $interpolate) {
return {
restrict: 'A',
link: function postLink(scope, element) {
$log.debug($interpolate(element.text())(scope));
}
};
});
Plunker
<span directiverestrict bind-value="binding"> {{binding}} </span>
SCRIPT
directive("directiverestrict", function () {
return {
restrict : "A",
scope : {
value : '=bindValue'
},
link : function (scope,ele,attr) {
alert(scope.value);
}
}
});
During the link phase the inner bindings are not evaluated, the easiest hack here would be to use $timeout service to delay evaluation of inner content to next digest cycle, such as
$timeout(function() {
console.log(elem[0].textContent);
},0);
Try ng-transclude. Be sure to set transclude: true on the directive as well. I was under the impression this was only needed to render the text on the page. I was wrong. This was needed for me to be able to get the value into my link function as well.

How can I add this directive to my application?

I would like to try using this directive:
appModule.directive('scrollpane', function ($compile) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
element.addClass('scroll-pane');
element.jScrollPane();
var api = element.data('jsp');
scope.$watch(function () { return element.find('.' + attrs.scrollpane).length }, function (length) {
api.reinitialise();
});
}
};
});
I think I already correctly added the jQuery and other scripts. Can someone tell me how I would call this?
Assuming you want to use this on a DIV:
<div scrollpane>Contents...</div>
The restriction to 'A' means you want to use your directive as an attribute.
I'm assuming that the appModule is your main module.
You can see on line 3 that this directive is restrict to an attribute (restrict: 'A').
So, all you have to do is create a html element with this attribute.
<div scrollpane></div>

Resources