Passing a function to an Angular validation directive - javascript

I have the following code, note the name-valid and validation-function tags on the input field.
<form name="createForm" novalidate>
<div style="display: flex; width: 300px">
<div style="flex: 3;">
Name
</div>
<div style="flex: 5;">
<input type="text" name="listName" ng-model="newListName"
ng-minlength="3" name-valid validation-function="someFunction"/>
</div>
</div>
<div ng-show="createForm.listName.$error.unique &&
!renameGoldenForm.listName.$error.minlength">already exists</div>
<div ng-show="createForm.listName.$error.minlength">too short</div>
<div style="margin-top: 10px">
<button ng-click="createList()" ng-disabled="createForm.listName.$invalid">
Create</button>
</div>
</form>
And here is the JS:
window.angular.module("myModule").directive("nameValid", [
"$log",
function($log) {
return {
require: "ngModel",
scope: {
validationFunction: "="
},
link: function(scope, ele, attrs, c) {
scope.$watch(attrs.ngModel, function() {
var v = scope[attrs.ngModel];
if (!v || !((v).trim()) || v.length < 4) {
c.$setValidity("unique", true);
c.$setValidity("minlength", false);
return;
}
scope.validationFunction(v, scope.selectedListId)
.success(function(data) {
c.$setValidity("unique", data.unique);
c.$setValidity("minlength", data.minlength);
});
});
}
};
}
]);
The problem is that having require and scope seems to break.
Is there a way to pass a custom validate function to my directive? I'm not sure how to go about it.
I've tried removing require: 'ngModel' and adding ngModel in scope, but that did not work either.
If I remove scope and hard code the function in the watch block, that works, but obviously, that defeats the purpose of having a pointer to a specific function.

To bind a controller function to your directive, you have to use the & bindings (expression binding) which allows the directive to call an expression or a function defined by a DOM attribute.
For example :
Controller
(function(){
function Controller($scope, $q) {
//Declare the func which will be bind to the directive
$scope.func = function (data1, data2) {
return new $q(function(resolve){
resolve(data1 === data2);
});
}
}
angular
.module('app', [])
.controller('ctrl', Controller);
})();
Then we will bind this function into your directive, and we can call it into the link function.
Directive
(function(){
function directive() {
return{
restrict: 'AE',
scope: {
function: '&'
},
link:function(scope, element, attrs) {
//Then, pass an object as argument to your function
var promise = scope.function({data1: 5, data2: 5});
//Retrieve result
promise.then(function(data){
console.log(data);
});
}
};
}
angular
.module('app')
.directive('directive', directive);
})();
To finish, you can call your directive with a function attribute, in order to bind the function to your directive.
HTML
<body ng-app="app" ng-controller="ctrl">
<directive function="func(data1, data2)"></directive>
</body>

Related

Angular 1 custom directive not executing

