Angular directive compile: "RangeError: Maximum call stack size exceeded" - angularjs

I want to add an 'ng-pattern' directive to an input element through a custom directive. I don't want to do it in the templates directly, but it looks I'm getting in a infinite loop.
I tried to set first the 'html' and compile the element after (Angular compile in directive seems to go into infinite loop) but scope is undefined. I don't know if it's related with replacing element's content.
Should i create a new scope? Do I'm missing something?
Thanks in advance!
var myHtml = iElem[0].outerHTML;
iElem.replaceWith(myHtml);
var compiledElement = $compile(iElem)(iElem.scope());
HTML:
<input type="text" ng-model="personal.testNumber_string" my-model="personal.testNumber" dot-to-comma>
Directive:
function dotToCommaConverter($compile) {
return {
require: 'ngModel',
restrict: 'A',
scope: {
myModel: '='
},
controllerAs: 'dot2Comma',
controller: function($scope) {
this.myModel = $scope.myModel;
},
compile: function(tElem, tAttrs) {
return {
pre: function(scope, iElem, iAttrs) {
},
post: function(scope, iElem, iAttrs, modelCtrl) {
iElem.attr('ng-pattern', '/^-?[0-9]+(?:\,[0-9]+)?$/');
var compiledElement = $compile(iElem)(iElem.scope());
iElem.replaceWith(compiledElement);
modelCtrl.$setViewValue(String(scope.dot2Comma.myModel).replace('.', ','));
modelCtrl.$render();
modelCtrl.$parsers.push(function(inputValue) {
var transformedInput = inputValue.replace(/[^0-9,.-]/g, '');
transformedInput = transformedInput.replace('.', ',');
transformedInput = transformedInput.replace(' ', '');
if (transformedInput !== inputValue) {
modelCtrl.$setViewValue(transformedInput);
modelCtrl.$render();
}
if (!isNaN(Number(transformedInput.replace(',', '.')))) {
scope.myModel = Number(transformedInput.replace(',', '.'));
} else {
scope.myModel = undefined;
}
return transformedInput;
});
}
};
}
};
}

I needed to remove my own directive from the Html content before re-compiling again, that's what caused the infinite loop.
iElem.removeAttr('dot-to-comma');
iElem.attr('ng-pattern', '/^-?[0-9]+(?:\,[0-9]+)?$/');
iElem.attr('ng-blur', 'dot2Comma.myBlurFunction()');
var compiledElement = $compile(iElem)(scope);
iElem.replaceWith(compiledElement);

here is an sample directive which replace dots with commas in a textbox :
script.js
angular.module('app', []);
angular.module('app')
.controller('ExampleController', ['$scope', function($scope) {
$scope.my = { number: '123.456' };
}]);
angular.module('app')
.directive('dotToComma', function() {
return {
restrict: 'A',
link: function (scope, element, attrs) {
scope.$watch(attrs.ngModel, function (value) {
var newValue = value.replace('.', ',');
element.val(newValue);
});
}
}
});
index.html
<html lang="en" ng-app="app">
<head>
<meta charset="utf-8">
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<form ng-controller="ExampleController">
<p>scope.my.number = {{my.number}}</p>
<label>In this textbox, dots will automatically be replaced with commas, even if you change its value :</label>
<input type="text" ng-model="my.number" dot-to-comma>
</form>
</body>
</html>
Here is a plunker : https://plnkr.co/edit/X6Fi0tnjBXKKhbwH0o2q?p=preview
Hope it helps !

Related

Angular - ng-click function on transcluded directive content is not triggered

I have two directives, parent directive should simply wrap around its child. The transclusion is used for this purpose.
However, then any other directive, such as ng-click, bound to the child directive element as its attribute does not work (is it not compiled?).
Here is the JS:
(function(angular) {
'use strict';
angular.module('docsIsoFnBindExample', [])
.controller('Controller', ['$scope', '$timeout', function($scope, $timeout) {
$scope.name = 'Tobias';
$scope.message = '';
$scope.hideDialog = function(message) {
$scope.message = message;
$scope.dialogIsHidden = true;
$timeout(function() {
$scope.message = '';
$scope.dialogIsHidden = false;
}, 2000);
};
}]) //controller is not important now
.directive('myDialog', function() { //parent directive
return {
restrict: 'E',
transclude: true,
scope: {
'close': '&onClose'
},
template: '<div class="alert"><a href class="close" ng-click="close({message: \'closing for now\'})">×</a><div ng-transclude></div></div>'
};
})
.directive('daka', function() { //child directive
return {
restrict: 'E',
scope: {
'input': '#'
},
link: function(scope, element, attributes) {
scope.func= function() {
console.log("blablabla"); //no console output after click event
};
}
};
});
})(window.angular);
HTML:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Example - example-directive-transclusion-scope-production</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body ng-app="docsIsoFnBindExample">
<div ng-controller="Controller">
{{message}}
<my-dialog ng-hide="dialogIsHidden" on-close="hideDialog(message)">
<daka ng-click="func()" input="11">BLABlABLA</daka>
</my-dialog>
</div>
</body>
</html>
It is trying to look ng-click in your parent directive.
So you can add click event for your child directive.
.directive('daka', function() { //child directive
return {
restrict: 'E',
scope: {
'input': '#'
},
link: function(scope, element, attributes) {
element.on('click', function() {
alert('outcome clicked: ');
});
}
}; });
working jsfiddle link -https://jsfiddle.net/p2vht8sb/

