ディレクティブを使用したAngularJSブラウザーの自動入力回避策


111

AngularJSでフォームを送信し、ブラウザーにパスワードを記憶する機能を使用し、その後のログイン試行で、ブラウザーにログインフォームにユーザー名とパスワードを入力$scopeさせる場合、自動入力に基づいてモデルは変更されません。

私が見つけた唯一の汚いハックは、次のディレクティブを使用することです:

app.directive("xsInputSync", ["$timeout" , function($timeout) {
    return {
        restrict : "A",
        require: "?ngModel",
        link : function(scope, element, attrs, ngModel) {
            $timeout(function() {
                if (ngModel.$viewValue && ngModel.$viewValue !== element.val()) {
                    scope.apply(function() {
                        ngModel.$setViewValue(element.val());
                    });
                }
                console.log(scope);
                console.log(ngModel.$name);
                console.log(scope[ngModel.$name]);
            }, 3000);
        }
    };
}]);

問題はngModel.$setViewValue(element.val());、がelement.val()戻り値に基づいてモデルもビューも変更しないことです。どうすればそれを達成できますか?


このコードは、最初は赤面で大丈夫に見えますが、残りの部分(マークアップなど)はどこですか?私たちが見ることができるフィドルやプランクはありますか?
ベンレッシュ2013

plunkは次のとおりです。plnkr.co / edit / CHrBAVU9Ycl2ex2DRr6R iframeで実行されるため、plunkerで直接動作するかどうかはわかりません。
lucassp 2013

1
あなたはscope。$ applyをangularの$ timeout内に置く必要はありません。ネイティブのwindow.setTimeout内で必要になる場合があります。しかし、angularのものを使用することをお勧めします。
gorpacrate 2013年

1
この問題については、Angular dev tboschからの「公式」ポリフィル修正があります。詳細については、下記の回答stackoverflow.com/a/25687396/3009639をご覧ください。
orszaczky 2014

残念ながら、「公式」の修正は放棄され、多くのブラウザでは機能しません。問題は報告されていますが対処されていないため、検索は続行されます...
cyberwombat

回答:


45

どうやらこれはAngularの既知の問題で、現在オープンしています

あなたがしようとしているようなある種の回避策以外に、あなたがここで何ができるかわかりません。順調に進んでいるようです。私のブラウザーがあなたのplunkのパスワードを覚えようとすることができなかったので、これが機能するかどうかはわかりませんが、見てみましょう:

app.directive('autoFillSync', function($timeout) {
   return {
      require: 'ngModel',
      link: function(scope, elem, attrs, ngModel) {
          var origVal = elem.val();
          $timeout(function () {
              var newVal = elem.val();
              if(ngModel.$pristine && origVal !== newVal) {
                  ngModel.$setViewValue(newVal);
              }
          }, 500);
      }
   }
});
<form name="myForm" ng-submit="login()">
   <label for="username">Username</label>
   <input type="text" id="username" name="username" ng-model="username" auto-fill-sync/><br/>
   <label for="password">Password</label>
   <input type="password" id="password" name="password" ng-model="password" auto-fill-sync/><br/>
   <button type="submit">Login</button>
</form>

私はあなたがあなたのアプローチを少し単純化する必要があるだけだと思います。私が間違いなくお勧めすることの1つは、ngModel.$pristine貧弱なユーザーの入力を上書きしていないことを確認することです。また、おそらく3秒は長すぎます。ところで、$ timeoutで$ apply()を呼び出す必要はありません。これにより、$ digestが自動的にキューに入れられるはずです。

本当のキャッチ:あなたのブラウザは実行にAngularを打ち負かしますか?私のブラウザはどうですか?

これはおそらく勝利できない戦争であり、それがAngular(またはKnockout)がそれを容易に解決できなかった理由です。ディレクティブが最初に実行されたときの入力のデータの状態は保証されていません。Angularの初期化時でもそうではありません...だから、解決するのは難しい問題です。


1
$ pristineの良い点。まあ、3秒はテストのためだけにあります。「パスワードの保存」ダイアログはSafariで機能するようです。それは私が調査しなければならない別の問題です。
lucassp 2013

ローカルストレージで彼をだますだけです。:) ...私はそれについて考え続けます。しかし、双方向のバインディングで自動入力を機能させることの実現可能性については疑問があります。
Ben Lesh 2013

