watch expression new value always equals to old value - angularjs

i'm using two AngularJS Bootstrap-ui Datepicker directives in a single controller.
The code is very simple, the view :
<div class="row">
<div class="col-md-6">
<p class="input-group">
<input type="text" class="form-control" datepicker-popup="dd-MM-yyyy" ng-model="startDate" is-open="startDateOpened" min="minDate" max="'2015-06-22'" ng-required="true" close-text="Close" />
<span class="input-group-btn">
<button class="btn btn-default" ng-click="open($event, 'startDateOpened')"><i class="glyphicon glyphicon-calendar"></i></button>
</span>
</p>
</div>
<div class="col-md-6">
<p class="input-group">
<input type="text" class="form-control" datepicker-popup="dd-MM-yyyy" ng-model="endDate" is-open="endDateOpened" min="minDate" max="'2015-06-22'" ng-required="true" close-text="Close" />
<span class="input-group-btn">
<button class="btn btn-default" ng-click="open($event, 'endDateOpened')"><i class="glyphicon glyphicon-calendar"></i></button>
</span>
</p>
</div>
</div>
The controller (part of it).
var now = new Date();
var startDate = $scope.startDate = new Date(now.getFullYear(), now.getMonth(), 1); // beginning of month
var endDate = $scope.endDate = now;
$scope.$watchCollection('[startDate, endDate]', function (newVals, oldVals) {
if(!(newVals[0] === oldVals[0] && newVals[1] === oldVals[1])){
console.log('old values', oldVals);
console.log('new values', newVals);
print();
}
});
function print() {
console.log('startDate !', startDate);
console.log('endDate !', endDate);
}
$scope.open = function($event, name) {
$event.preventDefault();
$event.stopPropagation();
$scope[name] = true;
};
I am assigning two models - startDate and endDate. Everything looks like it is working fine, when I choose a date in either of the date pickers $scope.$watchCollection() callback is fired up but oldVals is always eqaul to newVals so the model doesn't really change (even though the change event listener is fired).
What am I doing wrong ? I'm guessing it's a lack of understanding something basic :)
Thanks
EDIT
#Maxim's answer of using deep watch did solve my problem of not going into the if statement. Now my other problem is that print function inside the callback always prints the old values of startDate and endDate

Try $watch with flag true aka deep watch:
$scope.$watch('[startDate, endDate]', function (newVals, oldVals) {
if(!(newVals[0] === oldVals[0] && newVals[1] === oldVals[1])){
console.log('old values', oldVals);
console.log('new values', newVals);
startDate = newVals[0];
endDate = newVals[1];
}
}, true);
Plunker

Related

Date picker validation in angularJS not working

