Le navigateur AngularJS autofill contournera en utilisant une directive

lors de la soumission d'un formulaire dans AngularJS et d'utiliser la fonctionnalité de mémoire de mot de passe du navigateur, et dans une tentative de connexion ultérieure vous laissez le navigateur remplir le formulaire de connexion avec le nom d'utilisateur et le mot de passe, le $scope modèle ne sera pas modifié sur la base de l'autofill.

le seul hack sale que j'ai trouvé est d'utiliser la directive suivante:

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

le problème est que Le ngModel.$setViewValue(element.val()); ne change pas le modèle ni la vue basée sur la element.val() valeur retournée. Comment puis-je le réaliser?

110
demandé sur lucassp 2013-02-19 23:40:23

23 réponses

apparemment c'est un problème connu avec L'angle et est actuellement ouvert

Je ne suis pas sûr de ce que vous pourriez faire ici à part une sorte de travail autour comme vous essayez. Il semble que vous êtes sur la bonne voie. Je n'ai pas pu obtenir mon navigateur pour essayer de se rappeler un mot de passe pour votre plugin, donc je ne suis pas sûr que cela fonctionnera, mais avoir un regard:

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>

je pense que vous avez juste besoin de simplifier votre approche un peu. Le une chose que je recommande certainement est de vérifier ngModel.$pristine et assurez-vous que vous ne écrasez pas une entrée d'utilisateur pauvre. De plus, 3 secondes est probablement trop long. Vous ne devriez pas avoir à appeler $apply() dans un $timeout, BTW, il devrait faire la file d'attente d'un $digest pour vous automatiquement.

Le vrai catch: votre navigateur battre Angulaire de l'exécution? Ce sujet de mon navigateur?

c'est probablement une guerre Impossible à gagner, ce qui explique pourquoi Angular (ou Knockout) n'a pas été capable de le résoudre facilement. Il n'y a aucune garantie de l'état des données dans votre entrée au moment de la directive de l'exécution initiale. Pas même au moment de l'Angulaire de l'initialisation.... Donc c'est un problème délicat à résoudre.

45
répondu Ben Lesh 2014-01-26 09:53:11

Voici une solution qui est beaucoup moins ringarde que d'autres solutions présentées et est sémantiquement bonne 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);
    }
  };
});

il vous suffit alors de joindre la directive à votre formulaire:

<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>
35
répondu Ezekiel Victor 2014-10-24 07:11:31

Vous n'avez pas à utiliser un $timeout ou quelque chose comme cela. Vous pouvez utiliser un système d'événements.

je pense que c'est plus anguleux et ne dépend pas de jQuery ou custom event catching.

par exemple sur votre gestionnaire soumettre:

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

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

et ensuite vous pouvez avoir une directive autofill comme ceci:

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

enfin, votre HTML sera comme:

<input type="text" name="username" ng-model="user.id" autofill="autofill"/>
35
répondu bekos 2014-10-24 07:34:23

code Sale, vérifiez si l'émission https://github.com/angular/angular.js/issues/1460#issuecomment-18572604 est fixé avant d'utiliser ce code. Cette directive déclenche des événements lorsque le champ est rempli, non seulement avant de soumettre (il est nécessaire si vous devez gérer les entrées avant de soumettre)

 .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);
                   }
               };
});
10
répondu OZ_ 2013-08-14 13:04:04

plus besoin de pirater! Angular dev 151970920 "tbosch fait un polyfill qui déclenche un événement de changement lorsque le navigateur change les champs de formulaire sans déclencher un événement de changement:

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

pour l'instant ils ne vont pas l'intégrer dans le code angulaire, car c'est un bugfix pour le navigateur, et fonctionne aussi sans Angular (par exemple pour des applications jQuery simples).

"Le polyfill va rechercher les modifications sur le document de charge et également lorsqu'une entrée est à gauche (dans la même forme). Cependant, vous pouvez déclencher le vérifier manuellement si vous le souhaitez.

le projet a des tests unitaires ainsi que des tests semi-automatiques, donc nous avons enfin un endroit pour recueillir tous les cas d'utilisation différents avec les paramètres requis du navigateur.

s'il vous Plaît note: Cette polyfill fonctionne avec la plaine Les applications AngularJS, avec les applications AngularJS/jQuery mais aussi avec les applications jQuery simples qui n'utilisent pas D'Angular."

il peut être installé avec:

bower install autofill-event --save

ajouter le script autofill-event.js après jQuery ou Angular dans votre page.

cela fera ce qui suit:

  • après DOMContentLoaded: vérifiez tous les champs d'entrée
  • un champ est laissé: cochez tous les autres champs sous la même forme

API (pour déclencher manuellement la case):

  • $el.checkAndTriggerAutoFillEvent() : exécutez la vérification pour tous les éléments DOM dans l'élément jQuery / jQLite donné.

Comment ça marche

  1. " souvenez-vous de toutes les modifications apportées aux éléments d'entrée par l'utilisateur (en écoutant les événements change) et aussi par JavaScript (en interceptant $el.val() pour jQuery / jQLite élément.) Cette valeur modifiée est stockée sur l'élément dans une propriété privée.

  2. vérification d'un élément pour le remplissage automatique: comparez la valeur courante de l'élément avec la valeur mémorisée. Si c'est différent, déclencher un événement de changement.

dépendances

AngularJS ou jQuery (fonctionne avec l'un ou les deux)

Plus d'infos et source sur le github page .

d'Origine Angulaire de la Question n ° 1460 sur Github peut être lu ici.

10
répondu orszaczky 2014-09-05 13:47:10

ressemble à une solution claire et droite. Pas de jQuery nécessaire.

mise à jour:

  • Modèle est mis à jour uniquement lorsque le modèle de la valeur n'est pas égale à l'entrée effective valeur.
  • vérification ne s'arrête pas sur le premier autofill. Dans le cas où si vous souhaitez utiliser un autre compte par exemple.

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();
        }
    }
}]);
5
répondu gorpacrate 2013-12-04 08:45:03