はい。ただし、ディレクティブが機能するようにモデルを更新できるようにする必要があります。
lucassp 2013

1
私はlocalStorageについて冗談を言っていましたが、パスワードをそこに保持したくないでしょう。
Ben Lesh 2013

1
この自動入力はユーザーによっていつでもトリガーされるため、これはSafariおよびクレジットカードの自動入力では機能しません
ダラスクラーク

35

ここに提示された他のソリューションよりもはるかにハッキングが少なく、意味的に適切なAngularJSのソリューションがあります:http : //victorblog.com/2014/01/12/fixing-autocomplete-autofill-on-angularjs-form-submit/

myApp.directive('formAutofillFix', function() {
  return function(scope, elem, attrs) {
    // Fixes Chrome bug: https://groups.google.com/forum/#!topic/angular/6NlucSskQjY
    elem.prop('method', 'POST');

    // Fix autofill issues where Angular doesn't know about autofilled inputs
    if(attrs.ngSubmit) {
      setTimeout(function() {
        elem.unbind('submit').submit(function(e) {
          e.preventDefault();
          elem.find('input, textarea, select').trigger('input').trigger('change').trigger('keydown');
          scope.$apply(attrs.ngSubmit);
        });
      }, 0);
    }
  };
});

次に、ディレクティブをフォームに添付するだけです。

<form ng-submit="submitLoginForm()" form-autofill-fix>
  <div>
    <input type="email" ng-model="email" ng-required />
    <input type="password" ng-model="password" ng-required />
    <button type="submit">Log In</button>
  </div>
</form>

2
これはうまくいきました。上記のほとんどを試しましたが、結果はありませんでした。ありがとう!!! 結果はmobbr.comにあります
Patrick Savalle 2014年

1
これはjQueryを使用するため、jQueryなしで機能することは想定されていません。
Quinn Strahl 2014年

1
ここに私たちは2018年にいますが、これはまだ修正です。エゼキエル、ありがとう!
スコットマニー

35

$timeoutこのようなものを使用する必要はありません。イベントシステムを利用できます。

私はそれがもっとAngularishで、jQueryやカスタムイベントのキャッチに依存していないと思います。

たとえば、送信ハンドラで:

$scope.doLogin = function() {
    $scope.$broadcast("autofill:update");

    // Continue with the login.....
};

そして、あなたはautofillこのようなディレクティブを持つことができます:

.directive("autofill", function () {
    return {
        require: "ngModel",
        link: function (scope, element, attrs, ngModel) {
            scope.$on("autofill:update", function() {
                ngModel.$setViewValue(element.val());
            });
        }
    }
});

最後に、HTMLは次のようになります。

<input type="text" name="username" ng-model="user.id" autofill="autofill"/>

14
私の場合、入力が空のときは送信ボタンが無効になっています。
gorpacrate 2013年

4
これは非同期なので問題はありませんか?イベントがレプリケートされる前にサーバーへのログインコールがすでに終了し、ディレクティブにスコープを更新する時間がありましたか?
サンダー2014年

12

もうハッキングする必要はありません!Angular dev tboschは、ブラウザーが変更イベントをトリガーせずにフォームフィールドを変更すると、変更イベントをトリガーするポリフィルを作成しました。

https://github.com/tbosch/autofill-event

現在のところ、これはAngularコードに組み込まれません。これは、ブラウザーのバグ修正であり、Angularなしでも機能するためです(たとえば、プレーンなjQueryアプリの場合)。

「ポリフィルは、ドキュメントの読み込み時および入力が残っているとき(同じフォームでのみ)の変更をチェックします。ただし、必要に応じてチェックを手動でトリガーできます。

プロジェクトには単体テストと半自動テストがあるので、必要なブラウザー設定とともにさまざまなユースケースをすべて収集する場所がついに完成しました。

注:このポリフィルは、AngularJS / jQueryの単純なアプリで動作しますが、Angularを使用しない単純なjQueryアプリでも動作します。」

次のようにインストールできます。

bower install autofill-event --save

ページのjQueryまたはAngularのautofill-event.js にスクリプトを追加します。