I am trying to implement validations for my date time picker. I have a 'From' date picker and a 'To' datepicker in an Angular partial view. I want the 'From' date picker to display error message if a past date is selected and the 'To' date picker should display error message if the selected date is before the 'From' date. The error messages are supposed to appear on selection of the date.
My HTML is :
<div>
<form id="edit-profile" novalidate name="editReservationForm" autocomplete="off" class="form-horizontal">
<fieldset>
<div class="control-group">
<label class="control-label" for="reservation.reservedFrom">Reserved From<sup>*</sup></label>
<div class="controls input-group date" data-provide="datepicker">
<input type="text" class="span4" style="width:150px" name="reservedFrom" placeholder="Reserved From" ng-model="reservation.reservedFrom"
validator="required" required-error-message="Date is required" valid-method="watch" id="startDate" />
<div class="input-group-addon">
<span class="glyphicon glyphicon-th"></span>
</div>
</div> <!-- /controls -->
</div> <!-- /control-group -->
<div class="control-group">
<label class="control-label" for="reservation.reservedTill">Reserved Till<sup>*</sup></label>
<div class="controls input-group date" data-provide="datepicker">
<input type="text" style="width:150px" class="span4" name="reservedTill" placeholder="Reserved Till" ng-model="reservation.reservedTill"
validator="required" required-error-message="Date is required" valid-method="checkErr" id="endDate" ng-change='checkErr()' />
<div class="input-group-addon">
<span class="glyphicon glyphicon-th"></span>
</div>
<span>{{errMessage}}</span>
</div> <!-- /controls -->
</div> <!-- /control-group -->
</fieldset>
</form>
</div>
Controller :
myApp.controller('editReservationController', ['$scope', '$filter', 'reservationResolved', 'pocResolved', 'accountResolved', 'reservationServices', '$location', '$state',
function ($scope, $filter, reservationResolved, pocResolved, accountResolved, reservationServices, $location, $state) {
$scope.reservation = new Object();
$scope.accounts = accountResolved.data;
$scope.pocs = pocResolved.data;
$scope.reservation.employee = reservationResolved.data;
$scope.updateReservation = function () {
if ($scope.editReservationForm.$valid) {
//TODO: fix it properly
$scope.reservation.reservedTill = '';
$scope.reservation.reservedFrom = '';
$scope.reservation.reservedFrom = $('#startDate').val();
$scope.reservation.reservedTill = $('#endDate').val();
reservationServices.updateReservation($scope.reservation).then(function (result) {
$scope.data = result.data;
if (!result.data.error) {
$state.transitionTo('employeeTalentPool', {
id: $state.params.id
});
}
});
}
};
$scope.cancel = function () {
$location.path("/reservations");
};
$scope.checkErr = function () {
var startDate = new Date($scope.reservation.reservedFrom),
endDate = new date($scope.reservation.reservedTill);
$scope.errMessage = '';
if (startDate < new Date()) {
$scope.errMessage = 'Start Date should be greater than or equal today';
return false;
}
if (new Date(endDate) < new Date()) {
$scope.errMessage = 'End Date should be greater than or equal today';
return false;
}
if (endDate < startDate) {
$scope.errorMsg = "End must be after start";
return false;
}
return true;
};
}]);
I am totally new to Angular and I'm trying to understand it by doing projects. Can anyone have a check and provide a solution?
Thanks in advance...
A different approach without displaying any error message and satisfying selection criteria as mentioned in problem statement
Here is the plunker of working solution bit slightly different from your implementation,I've used bootstrap datepicker for this example which is almost similar to datetimepicker. Hope this will give you an understanding.
In the controller we can control when and what from and to dates should be disabled on their corresponding selection.Using minDate provided by datepicker we can change the min date of To date field to From date's.
By doing this we can eliminate the display of error message and which will also satisfy your selection criteria of From & To dates.
angular.module('ui.bootstrap.demo', ['ngAnimate', 'ui.bootstrap']);
angular.module('ui.bootstrap.demo').controller('DatepickerPopupDemoCtrl', function($scope) {
$scope.datePicker = {};
$scope.start = new Date();
$scope.end = new Date();
$scope.datePicker.minStartDate = new Date();
// $scope.datePicker.maxStartDate = $scope.end;
$scope.datePicker.minEndDate = $scope.start;
// $scope.datePicker.maxEndDate = $scope.end; //fixed date same as $scope.maxStartDate init value
// watcher to watch the "From" date and set the min date for 'To' datepicker
$scope.$watch('start', function(v) {
$scope.datePicker.minEndDate = v;
$scope.dateOptions2.minDate = v;
});
$scope.dateOptions1 = {
//dateDisabled: disabled,
formatYear: 'yyyy',
// maxDate: $scope.datePicker.maxStartDate,
minDate: $scope.datePicker.minStartDate,
startingDay: 1
};
$scope.dateOptions2 = {
//dateDisabled: disabled,
formatYear: 'yyyy',
// maxDate: $scope.datePicker.maxEndDate,
minDate: $scope.datePicker.minEndDate,
startingDay: 1
};
// Disable weekend selection
function disabled(data) {
var date = data.date,
mode = data.mode;
return mode === 'day' && (date.getDay() === 0 || date.getDay() === 6);
}
$scope.open1 = function() {
$scope.popup1.opened = true;
};
$scope.open2 = function() {
$scope.popup2.opened = true;
};
$scope.formats = ['dd.MM.yyyy'];
$scope.format = $scope.formats[0];
$scope.altInputFormats = ['M!/d!/yyyy'];
$scope.popup1 = {
opened: false
};
$scope.popup2 = {
opened: false
};
});
In your HTML you can display like below
<div ng-controller="DatepickerPopupDemoCtrl">
<h5>From Date</h5>
<p class="input-group">
<input type="text"
class="form-control"
uib-datepicker-popup="{{format}}"
ng-model="start"
is-open="popup1.opened"
datepicker-options="dateOptions1"
close-text="Close"
readonly="true" />
<span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="open1()">
<i class="glyphicon glyphicon-calendar"></i>
</button>
</span>
</p>
<hr>
<h5>To Date</h5>
<p class="input-group">
<input type="text"
class="form-control"
uib-datepicker-popup="{{format}}"
ng-model="end"
is-open="popup2.opened"
datepicker-options="dateOptions2"
close-text="Close"
readonly="true"/>
<span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="open2()">
<i class="glyphicon glyphicon-calendar"></i>
</button>
</span>
</p>
</div>

