Angular directive instances $watch not working - javascript

I have two timepicker inputs that has a total that I need to calculate.
The code in my directive works fine but the problem comes when I have more than one of the directives on the page.
I tried setting the watches in my directive's controller or in the link function but the watches are only working on the last instantiated directive.
What am I possibly missing?
Edit: Sorry wrong plunkr
Here's a plunkr: https://plnkr.co/edit/uC38NIbYsy9Vv3S38xHh?p=preview
Directive code:
myApp.directive('myTimepicker', function() {
return {
restrict: 'A',
scope: {
tmodel: '=',
ttitle: '#'
},
link: function(scope, $element, attr) {
console.log(scope);
scope.tform = scope.tmodel;
scope.$watch('tform.on', function(newValue, oldValue) {
// console.log("calc on"+scope.ttitle);
_calctotal();
});
scope.$watch('tform.off', function(newValue, oldValue) {
// console.log("calc off");
_calctotal();
});
_calctotal = function() {
var on = new Date(scope.tform.on);
var off = new Date(scope.tform.off);
var total = off.getHours() - on.getHours();
var totalmin = off.getMinutes() - on.getMinutes();
if (totalmin < 0) {
total = total - 1;
totalmin = totalmin * -1;
}
if (total < 0) {
total = "Invalid";
totalmin = "";
}
if (totalmin < 10) totalmin = "0" + totalmin;
scope.tform.total = total + ":" + totalmin;
};
_calctotal();
},
controller: function($scope) {
// console.log($scope);
},
templateUrl: "mytimepicker.html"
}
});

Try using ng-change instead of $watch, it's cleaner and easier to follow.

Its with your _calc function declaration without a var.
link: function(scope, $element, attr) {
console.log(scope);
scope.tform = scope.tmodel;
var _calctotal = function() {
var on = new Date(scope.tform.on);
var off = new Date(scope.tform.off);
var total = off.getHours() - on.getHours();
var totalmin = off.getMinutes() - on.getMinutes();
if (totalmin < 0) {
total = total - 1;
totalmin = totalmin * -1;
}
if (total < 0) {
total = "Invalid";
totalmin = "";
}
if (totalmin < 10) totalmin = "0" + totalmin;
scope.tform.total = total + ":" + totalmin;
};
scope.$watch('tform.on', function(newValue, oldValue) {
// console.log("calc on"+scope.ttitle);
_calctotal();
});
scope.$watch('tform.off', function(newValue, oldValue) {
// console.log("calc off");
_calctotal();
});
_calctotal();
},
Working sample on Plnkr

Related

Binding child element value from Angular directive