Angularjs $viewValue without changing $modelValue

In Angular (1.4.x), is the there a way to dynamically change the input context without changing the $modelValue? So for example: is it possible to dynamically toggle a time (moment) input text/content for local/utc without changing the $modelValue. Here's an example that will change both view and model values. I just need to mask the input context and not the model value.
Thanks!
var app = angular.module('testparser', []);
app.controller('MainCtrl', function($scope) {
$scope.data = {
name: ''
};
});
app.directive('changetime', function() {
return {
restrict: 'EA',
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
//format text going to user (model to view)
ngModel.$formatters.push(function(value) {
return value;
});
//format text from the user (view to model)
ngModel.$parsers.push(function(value) {
return value;
});
scope.data.time = moment().format('HH:mm:ss')
scope.setLocalTime = function() {
scope.data.time = moment().local().format('HH:mm:ss');
}
scope.setUtcTime = function() {
scope.data.time = moment().utc().format('HH:mm:ss');
}
}
}
});
<html ng-app="testparser">
<head>
<meta charset="utf-8" />
<script data-require="angular.js#1.4.8" data-semver="1.4.8" src="https://code.angularjs.org/1.4.8/angular.js"></script>
<script data-require="moment.js#*" data-semver="2.10.2" src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.2/moment.min.js"></script>
</head>
<body ng-controller="MainCtrl">
<input type="button" value="set to local" ng-click="setLocalTime()" />
<input type="button" value="set to utc" ng-click="setUtcTime()" />
<input changetime ng-model="data.time" />
<pre>model is: {{data.time}}</pre>
</body>
</html>
You were really close.
var app = angular.module('testparser', []);
app.controller('MainCtrl', function($scope) {
$scope.data = {
name: ''
};
});
app.directive('changetime', function() {
return {
restrict: 'EA',
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
//format text going to user (model to view)
ngModel.$formatters.unshift(function(value) {
return value;
});
//format text from the user (view to model)
ngModel.$parsers.unshift(function(value) {
return value;
});
scope.data.time = moment().format('HH:mm:ss')
scope.setLocalTime = function() {
ngModel.$viewValue = moment().local().format('HH:mm:ss');
ngModel.$render();
//scope.data.time = moment().local().format('HH:mm:ss');
}
scope.setUtcTime = function() {
ngModel.$viewValue = moment().utc().format('HH:mm:ss');
ngModel.$render();
//scope.data.time = moment().utc().format('HH:mm:ss');
}
}
}
});

Sharing logic between directives

I'm trying to share the logic between these two directives, I'm still learning Angular and don't quite understand how to accomplish this. I'm getting a $compile:ctreq error. I have watched some tutorials and I believe the logic is supposed to be in the controller but I get an error and the page wont load. I have a simple Pomodoro timer and would like the buttons to each be there own directive. Maybe I should be doing this with controllers but either way I would like to know how this works. Thanks..
var app = angular.module('pomodoro_timer', ['ui.router', 'firebase']);
app.directive("timer", ['$interval', function($interval) {
return {
restrict: "E",
transclude: true,
controller: function() {
},
templateUrl: "/templates/timer.html",
link: function(scope,element,attributes) {
scope.intrvl;
scope.t = 10;
var tDiv = $(element).find('#time');
scope.min = "25";
scope.sec = "00";
scope.interval = function() {
scope.intrvl = $interval(function(){
if (scope.t == 0) {
scope.resetTimer();
scope.sessionComplete = false;
} else {
scope.t -= 1;
scope.displayTime()
}
},1000)
}
scope.toggleClass = function() {
tDiv.toggleClass('notWorking working');
}
}
};
}]);
app.directive('start', function() {
return {
restrict: 'E',
transclude: true,
scope: {},
require: "^timer",
templateUrl: '/templates/start.html',
link: function (scope, element, attr, timerCtrl) {
scope.startTimer = function() {
if (tDiv.hasClass("notWorking")) {
// scope.working = true;
scope.interval(scope.t);
scope.toggleClass();
}
};
}
};
});
HTML
<!DOCTYPE html>
<html ng-app="pomodoro_timer">
<head lang="en">
<title>Pomodoro Timer</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/css/style.css">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.2/angular.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.8/angular-ui-router.min.js"></script>
<script src="https://cdn.firebase.com/js/client/2.2.4/firebase.js"></script>
<script src="https://cdn.firebase.com/libs/angularfire/1.1.1/angularfire.min.js"></script>
</head>
<body>
<timer></timer>
<start></start>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script type="application/javascript" src="/js/app.js"></script>
</body>
</html>
As you are using require: '^timer' inside start, that means you are assuming that your start directive should be inside timer directive so that start directive can get access to the controller of timer directive.
Also you should place the expo-sable method inside controller rather than placing it into link function, controller could be accessible by the directive which require this controller.
Markup
<timer>
<start></start>
</timer>
Code
app.directive("timer", ['$interval', function($interval) {
return {
restrict: "E",
transclude: true,
controller: function($scope) {
$scope.interval = function() {
$scope.intrvl = $interval(function() {
if (scope.t == 0) {
$scope.resetTimer();
$scope.sessionComplete = false;
} else {
$scope.t -= 1;
$scope.displayTime()
}
}, 1000)
};
$scope.toggleClass = function() {
tDiv.toggleClass('notWorking working');
};
},
templateUrl: "/templates/timer.html",
link: function(scope, element, attributes) {
scope.intrvl = 0; //set default value
scope.t = 10;
var tDiv = $(element).find('#time');
scope.min = "25";
scope.sec = "00";
}
};
}]);
app.directive('start', function() {
return {
restrict: 'E',
transclude: true,
scope: {},
require: "^timer",
templateUrl: '/templates/start.html',
link: function(scope, element, attr, timerCtrl) {
scope.startTimer = function() {
if (tDiv.hasClass("notWorking")) {
//calling method of `timer` directive controller
timerCtrl.interval(scope.t);
timerCtrl.toggleClass();
}
};
}
};
});