これにより、次のことが行われます。

  • DOMContentLoadedの後:すべての入力フィールドを確認します
  • フィールドが残っています:同じフォームの他のすべてのフィールドを確認してください

API(チェックを手動でトリガーするため):

  • $el.checkAndTriggerAutoFillEvent():指定されたjQuery / jQLite要素内のすべてのDOM要素のチェックを実行します。

使い方

  1. ユーザー(変更イベントをリッスンする)とJavaScript(jQuery / jQLite要素の$ el.val()をインターセプトする)によるinput要素へのすべての変更を覚えておいてください。その変更された値は、プライベートプロパティの要素に格納されます。

  2. 要素の自動入力を確認する:要素の現在の値と記憶されている値を比較します。異なる場合は、変更イベントをトリガーします。

依存関係

AngularJSまたはjQuery(一方または両方で動作)

詳細とソースはgithubページにあります

Githubの元のAngular Issue#1460は、こちらで読むことができます。


2
これは私のケースでは機能しません。checkAndTriggerAutoFillEvent()がトリガーされてイベントを生成しますが、AngularJSはモデルを更新せず、フィールドが空であると見なします。
Splaktar 2014年

私はこのpolyillを使って何の問題もありませんでした。正しく機能しない場合は、コードを含む別の質問を作成してください。バグを見つけた場合は、githubでチケットを確認するか、新しいチケットを開いてください。
orszaczky 2014年

はい、私はそれがこのバグに関連していると思います:github.com/tbosch/autofill-event/issues/11
Splaktar

1
ngModelOptionsと組み合わせると、モデルが正しく同期されるように、イベントの1つとしてautofill-event指定できるようになります。changeupdateOn
morloch

10

汚いコード。このコードを使用する前に、問題https://github.com/angular/angular.js/issues/1460#issuecomment-18572604が修正されているかどうかを確認してください。このディレクティブは、送信前だけでなく、フィールドが入力されたときにイベントをトリガーします(送信前に入力を処理する必要がある場合に必要です)

 .directive('autoFillableField', function() {
    return {
                   restrict: "A",
                   require: "?ngModel",
                   link: function(scope, element, attrs, ngModel) {
                       setInterval(function() {
                           var prev_val = '';
                           if (!angular.isUndefined(attrs.xAutoFillPrevVal)) {
                               prev_val = attrs.xAutoFillPrevVal;
                           }
                           if (element.val()!=prev_val) {
                               if (!angular.isUndefined(ngModel)) {
                                   if (!(element.val()=='' && ngModel.$pristine)) {
                                       attrs.xAutoFillPrevVal = element.val();
                                       scope.$apply(function() {
                                           ngModel.$setViewValue(element.val());
                                       });
                                   }
                               }
                               else {
                                   element.trigger('input');
                                   element.trigger('change');
                                   element.trigger('keyup');
                                   attrs.xAutoFillPrevVal = element.val();
                               }
                           }
                       }, 300);
                   }
               };
});

5

明確でわかりやすいソリューションのようです。jQueryは必要ありません。

更新:

  • モデルは、モデルの値が実際の入力値と等しくない場合にのみ更新されます。
  • 最初の自動入力でチェックが停止することはありません。たとえば別のアカウントを使用したい場合。

app.directive('autofillable', ['$timeout', function ($timeout) {
    return {
        scope: true,
        require: 'ngModel',
        link: function (scope, elem, attrs, ctrl) {
            scope.check = function(){
                var val = elem[0].value;
                if(ctrl.$viewValue !== val){
                    ctrl.$setViewValue(val)
                }
                $timeout(scope.check, 300);
            };
            scope.check();
        }
    }
}]);

4
私が思いついたのと同様の+1の良い解決策。私が提案する唯一の追加は、まずモデルが$pristine変更される前かどうかを確認し、変更後は$pristine状態を維持することです。それ以外の場合、自動入力データのないフォームが読み込まれた場合でも、上記のディレクティブはモデルを更新しdirty、フォームコントロールはまだ変更されていないと見なされますが、その後もモデルを作成します。ここでは、ディレクティブを使用した例を示します。私が追加するコードをコメントアウトしました:jsfiddle.net/OACDesigns/L8Sja/4
OACDesigns

1
また、私はそうあるscope:trueべきだと思いますscope:{}
OACDesigns 2013

4

