Vấn đề
Firefox (trước phiên bản 57) không hỗ trợ <input type="date">. Nó render thành text input thường, và ng-model nhận giá trị string thay vì Date object. Điều này gây lỗi khi binding ngược (two-way binding).
Giải pháp: Đồng bộ 2 biến (Date object + String)
<!DOCTYPE html>
<html ng-app="dateApp">
<head>
<meta charset="UTF-8">
<title>AngularJS Date Input Fix</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script>
<style>
.form-group { margin-bottom: 1rem; }
label { display: block; margin-bottom: 0.5rem; }
input { padding: 0.5rem; border: 1px solid #ccc; border-radius: 4px; }
.debug { background: #f5f5f5; padding: 1rem; margin-top: 1rem; font-family: monospace; white-space: pre; }
</style>
</head>
<body ng-controller="MainCtrl">
<div class="form-group">
<label>Ngày (type=date):</label>
<input type="date" ng-model="dateString" />
</div>
<div class="form-group">
<label>Fallback text input (Firefox):</label>
<input type="text" ng-model="dateString" placeholder="YYYY-MM-DD" />
</div>
<div class="debug">
date (Date object): {{ date | date:'fullDate' }}
dateString (model): {{ dateString }}
raw date: {{ date }}
</div>
<script>
angular.module('dateApp', [])
.controller('MainCtrl', ['$scope', '$filter', function($scope, $filter) {
// Date object thật sự (dùng cho logic nghiệp vụ)
$scope.date = new Date();
// String model cho input (YYYY-MM-DD)
$scope.dateString = $filter('date')($scope.date, 'yyyy-MM-dd');
// Sync: Date object → String (cho input hiển thị)
$scope.$watch('date', function(newDate) {
if (newDate) {
$scope.dateString = $filter('date')(newDate, 'yyyy-MM-dd');
}
});
// Sync: String → Date object (khi user nhập/chọn)
$scope.$watch('dateString', function(newString) {
if (newString && /^\d{4}-\d{2}-\d{2}$/.test(newString)) {
// Parse an toàn: tránh timezone issue
var parts = newString.split('-');
$scope.date = new Date(parts[0], parts[1] - 1, parts[2]);
}
});
}]);
</script>
</body>
</html>
Giải pháp hiện đại hơn (AngularJS 1.3+): NgModelController + $parsers/$formatters
angular.module('dateApp')
.directive('dateInput', function($filter) {
return {
require: 'ngModel',
link: function(scope, element, attrs, ngModelCtrl) {
// Format: Date object → String (YYYY-MM-DD) cho view
ngModelCtrl.$formatters.push(function(date) {
return date ? $filter('date')(date, 'yyyy-MM-dd') : '';
});
// Parse: String → Date object cho model
ngModelCtrl.$parsers.push(function(value) {
if (!value || !/^\d{4}-\d{2}-\d{2}$/.test(value)) {
return null;
}
var parts = value.split('-');
// Tạo Date ở local timezone (tránh UTC shift)
return new Date(parts[0], parts[1] - 1, parts[2]);
});
// Validation
ngModelCtrl.$validators.dateFormat = function(modelValue) {
return !modelValue || modelValue instanceof Date;
};
}
};
});
Sử dụng:
<input type="date" ng-model="myDate" date-input />
Phát hiện hỗ trợ type="date"
function supportsDateInput() {
var input = document.createElement('input');
input.setAttribute('type', 'date');
var notSupported = input.type === 'text';
return !notSupported;
}
// Sử dụng
if (!supportsDateInput()) {
// Load polyfill hoặc hiển thị fallback UI
document.documentElement.classList.add('no-date-input');
}
Polyfill khuyên dùng
- Webshims Lib - Polyfill đầy đủ HTML5 forms
- Angular UI Bootstrap Datepicker - Directive
<datepicker>thay thế - Flatpickr - Nhẹ, không dependency, có Angular directive
Lưu ý quan trọng về Timezone
Khi parse YYYY-MM-DD sang Date:
// ❌ SAI: new Date('2024-01-15') → Tạo Date ở UTC midnight → có thể lệch ngày ở timezone âm
// ✅ ĐÚNG: new Date(2024, 0, 15) → Tạo Date ở local timezone (month 0-based)
var parts = '2024-01-15'.split('-');
var date = new Date(parseInt(parts[0]), parseInt(parts[1]) - 1, parseInt(parts[2]));