Binding values with $scope.minutes = 1 to ng-bind="minutes" not working when I add scope: {finishcallback: "&"}, to my directive.
I'm trying to implement a countdown timer with Angular directives but cannot set the value of the remaining minute and second to the child span element when I define a scope function in the directive.
<time id="countdown_{{order.Id}}" ng-if="order.StatusCode == 1" countdown="{{order.RemainingTimeToPrepareOrder}}" finishcallback="vm.countdownfinished(parameter)" callbackparameter="{{order.Id}}" countdownfinished="toggle()">
<b> <span class="value" ng-bind="minutes"></span> dakika <span class="value" ng-bind="seconds">--</span> saniye</b>
</time>
And here is my directive code.
function countdown() {
return {
restrict: 'A',
scope: {
finishcallback: "&"
},
controller: function ($scope, $attrs, $timeout) {
$attrs.$observe('countdown', function (value) {
var ds = new Date();
ds.setTime(value * 1000);
$scope.days = '-';
$scope.hours = $scope.minutes = $scope.seconds = '--';
$scope.timeout = $timeout(update, 1000);
function update() {
now = +new Date();
$scope.delta = Math.round((ds - now) / 1000);
if ($scope.delta >= 0) {
$timeout(update, 1000);
} else if ($attrs.countdownfinished) {
$scope.$apply($attrs.countdownfinished);
}
}
});
},
link: function ($scope, $element, $attrs) {
$scope.$watch('delta', function (delta) {
if (typeof delta === 'undefined') return;
if (delta < 0) {
delta = 0;
}
$scope.days = Math.floor(delta / 86400);
$scope.hours = forceTwoDigits(Math.floor(delta / 3600) % 24);
$scope.minutes = forceTwoDigits(Math.floor(delta / 60) % 60);
$scope.seconds = forceTwoDigits(delta % 60);
});
$scope.toggle = function () {
$scope.finishcallback({ parameter: $attrs.callbackparameter });
}
function forceTwoDigits(num) {
return String(num < 10 ? '0' + num : num);
}
}
}
}
All the functionality is working until I add finishcallback: "&" scope variable in my directive. I added this to enable custom function calls when the countdown finished. But when I add this my assignments like $scope.minutes stopped to change values in my spans.
How do I change span values dynamically even I define a scope in my directive ?
I'd recommend to just use a template:
function countdown($timeout) {
return {
restrict: 'A',
scope: {
finishcallback: "&"
},
template: `<b> <span class="value" ng-bind="minutes"></span> dakika <span class="value" ng-bind="seconds">--</span> saniye</b>`,
controller: function($scope, $attrs) {
$attrs.$observe('countdown', function(value) {
var ds = new Date();
ds.setTime(value * 1000);
$scope.days = '-';
$scope.hours = $scope.minutes = $scope.seconds = '--';
$scope.timeout = $timeout(update, 1000);
function update() {
now = +new Date();
$scope.delta = Math.round((ds - now) / 1000);
if ($scope.delta >= 0) {
$timeout(update, 1000);
} else if ($attrs.countdownfinished) {
$scope.$apply($attrs.countdownfinished);
}
}
});
},
link: function($scope, $element, $attrs) {
$scope.$watch('delta', function(delta) {
if (typeof delta === 'undefined') return;
if (delta < 0) {
delta = 0;
}
$scope.days = Math.floor(delta / 86400);
$scope.hours = forceTwoDigits(Math.floor(delta / 3600) % 24);
$scope.minutes = forceTwoDigits(Math.floor(delta / 60) % 60);
$scope.seconds = forceTwoDigits(delta % 60);
});
$scope.toggle = function() {
$scope.finishcallback({
parameter: $attrs.callbackparameter
});
}
function forceTwoDigits(num) {
return String(num < 10 ? '0' + num : num);
}
}
}
}
angular.module('app', [])
.controller('ctrl', function($scope, $interval) {
$scope.order = {
Id: 1,
StatusCode: 1,
RemainingTimeToPrepareOrder: Date.now() + 5 * 60 * 1000,
};
$scope.countdownfinished = function(parameter) {
console.log(parameter);
}
$scope.toggle = function() {
console.log("Toggle");
}
$interval(function() {
$scope.order.RemainingTimeToPrepareOrder -= 1000;
}, 1000);
})
.directive('countdown', countdown);
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.js"></script>
<div ng-app="app" ng-controller="ctrl">
<time id="countdown_{{order.Id}}" ng-if="order.StatusCode == 1" countdown="{{order.RemainingTimeToPrepareOrder}}" finishcallback="countdownfinished(parameter)" callbackparameter="{{order.Id}}" countdownfinished="toggle()">
</time>
</div>

Angular JS dual input Directive UpDate View Value

