Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> JavaScript

AngularJS và HTML5 date input: Hiển thị ngày readable trên Firefox


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]));