Transcluding an ng-model - javascript

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

Related

Query selector in nested AngularJS Directives

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

How to set watcher for input.$error object with directive in AngularJS

I have a form that contain date inputs and select elements,
I tried to use ngChange but I found it not very useful when handling dates errors.
So I'm trying to set a watcher in a directive for every input.$error in order to display error message to the user.
my directive:
module.directive('validator', [function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attr, ctrl) {
scope.input = ctrl;
scope.$watch('input.$error', function() {
console.log(scope.input);
}, true);
}
}
}]);
The problem with the directive is that the scope.$watch fires only when an error object of dates is changing, and the scope.input of every input becomes similar to the ctrl of the changed date input.
JSFiddle
Update your directive to watch if the input is invalid
module.directive('validator', [function() {
return {
restrict: 'A',
require: '^form',
link: function(scope, elem, attr, form) {
var inputName = attr.name;
console.log(inputName, form[inputName].$error)
function watcherForRequired(){
return form[inputName].$error.required;
}
function watcherForMin(){
return form[inputName].$error.min;
}
scope.$watch(watcherForRequired, function(required) {
console.log("required", required);
})
scope.$watch(watcherForMin, function(min) {
console.log("min error", min);
})
}
}
also update ng-min to min="{{loan.loaned}}"
here is JSFiddle
After deep checking of the directive behavior. I find out that the scope.input of the last element infect all the others scopes.
So I set the $watch to listen to ctrl.$error, and it solved the problem.
The directive:
module.directive('validator', [function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attr, ctrl) {
scope.$watch(function() {return ctrl.$error}, function() {
if (ctrl.$invalid) {
console.log(attr.name, 'invalid');
}
else {
console.log(attr.name, 'valid');
}
}, true);
}
}
}]);
Updated JSFiddle
You're doing this to display error messages? Why not simply do this in the template?
<form name="myform">
<input ng-model="myform.model.user-name" name="user-name" id="my-form-user-name" />
<div ng-messages="myform.user-name.$error">
<p ng-if="myform.user-name.$invalid">My Error Message Here</p>
</div>
</div>

what if I want to create directive that match only for custom element && atribute

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]
}
}
}
})

Angularjs directive

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.

Parser function does not get called for change in input textbox

I am new to parsers and formatters. I have a directive that will be doing validation on change of the model.One way to do this is the $watch but from what I understand that is not a good way since it allows the model to be updated.
So I was looking at parsers and tried this code
app.directive('myDirective', function($compile) {
return {
restrict: 'E',
require: 'ngModel',
scope: {
},
link: function($scope, elem, attr, ctrl) {
console.debug($scope);
ctrl.$formatters.push(function(value) {
console.log("hello1");
return value;
});
ctrl.$parsers.unshift(function(value) {
debugger;
console.log("hello");
return value;
});
}
};
});
But the parser function is never called. The formatter is called once. Please see the plunkr .Can anyone tell me what I am doing wrong ,why is the parser function not getting called when i type in the textbox ?
This is a late response, but for reference:
I think you where missing the "glue" that will call the $parsers while ui changes occurs. This should be something like:
app.directive('myDirective', function($compile) {
return {
restrict: 'E',
require: 'ngModel',
scope: {
},
link: function($scope, elem, attr, ctrl) {
console.debug($scope);
ctrl.$formatters.push(function(value) {
console.log("hello1");
return value;
});
ctrl.$parsers.unshift(function(value) {
return value;
});
scope.$watch('directive model here', function() {
ctrl.$setViewValue(<build model from view here>);
});
}
};
});
For a full reference please see this (awesome) post.
Your link function is not called because the associated DOM element is not changing, just the model is. This works:
HTML:
This scope value <input ng-model="name" my-directive>
JS:
app.directive('myDirective', function($compile) {
return {
require: 'ngModel',
link: function($scope, elem, attr, ctrl) {
ctrl.$parsers.unshift(function(value) {
console.log("hello");
});
}
};
});
See here for more on when the link function is called.

Categories