javascript - open - ng-form angularjs
SoluciĆ³n de autocompletar de navegador AngularJS mediante el uso de una directiva (23)
Cuando envíe un formulario en AngularJS y utilice el navegador recuerde la funcionalidad de la contraseña, y en un intento de inicio de sesión posterior permita que el navegador complete el formulario de inicio de sesión con el nombre de usuario y la contraseña, el modelo $scope
no cambiará en función del autorrelleno.
El único truco sucio que encontré es usar la siguiente directiva:
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);
}
};
}]);
El problema es que ngModel.$setViewValue(element.val());
no cambia el modelo ni la vista en función del element.val()
valor devuelto. ¿Cómo puedo lograr eso?
Aquí hay otra solución que es menos hacky, pero requiere un código adicional en el controlador.
HTML:
<form ng-submit="submitForm()" ng-controller="FormController">
<input type="text" ng-model="username" autocomplete-username>
<input type="submit">
</form>
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...
]
Aquí hay una solución que es mucho menos hacky que otras soluciones presentadas y es semánticamente sólida 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);
}
};
});
Luego, simplemente adjunte la directiva a su formulario:
<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>
Bueno, la forma más fácil es emular el comportamiento del navegador, por lo que si hay un problema con el evento de cambio, solo ignóralo. Mucho más simple
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:
<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>
Puede hacer algunas variaciones, como colocar la directiva en el formulario y activar el evento en todas las entradas con la clase .dirty en el envío del formulario.
Código sucio, compruebe si se solucionó el problema https://github.com/angular/angular.js/issues/1460#issuecomment-18572604 antes de usar este código. Esta directiva desencadena eventos cuando se llena el campo, no solo antes del envío (es necesario si debe manejar la entrada antes de enviarla)
.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);
}
};
});
Cambiar el valor del modelo, en lugar de utilizar una función de tiempo de espera, funcionó para mí.
Aquí está mi 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;
}
}
};
}]);
Esta es la única solución que he encontrado que permite que todas mis validaciones Angular ''funcionen según lo diseñado, incluida la desactivación / habilitación del botón de envío. Se instala con bower y 1 etiqueta de script. Bazinga!
Esta es la solución que terminé usando en mis formularios.
.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''
};
}]);
Y la plantilla del formulario será
<form autofill-sync name="user.loginForm" class="login-form" novalidate ng-submit="signIn()">
<!-- Input fields here -->
</form>
De esta forma, pude ejecutar cualquier analizador / formateador que tengo en mi ng-model y hacer que la funcionalidad de envío sea transparente.
Esta es una solución simple que funciona para todos los casos que he probado tanto en Firefox como en Chrome. Tenga en cuenta que con la respuesta superior (directiva w / timeout) tuve problemas con -
- Botones de navegación hacia atrás / adelante, no reiniciar eventos de carga de página (por lo que la corrección no se aplica)
- Carga de credenciales un poco después de la carga de la página. por ejemplo, en Firefox, haga doble clic en el cuadro de inicio de sesión y seleccione entre las credenciales almacenadas.
- Necesito una solución que se actualice antes de enviar el formulario, ya que deshabilito el botón de inicio de sesión hasta que se proporcione una entrada válida
Esta solución es obviamente muy tonta y hacky, pero funciona el 100% del tiempo,
function myScope($scope, $timeout) {
// ...
(function autoFillFix() {
$timeout(function() {
$(''#username'').trigger(''change'');
$(''#password'').trigger(''change'');
autoFillFix(); }, 500);
})();
}
Forzo un $ setValue (val ()) en submit: (esto funciona sin 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);
Ninguna de estas soluciones funcionó para mi caso de uso. Tengo algunos campos de formulario que usan ng-change para observar el cambio. Usar $watch
no es de ayuda, ya que no se desencadena por autocompletar. Como no tengo un botón de envío, no hay una manera fácil de ejecutar algunas de las soluciones y no tuve éxito al usar los intervalos.
Terminé desactivando el Autocompletar, no ideal, pero mucho menos confuso para el usuario.
<input readonly onfocus="this.removeAttribute(''readonly'');">
Encontré la respuesta here
No tiene que usar un $timeout
ni nada de esto. Puedes usar un sistema de eventos.
Creo que es más anguloso y no depende de jQuery ni de la captura personalizada de eventos.
Por ejemplo, en tu controlador de envío:
$scope.doLogin = function() {
$scope.$broadcast("autofill:update");
// Continue with the login.....
};
Y luego puedes tener una directiva 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, su HTML será como:
<input type="text" name="username" ng-model="user.id" autofill="autofill"/>
Parece una solución clara y directa. No se necesita jQuery.
ACTUALIZAR:
- El modelo se actualiza solo cuando el valor del modelo no es igual al valor de entrada real.
- La verificación no se detiene en el primer autocompletado. En caso de que desee utilizar otra cuenta, por ejemplo.
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();
}
}
}]);
Puedes probar 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'');
}
}
}
});
Si está utilizando jQuery, puede hacerlo al enviar el formulario:
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();
}
Si quiere mantenerlo simple, solo obtenga el valor usando javascript
En su controlador angular js:
var username = document.getElementById (''username''). value;
Solución de una sola línea en el controlador de envío (requiere jQuery):
if (!$scope.model) $scope.model = $(''#input_field'').val();
Solución sin directivas:
.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);
}])
Soy muy nuevo en Angularjs, pero encontré una solución simple para ese problema => Force Angular para reevaluar la expresión ... ¡cambiándola! (por supuesto, debe recordar el valor inicial para volver al estado inicial) Aquí está la forma en que va en su función de controlador para enviar el formulario:
$scope.submit = function () {
var oldpassword = $scope.password;
$scope.password = '''';
$scope.password = oldpassword;
//rest of your code of the submit function goes here...
donde, por supuesto, el valor ingresado en la entrada de su contraseña ha sido establecido por Windows y no por el usuario.
Una modificación menor a esta respuesta ( https://.com/a/14966711/3443828 ): use un $ interval en lugar de un $ timeout para que no tenga que competir con el 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
}
});
Aparentemente este es un problema conocido con Angular y actualmente está abierto
No estoy seguro de lo que podrías hacer aquí además de algún tipo de trabajo como lo estás intentando. Parece que estás en el camino correcto. No pude conseguir que mi navegador intentara recordar una contraseña para su plunk, así que no estoy seguro si esto funcionará, pero eche un vistazo:
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>
Creo que solo necesitas simplificar tu enfoque un poco. Lo único que definitivamente recomiendo es comprobar ngModel.$pristine
y asegurarse de no sobrescribir la entrada de algún usuario pobre. Además, 3 segundos es probablemente demasiado largo. No debería tener que llamar a $ apply () en un $ timeout, por cierto, debería hacer una cola de $ digest automáticamente.
El truco real: ¿su navegador batirá Angular a la ejecución? ¿Qué hay de mi navegador?
Esta es probablemente una guerra imposible de ganar, por lo que Angular (o Knockout) no ha podido resolverlo fácilmente. No hay garantía del estado de los datos en su entrada en el momento de la ejecución inicial de la directiva. Ni siquiera en el momento de la inicialización de Angular ... Así que es un problema complicado de resolver.
¡Ya no es necesario hackear! Angular dev tbosch hizo un polyfill que desencadena un evento de cambio cuando el navegador cambia campos de formulario sin activar un evento de cambio:
https://github.com/tbosch/autofill-event
Por ahora no van a construir esto en el código angular, ya que esto es una corrección de errores para el navegador, y también funciona sin Angular (por ejemplo, para aplicaciones jQuery simples).
"El polyfill verificará los cambios en la carga del documento y también cuando quede una entrada (solo en el mismo formulario). Sin embargo, puede activar el cheque manualmente si así lo desea.
El proyecto tiene pruebas unitarias, así como pruebas semiautomáticas, por lo que finalmente tenemos un lugar para recopilar todos los casos de uso diferentes junto con la configuración requerida del navegador.
Tenga en cuenta: este polyfill funciona con aplicaciones sencillas de AngularJS, con aplicaciones AngularJS / jQuery pero también con aplicaciones jQuery simples que no usan Angular ".
Se puede instalar con:
bower install autofill-event --save
Agregue el script autofill-event.js
después de jQuery o Angular en su página.
Esto hará lo siguiente:
- después de DOMContentLoaded: verifique todos los campos de entrada
- un campo queda: verifique todos los demás campos en la misma forma
API (para activar manualmente el cheque):
-
$el.checkAndTriggerAutoFillEvent()
:$el.checkAndTriggerAutoFillEvent()
la comprobación de todos los elementos DOM en el elemento jQuery / jQLite dado.
Cómo funciona
Recuerde todos los cambios en los elementos de entrada por parte del usuario (escuchando eventos de cambio) y también por JavaScript (interceptando $ el.val () para elementos jQuery / jQLite). Ese valor cambiado se almacena en el elemento en una propiedad privada.
Verificación de un elemento para autocompletar: compare el valor actual del elemento con el valor recordado. Si es diferente, active un evento de cambio.
Dependencias
AngularJS o jQuery (funciona con uno o ambos)
Más información y fuente en la https://github.com/tbosch/autofill-event .
La edición angular original n.º 1460 de Github se puede leer aquí.
Esta es una manera jQuery:
$(window).load(function() {
// updates autofilled fields
window.setTimeout(function() {
$(''input[ng-model]'').trigger(''input'');
}, 100);
});
Esta es una forma 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 />
Solución 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:
<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>
Solución 2 [Uso de eventos angulares]:
Ref:
Directiva:
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>
Solución 3 [Uso de llamadas de método de retransmisión]:
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:
<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>