Datepicker validation not working

I have added a datepicker to a popup in my application which works fine, But when I try to submit the form I am not able to save the entries .My form validator returns false even though there is a valid date in the input text, But if I change the date then form validator returns true..
This is my code
<p class="input-group">
<input type="text" ng-click="open($event)" class="form-control registration-input"
datepicker-popup="{{format}}" show-button-bar="false"
ng-model="CompanyData.dateOfRegistration" name="dateOfRegistration"
is-open="opened" datepicker-options="dateOptions" /> <span
class="input-group-btn">
<button type="button" class="btn btn-default"
ng-click="open($event)">
<i class="glyphicon glyphicon-calendar"></i>
</button>
</span>
</p>
Below is the js code
// Disable weekend selection
$scope.disabled = function(date, mode) {
return ( mode === 'day' && ( date.getDay() === 0 || date.getDay() === 6 ) );
};
$scope.dates = [{date:'01-05-2001'}, {date:'05-05-2014'}, {date:'10-11-2008'}]
$scope.open = function($event) {
$event.preventDefault();
$event.stopPropagation();
$scope.opened = true;
};
$scope.dateOptions = {
formatYear: 'yy',
startingDay: 1,
showWeeks:'false'
};
$scope.format = 'dd-MMMM-yyyy'
Can anyone tell me what shuld I do in order to make this work..
My form validator returns false even though there is a valid date in the input text ?
It's because of ngModel CompanyData.dateOfRegistration is not set initially and it is undefined which is false.
Even there valid date in datepicker but because ngModel is not initialised it is return false;
Set the CompanyData.dateOfRegistration to valid date.

UI Boostrap min-date and max-dates not working in ui-bootstrap 1.3.3

This question has been posted a couple of times before, but the issues seems to be specific to different releases of ui-bootstrap-tpls.js. As mentioned in the title, I am using version 1.3.3, and I simple can't get min-date and max-dates to work. My code can be seen in the following; https://plnkr.co/edit/Qr7PREKnNc4b8tfxSNzT?p=preview.
<div class="input-group">
<input type="text" class="form-control" uib-datepicker-popup="{{format}}" ng-model="dt" min-date="minDate" max-date="maxDate" is-open="popup1.opened" datepicker-options="dateOptions" ng-required="true" close-text="Close" alt-input-formats="altInputFormats"
/>
<span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="open1()"><i class="glyphicon glyphicon-calendar"></i></button>
</span>
</div>
In my controller, I have the following bits of code;
//set min and max dates
$scope.minDate = function() {
$scope.minDate = new Date();
};
$scope.minDate ();
$scope.maxDate = function() {
$scope.maxDate = new Date();
};
$scope.maxDate ();
I have forked your plnkr
https://plnkr.co/edit/OEBAS07JS8rDwXBNeoNE?p=preview
$scope.dateOptions.minDate = new Date();
This plnkr shows the fixed thing.
You basically need to set the $scope.dateOptions.minDate to new Date. I have commented your toggle function as your inlineOptions.minDate for never set to null, so it never got initialized to the new Date()
You can do the same for max date.