Hi I have a situation here,
I have created a dual input directive. Can you please help to in the below scenario.
When I change the model value via controller to undefined, the view values are not cleared. Here is the Codes,
My Dual Input Directive is as follows,
angular.module("awcQuoteBuy.directives")
.directive('dualInput', function($timeout, inputValidationService) {
return {
restrict: 'E',
templateUrl: 'app/partials/common/doubleInput.html',
scope: {
modelValue: '=',
size: '#',
fieldError: '#',
blurFn: '&loseFocus'
},
link: function postLink(scope, element, attrs, ctrl) {
scope.leftFocused = false;
scope.rightFocused = false;
scope.$watch('left', function(newVal, oldVal) {
if (newVal!==oldVal) {
var tmp = (newVal) ? inputValidationService.formatNumber(newVal+'') : '';
scope.modelValue = tmp + '|'+ scope.getOtherValue(scope.right);
}
});
scope.$watch('right', function(newVal, oldVal) {
if (newVal!==oldVal) {
var tmp = (newVal) ? inputValidationService.formatNumber(newVal+'') : '';
scope.modelValue = scope.getOtherValue(scope.left) + '|' + tmp;
}
});
scope.getOtherValue = function(value) {
return (value && value!=='') ? inputValidationService.formatNumber(value+'') : '';
};
//will set the value initially
$timeout(function() {
if (!scope.modelValue || scope.modelValue===null) {
scope.modelValue = '';
}
if (scope.modelValue!=='') {
var splitIndex = scope.modelValue.indexOf('|');
if (splitIndex>=0) {
var values = scope.modelValue.split('|');
scope.left = values[0];
scope.right = values[1];
} else {
scope.left = scope.modelValue;
}
}
});
/*
Below functions will add on-blur (lose focus) support to both fields.
*/
scope.focusLeft = function() {
scope.leftFocused = true;
};
scope.blurLeft = function() {
scope.leftFocused = false;
$timeout(function() {
if (!scope.rightFocused) {
scope.blurFn();
}
}, 100);
};
scope.focusRight = function() {
scope.rightFocused = true;
};
scope.blurRight = function() {
scope.rightFocused = false;
$timeout(function() {
if (!scope.leftFocused) {
scope.blurFn();
}
}, 100);
};
}
};
});
The HTML Piece of code is as follows,
<dual-input model-value="dualInput[$index]" ng-switch-when="DUAL_NUMBER" size="{{q.length}}"
field-error="{{q.invalid || (nextClicked && !validateGeneralQuestion(acc.memberId, q))}}" id="{{'gQDual'+$index}}"
lose-focus="saveGeneralAnswer(acc.memberId, q)"></dual-input>
In My Controller when I set the scope value to undefined or null, the entered values in the view is not cleared. Please help me here what I should do to clear this value
$scope.dualInput[$index]=undefined;
The directive itself has got the auto initialize feature. So I had to re render the directive whenever I wanted to reinitialize.
If you want to explicitly update user ctrl.$setViewvalue = ""

Interference between a countdown directive and an angular controller?

