I have two simple directives that I want to apply to an element.
This directive gives focus to a particular element when a condition changes to be true:
.directive('focusOnCondition', function ($timeout) {
return {
restrict: 'A',
scope: {
condition: '=focusOnCondition',
},
link: function (scope, el, attr) {
scope.$watch('condition', function (value) {
if (value) {
$timeout(function () {
el[0].focus();
});
}
});
}
};
});
This directive calls a function when the user presses 'enter':
.directive('triggerOnEnter', function () {
return {
restrict: "A",
scope: {
func: '&triggerOnEnter'
},
link: function (scope, el, attr) {
$(el).keypress(function (event) {
if (event.which == 13) scope.func();
});
}
};
})
These two directives work separately, but they don't work if I try to apply them to the same element. For example:
<form ng-show="pastingResults">
<textarea placeholder="Paste results and press 'enter'"
ng-model="pastedResultData"
trigger-on-enter="pasteResults()"
focus-on-condition="pastingResults"></textarea>
<button type="submit" ng-click="pasteResults()">Submit</button>
</form>
Result in an error, [$compile:multidir] Multiple directives (...) on (...).
How can I resolve this? I am new to Angular JS, and 'put it in an isolated scope' is the only thing I know how to do when making directives. I assume it must be possible to implement these without using an isolated scope, but I don't know how.
Can anyone give an example?
If you remove the isolated scopes, you can access the options by looking at the attributes on the element directly using the attrs object passed to the linking function, together with $eval to evaluate it if needs be:
app.directive('focusOnCondition', function ($timeout) {
return {
restrict: 'A',
link: function (scope, el, attr) {
scope.$watch(attr.focusOnCondition, function (value) {
if (value) {
$timeout(function () {
el[0].focus();
});
}
});
}
};
});
and
app.directive('triggerOnEnter', function () {
return {
restrict: "A",
link: function (scope, el, attr) {
$(el).keypress(function (event) {
if (event.which == 13) {
scope.$eval(attr.triggerOnEnter);
}
});
}
};
});
These can be seen working at http://plnkr.co/edit/1iOBNVlmRxm8HGRrObBd?p=preview
Related
I'm using two directives in this HTML code:
<div animations>
<h2>Title</h2>
<span class="animateBtn">Animate!</span>
<info-section></info-section>
</div>
The first, is an Attribute Directive:
angular.module('app').directive('animations', ['$window', ($window: any) => {
return {
restrict: "A",
link: function ($scope: any, element: any, attrs: any) {
angular.element(document).ready(() => {
let animateBtns = angular.element(element[0].querySelector('.animateBtn'));
if (animateBtns && animateBtns.length) {
for (let i = 0, animateBtnsLength = animateBtns.length; i < animateBtnsLength; i++) {
let currentBtn = animateBtns[i];
currentBtn.addEventListener("click", function () {
.... other code....
});
}
}
..... other code .....
});
}
};
}])
So, it simply does a querySelector to select all buttons that, at the click, have to start a certain function.
And it works. The problem is that the second directive also contains an "animateBtn":
.directive('infoSection', function() {
return {
replace: true,
restrict: 'E',
template: '<div><span class="animateBtn">Animate with this too</span></div>'
}
});
The problem is that in the first directive (even if I user (document).ready()), the selector returns just one element (the span under the title), and it doesn't include the "animateBtn" of the second directive.
Here you can find the full code: PLNKR
With AngularJS one uses directives to attach code to elements instead of query selectors:
app.directive("animateBtn", function($window) {
return {
restrict: 'C',
link: postLink
};
function postLink (scope, elem, attrs) {
elem.on('click', function() {
.... other code....
});
.... other code....
}
})
The above directive will attach the click handler and associated code to each element with the class animateBtn when the element is added to the DOM by the AngularJS framework.
if a write a function inside "animations" directive, how can I trigger it inside "animatBtn" directive? I mean, in your code, inside the first line of "..... other code...." how can I call a function written in the "animations" directive?
Use the require property of the DDO to access the controller of the animations directive:
app.directive("animateBtn", function($window) {
return {
restrict: 'C',
link: postLink,
require: '^animations'
};
function postLink (scope, elem, attrs, animations) {
elem.on('click', function() {
.... other code....
});
//call animations API
animations.someMethod(args);
}
})
In the animations directive:
app.directive("animations", function() {
return {
controller: ctrl
}
function ctrl($element, $scope) {
this.someMethod = function(args) {
//code here
};
}
})
For more information, see AngularJS Comprehensive Directive API Reference - require
let assume that I want to create directive that matched only for element that match amInput[type=dropdown] how can I do that?
I can for example:
.directive('amInput',function () {
return {
restrict: "E",
scope: {
data:'#'
},
compile:function(tElement, tAttrs){
if (tAttrs.type != 'dropdown') return;
return function link ($scope, element, attr, ctrl) {
var parseResult = parse($scope.data);
}
}
}
});
but if I define another directive with isolate scope for am-input[type=checkbox]
.directive('amInput',function () {
return {
restrict: "E",
scope: {
data2:'#'
},
compile:function(tElement, tAttrs){
if (tAttrs.type != 'checkbox') return;
return function link ($scope, element, attr, ctrl) {
var parseResult = parse($scope.data2);
}
}
}
});
angular#$compile throw exception about two directives define isolate scope.
Error: [$compile:multidir] Multiple directives [amInput, amInput] asking for new/isolated scope on: <am-input type="checkbox"></am-input>
directive name should be unique (as long as they match the same restrict)
in your case you should probably merge them into one.
(just as a reference, this might help: Angular directive name: only lower case letters allowed?)
Ok, I end up with that solution that make the differntes between the directives by postlink. when prelink is for everythig is similar between them:
directive('amInput',function () {
var template = {
'dropdown' :require('./dropdown.drv.html'),
'checkbox-group' :require('./checkbox-group.drv.html')
};
var postLinkPerType = {
'dropdown': require('./postLink-OneFromMany'),
'checkbox-group':require('./postLink-ManyFromMany')
};
return {
restrict: "E",
require: 'ngModel',
scope:{
selectAll:'='
//,...
},
transclude:true,
controller: function(scope, element, attr) {
/*for binding stuff that use by post links */
},
controllerAs:'inputCtrl',
bindToController:false,
template: function (element, attr) {
return template[attr.type];
},
compile: function (element, attr, ctrl, transcludeFn) {
return {
pre: function (scope, element, attr, ctrl, transcludeFn) {
/*******************************************/
/* some general/init code for all collection*/
/*******************************************/
/* init options list */
if( attr.data ){
/***********/
/* some code just if there is some attribute*/
/***********/
}
},
/*the little different code that make every directive in collection different*/
/*different by TYPE attribute*/
post: postLinkPerType[attr.type]
}
}
}
})
I want to have an attribute directive a bit similar to ng-model. I just want to additionally bind an input fields value to a scope variable (just in one direction input field -> scope variable). So I have just tried this directive but I can not get the directive called anyway.
script:
.directive('passivemodel', function () {
return {
restrict: "A",
scope: {
passivemodel: '='
},
link: function (scope, element, attrs) {
scope.$watch('passivemodel', function(newPassivemodel, oldPassivemodel) {
console.log("passive model", newPassivemodel);
});
}
};
})
html:
<input data-passivemodel="keyword">
Edit:
Hmmm .. based on vilo20 answer I am running into a very strange behavior.
while this code is working very well:
<input data-ng-model="keyword" data-passivemodel="keyword">
this one does not (note the value of passivemodel):
<input data-ng-model="keyword" data-passivemodel="keyword2">. Sure I have defined the variable in the controller.
Controller:
.controller('SearchController', function($scope, $routeParams, $search) {
$scope.search = new $search();
$scope.keyword = "";
$scope.keyword2 = "";
})
Edit2: here is a fiddle http://jsfiddle.net/HB7LU/12107/
try this:
.directive('passivemodel', function () {
return {
restrict: "A",
scope: {
passivemodel: '='
},
link: function (scope, element, attrs) {
console.log("passive model", scope.passivemodel);
$scope.$watch('passivemodel', function(newPassivemodel, oldPassivemodel) {
//put your logic when passivemodel changed
});
}
};
})
Hope it helps
Edit: Here is a plunker http://plnkr.co/edit/T039I02ai5rBbiTAHfzv?p=preview
Use the scope attribute:
.directive('passivemodel', function () {
return {
restrict: "A",
scope: {
"passivemodel": "="
},
link: function (scope, element, attrs) {
console.log("access passivemodel: ", scope.passivemodel);
}
};
})
Finally it was as simple as that:
.directive('modelRed', [function(){
return {
require: 'ngModel',
restrict: 'A',
scope: {},
link: function (scope, element, attrs, ngModel) {
scope.$watch(function () {
return ngModel.$modelValue;
}, function(newValue) {
scope.$parent[attrs.modelRed] = newValue;
//console.log(attrs.modelRed, newValue, scope);
});
}
}
}])
And in the html:
<p><input type="text" data-ng-model="testInput" data-model-red="redInput">{{redInput}}</p>
Of course you have to define testInput and redInput in the $scope object.
I am trying to write a "read-only" directive that will transform an input (with ng-model) to a span that has the value of that model. I would like to do this by simply adding "my-read-only" attribute to an element.
See: http://jsfiddle.net/cbW83/4/
angular.module("myapp", []).directive("myReadOnly", [function () {
return {
restrict: "A",
transclude: "element",
require: "?ngModel",
priority: 100,
compile: function (tElem, tAttrs, transclude) {
var template = angular.element("<span></span>");
return function (scope, elem, attrs, ngModelCtrl) {
if (ngModelCtrl && scope.$eval(attrs.myReadOnly) === true) {
scope.$watch(function () {
return scope.$eval(attrs.ngModel);
}, function (newVal) {
template.html(newVal);
});
elem.after(template);
}
else {
elem.after(transclude(scope));
}
}
}
}
}]);
<!--Read-Only Directive set to false:-->
<input type="text" ng-model="user.firstname" my-read-only="false" />
I have tried to give the my-read-only directive a higher priority. This allows the textbox to bind to the model, but the span is no longer used.
How can I achieve "toggling" the elements contents and still retain the original element if my-read-only is not set to true? It appears ng-model is being compiled on the element first and this is lost when I transclude.
Can change to a span that uses ng-bind as follows:
mod.directive("myReadOnly", function ($compile) {
return {
restrict: "A",
link:function(scope,elem,attrs){
if(attrs.myReadOnly=='true'){
var template=$compile('<span ng-bind="'+attrs.ngModel+'"></span>')(scope)
elem.replaceWith(template);
}
}
}
});
DEMO
how do I share a method between two directives? When I try it now the console says scope.triggerInput(); is undefined; If I replace the scope.triggerinput with just a string it works. So I guess I do it somewhere wrong in the attribute.
app.directive('myImage', function () {
return{
restrict: 'A',
template: ' <img ng-src="images/needed/noPhoto.png"class="hover">',
link: function (scope, element, attr) {
element.bind('click', function () {
scope.triggerInput();
});
}
};
});
app.directive('myFileInput', function (httpPostFactory) {
return{
restrict: 'A',
scope: {
triggerInput: '='
},
link: function (scope, element, attr) {
scope.triggerInput = function () {
console.log('triggerInput');
element[0].click();
};
}
};
});
html
<div data-my-image></div>
<input data-my-file-input type="file" name="file" triggerInput='triggerInput()'>
Very good question! If I understand your situation correctly, Angular Service is what you need to use for situations like these.
app.service('clickResponder',function(){
var srvc={};
srvc.triggerInput=function(){
// Do your stuff here.
}
return srvc;
}
Now, to use this service, its very simple just inject it on your directive like below:
app.directive('myImage', function (clickResponder) {
return{
restrict: 'A',
template: ' <img ng-src="images/needed/noPhoto.png"class="hover">',
link: function (scope, element, attr) {
element.bind('click', function () {
clickResponder.triggerInput();
});
}
};
});
Thats all you need to do. Hope this helps you in some way!