Solution 1 [à l'Aide de $timeout]:

Directive:

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>

Solution 2 [utilisant des événements angulaires]:

Réf: réponse de Becko

Directive:

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>

Solution 3 [utilisant la méthode des appels relais]:

Directive:

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>
4
répondu Robin Rizvi 2017-05-23 11:54:56

Eh bien, la façon la plus facile c'est d'émuler le comportement du navigateur, donc s'il y a un problème avec l'événement de changement, juste le feu vous-même. Beaucoup plus simple.

Directive:

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>

vous pouvez faire quelques variations, comme mettre la directive sur le formulaire et tirer l'événement sur toutes les entrées avec le .classe sale sur le formulaire soumettre.

4
répondu pedroassis 2017-05-11 18:23:27

C'est jQuery way :

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

C'est Angulaire :

 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 /> 
3
répondu Nishchit Dhanani 2014-07-09 09:45:50

voici une autre solution qui est moins hacky, mais nécessite un peu de code supplémentaire dans le contrôleur.

HTML:

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

Directive (CoffeeScript):

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

contrôleur:

controllers.controller 'FormController', [->
    $scope.submitForm = ->
        username = $scope.getUsername?() ? $scope.username
        # HTTP stuff...
]
1
répondu jab 2013-06-19 19:11:22

C'est la seule solution que j'ai trouvé qui a permis à toutes mes validations angulaires de fonctionner comme prévu, y compris Désactiver/Activer le bouton Soumettre. S'installe avec la balise bower et 1 script tag. Bazinga!

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

1
répondu Roland Hordos 2014-03-24 01:43:48

changer la valeur du modèle, au lieu d'utiliser une fonction de temporisation a fonctionné pour moi.

Voici mon code:

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
répondu Swaron Chen 2015-08-04 14:25:39

solution de contournement à une doublure dans le gestionnaire de soumission (nécessite jQuery):

if (!$scope.model) $scope.model = $('#input_field').val();
1
répondu archpollux 2017-05-11 18:34:54

je force un $setValue(val()) sur soumettre: (cela fonctionne sans 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
répondu malix 2013-10-16 17:45:57

je suis très nouveau à Angularjs, mais j'ai trouvé une solution simple à ce problème=> Force angulaire pour réévaluer l'expression... par de le changer! (bien sûr, vous devez vous rappeler la valeur initiale pour revenir à l'état initial) Voici la façon dont il va dans votre fonction de contrôleur pour soumettre le formulaire:

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

où, bien sûr, la valeur saisie dans votre mot de passe a été définie par windows et non par l'utilisateur.

0
répondu Le neveu 2014-02-05 09:30:48

vous pouvez essayer ce code:

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
répondu Rohit 2014-03-20 10:40:39

une modification mineure à cette réponse ( https://stackoverflow.com/a/14966711/3443828 ): utilisez un $interval au lieu d'un $timeout pour ne pas avoir à faire la course avec le navigateur.

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
répondu user3443828 2017-05-23 11:47:15

C'est la solution que j'ai fini par utiliser dans mes formulaires.

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

et le modèle du formulaire sera

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

de cette façon j'ai pu lancer tous les analyseurs/formatteurs que j'ai sur mon modèle ng et avoir la fonctionnalité submit transparente.

0
répondu Anirudhan J 2014-04-08 10:58:18

Solution sans directives:

.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
répondu ReklatsMasters 2014-04-10 11:13:53

il s'agit d'une correction simple qui fonctionne pour tous les cas que j'ai testés dans les deux Firefox et Chrome. Notez qu'avec le haut de réponse (directive w/ timeout) j'ai eu des problèmes avec

  • boutons de retour / avant du navigateur, ne tirez pas sur les événements de chargement de la page (de sorte que le correctif ne s'applique pas)
  • chargement des justificatifs d'identité un certain temps après le chargement de la page. par exemple, dans Firefox, double-cliquez sur la boîte de connexion et sélectionnez parmi les informations stockées.
  • besoin une solution qui se met à jour avant la soumission du formulaire puisque je désactive le bouton de connexion jusqu'à ce que l'entrée valide fourni

ce correctif est évidemment très stupide et bâclé, mais il fonctionne 100% du temps -

function myScope($scope, $timeout) {
    // ...
    (function autoFillFix() {
        $timeout(function() { 
            $('#username').trigger('change'); 
            $('#password').trigger('change'); 
            autoFillFix(); }, 500);                    
    })();
}
0
répondu Richard Nichols 2014-04-24 00:27:23

aucune de ces solutions ne fonctionnait pour mon cas d'utilisation. J'ai quelques champs de formulaire qui utilisent ng-change pour surveiller le changement. Utiliser $watch n'est d'aucune aide car il n'est pas déclenché par autofill. Depuis je n'ai pas de bouton "soumettre" il n'est pas facile à exécuter certaines des solutions et je n'ai pas réussi à l'aide d'intervalles.

j'ai fini par désactiver auto - remplir-pas idéal mais beaucoup moins déroutant pour l'utilisateur.

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

trouvé la réponse ici

0
répondu cyberwombat 2017-05-23 12:10:11

si vous utilisez jQuery vous pouvez le faire sur le formulaire soumettre:

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
répondu Hans Kerkhof 2013-10-21 15:48:16

si vous voulez rester simple, il suffit d'obtenir la valeur en utilisant javascript

dans votre contrôleur Angulaire js:

var username = document.getElementById ("username").valeur;

-1
répondu user361697 2014-10-04 07:17:30