Integrate jquery masked input as angularjs directive

I wish to use masking in form inputs. I have created a directive uiMask which takes predefined masking formats like DoB or zip. In order to initiate masking, I apply the masking in directive's link function. And to update the model I manually trigger the digest cycle using $apply on keyup. Is this approach correct?
angular.module('formApp', [])
.controller("DemoFormController",['$scope',function($scope){
}])
.directive('uiMask', [
function () {
return {
require:'ngModel',
scope: {
type : "#uiMask"
},
controller: function($scope){
$scope.dob = "99/99/9999";
$scope.zip = "99999";
},
link:function ($scope, element, attrs, controller) {
var $element = $(element[0]);
$element.mask($scope.$eval($scope.type));
/* Add a parser that extracts the masked value into the model but only if the mask is valid
*/
controller.$parsers.push(function (value) {
var isValid = value.length && value.indexOf("_") == -1;
return isValid ? value : undefined;
});
/* When keyup, update the view value
*/
element.bind('keyup', function () {
$scope.$apply(function () {
controller.$setViewValue(element.val());
});
});
}
};
}
]);
<!doctype html>
<html ng-app="formApp">
<head>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular.min.js"></script>
<script src="http://code.jquery.com/jquery-1.10.2.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery.maskedinput/1.3.1/jquery.maskedinput.js"></script>
</head>
<body>
<div ng-controller="DemoFormController" class="container">
{{employee | json}}
<input type="text" class="form-control" id="dob" name="dob" placeholder="Date of Birth" ui-mask="dob" ng-model="employee.dob">
<input type="text" class="form-control" id="zip" placeholder="Zip" ui-mask="zip" ng-model="employee.zip" >
</div>
</body>
</html>
I did something similar with putting a jQuery "plugin" (uniform.js) into an angular directive. I think what might help is if you attached a $watch to the element. Here is what I did for the uniform directive:
(function () {
'use strict';
angular.module('pfmApp').directive('uniform', function($timeout) {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
$(element).addClass('uniform').uniform();
//$(element).uniform.update();
scope.$watch(function() { return ngModel.$modelValue }, function() {
$timeout(jQuery.uniform.update, 0);
});
}
}
});
}());
I hope this helps or perhaps points you in the right direction.

Passing arguments to AngularJS directive not working

I'm trying to code my first AngularJS directive. This directive should extend the DIV element to include a picture and a parameter (thumbnail). If thumbnail == true, the image should be resized. I managed to display the picture, yet it is not resized.
<!DOCTYPE html>
<html ng-app="ExampleDirective">
<head>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.6/angular.min.js"></script>
<script>
angular.module('ExampleDirective', [])
.controller('MyController', function($scope, $window) {
})
.directive('mypicture', function () {
return {
restrict: 'A',
template: '<img src="picture.gif" width="calc()" />',
scope: {
thumbnail: '=',
},
link: function (scope, elem, attrs) {
scope.calc = function() {
if (scope.thumbnail === 'true') {
return 50;
}
};
}
}
});
</script>
</head>
<body ng-controller="MyController">
<div mypicture thumbnail="true" ></div>
</body>
</html>
Any idea how to fix it ?
Thanks!
Try to use ng-style instead:
js
app.directive('mypicture', function () {
return {
restrict: 'A',
template: '<img src="http://pagead2.googlesyndication.com/simgad/8173680700251715003"
ng-style="calc()" />',
scope: {
thumbnail: '=',
},
link: function (scope, elem, attrs) {
scope.calc = function() {
if (scope.thumbnail == true) {
return {width: 150 + 'px'};
}
return {width: 100 + 'px'}
};
}
}
});
Demo Fiddle

Resources