Solução alternativa de preenchimento automático do navegador AngularJS usando uma diretiva

Ao enviar um formulário no AngularJS e usar o navegador, lembre-se da funcionalidade de senha e, em uma tentativa de login subsequente, deixe o navegador preencher o formulário de login com o nome de usuário e senha, o modelo $scope não será alterado com base no preenchimento automático.

O único hack sujo que encontrei é usar a seguinte diretiva:

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

O problema é que o ngModel.$setViewValue(element.val()); não altera o modelo nem a visão com base no valor retornado element.val() . Como posso conseguir isso?

Aparentemente, esse é um problema conhecido do Angular e está atualmente aberto

Eu não tenho certeza do que você poderia fazer aqui além de algum tipo de trabalho como se estivesse tentando. Parece que você está no caminho certo. Eu não consegui meu navegador para tentar lembrar uma senha para o seu plunk, então eu não tenho certeza se isso vai funcionar, mas dê uma olhada:

 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); } } }); 
 


Eu acho que você só precisa simplificar um pouco a sua abordagem. A única coisa que eu definitivamente recomendo é verificar ngModel.$pristine e ter certeza de que você não está sobrescrevendo a input de algum usuário ruim. Além disso, 3 segundos é provavelmente muito longo. Você não deveria ter que chamar $ apply () em um $ timeout, BTW, ele deveria enfileirar um $ digest para você automaticamente.

A pegadinha real: o seu navegador vai bater o Angular na execução? E quanto ao meu navegador?

Esta é provavelmente uma guerra invencível, e é por isso que Angular (ou Knockout) não conseguiu resolvê-la prontamente. Não há garantia do estado dos dados em sua input no momento da execução inicial da diretiva. Nem mesmo no momento da boot do Angular …. Então, é um problema difícil de resolver.

Aqui está uma solução que é muito menos hacky do que outras soluções apresentadas e é semanticamente sonora 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); } }; }); 

Então você simplesmente anexa a diretiva ao seu formulário:

 

Você não precisa usar um $timeout ou algo assim. Você pode usar um sistema de events.

Eu acho que é mais angular e não depende de jQuery ou captura de evento personalizado.

Por exemplo, no seu manipulador de envio:

 $scope.doLogin = function() { $scope.$broadcast("autofill:update"); // Continue with the login..... }; 

E então você pode ter uma diretiva de autofill como esta:

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

Finalmente, seu HTML será como:

  

Código sujo, verifique se o problema https://github.com/angular/angular.js/issues/1460#issuecomment-18572604 foi corrigido antes de usar este código. Esta diretiva aciona events quando o campo é preenchido, não apenas antes do envio (é necessário se você tiver que manipular a input antes de enviar)

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

Não há necessidade de hackear mais! Angular dev tosch fez um polyfill que aciona um evento de mudança quando o navegador altera campos de formulário sem acionar um evento de mudança:

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

Por enquanto eles não irão construir isso no código Angular, já que este é um bugfix para o navegador, e também funciona sem o Angular (por exemplo, para aplicativos simples do jQuery).

“O polyfill irá verificar se há alterações na carga do documento e também quando uma input é deixada (somente no mesmo formulário). No entanto, você pode acionar a verificação manualmente se desejar.

O projeto tem testes de unidade, bem como testes semi-automáticos, então, finalmente, temos um local para coletar todos os casos de uso diferentes, juntamente com as configurações necessárias do navegador.

Por favor, note: Este polyfill funciona com aplicativos comuns do AngularJS, com aplicativos AngularJS / jQuery, mas também com aplicativos simples do jQuery que não usam o Angular. ”

Pode ser instalado com:

 bower install autofill-event --save 

Adicione o script autofill-event.js após jQuery ou Angular em sua página.

Isso fará o seguinte:

  • após DOMContentLoaded: verifica todos os campos de input
  • um campo é deixado: verifique todos os outros campos no mesmo formulário

API (para triggersr manualmente o cheque):

  • $el.checkAndTriggerAutoFillEvent() : Execute a verificação de todos os elementos DOM no elemento jQuery / jQLite fornecido.

Como funciona

  1. Lembre-se de todas as alterações feitas nos elementos de input pelo usuário (escutando events de alteração) e também por JavaScript (interceptando $ el.val () para elementos jQuery / jQLite). Esse valor alterado é armazenado no elemento em uma propriedade privada.

  2. Verificando um elemento para preenchimento automático: Compare o valor atual do elemento com o valor lembrado. Se for diferente, acione um evento de mudança.

Dependências

AngularJS ou jQuery (funciona com um ou ambos)

Mais informações e fonts na página do github .

O problema angular original nº 1460 no Github pode ser lido aqui.

Parece uma solução clara e direta. Não é necessário jQuery.

ATUALIZAR:

  • O modelo é atualizado apenas quando o valor do modelo não é igual ao valor de input real.
  • A verificação não é interrompida no primeiro preenchimento automático. No caso, se você deseja usar outra conta por exemplo.

 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(); } } }]); 