I have some extremely weird interference between two angular functions in my application that I cannot explain. The end result of the problem causes this to happen:
I have a countdown directive embedded in one of my pages which is also the domain of an angular controller. Here is the countdown directive:
(function() {
var app = angular.module('app');
app.directive('countdown', ['$interval', function($interval) {
return {
restrict: 'E',
scope: {
specificity: '=',
countdownTo: '=',
callback: '&?'
},
link: function($scope, elem, attrs) {
$scope.isLaunchExact = ($scope.specificity == 6 || $scope.specificity == 7);
$scope.$watch('specificity', function(newValue) {
$scope.isLaunchExact = (newValue == 6 || newValue == 7);
});
var countdownProcessor = function() {
var launchUnixSeconds = $scope.launchUnixSeconds;
var currentUnixSeconds = Math.floor(Date.now() / 1000);
if (launchUnixSeconds >= currentUnixSeconds) {
$scope.secondsAwayFromLaunch = launchUnixSeconds - currentUnixSeconds;
var secondsBetween = $scope.secondsAwayFromLaunch;
// Calculate the number of days, hours, minutes, seconds
$scope.days = Math.floor(secondsBetween / (60 * 60 * 24));
secondsBetween -= $scope.days * 60 * 60 * 24;
$scope.hours = Math.floor(secondsBetween / (60 * 60));
secondsBetween -= $scope.hours * 60 * 60;
$scope.minutes = Math.floor(secondsBetween / 60);
secondsBetween -= $scope.minutes * 60;
$scope.seconds = secondsBetween;
$scope.daysText = $scope.days == 1 ? 'Day' : 'Days';
$scope.hoursText = $scope.hours == 1 ? 'Hour' : 'Hours';
$scope.minutesText = $scope.minutes == 1 ? 'Minute' : 'Minutes';
$scope.secondsText = $scope.seconds == 1 ? 'Second' : 'Seconds';
} else {
}
if (attrs.callback) {
$scope.callback();
}
};
// Countdown here
if ($scope.isLaunchExact) {
$scope.launchUnixSeconds = moment($scope.countdownTo).unix();
$interval(countdownProcessor, 1000);
} else {
$scope.countdownText = $scope.countdownTo;
}
},
templateUrl: '/js/templates/countdown.html'
}
}]);
})();
Here is the Angular controller that is being interfered with:
(function() {
var app = angular.module('app', []);
//app.value('duScrollDuration', 1000);
app.controller("homeController", ['$scope', 'Statistic', function($scope, Statistic) {
$scope.statistics = [];
$scope.activeStatistic = false;
$scope.goToClickedStatistic = function(statisticType) {
history.replaceState('', document.title, '#' + statisticType);
$scope.activeStatistic = statisticType;
};
$scope.goToNeighborStatistic = function(index) {
if (index >= 0 && index < $scope.statistics.length) {
var stat = $scope.statistics[index];
history.replaceState('', document.title, '#' + stat.camelCaseType); // WhyIsThisFlashing
$scope.activeStatistic = stat.camelCaseType;
return stat.camelCaseType;
} else {
$scope.goHome();
}
};
$scope.goToFirstStatistic = function() {
};
$scope.goHome = function() {
history.replaceState('', document.title, window.location.pathname);
$scope.activeStatistic = false;
return 'home';
};
/*$window.on('scroll',
$.debounce(100, function() {
$('div[data-stat]').fracs('max', 'visible', function(best) {
$scope.activeStatistic($(best).data('stat'));
});
})
);*/
(function() {
laravel.statistics.forEach(function(statistic) {
$scope.statistics.push(new Statistic(statistic));
});
if (window.location.hash) {
$scope.activeStatistic = window.location.hash.substring(1);
}
})();
}]);
app.factory('Statistic', function() {
return function(statistic) {
var self = {};
self.changeSubstatistic = function(newSubstatistic) {
self.activeSubstatistic = newSubstatistic;
};
statistic.forEach(function(substatistic) {
if (!self.substatistics) {
self.substatistics = [];
self.activeSubstatistic = substatistic;
self.type = substatistic.type;
self.camelCaseType = self.type.replace(" ", "");
}
self.substatistics.push(substatistic);
});
return self;
}
});
})();
Each time my countdown directive's countdownProcessor function present in the $interval is run, goToNeighborStatistic in my angular controller is, somehow, called. I don't have a clue why.
If I step through my countdown directive, after stepping through the countdownProcessor function, an "Anonymous Script" named SCRIPT0 is called with the following contents:
"use strict";
var fn=function(s,l,a,i){var v0,v1,v2,v3=l&&('goToNeighborStatistic' in l),v4,v5,v6=l&&('\u0024index' in l);v2=v3?l:s;if(!(v3)){if(s){v1=s.goToNeighborStatistic;}}else{v1=l.goToNeighborStatistic;}if(v1!=null){ensureSafeFunction(v1,text);if(!(v6)){if(s){v5=s.$index;}}else{v5=l.$index;}v4=ifDefined(v5,0)-ifDefined(1,0);ensureSafeObject(v2,text);v0=ensureSafeObject(v2.goToNeighborStatistic(ensureSafeObject(ifDefined(v5,0)-ifDefined(1,0),text)),text);}else{v0=undefined;}return v0;};return fn;
Why is "goToNeighborStatistic" present in this? Why is this script even present? From there, goToNeighborStatistic() runs, at which point, the countdown is called again and everything repeats. What's going on here?
EDIT:
In Chrome, stepping through everything produces different results. At the end of the countdownProcessor function, stepping into produces this:

