AngularJS browser autofill workaround revisited

在經歷過了 AngularJS browser autofill workaround by using a directive 之後過了幾週,有一天我嘗試對著 login 用的表單亂來(?),發現上的 workaround 其實只能針對在頁面一載入時的自動填入,如果是先清除它,在經過人為的操作之後的自動填入,還是都抓不到… (畢竟 $timeout 只用了一次當然…)

revisit

既然一次的 $timeout 不夠用,而現在要解決的情況就是它「隨時」會在 AngularJS 預設無法察覺的情況下被填入資料,我想最直覺的選擇就是改跑 interval 了。

$interval

在 AngularJS 1.2.0 之後加入了 $interval 這個 service,使用上要注意的是它預設每一個 tick 都會觸發一次 $digest,而我現在其實並不希望每個 tick 都觸發,而是在真的有被自動填入之後。

因此這邊在 $interval 的第四個參數帶入 false,讓它不會自動做 $apply() 的動作,靠我判斷後再決定是否 $apply()

/* file: auto-fill-sync.js */

app
.directive('autoFillSync', function($interval) {
  return {
    require: 'ngModel',
    link: function(scope, elem, attrs, ngModel) {
      var INTERVAL_DELAY = 100;
      
      var intervalPromise;
      intervalPromise = $interval(function () {
        var newVal = elem.val();
        if (ngModel.$viewValue !== newVal) {
          scope.$apply(function () {
            ngModel.$setViewValue(newVal);
          });
        }
      }, INTERVAL_DELAY, 0, false);

      elem.bind('$destroy', function () {
        $interval.cancel(intervalPromise);
      });
    }
  };
});

$window.setInterval

其實應該很多人已上線的產品都還沒有升到 1.2.x (或著還在 1.0.x),在沒有 $interval 可以用的情況下,用 $window.setInterval 來擋一下也是合情合理的。

/* file: auto-fill-sync.js */

.directive('autoFillSync', function($window) {
  return {
    require: 'ngModel',
    link: function(scope, elem, attrs, ngModel) {
      var INTERVAL_DELAY = 100;

      var intervalHandle;
      intervalHandle = $window.setInterval(function () {
        var newVal = elem.val();
        if (ngModel.$viewValue !== newVal) {
          scope.$apply(function () {
            ngModel.$setViewValue(newVal);
          });
        }
      }, INTERVAL_DELAY);

      elem.bind('$destroy', function () {
        $window.clearInterval(intervalHandle);
      });
    }
  };
});

後記

自動完成實在太方便了… 請務必一定要讓你的 AngularJS app 支援啊!