AngularJS浏览器通过使用指令自动填充变通办法
当在AngularJS中提交表单并使用浏览器记住密码function时,并且在随后的login尝试中,您允许浏览器使用用户名和密码填写login表单, $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()
返回值来查看视图。 我怎么能做到这一点?
显然这是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>
我想你只需要简化一下你的方法。 我绝对推荐的一件事是检查ngModel.$pristine
,并确保你不覆盖一些不好的用户的input。 另外,3秒可能太长。 你不应该在$ timeout中调用$ apply(),顺便说一句,它应该为你自动排列一个$摘要。
真正的问题:你的浏览器会打败Angular执行吗? 那我的浏览器呢?
这可能是一场不可战胜的战争,这就是为什么Angular(或Knockout)无法轻易解决的原因。 在指令初始执行时,不能保证input数据的状态。 甚至在Angular初始化的时候……所以这是一个棘手的问题要解决。
你不必使用$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"/>
这里是一个解决scheme,比其他解决scheme呈现的less得多,语义上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>
肮脏的代码,检查是否问题https://github.com/angular/angular.js/issues/1460#issuecomment-18572604之前已经修复使用此代码。; 该指令在填充字段时触发事件,不仅在提交之前触发事件(如果在提交之前必须处理input,则必须)
.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); } }; });
不需要破解了! Angular dev tbosch做了一个polyfill ,当浏览器改变表单域而不触发更改事件时触发一个更改事件:
https://github.com/tbosch/autofill-event
目前,他们不会将其构build到Angular代码中,因为这是针对浏览器的错误修正,也可以在没有Angular的情况下工作(例如,对于普通的jQuery应用程序)。
“polyfill将检查文档加载的变化,以及还有一个input时(只能以相同的forms)。但是,如果您愿意,可以手动触发检查。
该项目有unit testing和半自动testing,所以我们终于有一个地方,收集所有不同的用例和所需的浏览器设置。
请注意:这个polyfill可以使用普通的AngularJS应用程序,AngularJS / jQuery应用程序,也可以使用不使用Angular的普通jQuery应用程序。
它可以安装在:
bower install autofill-event --save
在页面中添加jQuery或Angular 之后的脚本autofill-event.js
。
这将做到以下几点:
- DOMContentLoaded之后:检查所有input字段
- 留下一个字段:检查同一表单中的所有其他字段
API(手动触发检查):
-
$el.checkAndTriggerAutoFillEvent()
:对给定的jQuery / jQLite元素中的所有DOM元素执行检查。
怎么运行的
-
记住用户(监听更改事件)和JavaScript(通过拦截jQuery / jQLite元素的$ el.val())对input元素的所有更改。 该改变的值被存储在私有属性中的元素上。
-
检查自动填充的元素:将元素的当前值与记住的值进行比较。 如果不同,则触发更改事件。
依赖
AngularJS或jQuery(与一个或两个一起工作)
更多信息和来源在github页面上 。
Github上的原始Angular Issue#1460 可以在这里阅读。
看起来像是直线前进的解决scheme。 没有jQuery需要。
更新:
- 模型仅在模型值不等于实际input值时更新。
- 检查不会在第一次自动填充时停止。 例如,如果你想使用另一个帐户例如。
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(); } } }]);
解决scheme1 [使用$超时]:
指示:
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>
解决scheme2 [使用angular度事件]:
参考: 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>
解决scheme3 [使用中继方法调用]:
指示:
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>
那么,模拟浏览器行为的最简单的方法是,如果更改事件发生问题,就自己开火。 简单得多。
指示:
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类的所有input来触发事件。
这是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 />
这是另一个解决方法,不太难懂,但需要一些额外的代码在控制器。
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... ]
这是我发现的唯一的解决scheme,允许我所有的Angular的validation按照devise工作,包括禁用/启用提交button。 安装与凉亭和1脚本标记。 Bazinga!
更改模型值,而不是使用超时function为我工作。
这是我的代码:
module.directive('autoFill', [ function() { return { require: 'ngModel', link:function(scope, element, attr, ngModel) { var origVal = element.val(); if(origVal){ ngModel.$modelValue = ngModel.$modelValue || origVal; } } }; }]);
提交处理程序中的单线程解决方法(需要jQuery):
if (!$scope.model) $scope.model = $('#input_field').val();
我强制提交一个$ 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);
我是Angularjs的新手,但是我发现了一个简单的解决scheme:>通过改变Force Force来重新评估expression式! (当然,您需要记住初始值以恢复到初始状态)下面是控制器函数提交表单的方式:
$scope.submit = function () { var oldpassword = $scope.password; $scope.password = ''; $scope.password = oldpassword; //rest of your code of the submit function goes here...
当然,在你的密码input中input的值已经由窗口设置,而不是由用户设置。
你可以试试这个代码:
yourapp.directive('autofill',function () { return { scope: true, require: 'ngModel', link: function (scope, elem, attrs, ctrl) { var origVal = elem.val(); if (origVal != '') { elem.trigger('input'); } } } });
这个答案( https://stackoverflow.com/a/14966711/3443828 )的一个小修改:使用$间隔,而不是$超时,所以你不必比赛浏览器。
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 } });
这是我最终使用的解决scheme。
.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模型上的任何parsing器/格式化器,并且提交function是透明的。
没有指令的解决scheme:
.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); }])
这是一个简单的修复,适用于我在Firefox和Chrome中testing过的所有情况。 请注意,与顶部答案(指令W /超时)我有问题 –
- 浏览器后退/前进button,不要重新启动页面加载事件(所以修复不适用)
- 在页面加载后一段时间加载凭证。 例如在Firefox中,双击login框并从存储的凭证中select。
- 在提交表单之前需要更新一个解决scheme,因为我禁用loginbutton,直到提供有效的input
这个修补程序显然非常愚蠢和黑客,但它的工作时间是100%
function myScope($scope, $timeout) { // ... (function autoFillFix() { $timeout(function() { $('#username').trigger('change'); $('#password').trigger('change'); autoFillFix(); }, 500); })(); }
这些解决scheme都不适合我的用例。 我有一些使用ng-change来监视变化的表单域。 使用$watch
是没有帮助的,因为它不是由自动填充触发的。 由于我没有提交button,所以没有简单的方法来运行一些解决scheme,并且我没有成功使用间隔。
我结束了禁用自动填充 – 不理想,但很less让用户感到困惑。
<input readonly onfocus="this.removeAttribute('readonly');">
在这里find答案
如果你使用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(); }
如果你想保持简单,只需使用JavaScript获取值
在你的angular度js控制器中:
var username = document.getElementById('username').value;