Angular js directive doesn't work on page load

I am new to angularjs the below directive is to support decimal and commas, it works fine when a change is made to the field how ever the data in the fields are not validated when the page loads
var app = angular.module('myApply.directives', [], function () {
});
app.directive('numericDecimalInput', function($filter, $browser, $locale,$rootScope) {
return {
require: 'ngModel',
priority: 1,
link: function($scope, $element, $attrs, ngModelCtrl) {
var replaceRegex = new RegExp($locale.NUMBER_FORMATS.GROUP_SEP, 'g');
var fraction = $attrs.fraction || 0;
var listener = function() {
var value = $element.val().replace(replaceRegex, '');
$element.val($filter('number')(value, fraction));
};
var validator=function(viewValue) {
ngModelCtrl.$setValidity('outOfMax', true);
ngModelCtrl.$setValidity(ngModelCtrl.$name+'Numeric', true);
ngModelCtrl.$setValidity('rangeValid', true);
if(!_.isUndefined(viewValue))
{
var newVal = viewValue.replace(replaceRegex, '');
var newValAsNumber = newVal * 1;
// check if new value is numeric, and set control validity
if (isNaN(newValAsNumber)) {
ngModelCtrl.$setValidity(ngModelCtrl.$name + 'Numeric', false);
} else
{
if (newVal < 0) {
ngModelCtrl.$setValidity(ngModelCtrl.$name + 'Numeric', false);
}
else {
newVal = newValAsNumber.toFixed(fraction);
ngModelCtrl.$setValidity(ngModelCtrl.$name + 'Numeric', true);
if (!(_.isNull($attrs.maxamt) || _.isUndefined($attrs.maxamt))) {
var maxAmtValue = Number($attrs.maxamt) || Number($scope.$eval($attrs.maxamt));
if (newVal > maxAmtValue) {
ngModelCtrl.$setValidity('outOfMax', false);
} else {
ngModelCtrl.$setValidity('outOfMax', true);
}
if(!(_.isNull($attrs.minamt) || _.isUndefined($attrs.minamt)))
{
var minAmtValue = Number($attrs.minamt)|| Number($scope.$eval($attrs.minamt));
if((newVal > maxAmtValue) || (newVal < minAmtValue)){
ngModelCtrl.$setValidity('rangeValid', false);
}
else
{
ngModelCtrl.$setValidity('rangeValid', true);
}
}
}
else if((!(_.isNull($attrs.minamt) || _.isUndefined($attrs.minamt))))
{
var minAmtValue = Number($attrs.minamt)|| Number($scope.$eval($attrs.minamt));
if(newVal < minAmtValue)
{
ngModelCtrl.$setValidity('outOfMin', false);
}
else
{
ngModelCtrl.$setValidity('outOfMin', true);
}
}
else {
ngModelCtrl.$setValidity('outOfMax', true);
}
}
}
return newVal;
}
};
// This runs when the model gets updated on the scope directly and keeps our view in sync
ngModelCtrl.$render = function() {
ngModelCtrl.$setValidity('outOfMax', true);
ngModelCtrl.$setValidity(ngModelCtrl.$name+'Numeric', true);
$element.val($filter('number')(ngModelCtrl.$viewValue, fraction));
};
$element.bind('change', listener);
$element.bind('keydown', function(event) {
var key = event.keyCode;
// If the keys include the CTRL, SHIFT, ALT, or META keys, home, end, or the arrow keys, do nothing.
// This lets us support copy and paste too
if (key == 91 || (15 < key && key < 19) || (35 <= key && key <= 40))
return;
});
$element.bind('paste cut', function() {
$browser.defer(listener);
});
ngModelCtrl.$parsers.push(validator);
ngModelCtrl.$formatters.push(validator);
}
};
});
Could some one please let me know as what I am missing .
Thanking you in advance.
Have you declared your app (doesn't show this in your code)? ie:
var app = angular.module('app', []);
Do you have ng-app in your HTML document anywhere?
<html lang="en-GB" ng-app="app">
Check out the documentation to get started with modules (Angular is very well documented, well worth reading): https://docs.angularjs.org/guide/module
app.directive('numericDecimalInput', function($filter, $browser, $locale,$rootScope) {
return {
require: 'ngModel',
priority: 1,
link: function($scope, $element, $attrs, ngModelCtrl) {
...
validator($element.val());
}
};
});

AngularJS - Pull down to refresh module not working

I want to implement "pull down to refresh" in Angular. A lot of the JS libraries don't work with Angular, but I found this one:
https://github.com/mgcrea/angular-pull-to-refresh
However, it doesn't work. I see the text "pull down to refresh" on top, so directive is installed properly. But that's it, nothing happens when I scroll down.
var feedApp = angular.module("feedApp", ['mgcrea.pullToRefresh']);
....
<div id="feed-list-content" data-role="content">
<ul class="feed-list" data-role="listview" pull-to-refresh="onReload()">
<li ng-repeat="summaryItem in summaryItems | orderBy: 'created':true" class="feed-item">
....
</li>
</ul>
</div>
Same questions here: Pull to refresh in Angular Js and here: Angular JS - Pull to refresh
but no solution to my problem.
The onReload method is neither called.
I'm testing on both Android Emulator and Android device. Can't test on iOS, is it caused by Android?
I modified this plugin and solved my problem.
Try like this:
directive("pullToRefresh", function ($compile, $timeout, $q) {
return {
scope: true,
restrict: "A",
transclude: true,
template: '<div class="pull-to-refresh"><i ng-class="icon[status]"></i> <span ng-bind="text[status]"></span></div><div ng-transclude></div>',
compile: function compile(elem, attrs, transclude) {
return function postLink(scope, iElem, iAttrs) {
var body = $('body');
var scrollElement = iElem.parent();
var ptrElement = window.ptr = iElem.children()[0];
scope.text = {
pull: "pull to refresh",
release: "release to refresh",
loading: "refreshing..."
};
scope.icon = {
pull: "fa fa-arrow-down",
release: "fa fa-arrow-up",
loading: "fa fa-refresh fa-spin"
};
scope.status = "pull";
var shouldReload = false;
var setScrolTop = false;
var setStatus = function (status) {
shouldReload = status === "release";
scope.$apply(function () {
scope.status = status;
});
};
var touchPoint = 0;
iElem.bind("touchmove", function (ev) {
var top = body.scrollTop();
if (touchPoint === 0) touchPoint = ev.touches[0].clientY;
var diff = ev.touches[0].clientY - touchPoint;
if (diff >= 130) diff = 130;
if (diff < 80 && shouldReload) {
setStatus("pull");
setScrolTop = true;
}
if (top <= 0) {
scrollElement[0].style.marginTop = diff + "px";
if (diff > 80 && !shouldReload) setStatus("release");
}
});
iElem.bind("touchend", function (ev) {
if (setScrolTop) {
setScrolTop = false;
body.scrollTop(0);
}
if (!shouldReload) {
touchPoint = 0;
scrollElement[0].style.marginTop = "0px";
return;
}
ptrElement.style.webkitTransitionDuration = 0;
ptrElement.style.margin = '0 auto';
setStatus("loading");
var start = +new Date();
$q.when(scope.$eval(iAttrs.pullToRefresh))
.then(function () {
var elapsed = +new Date() - start;
$timeout(function () {
body.scrollTop(0);
touchPoint = 0;
scrollElement[0].style.marginTop = "0px";
ptrElement.style.margin = "";
ptrElement.style.webkitTransitionDuration = "";
scope.status = "pull";
}, elapsed < 400 ? 400 - elapsed : 0);
});
});
scope.$on("$destroy", function () {
iElem.unbind("touchmove");
iElem.unbind("touchend");
});
}
}
};
});

Categories

Resources