web-dev-qa-db-fra.com

Solution de contournement automatique du navigateur AngularJS à l'aide d'une directive

Lorsque vous soumettez un formulaire dans AngularJS et utilisez la fonctionnalité de mémorisation du mot de passe du navigateur, vous devez laisser le navigateur remplir le formulaire de connexion avec le nom d'utilisateur et le mot de passe. Le modèle $scope ne sera pas modifié en fonction du remplissage automatique.

Le seul bidouillage 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 la fonction ngModel.$setViewValue(element.val()); ne modifie ni le modèle ni la vue basée sur la valeur renvoyée par element.val(). Comment puis-je accomplir cela?

110
lucassp

Apparemment, c’est un problème connu avec Angular et il est actuellement ouvert

Je ne sais pas ce que vous pourriez faire ici, à part un travail comme vous essayez. Il semble que vous soyez sur la bonne voie. Je n'arrive pas à faire en sorte que mon navigateur essaie de retenir un mot de passe pour votre plunk, alors je ne sais pas si cela fonctionnera, mais jetez un coup d'oeil:

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 un peu votre approche. La seule chose que je recommande vraiment est de vérifier ngModel.$pristine et de vous assurer que vous n'écrasez pas les données d'un mauvais utilisateur. En outre, 3 secondes est probablement trop long. Vous ne devriez pas avoir à appeler $ apply () dans un délai d'attente de $, BTW, il devrait automatiquement mettre un $ digest en file d'attente.

Le vrai problème: votre navigateur battra-t-il Angular à l'exécution? Qu'en est-il de mon navigateur?

C'est probablement une guerre impossible à gagner, c'est pourquoi Angular (ou Knockout) n'a pas été en mesure de la résoudre rapidement. Il n'y a aucune garantie de l'état des données dans votre entrée au moment de l'exécution initiale de la directive. Pas même au moment de l'initialisation d'Angular ... C'est donc un problème délicat à résoudre.

45
Ben Lesh

Voici une solution beaucoup moins astucieuse que les autres solutions présentées et sémantiquement saine 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);
    }
  };
});

Ensuite, vous attachez simplement 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
Ezekiel Victor

Vous n'êtes pas obligé d'utiliser un $timeout ou quoi que ce soit du genre. Vous pouvez utiliser un système d'événements.

Je pense que c'est plus angulaire et ne dépend pas de la capture d'événements jQuery ou personnalisés.

Par exemple sur votre gestionnaire d'envoi:

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

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

Et vous pouvez alors 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 code HTML ressemblera à:

<input type="text" name="username" ng-model="user.id" autofill="autofill"/>
35
bekos

Plus besoin de bidouiller! Angular dev TBOSCH a créé un polyfill qui déclenche un événement change lorsque le navigateur modifie les champs de formulaire sans déclencher d'événement:

_ { https://github.com/tbosch/autofill-event } _

Pour l'instant, ils ne l'intégreront pas dans le code angulaire, car il s'agit d'un correctif pour le navigateur et fonctionne également sans Angular (par exemple, pour les applications jQuery simples).

"Le polyfill vérifiera les modifications apportées lors du chargement du document et lorsqu’une entrée est laissée (sous la même forme). Cependant, vous pouvez déclencher la vérification manuellement si vous le souhaitez.

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

Remarque: ce polyfill fonctionne avec les applications simples AngularJS, AngularJS/jQuery mais également avec les applications jQuery simples qui n'utilisent pas Angular. "

Il peut être installé avec:

bower install autofill-event --save

Ajoutez le script autofill-event.jsafter jQuery ou Angular dans votre page.

Cela fera ce qui suit:

  • après DOMContentLoaded: vérifie tous les champs de saisie
  • un champ est laissé: cochez tous les autres champs du même formulaire

API (pour déclencher manuellement la vérification):

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

Comment ça marche

  1. Mémorisez toutes les modifications apportées aux éléments en entrée par l'utilisateur (écoute des événements de modification) et également par JavaScript (en interceptant $ el.val () pour les éléments jQuery/jQLite). Cette valeur modifiée est stockée sur l'élément dans une propriété privée.

  2. Vérification du remplissage automatique d'un élément: Compare la valeur actuelle de l'élément avec la valeur mémorisée. Si c'est différent, déclenchez un événement de changement.

Les dépendances

AngularJS ou jQuery (fonctionne avec un ou les deux)

Plus d'infos et source sur la page github .

Numéro angulaire original n ° 1460 sur Github à lire ici.

10
orszaczky

Code sale, vérifiez si le problème https://github.com/angular/angular.js/issues/1460#issuecomment-18572604 est corrigé avant d'utiliser ce code . Cette directive déclenche des événements lorsque le champ est rempli, non seulement avant de soumettre (c'est nécessaire si vous devez gérer l'entrée 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
OZ_

Cela semble être une solution claire et directe. Pas besoin de jQuery.

METTRE À JOUR:

  • Le modèle est mis à jour uniquement lorsque la valeur du modèle n'est pas égale à la valeur d'entrée Réelle.
  • La vérification ne s'arrête pas au premier remplissage automatique. Au cas où vous souhaiteriez 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
gorpacrate

Solution 1 [Utilisation 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 [Utilisation d'é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 [Utilisation des appels de méthode 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
Robin Rizvi

Eh bien, le moyen le plus simple consiste à imiter le comportement du navigateur. Par conséquent, s'il y a un problème avec l'événement change, déclenchez-le vous-même. Beaucoup plus simple.

Directif:

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 certaines variations, comme mettre la directive sur le formulaire et déclencher l'événement sur toutes les entrées avec la classe .dirty lors de l'envoi du formulaire.

4
pedroassis

Ceci est la façon jQuery:  

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

Ceci est la manière 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
Nishchit Dhanani

Voici une autre solution de contournement moins efficace, mais nécessitant du 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()

Manette:

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

C’est la seule solution que j’ai trouvée qui permette à toutes les validations de mon Angulaire de fonctionner comme prévu, y compris les boutons désactiver/activer le bouton Envoyer. Installe avec bower et 1 balise de script. Bazinga!

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

1
Roland Hordos

Solution de contournement à une ligne dans le gestionnaire de soumission (requiert jQuery):

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

Changer la valeur du modèle au lieu d'utiliser une fonction de délai d'attente 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
Swaron Chen

Je force un $ setValue (val ()) sur submit: (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
malix

Il s’agit d’une solution simple qui fonctionne dans tous les cas que j’ai testés sous Firefox et Chrome. Notez qu'avec la réponse du haut (directive w/timeout), j'ai eu des problèmes avec - 

  • Boutons Précédent/Suivant du navigateur, ne ré-activez pas les événements de chargement de page (le correctif ne s'applique donc pas)
  • Chargement des identifiants quelque temps après le chargement de la page. par exemple. dans Firefox, double-cliquez sur la zone de connexion et sélectionnez un identifiant stocké.
  • Besoin d'une solution à mettre à jour avant la soumission du formulaire, car je désactive le bouton de connexion jusqu'à la saisie d'une entrée valide

Ce correctif est évidemment très stupide et hacky, mais il fonctionne 100% du temps - 

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

Une modification mineure à cette réponse ( https://stackoverflow.com/a/14966711/3443828 ): utilisez un intervalle $ au lieu d'un délai d'attente $ pour ne pas avoir à courir 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
user3443828

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
Rohit

Je suis très nouveau dans Angularjs, mais j’ai trouvé une solution simple à ce problème => Forcer Angular à réévaluer l’expression ... en le modifiant! (Bien sûr, vous devez vous rappeler la valeur initiale pour revenir à la valeur initiale. état) Voici comment cela se passe dans la fonction de votre 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 entrée dans votre mot de passe a été définie par Windows et non par l'utilisateur.

0
Le neveu

C'est la solution que j'ai finalement utilisée 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 de 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 exécuter tous les analyseurs/formateurs que j'ai sur mon modèle et avoir la fonctionnalité d'envoi transparente.

0
Anirudhan J

Aucune de ces solutions n'a fonctionné pour mon cas d'utilisation. Certains champs de formulaire utilisent ng-change pour surveiller les modifications. Utiliser $watch n'est pas une aide, car il n'est pas déclenché par le remplissage automatique. Comme je n'ai pas de bouton d'envoi, il n'existe pas de moyen facile d'exécuter certaines des solutions et je n'ai pas réussi à utiliser les intervalles.

J'ai fini par désactiver le remplissage automatique - pas idéal mais beaucoup moins déroutant pour l'utilisateur.

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

Trouvé la réponse ici

0
cyberwombat

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
ReklatsMasters