Solução 1 [Usando $ timeout]:

Directiva:

 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:

 


Solução 2 [Usando events angulares]:

Ref: resposta de Becko

Directiva:

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

HTML:

 


Solução 3 [Usando as chamadas do método de retransmissão]:

Directiva:

 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:

 

Bem, a maneira mais fácil é emular o comportamento do navegador, por isso, se houver um problema com o evento de alteração, basta ativá-lo. Muito mais simples.

Directiva:

 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:

 

Você pode fazer algumas variações, como colocar a diretiva no formulário e triggersr o evento em todas as inputs com a class .dirty no envio de formulário.

Esta é a maneira jQuery:

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

Esta é a maneira 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

  

Aqui está outra solução alternativa que é menos hacky, mas requer algum código extra no controlador.

HTML:

 

Directiva (CoffeeScript):

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

Controlador:

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

Esta é a única solução que encontrei que permitiu que todas as validações do Angular funcionassem como planejadas, incluindo a desativação / ativação do botão de envio. Instala com bower e 1 tag de script. Bazinga!

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

Alterar o valor do modelo, em vez de usar uma function de tempo limite funcionou para mim.

Aqui está o meu código:

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

Solução alternativa de uma linha no manipulador de envio (requer jQuery):

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

Eu forço um $ setValue (val ()) em submit: (isso funciona sem 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); 

Sou muito novo no Angularjs, mas encontrei uma solução simples para esse problema => Force Angular para reavaliar a expressão … alterando-a! (claro que você precisa lembrar o valor inicial para reverter para o estado inicial) Aqui está a maneira como ele vai na sua function de controlador para enviar o formulário:

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

onde, claro, o valor inserido na sua input de senha foi definido pelo Windows e não pelo usuário.

Você pode tentar este código:

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

Uma pequena modificação a esta resposta ( https://stackoverflow.com/a/14966711/3443828 ): use um intervalo $ em vez de um tempo limite $ para que você não tenha que correr o navegador.

 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 } }); 

Esta é a solução que acabei usando em meus formulários.

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

E o modelo do formulário será

  

Dessa forma, consegui executar quaisquer analisadores / formatadores que eu tenha no meu ng-model e ter a funcionalidade de envio transparente.

Solução sem diretivas:

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

Esta é uma correção simples que funciona para todos os casos que testei no Firefox e no Chrome. Note que com a resposta top (diretiva w / timeout) eu tive problemas com –

  • Botões de voltar / avançar do navegador, não reativar os events de carregamento da página (para que a correção não se aplique)
  • Carregamento de credenciais algum tempo após o carregamento da página. Por exemplo, no Firefox, clique duas vezes na checkbox de login e selecione as credenciais armazenadas.
  • Precisa de uma solução que atualize antes do envio do formulário, pois desativo o botão Login até que seja fornecida uma input válida

Esta correção é obviamente muito burra e hacky, mas funciona 100% do tempo –

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

Nenhuma dessas soluções funcionou no meu caso de uso. Eu tenho alguns campos de formulário que usam ng-change para observar a mudança. Usar $watch não ajuda, pois não é acionado pelo preenchimento automático. Como não tenho um botão de envio, não há uma maneira fácil de executar algumas das soluções e não obtive êxito usando intervalos.

Acabei desativando o preenchimento automático – não ideal, mas muito menos confuso para o usuário.

  

Encontrei a resposta aqui

Se você estiver usando o jQuery, poderá fazer isso no formulário de envio:

HTML:

 

JS:

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

Se você quiser mantê-lo simples, é só pegar o valor usando o javascript

Em seu controlador js angular:

var username = document.getElementById (‘username’). value;