解決策1 [$ timeoutを使用]:

指令:

app.directive('autoFillSync', function($timeout) {
    return {
      require: 'ngModel',
      link: function(scope, elem, attrs, model) {
          var origVal = elem.val();
          $timeout(function () {
              var newVal = elem.val();
              if(model.$pristine && origVal !== newVal) {
                  model.$setViewValue(newVal);
              }
          }, 500);
      }
    };
});

HTML:

<form name="myForm" ng-submit="login()">
  <label for="username">Username</label>
  <input type="text" id="username" name="username" ng-model="username" auto-fill-sync/><br/>
  <label for="password">Password</label>
  <input type="password" id="password" name="password" ng-model="password" auto-fill-sync/><br/>
  <button type="submit">Login</button>
</form>

解決策2 [角度イベントの使用]:

参照:ベッコの答え

指令:

app.directive("autofill", function () {
    return {
        require: "ngModel",
        link: function (scope, element, attrs, ngModel) {
            scope.$on("autofill:update", function() {
                ngModel.$setViewValue(element.val());
            });
        }
    };
});

HTML:

<form name="myForm" ng-submit="login()">
  <label for="username">Username</label>
  <input type="text" id="username" name="username" ng-model="username" autofill/><br/>
  <label for="password">Password</label>
  <input type="password" id="password" name="password" ng-model="password" autofill/><br/>
  <button type="submit">Login</button>
</form>

解決策3 [リレーメソッド呼び出しの使用]:

指令:

app.directive('autoFill', function() {
    return {
        restrict: 'A',
        link: function(scope,element) {
            scope.submit = function(){
                scope.username = element.find("#username").val();
                scope.password = element.find("#password").val();
                scope.login();//call a login method in your controller or write the code here itself
            }

        }
    };
});

HTML:

<form name="myForm" auto-fill ng-submit="submit()">
   <label for="username">Username</label>
   <input type="text" id="username" name="username" ng-model="username" />
   <label for="password">Password</label>
   <input type="password" id="password" name="password" ng-model="password" />
   <button type="submit">Login</button>
</form>

2
ソリューション1はしばらくの間非常にうまく機能していましたが、現在はangularjs 1.4.9では機能しません。チェックmodel.$validインディレクティブは前後にtrueを返します$setViewValueが、要素はまだng-invalidクラスに含まれています
John

4

まあ、ブラウザの動作をエミュレートするのが最も簡単な方法なので、changeイベントに問題がある場合は、自分で起動してください。はるかに簡単です。

指令:

yourModule.directive('triggerChange', function($sniffer) {
    return {
        link : function(scope, elem, attrs) {
            elem.on('click', function(){
                $(attrs.triggerChange).trigger(
                    $sniffer.hasEvent('input') ? 'input' : 'change'
                );
            });
        },
        priority : 1
    }
});

HTML:

<form >
    <input data-ng-model="user.nome" type="text" id="username">

    <input data-ng-model="user.senha" type="password" id="password" >

    <input type="submit" data-ng-click="login.connect()" id="btnlogin" 
           data-trigger-change="#password,#username"/>
</form>

ディレクティブをフォームに配置し、フォーム送信時に.dirtyクラスを使用してすべての入力でイベントを発生させるなど、いくつかのバリエーションを実行できます。


3

これはjQueryの方法です:

$(window).load(function() {
   // updates autofilled fields
   window.setTimeout(function() {
     $('input[ng-model]').trigger('input');
   }, 100);
 });

これは角度の方法です:

 app.directive('autofill', ['$timeout', function ($timeout) {
    return {
        scope: true,
        require: 'ngModel',
        link: function (scope, elem, attrs, ctrl) {
            $timeout(function(){
                $(elem[0]).trigger('input');
                // elem.trigger('input'); try this if above don't work
            }, 200)
        }
    }
}]);

HTML

<input type="number" autofill /> 

2
完全に非角度の方法。
gorpacrate 2013年

1
@gorpacrate:角度のある方法でチェックしてください。
Nishchit Dhanani 2013年

2
Angular Wayのタブにはスペースが多すぎます。冗談だよ。横スクロールが大好きです。
Daniel Birowsky Popeski、2013

要素に関数トリガーがないため、これは角度では機能しません。これにはjqueryを含める必要があると思います
Tomas