I have my directive defined as follows:
'use strict;'
angular.module('clientApp')
.directive('afterLast', ['$ocLazyLoad','$timeout',function($ocLazyLoad, $timeout){
console.log('entered directive');
return {
restrict: 'A',
link: function(scope, elem, attr){
if (scope.$last === true){
console.log('entered directive')
$timeout(function(){
$ocLazyLoad.load(['some files'])
})
}
}
}
}]);
And, I am using it as an attribute as follows:
<div ng-repeat="topicObject in module.topics track by $index" afterLast>
<div class="ft-section">
<div ng-repeat="learningPointObject in topicObject.learningPoints track by $index">
<div class="ft-page">
<h2 class="module-name" style="text-align: center;">{{module.name | capitalize:true}}</h2>
<h3 class="topic-name">{{topicObject.name | capitalize:true}}</h3>
<h4>{{learningPointObject.name | capitalize}}</h4>
<hr>
</div>
</div>
</div>
</div>
But my directive is not executing. Even the console.log statements inside and outside the link function are not working.
1. Am I using directives the correct way?
2. If yes, what could be the reasons for it not working?
In the HTML the directive name needs to be in kebab-case, not camelCase.
<!-- ERRONEOUS camelCase
<div ng-repeat="topicObject in module.topics track by $index" afterLast>
-->
<!-- USE kebab-case -->
<div ng-repeat="topicObject in module.topics track by $index" after-last>
For more information, see AngularJS Developer Guide - Directive Normalization
directive : for last watch of ng-repeat..
app.directive('afterLast',function(){
return {
restrict: 'A',
scope: {},
link: function (scope, element, attrs) {
if (attrs.ngRepeat) {
if (scope.$parent.$last) {
if (attrs.afterLast !== '') {
if (typeof scope.$parent.$parent[attrs.afterLast] === 'function') {
// Executes defined function
scope.$parent.$parentattrs.afterLast;
} else {
// For watcher, if you prefer
scope.$parent.$parent[attrs.afterLast] = true;
}
}
}
} else {
throw 'ngRepeatEndWatch: ngRepeat Directive required to use this Directive';
}
}
}
});
function call on last call
$scope.layoutDone = function () {
you want your desire data here
}
html
{{module.name | capitalize:true}}
{{topicObject.name | capitalize:true}}
{{learningPointObject.name | capitalize}}

Reuse directive multiplie times with dynamic attributes in another directive's template

What i want to do is to be able to use a directive with different attributes in the same ng-app. The main goal is to run different code when the directive's input (ng-model) changes.
This is what i have now:
app.directive('customInput',
function ($compile) {
var customInputDefinitionObject = {
restrict: 'E',
replace: true,
scope: {
ident: '#'
},
template: '<input type="text" >',
controller: 'customInputController',
compile: function (tElement, tAttrs) {
$('input').removeAttr('ident')
.attr('ng-model', tAttrs.ident)
.attr('ng-change', tAttrs.ident + 'Change()');
var elemLinkFn = $compile(tElement);
return function (scope, element) {
elemLinkFn(scope, function (clone) {
element.replaceWith(clone);
})
}
}
}
return customInputDefinitionObject;
});
It works well in html e.g.:
<custom-input ident="var1"></custom-input>
<custom-input ident="var2"></custom-input>
i'm going to get to input with different ng-model and ng-change function, the controller uses dynamic names to get the $scope variables( $scope.var1Change).
The problem start when i want to use this directive inside another template.
app.directive('customInputGroup', function ($compile) {
var customInputGroupDefinitonObject = {
restrict: 'E',
replace: true,
scope: {
rident: '#',
},
template:''+
'<div>'+
'<custom-input id="first"></custom-input>'+
'<custom-input id="second"></custom-input>'+
'</div>',
controller: 'customInputGroupController',
compile: function (elem, attrs) {
$('#first', elem).removeAttr('id').attr('ident', attrs.rident + 'Start');
$('#second', elem).removeAttr('id').attr('ident', attrs.rident + 'End');
var rangeLinkFn = $compile(elem);
return function (scope, element) {
rangeLinkFn(scope, function (clone) {
element.replaceWith(clone);
})
}
}
}
return customInputGroupDefinitonObject;
});
In this case if i'm going to use it inside the HTML e.g.:
<custom-input-group rident='sg'></custom-input-group>
what i get rendered:
<div>
<input ng-model="sgEnd" ng-change="sgEndChange()">
<input ng-model="sgEnd" ng-change="sgEndChange()">
<input ng-model="sgEnd" ng-change="sgEndChange()">
</div>
For the 3rd rendered input the ng-change does not working.
If set terminal:ture in the inputGroup directive i get only to "input" rendered but both of them has the same ng-model and ng-change.
So how can i make it to render something like this:
<div>
<input ng-model="sgStart" ng-change="sgStartChange()">
<input ng-model="sgEnd" ng-change="sgEndChange()">
</div>
And if u know how would u be so nice to let me know only the "how" but the "why" aswell.
Thank you in advance.

Detect who change the model (user input vs controller) angularjs