Angular UI datepicker popup open without ng-click

According to official Angular UI documentation for datepicker users in popup style I need to create additional button with ng-click event on it to change $scope property which was binded to is-open attribute like so:
<p class="input-group">
<input type="text" class="form-control" uib-datepicker-popup="{{format}}" ng-model="dt" is-open="popup1.opened" min-date="minDate" max-date="maxDate" datepicker-options="dateOptions" date-disabled="disabled(date, mode)" ng-required="true" close-text="Close" alt-input-formats="altInputFormats" />
<span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="open1()"><i class="glyphicon glyphicon-calendar"></i></button>
</span>
</p>
In my application it is possible to have more than 10 such datepickers per view so I need to implement property for is-open attribute per each.
Is there any way to open datepicker popup without is-open attribute?
If you have +10 datepickers and repeat the same markup over and over, and need to create $scope functions without any real purpose - then it is almost screaming for a directive to do the trivial tasks! The markup you are repeating can be placed in a template :
<script type="text/ng-template" id="dateAutomater.html">
<input type="text" class="form-control"/>
<span class="input-group-btn">
<button type="button" class="btn btn-default">
<i class="glyphicon glyphicon-calendar"></i>
</button>
</span>
</script>
The directive (the very basics) could look like this :
.directive('dateAutomater', ['$compile', function($compile) {
return {
transclude: true,
templateUrl: 'dateAutomater.html',
restrict: 'AE',
link: function ($scope, element, attrs) {
$scope.dateInfo = $scope.dateInfo || {};
var dateInfo = $scope.dateInfo,
input = element.find('input'),
button = element.find('button'),
name = input.name || 'date'+Object.keys($scope.dateInfo).length,
info = {
open: false,
click: function() {
this.open = true
}
}
dateInfo[name] = info;
input.attr('ng-model', attrs.dateAutomater);
input.attr('uib-datepicker-popup', 'dd-MMMM-yyyy');
input.attr('is-open', 'dateInfo[\"'+name+'\"].open')
button.attr('ng-click', 'dateInfo[\"'+name+'\"].click()');
$compile(element.contents())($scope);
}
}
}])
It simply takes the model as argument, injects the markup from the template and bind the important variable is-open and ng-click function to a self maintained object, $scope.dateInfo. Usage
<p class="input-group" date-automater="dt"></p>
<p class="input-group" date-automater="date"></p>
<p class="input-group" date-automater="yesterDay"></p>
...
demo -> http://plnkr.co/edit/H6hgYdF420R4IKdjCBGM?p=preview
Now expand the directive / template to set other default properties you want on the datepicker, like min-date and so on.

Angularjs min-date max-date validation messages

