Sharing logic between directives - angularjs

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();
}
};
}
};
});

Related

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

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 !

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/

Using ng-hide on a loader in a directive in Angular

I've been trying to replicate the loaing example on this page (Showing Spinner GIF during $http request in angular) by #punkrockpolly. But either the example is wrong or I'm not getting something.
Basically i have a directive
.directive('loading', ['$http', function ($http) {
var html = '<div class="loader" data-loading></div>';
return {
restrict: 'E',
replace: true,
template: html,
link: function (scope, element, attrs) {
console.log(element);
scope.isLoading = function () {
console.log("is loading");
return $http.pendingRequests.length > 0;
};
scope.$watch(scope.isLoading, function (value) {
console.log(value);
if (value) {
element.removeClass('ng-hide');
} else {
element.addClass('ng-hide');
}
});
}
};
}]);
That I'm trying to turn on or off based on the $http request.
Here's what I have on my HTML page
<loading></loading>
What am I missing here.
Yes it works. I don't have the css for the spinner, but the ng-hidepart works perfect.
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
});
app.directive('loading', ['$http', function ($http) {
var html = '<div class="loader" data-loading>test</div>';
return {
restrict: 'E',
replace: true,
template: html,
link: function (scope, element, attrs) {
scope.isLoading = function () {
// console.log("is loading");
return $http.pendingRequests.length > 0;
};
// just to have some pending requests
setInterval(function(){
$http.get("https://www.google.com").then(function(){
});
},200)
scope.$watch(scope.isLoading, function (value) {
// console.log(value);
if (value) {
element.removeClass('ng-hide');
} else {
element.addClass('ng-hide');
}
});
}
};
}]);
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script>document.write('<base href="' + document.location + '" />');</script>
<link href="style.css" rel="stylesheet" />
<script data-semver="1.4.9" src="https://code.angularjs.org/1.4.9/angular.js" data-require="angular.js#1.4.x"></script>
<script src="app.js"></script>
</head>
<body ng-app="plunker" ng-controller="MainCtrl">
<p>Hello {{name}}!</p>
<loading></loading>
</body>
</html>
To get app wide spinner use angular interceptors
angular.module("MyApp", ["ngResource"])
.config(function ($httpProvider) {
$httpProvider.interceptors.push(function($q, $rootScope) {
return {
'request': function(config) {
$rootScope.$broadcast('http:request:start');
return config;
},
'response': function(response) {
$rootScope.$broadcast('http:request:end');
return $q.reject(response);
}
};
});
})
.directive('loading', ['$http', function ($http) {
var html = '<div class="loader" data-loading></div>';
return {
restrict: 'E',
replace: true,
template: html,
link: function (scope) {
scope.isLoading = false;
scope.$on('http:request:start', function() {
scope.isLoading = true;
});
scope.$on('http:request:end', function() {
scope.isLoading = false;
});
}
};
}]);

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

Transcluded element not being deleted on removal of list item when using ng-repeat

I have a widget that I'm instantiating using ng-repeat. Initial creation works fine, but after that it stops updating. Here's an excerpt from index.html:
<div>
<x-node ng-repeat="node in nodes"></x-node>
</div>
partials/node.html:
<div>{{node.name}}</div>
And the directive:
angular.module('directive', []).directive('node', function() {
return {
restrict: 'E',
scope: true,
templateUrl: 'partials/node.html',
replace: true,
compile: function(tElement, tAttrs, transclude) {
return {
post: function(scope, iElement, iAttrs) {
scope.$on('$destroy', function(event) {
console.log('destroying');
});
}
};
}
};
});
If I modify the list of nodes in the console like this:
var e = angular.element($0);
var s = e.scope();
s.nodes.splice(1,1);
s.$apply()
... then the $destroy callback runs, but the rendered elements do not change. Is there something I'm missing from my directive?
Demo: Plunker
It seems this was indeed a bug, which is fixed in the 1.2 series of AngularJS. Here's an updated demo that uses 1.2.
index.html:
<!DOCTYPE html>
<html ng-app="my-app">
<head lang="en">
<meta charset="utf-8">
<title>Custom Plunker</title>
<link rel="stylesheet" href="style.css">
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.21/angular.min.js"></script>
<script src="app.js"></script>
</head>
<body ng-controller="AppController">
<div id="ct">
<x-node ng-repeat="node in nodes"></x-node>
</div>
<button id="test">Remove element [1]</button>
</body>
</html>
app.js:
var app = angular.module('my-app', [], function () {
})
app.controller('AppController', function ($scope) {
$scope.nodes = [{
name: 'one'
}, {
name: 'two'
}, {
name: 'three'
}];
})
app.directive('node', function() {
return {
restrict: 'E',
scope: true,
templateUrl: 'node.html',
replace: true,
compile: function(tElement, tAttrs, transclude) {
return {
post: function(scope, iElement, iAttrs) {
scope.$on('$destroy', function(event) {
console.log('destroying');
});
}
};
}
};
});
$(function(){
$('#test').click(function(){
var el = $('#ct').children().first();
if(el.length){
var e = angular.element(el[0]);
var s = e.scope();
s.nodes.splice(1,1);
s.$apply()
}
})
});
node.html:
<div>{{node.name}}</div>

Resources