I have an input which connected to model. Also, the input has directive which $watch the model.
There are 2 ways that the model will change.
The user will type in the textbox.
The code will change it (no matter what is the reason)
My question is
Is there a way to find out who change the model, the user interaction or the code, in the directive?
Example:
angular.module('app', [])
.controller('ctrl', function($scope) {
})
.directive('dir', function($rootScope){
return {
require: 'ngModel',
link: function(scope, element, attrs) {
$rootScope.logs = [];
scope.$watch(attrs.ngModel, function() {
// here I need to check if the change was from the UI or from the controller
$rootScope.logs.push('change');
});
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
<div data-ng-app="app" data-ng-controller="ctrl">
<input type="text" data-ng-model="model" data-dir="" />
<button data-ng-click="model = 'asd'">Set "model" to defferent value</button>
{{model}}
<hr />
<h3>console <button data-ng-click="$root.logs = []">clear console</button></h3>
<ul>
<li data-ng-repeat="log in $root.logs track by $index" data-ng-bind="log"></li>
</ul>
</div>
http://jsbin.com/vufawur/edit?html,js,output
Update
Example2:
angular.module('app', [])
.controller('ctrl', function($scope, $timeout) {
$timeout(function() {
$scope.model = 'asd';
}, 3000);
})
.directive('dir', function($rootScope){
return {
require: 'ngModel',
link: function(scope, element, attrs) {
$rootScope.logs = [];
scope.$watch(attrs.ngModel, function() {
// here I need to check if the change was from the UI or from the controller
$rootScope.logs.push('change');
});
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
<div data-ng-app="app" data-ng-controller="ctrl">
...wait until data "return from the server"<br />
<input type="text" data-ng-model="model" data-dir="" />
{{model}}
<hr />
<h3>console <button data-ng-click="$root.logs = []">clear console</button></h3>
<ul>
<li data-ng-repeat="log in $root.logs track by $index" data-ng-bind="log"></li>
</ul>
</div>
ext-change External Change Directive for ng-model
Use a $viewChangeListener to save the last user input and have the watch handler compare that to discriminate external changes to the model from user input changes to the model.
.directive('extChange', function(){
return {
require: 'ngModel',
link: function(scope, element, attrs, modelCtrl) {
var lastUserInput = modelCtrl.$viewValue;
modelCtrl.$viewChangeListeners.push(function() {
lastUserInput = modelCtrl.$viewValue;
});
scope.$watch(attrs.ngModel, function watchHandler (value) {
if (value!==lastUserInput) {
scope.$eval(attrs.extChange, {$value:value});
}
});
}
}
});
The example directive saves that last user input. When the watch handler gets a value that is different, it invokes the Angular expression defined by the ext-change attribute. The value of the change is exposed as $value.
<input ng-model="someInput"
ng-change="userInput=someInput"
ext-change="extInput=$value">
The ext-change directive works with the ng-model directive and complements the ng-change directive.
In this example, the ext-change directive only updates the extInput variable on external changes to the model. The ng-change directive only updates the userInput variable for user changes.
The DEMO on JSFiddle
The directive can also be used to invoke functions.
<input ng-model="someInput"
ng-change="userEvent(someInput)"
ext-change="externalEvent($value)">
Do not use $watch. You should not use it, you have to not use it, you are going to have trouble if you use $watch, you are already in trouble, don't use it.
Angular JS - you probably shouldn't use $watch in your controllers.
Is it an antipattern to use angular's $watch in a controller?
Use control flow and events. It is possible that you already have a lot of watcher and scope soup, it is not too late, refactor as soon as possible, it is for your best.
angular.module('app', [])
.controller('ctrl', function($scope) {
})
.directive('dir', function($rootScope) {
return {
require: 'ngModel',
link: function($scope, element, attrs) {
$rootScope.logs = [];
$scope.modelChange = function(reason) {
$rootScope.logs.push(reason);
};
$scope.modelChangedFromInput = function(model) {
$scope.modelChange('From input');
};
$scope.buttonClick = function() {
$scope.model = 'asd';
$scope.modelChange('From button');
};
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
<div data-ng-app="app" data-ng-controller="ctrl">
<input type="text" data-ng-model="model" data-dir="" data-ng-change="modelChangedFromInput()" />
<button data-ng-click="buttonClick()">Set "model" to different value</button>
{{model}}
<hr />
<h3>console <button data-ng-click="$root.logs = []">clear console</button>
</h3>
<ul>
<li data-ng-repeat="log in $root.logs track by $index" data-ng-bind="log"></li>
</ul>
</div>

Angularjs - ngModel.$setViewValue is not a function

Here is my plunker and the code I can't get to work starts on line 32
http://plnkr.co/edit/pmCjQL39BWWowIAgj9hP?p=preview
I am trying to apply an equivalent to markdown filter onto a directive... I created the filter and tested with manually applying the filter and it works that way,, but I should only use the filter conditionally when the type of content on directive is set to markdown.
I am trying to accomplish this by updating ng-model >>> ngModel.$setViewValue(html) but I am getting an error
ngModel.$setViewValue is not a function.. which makes me thing that the controller is not recognized although it is required by the directive.
Here is a working controller:
var app = angular.module('testOne', ["ngResource", "ngSanitize"]);
app.controller('testOneCtrl', function ($scope) {
$scope.product = {
id:12,
name:'Cotton T-Shirt, 2000',
description:'### markdown\n - list item 1\n - list item 2',
price:29.99
};
});
app.directive("myText", function ($parse) {
return {
restrict: "E",
require: "?ngModel",
scope:{
css: "#class", type: "#type"
},
controller: function ($scope, $element, $attrs) {},
templateUrl: "template.html",
compile: function(elm, attrs, ngModel){
var expFn = $parse(attrs.contentType + '.' + attrs.value);
return function(scope,elm,attrs){
scope.$parent.$watch(expFn, function(val){
scope.exp = { val: val };
if ( attrs.type == 'markdown'){
var converter = new Showdown.converter();
var html = converter.makeHtml(val);
//scope.exp.val = html;
ngModel.$setViewValue(html);
ngModel.$render();
}
})
scope.$watch('exp.val', function(val){
expFn.assign(scope.$parent, val)
})
}
}
}
})
This is a filter for markdown which works when applied.. (I would consider using the filter if I could figure out the way to conditionally apply it to existing directive but I'd rather do it with ng-model)
/*
app.filter('markdown', function ($sce) {
var converter = new Showdown.converter();
return function (value) {
var html = converter.makeHtml(value || '');
return $sce.trustAsHtml(html);
};
});
*/
Here is the directive template
<div ng-class="{{css}}"
ng-click="view = !view"
ng-bind-html="exp.val">
</div>
<div>
<textarea rows="4" cols="30" ng-model="exp.val"></textarea>
</div>
This is the directive in use:
<mb-text ng-cloak
type="markdown"
content-type="product"
value="description"
class="test-one-text-2">
</mb-text>
Why ngModel is empty?
When using require on a directive the controller is passed as the 4th argument to the linking function. In you code you try to reference it as an argument of the compile function. The controller is only instantiated before the linking phase so it could never be passed into the compile function anyway.
The bigger issue is that require can only get a controller of the same element ({ require: 'ngModel' }), or parent elements ({ require: '^ngmodel' } ). But you need to reference a controller from a child element (within the template).
How to get ngModel?
Do not use require at all as you cannot get child element's controller with it.
From angular.element docs:
jQuery/jqLite Extras
controller(name) - retrieves the controller of the current element or its parent. By default retrieves controller associated with the ngController directive. If name is provided as camelCase directive name, then the controller for this directive will be retrieved (e.g. 'ngModel').
Inside the linking function you can get the hold of the controller like so:
var ngModel = elm.find('textarea').controller('ngModel');
I fixed your directive:
here is a plunker: http://plnkr.co/edit/xFpK7yIYZtdgGNU5K2UR?p=preview
template:
<div ng-class="{{css}}" ng-bind-html="exp.preview"> </div>
<div>
<textarea rows="4" cols="30" ng-model="exp.val"></textarea>
</div>
Directive:
app.directive("myText", function($parse) {
return {
restrict: "E",
templateUrl: "template.html",
scope: {
css: "#class",
type: "#type"
},
compile: function(elm, attrs) {
var expFn = $parse(attrs.contentType + '.' + attrs.value);
return function(scope, elm, attrs) {
scope.exp = {
val: '',
preview: null
};
if (attrs.type == 'markdown') {
var converter = new Showdown.converter();
var updatePreview = function(val) {
scope.exp.preview = converter.makeHtml(val);
return val;
};
var ngModel = elm.find('textarea').controller('ngModel');
ngModel.$formatters.push(updatePreview);
ngModel.$parsers.push(updatePreview);
}
scope.$parent.$watch(expFn, function(val) {
scope.exp.val = val;
});
scope.$watch('exp.val', function(val) {
expFn.assign(scope.$parent, val);
});
};
}
};
});

AngularJS The scope for dynamic content through $compile isn't attached to the controller scope

When I generate a new element through a string that has a directive (that's why I need to compile) and that directive generates an association with a variable in the controller scope through "=", the variable in my controller isn't associated to the one in the directive.
I created a jsfiddle to show the example where the "door" ng-model value should be associated to all the directives model values.
See this fiddle: http://jsfiddle.net/aVJqU/2/
Another thing I notice is that the directive that run from elements present in the html show the correct association through the variables (controller and directive).
The html (there is the directive that binds <door>):
<body ng-app="animateApp">
<div ng-controller="tst">
<h2> Controller with its model </h2>
<input ng-model="doorval" type="text"> </input>
{{doorval}}
<h2> Directive render directly from the html </h2>
<door doorvalue="doorval"></door> <key></key>
<h2> Directives that are compiled </h2>
<list-actions actions="actions"></list-actions>
</div>
</body>
This is the directive:
animateAppModule.directive('door', function () {
return {
restrict: "E",
scope: {
doorvalue:"="
},
template: '<span>Open the door <input type="text" ng-model="doorvalue"> </input> {{doorvalue}}</span>',
replace: true
}
})
This is the controller:
var animateAppModule = angular.module('animateApp', [])
animateAppModule.controller('tst', function ($scope, tmplService) {
$scope.doorval = "open"
$scope.actions = tmplService;
})
animateAppModule.service('tmplService', function () {
return [{
form_layout: '<door doorvalue="doorval"></door> <key></key>'
}, {
form_layout: '<door doorvalue="doorval"></door> with this <key></key>'
}]
})
And finally this is the directive that compiles the string that has the directive that doesn't bind:
animateAppModule.directive('listActions', function ($compile) {
return {
restrict: "E",
replace: true,
template: '<ul></ul>',
scope: {
actions: '='
},
link: function (scope, iElement, iAttrs) {
scope.$watch('actions', function (neww, old,scope) {
var _actions = scope.actions;
for (var i = 0; i < _actions.length; i++) {
//iElement.append('<li>'+ _actions[i].form_layout + '</li>');
//$compile(iElement.contents())(scope)
iElement.append($compile('<li>' + _actions[i].form_layout + '</li>')(scope))
}
})
}
}
})
What can I do to bind all the "door" ng-model values together?
Where is the compiled directive binding to?
You just have to pass the doorval reference down through all directives without skip any one. The problem was the listActions directive didn't had access to doorval in its scope.
Check this out: http://jsfiddle.net/aVJqU/5/
#Danypype is basically correct as the problem occurs due to scope isolation, as explained in the documentation.
An alternative solution is to simply eliminate the scope isolation by removing the scope block from within the directive definition.

Categories

Resources