1
triggerHandler('input')本格的なjqueryがない場合は問題ありません
Mutmatt

1

ハックは少ないですが、コントローラーに追加のコードが必要な別の回避策があります。

HTML:

<form ng-submit="submitForm()" ng-controller="FormController">
    <input type="text" ng-model="username" autocomplete-username>
    <input type="submit">
</form>

ディレクティブ(CoffeeScript):

directives.directive 'autocompleteUsername', ->
    return (scope, element) ->
        scope.getUsername = ->
            element.val()

コントローラ:

controllers.controller 'FormController', [->
    $scope.submitForm = ->
        username = $scope.getUsername?() ? $scope.username
        # HTTP stuff...
]

バニラJavaScriptでこれを持っていますか?
f1lt3r 2015年

CoffeeScript.orgは翻訳を提供します:ここで行われます
jab

1

これは、Angularの検証のすべてが送信ボタンの無効化/有効化を含む設計どおりに機能することを許可した唯一の解決策です。bowerと1つのスクリプトタグを使用してインストールします。バジンガ!

https://github.com/tbosch/autofill-event


1
このポリゴンの塗りつぶしは、フォームの状態への送信ボタン/入力バインディングに適合しません。ダイジェストをトリガーしたいページをクリックしない限り(アクションがブラウザーに実際の入力をトリガーするようです)、ボタンが有効になります。手動テストのテストページ
e-cloud

1

タイムアウト関数を使用する代わりに、モデル値を変更するとうまくいきました。

これが私のコードです:

module.directive('autoFill', [ function() {
    return {
        require: 'ngModel',
        link:function(scope, element, attr, ngModel) {
            var origVal = element.val();
            if(origVal){
                ngModel.$modelValue = ngModel.$modelValue || origVal;
            }
        }
    };
}]);

1

送信ハンドラーの1行の回避策(jQueryが必要):

if (!$scope.model) $scope.model = $('#input_field').val();

それがディレクティブにある場合、それは実際には1行ではありません。
isherwood 2017年

0

送信時に$ setValue(val())を強制します(これはjQuery なしで機能します)

   var ValidSubmit = ['$parse', function ($parse) {
    return {
        compile: function compile(tElement, tAttrs, transclude) {
            return {
                post: function postLink(scope, element, iAttrs, controller) {
                    var form = element.controller('form');
                    form.$submitted = false;
                    var fn = $parse(iAttrs.validSubmit);
                    element.on('submit', function(event) {
                        scope.$apply(function() {
                            var inputs = element.find('input');
                            for(var i=0; i < inputs.length; i++) {
                                var ele = inputs.eq(i);
                                var field = form[inputs[i].name];
                                field.$setViewValue(ele.val());
                            }
                            element.addClass('ng-submitted');
                            form.$submitted = true;
                            if(form.$valid) {
                                fn(scope, {$event:event});
                            }
                        });
                    });
                    scope.$watch(function() { return form.$valid}, function(isValid) {
                        if(form.$submitted == false) return;
                        if(isValid) {
                            element.removeClass('has-error').addClass('has-success');
                        } else {
                            element.removeClass('has-success');
                            element.addClass('has-error');
                        }
                    });
                }
            }
        }
    }
}]
app.directive('validSubmit', ValidSubmit);

0