Angularjs - bootstraup UI- trying to place min-date max-date validation error messages.
but it is not working, not showing any min-date or max-date validation message.
I also tried form.offerEndDate.$error.min or form.offerEndDate.$error.max still didn't work
<p class="input-group">
<input type="text" class="form-control" datepicker-popup="{{format}}" ng-model="form.offerEndDate" is-open="opened" min-date="minDate" max-date="'2015-06-22'" datepicker-options="dateOptions" ng-required="true" close-text="Close" />
<span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="open($event)"><i class="glyphicon glyphicon-calendar"></i></button>
</span>
</p>
<div ng-show="form.offerEndDate.$dirty && form.offerEndDate.$invalid">
<span class="alert-danger" ng-show="form.offerEndDate.$error.required">
Offer End Date is required.
</span>
<span class="alert-danger" ng-show="form.offerEndDate.$error.minDate">
Min Date Error.
</span>
<span class="alert-danger" ng-show="form.offerEndDate.$error.maxDate">
Max Date Error.
</span>
</div>
Here's finally my workaround for this problem:
<div class="input-group">
<input name="endDate" class="form-control" type="text" is-open="openedEnd"
datepicker-popup="shortDate" ng-focus="openedEnd=true"
date-disabled="disabledEnd(date,mode)"
datepicker-options="dateOptions"
ng-model="offre.endDate" required="" min="endMinDate" max="endMaxDate"
ts-valid-date="" />
<span class="input-group-btn">
<button type="button" class="btn btn-default btn-default-bordered" ng-click="openEnd($event)">
<i class="fa fa-calendar"></i>
</button>
</span>
</div>
<div class="alert alert-danger"
ng-messages="formOffre.endDate.$error"
ng-if="formOffre.endDate.$dirty || vm.showOffreValidation">
<div ng-message="required">#L("RemplissezLaDateDeFin")</div>
<div ng-message="date">#L("DateIncorrecte")! {{vm.endDateInvalidMessage}} </div>
<div ng-message="invalidDate">#L("DateIncorrecte")! {{vm.startDateInvalidMessage}}</div>
<div ng-messages-include="error-messages"></div>
ts-valid-date is a custom directive:
angular.module('app')
.directive('tsValidDate', ['$parse', function ($parse) {
return {
require: 'ngModel',
link: function (scope, elem, attrs, ctrl) {
scope.$watch(function () {
return $parse(attrs.min)(scope) <= ctrl.$modelValue && $parse(attrs.max)(scope) >= ctrl.$modelValue;
}, function (currentValue) {
ctrl.$setValidity('invalidDate', currentValue);
});
}
};
}]);
This gives me the wanted solution, invalid the input and show ng-message with the minDate and maxDate allowed
Here's the javascript part of html:
$scope.dateOptions = {
showButtonBar: false,
startingDay: 1
};
$scope.startMinDate = new Date();
$scope.startMaxDate = new Date(new Date().getFullYear() + 5, new Date().getMonth(), new Date().getDate());
$scope.endMinDate = new Date();
$scope.endMaxDate = new Date(new Date().getFullYear() + 5, new Date().getMonth(), new Date().getDate());
vm.startDateInvalidMessage = abp.utils.formatString($scope.dateInvalidMessage,
$filter('date')($scope.startMinDate), $filter('date')($scope.startMaxDate));
vm.endDateInvalidMessage = abp.utils.formatString($scope.dateInvalidMessage,
$filter('date')($scope.endMinDate), $filter('date')($scope.endMaxDate));
$scope.disabledStart = function (date, mode) {
return date <= $scope.startMinDate || date > $scope.startMaxDate;
};
$scope.disabledEnd = function (date, mode) {
return date < $scope.endMinDate || date >= $scope.endMaxDate;
};
$scope.verifyDate = function () {
if ($scope.offre.startDate) {
$scope.endMinDate = $scope.offre.startDate;
vm.startDateInvalidMessage = abp.utils.formatString($scope.dateInvalidMessage,
$filter('date')($scope.startMinDate), $filter('date')($scope.startMaxDate));
vm.endDateInvalidMessage = abp.utils.formatString($scope.dateInvalidMessage,
$filter('date')($scope.endMinDate), $filter('date')($scope.endMaxDate));
}
if ($scope.offre.endDate) {
$scope.startMaxDate = $scope.offre.endDate;
vm.startDateInvalidMessage = abp.utils.formatString($scope.dateInvalidMessage,
$filter('date')($scope.startMinDate), $filter('date')($scope.startMaxDate));
vm.endDateInvalidMessage = abp.utils.formatString($scope.dateInvalidMessage,
$filter('date')($scope.endMinDate), $filter('date')($scope.endMaxDate));
}
};
$scope.$watch('offre.endDate', function () {
$scope.verifyDate();
});
$scope.$watch('offre.startDate', function () {
$scope.verifyDate();
});
$scope.openStart = function ($event) {
$event.preventDefault();
$event.stopPropagation();
$scope.openedStart = true;
};
$scope.openEnd = function ($event) {
$event.preventDefault();
$event.stopPropagation();
$scope.openedEnd = true;
};
It can be connected to this issue.
Try to change min-date to min. and max-date to max.
Also keep in mind this issue. So you will be still able to type in manually date in the input field, though your calender will be disabled.

Resources