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()返回的值不会更改模型或视图。我该怎么做?


乍一看,这段代码看起来还不错...但是其余的(标记等)在哪里?我们有没有看到小提琴或小东西?
Ben Lesh

这是插件plnkr.co/edit/CHrBAVU9Ycl2ex2DRr6R我不确定它是否可以直接在插件上运行,因为它在iframe中运行。
lucassp

1
您不需要进行作用域。$ apply在angular的$ timeout内。您可能需要在本机window.setTimeout中使用它。但是,最好使用angular的那个。
gorpacrate

1
Angular dev tbosch提供了一个“官方” polyfill 修复程序解决此问题。请在下面的答案stackoverflow.com/a/25687396/3009639中查看详细信息。
orszaczky 2014年

不幸的是,“官方”修复程序是废弃软件,无法在许多浏览器中使用。问题已提交,但未解决,因此搜索继续进行……
cyberwombat

Answers:


45

显然,这是Angular的一个已知问题,目前已打开

除了像您正在尝试的工作以外,我不确定您可以在这里做什么。看来您步入正轨。我无法让我的浏览器记住您的密码,因此不确定是否可以使用,但请看一下:

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>

我认为您只需要稍微简化一下方法即可。我绝对推荐的一件事是检查ngModel.$pristine并确保您不会覆盖某些不良用户的输入。另外,3秒可能太长。您不必在$ timeout中调用$ apply(),顺便说一句,它应该为您自动排队$ digest。

真正的收获是:您的浏览器会击败Angular来执行吗?那我的浏览器呢?

这可能是一场无法战胜的战争,这就是Angular(或淘汰赛)无法轻松解决它的原因。指令初次执行时,无法保证输入中数据的状态。甚至在Angular初始化时也没有。...因此,这是一个棘手的问题。


1
关于$ pristine的好点。好吧,这里有3秒只是用于测试目的。Safari上似乎可以使用“保存密码”对话框。这是我必须调查的另一个问题。
lucassp

只是用本地存储来欺骗他。:) ...我会继续考虑。但是,我对通过任何双向绑定使自动填充工作的可行性感到怀疑。
Ben Lesh

是的,但是我仍然需要能够从指令中更新模型以使其起作用。
lucassp

1
我开玩笑的是localStorage,您不想在此保留密码。
Ben Lesh

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年,现在仍然可以解决。谢谢以西结!
Scott Manny

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

4
因为它是异步的,所以您不会遇到麻烦吗?复制事件之前,服务器的登录调用已经离开了什么地方,并且指令有时间更新作用域?
桑德2014年

12

无需再破解了!Angular开发人员tbosch制作了一个polyfill,当浏览器更改表单字段而不触发change事件时,它会触发change事件:

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

目前,他们不会将其内置到Angular代码中,因为这是浏览器的错误修正,并且在没有Angular的情况下也可以工作(例如,对于普通的jQuery应用程序)。

“ polyfill将检查文档加载时的更改以及左输入(仅以相同形式)时的更改。但是,您可以根据需要手动触发该检查。

该项目既有单元测试,也有半自动测试,所以我们终于可以在这里收集所有不同的用例以及所需的浏览器设置。

请注意:此polyfill可以与普通的AngularJS应用程序,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())。更改后的值存储在私有属性中的元素上。

  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 2014年

1
ngModelOptions与结合使用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 2013年

1
同样,我认为scope:true应该是scope:{}
OACDesigns

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 [使用角度事件]:

参考:Becko的答案

指示:

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在in之前和之后$setViewValue,签入指令均返回true ,但该元素仍在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);
 });

这是Angular方式:

 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

1
@gorpacrate:现在以有角度的方式检查一下。
Nishchit Dhanani

2
Angular Way的选项卡有太多空格。开玩笑而已。我喜欢水平滚动。
Daniel Birowsky Popeski

这在角度上不起作用,因为元素上没有功能触发器。我认为这将需要包括jquery
Tomas

1
triggerHandler('input')如果您没有完整的jQuery,应该没问题
Mutmatt 2015年

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个脚本标签。Bazinga!

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

提交处理程序中的单线解决方法(需要jQuery):

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

如果在指令中,它实际上不是单线的,它当然应该是。
isherwood '17

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...

当然,您在密码输入中输入的值是由Windows而非用户设置的。


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):使用$ interval而不是$ timeout,因此您不必竞争浏览器。

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

-1

如果您想保持简单,请使用javascript获取值

在您的angular js控制器中:

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


我认为您不明白问题是什么
阿罗
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.