私はAngularjsに非常に慣れていませんが、その問題の簡単な解決策を見つけました=> Angularに式を再評価させる...変更することで!(もちろん、初期状態に戻すには、初期値を覚えておく必要があります)フォームを送信するためのコントローラー関数での方法は次のとおりです。

    $scope.submit = function () {
                var oldpassword = $scope.password;
                $scope.password = '';
                $scope.password = oldpassword;
//rest of your code of the submit function goes here...

もちろん、パスワード入力に入力された値は、ユーザーではなくウィンドウによって設定されています。


0

あなたはこのコードを試すことができます:

yourapp.directive('autofill',function () {

    return {
        scope: true,
        require: 'ngModel',
        link: function (scope, elem, attrs, ctrl) {
            var origVal = elem.val();
            if (origVal != '') {
                elem.trigger('input');
            }
        }
    }
});

0

この回答へのマイナーな変更(https://stackoverflow.com/a/14966711/3443828):$ timeoutの代わりに$ intervalを使用して、ブラウザーを競合させる必要がないようにします。

mod.directive('autoFillSync', function($interval) {
    function link(scope, element, attrs, ngModel) {
        var origVal = element.val();
        var refresh = $interval(function() {
          if (!ngModel.$pristine) {
            $interval.cancel(refresh);
          }else{
            var newVal = element.val();
            if (origVal !== newVal) {
              ngModel.$setViewValue(newVal);
              $interval.cancel(refresh);
            }
          }
        }, 100);
    }

    return {
      require: 'ngModel',
      link: link
    }
  });

0

これが私のフォームで使用することになったソリューションです。

.directive('autofillSync', [ function(){
  var link = function(scope, element, attrs, ngFormCtrl){
    element.on('submit', function(event){
      if(ngFormCtrl.$dirty){
        console.log('returning as form is dirty');
        return;
      }   
      element.find('input').each(function(index, input){
        angular.element(input).trigger('input');
      }); 
    }); 
  };  
  return {
    /* negative priority to make this post link function run first */
    priority:-1,
    link: link,
    require: 'form'
  };  
}]);

そしてフォームのテンプレートは

<form autofill-sync name="user.loginForm" class="login-form" novalidate ng-submit="signIn()">
    <!-- Input fields here -->
</form>

このようにして、ng-modelにあるパーサー/フォーマッターを実行し、送信機能を透過的にすることができました。


0

ディレクティブなしのソリューション:

.run(["$window", "$rootElement", "$timeout", function($window, $rootElement, $timeout){

        var event =$window.document.createEvent("HTMLEvents");
        event.initEvent("change", true, true);

        $timeout(function(){

            Array.apply(null, $rootElement.find("input")).forEach(function(item){
                if (item.value.length) {
                    item.$$currentValue = item.value;
                    item.dispatchEvent(event);
                }
            });

        }, 500);
    }])

0

これは、FirefoxとChromeの両方でテストしたすべてのケースで機能する単純な修正です。上の答え(タイムアウト付きのディレクティブ)で私が問題を抱えていたことに注意してください-

  • ブラウザの戻る/進むボタン、ページ読み込みイベントを再起動しない(修正は適用されません)
  • ページのロード後、しばらくして資格情報をロードします。たとえば、Firefoxでは、ログインボックスをダブルクリックして、保存されている資格情報から選択します。
  • 有効な入力が提供されるまでログインボタンを無効にするため、フォーム送信前に更新するソリューションが必要

この修正は明らかに非常に馬鹿げてハックですが、100%の時間で機能します-

function myScope($scope, $timeout) {
    // ...
    (function autoFillFix() {
        $timeout(function() { 
            $('#username').trigger('change'); 
            $('#password').trigger('change'); 
            autoFillFix(); }, 500);                    
    })();
}

1
置き換えることができる$timeout$interval未来を与えるためには、もう少しコンテキストを開発者とそこに行く奇妙な再帰呼び出しを取り除く
Mutmatt

0

これらのソリューションはどれも私のユースケースでは機能しませんでした。ng-changeを使用して変更を監視するフォームフィールドがいくつかあります。使用する$watchオートフィルによってトリガーされないため、ても役に立ちません。私には送信ボタンがないので、いくつかのソリューションを実行する簡単な方法がなく、間隔を使用して成功しませんでした。

結局、自動入力を無効にしてしまいました-理想的ではありませんが、ユーザーを混乱させません。

<input readonly onfocus="this.removeAttribute('readonly');">

ここで答えが見つかりました


-1

jQueryを使用している場合は、フォーム送信時にこれを行うことができます。

HTML:

<form ng-submit="submit()">
    <input id="email" ng-model="password" required 
           type="text" placeholder="Your email">
    <input id="password" ng-model="password" required 
           type="password" placeholder="Password">
</form>

JS:

 $scope.submit = function() {
     $scope.password = $('#password').val();
}

1
これはうまくいくかもしれませんが、より複雑なフォームでは合理的ではありません。
origin1tech 2014年

-1

シンプルにしたい場合は、JavaScriptを使用して値を取得してください

あなたの角度のjsコントローラで:

var username = document.getElementById( 'username')。value;


私はあなたが問題が何であるかを理解していないと思います
aelor 2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.