AngularJS 浏览器使用指令自动填充解决方法

Posted

技术标签:

【中文标题】AngularJS 浏览器使用指令自动填充解决方法【英文标题】:AngularJS browser autofill workaround by using a directive 【发布时间】:2013-02-04 15:14:05 【问题描述】:

在 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() 返回值更改模型或视图。我怎样才能做到这一点?

【问题讨论】:

这段代码乍一看还不错……但其余的(标记等)在哪里?你有我们能看到的小提琴或笨蛋吗? 这是 plunk:plnkr.co/edit/CHrBAVU9Ycl2ex2DRr6R 我不确定它是否直接在 plunker 上工作,因为它在 iframe 中运行。 您不需要在 Angular 的 $timeout 中设置范围。$apply。您可能需要在本机 window.setTimeout 中使用它。但最好使用 angular 的。 Angular 开发人员 tbosch 有一个 “官方”polyfill 修复 来解决这个问题。请在下面的答案***.com/a/25687396/3009639 中查看详细信息。 不幸的是,“官方”修复是废弃软件,在许多浏览器中不起作用。问题已提交但未解决,因此搜索继续... 【参考方案1】:

Apparently this is a known issue with Angular and is currently open

除了像您正在尝试的某种解决方法外,我不确定您可以在这里做什么。看来你在正确的轨道上。我无法让我的浏览器尝试记住您的 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>

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

真正的收获:你的浏览器会在执行方面击败 Angular 吗?我的浏览器呢?

这可能是一场无法取胜的战争,这就是为什么 Angular(或 Knockout)无法轻易解决它的原因。在指令初始执行时,无法保证输入中的数据状态。甚至在 Angular 初始化的时候都没有……所以这是一个很难解决的问题。

【讨论】:

关于 $pristine 的优点。嗯,3 秒只是为了测试目的。 “保存密码”对话框似乎可以在 Safari 上运行。这是我必须调查的另一个问题。 用本地存储骗他。 :) ...我会继续考虑的。但我怀疑让自动填充与任何双向绑定一起工作的可行性。 是的,但我仍然需要能够从指令更新模型才能使其工作。 我在开玩笑说 localStorage,你不会想把密码放在那里的。 这不适用于 Safari 和信用卡自动填充,因为此自动填充由用户随时触发【参考方案2】:

这是一个比其他解决方案更简单的解决方案,并且在语义上是合理的 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>

【讨论】:

这个有效。我们尝试了上述大部分内容,但没有任何结果。谢谢!!!结果在mobbr.com 因为它使用 jQuery,所以没有 jQuery 就无法工作。 我们在 2018 年,这仍然是修复。谢谢以西结!【参考方案3】:

您不必使用$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"/>

【讨论】:

在我的情况下,当输入为空时,提交按钮被禁用。 因为它是异步的,所以您没有遇到过这个问题吗?在复制事件之前对服务器的登录调用已经离开并且指令有时间更新范围?【参考方案4】:

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

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

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

“polyfill 将检查文档加载时的更改以及留下输入时的更改(仅在相同的表单中)。但是,如果您愿意,您可以手动触发检查。

该项目有单元测试和半自动测试,所以我们终于有一个地方可以收集所有不同的用例以及所需的浏览器设置。

请注意:此 polyfill 适用于普通的 AngularJS 应用程序、AngularJS/jQuery 应用程序以及不使用 Angular 的普通 jQuery 应用程序。”

可以安装:

bower install autofill-event --save

在您的页面中添加脚本autofill-event.jsafter jQuery 或 Angular。

这将执行以下操作:

DOMContentLoaded 后:检查所有输入字段 留下一个字段:检查同一表单中的所有其他字段

API(手动触发检查):

$el.checkAndTriggerAutoFillEvent():对给定的 jQuery / jQLite 元素中的所有 DOM 元素执行检查。

工作原理

    记住用户对输入元素的所有更改(监听更改事件)以及 javascript(通过拦截 jQuery / jQLite 元素的 $el.val())。更改后的值存储在私有属性中的元素上。

    检查元素是否自动填充:将元素的当前值与记住的值进行比较。如果不同,则触发更改事件。

依赖关系

AngularJS 或 jQuery(使用其中之一或两者)

更多信息和来源请访问github page。

Github 上的原始 Angular 问题 #1460 can be read here.

【讨论】:

这不适用于我的情况。 checkAndTriggerAutoFillEvent() 被触发并生成事件,但 AngularJS 从不更新其模型并认为字段为空。 我在使用这个 polyill 时没有遇到任何问题。如果你不能让它正常工作,你应该创建一个单独的问题,包括一些代码。如果你发现了一个bug,你应该去github上查票,或者开一个新的。 是的,我认为它与这个错误有关:github.com/tbosch/autofill-event/issues/11 ngModelOptions 结合autofill-event 意味着您现在可以将change 指定为updateOn 事件之一,以确保您的模型正确同步。【参考方案5】:

脏代码,请在使用此代码之前检查问题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);
                   
               ;
);

【讨论】:

【参考方案6】:

似乎是直截了当的解决方案。不需要 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();
        
    
]);

【讨论】:

+1 很好的解决方案,类似于我想出的。我建议的唯一补充是在更改之前首先检查模型是否为$pristine,然后在更改后保持$pristine 状态。否则,您会发现如果加载的表单没有自动填充数据,上述指令仍将更新模型并随后使其成为dirty,即使表单控件仍应被视为未触及。这里的例子,使用你的指令。我已经注释掉了我要添加的代码:jsfiddle.net/OACDesigns/L8Sja/4 另外,我认为scope:true 应该是scope:【参考方案7】:

解决方案 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's answer

指令:

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>

【讨论】:

解决方案 1 曾经运行得很好,但现在在 angularjs 1.4.9 上它不再起作用了。在指令中检查model.$valid$setViewValue 之前和之后都返回true,但该元素仍属于ng-invalid【参考方案8】:

嗯,最简单的方法是模拟浏览器的行为,所以如果更改事件有问题,请自行触发。简单得多。

指令:

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 类在所有输入上触发事件。

【讨论】:

【参考方案9】:

这是 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 /> 

【讨论】:

完全无角度的方式。 @gorpacrate :现在用角度方式检查一下。 Angular Way 的标签有太多空格。开玩笑而已。我喜欢水平滚动。 这在角度上不起作用,因为元素上没有函数触发器。我认为这需要包括 jquery triggerHandler('input') 如果你没有完整的 jquery 应该没问题【参考方案10】:

这是另一种不那么 hacky 的解决方法,但需要在控制器中添加一些额外的代码。

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 中有这个吗? CoffeeScript.org 提供翻译:done here【参考方案11】:

这是我发现的唯一解决方案,它允许我的所有 Angular 验证按设计工作,包括禁用/启用提交按钮。使用 bower 和 1 个脚本标签安装。巴津加!

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

【讨论】:

此多边形填充不适合与表单状态的提交按钮/输入绑定。除非你点击页面上任何你想触发摘要的点(似乎动作触发了浏览器做一些真正的填充),然后按钮变为启用。Test page with manual tests【参考方案12】:

更改模型值,而不是使用超时功能对我有用。

这是我的代码:

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

【讨论】:

【参考方案13】:

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

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

【讨论】:

如果它在指令中,它并不是真正的单行,它当然应该是。【参考方案14】:

我在提交时强制使用 $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);

【讨论】:

【参考方案15】:

我对 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 设置的,而不是用户设置的。

【讨论】:

【参考方案16】:

你可以试试这个代码:

yourapp.directive('autofill',function () 

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

【讨论】:

【参考方案17】:

对此答案 (https://***.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
    
  );

【讨论】:

【参考方案18】:

这是我最终在表单中使用的解决方案。

.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 上拥有的任何解析器/格式化程序,并使提交功能透明。

【讨论】:

【参考方案19】:

没有指令的解决方案:

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

【讨论】:

【参考方案20】:

这是一个简单的修复程序,适用于我在 Firefox 和 Chrome 中测试过的所有情况。请注意,对于最佳答案(带超时的指令),我遇到了问题 -

浏览器后退/前进按钮,不要重新触发页面加载事件(因此修复不适用) 在页面加载后的某个时间加载凭据。例如在 Firefox 中,双击登录框并从存储的凭据中进行选择。 需要在提交表单之前更新的解决方案,因为在提供有效输入之前我禁用了登录按钮

这个修复显然是非常愚蠢和 hacky,但它在 100% 的时间里都有效 -

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

【讨论】:

可以将 $timeout 替换为 $interval,为未来的开发人员提供更多背景信息,并摆脱在那里进行的看起来很奇怪的递归调用【参考方案21】:

这些解决方案都不适用于我的用例。我有一些表单字段使用 ng-change 来观察变化。使用$watch 没有帮助,因为它不是由自动填充触发的。因为我没有提交按钮,所以没有简单的方法来运行一些解决方案,而且我没有成功使用间隔。

我最终禁用了自动填充 - 不理想,但不会让用户感到困惑。

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

找到答案here

【讨论】:

【参考方案22】:

如果您使用的是 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();

【讨论】:

虽然这可能有效,但对于更复杂的表单来说是不合理的。【参考方案23】:

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

在你的 Angular js 控制器中:

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

【讨论】:

我想你不明白问题是什么

以上是关于AngularJS 浏览器使用指令自动填充解决方法的主要内容,如果未能解决你的问题,请参考以下文章

angularjs 在 ng-repeat 指令中预填充输入文本

AngularJS指令范围绑定未在链接函数中立即填充

Ng-cloak解决angularJs中的闪烁问题

编写自定义指令以在 AngularJS (TypeScript) 中向 ng-options 添加工具提示

解决Google浏览器保存密码自动填充

如何自动大写AngularJS输入字段中的第一个字符?