¿Cómo hacer un filtrado bidireccional en AngularJS?

124

Una de las cosas interesantes que AngularJS puede hacer es aplicar un filtro a una expresión de enlace de datos en particular, que es una forma conveniente de aplicar, por ejemplo, el formato de fecha o moneda específico de la cultura de las propiedades de un modelo. También es bueno tener propiedades calculadas en el ámbito. El problema es que ninguna de estas características funciona con escenarios de enlace de datos bidireccionales, solo enlace de datos unidireccional desde el alcance hasta la vista. Esto parece ser una omisión flagrante en una biblioteca de otra manera excelente, ¿o me estoy perdiendo algo?

En KnockoutJS , pude crear una propiedad calculada de lectura / escritura, que me permitió especificar un par de funciones, una que se llama para obtener el valor de la propiedad y otra que se llama cuando se establece la propiedad. Esto me permitió implementar, por ejemplo, una entrada consciente de la cultura, lo que permitió al usuario escribir "$ 1.24" y analizarla en un flotante en ViewModel, y tener cambios en ViewModel reflejados en la entrada.

Lo más parecido que puedo encontrar similar a esto es el uso de $scope.$watch(propertyName, functionOrNGExpression);Esto me permite tener una función invocada cuando una propiedad en los $scopecambios. Pero esto no resuelve, por ejemplo, el problema de entrada consciente de la cultura. Observe los problemas cuando intento modificar la $watchedpropiedad dentro del $watchpropio método:

$scope.$watch("property", function (newValue, oldValue) {
    $scope.outputMessage = "oldValue: " + oldValue + " newValue: " + newValue;
    $scope.property = Globalize.parseFloat(newValue);
});

( http://jsfiddle.net/gyZH8/2/ )

El elemento de entrada se confunde mucho cuando el usuario comienza a escribir. Lo mejoré dividiendo la propiedad en dos propiedades, una para el valor no analizado y otra para el valor analizado:

$scope.visibleProperty= 0.0;
$scope.hiddenProperty = 0.0;
$scope.$watch("visibleProperty", function (newValue, oldValue) {
    $scope.outputMessage = "oldValue: " + oldValue + " newValue: " + newValue;
    $scope.hiddenProperty = Globalize.parseFloat(newValue);
});

( http://jsfiddle.net/XkPNv/1/ )

Esto fue una mejora con respecto a la primera versión, pero es un poco más detallado, y observe que todavía hay un problema con la parsedValuepropiedad de los cambios de alcance (escriba algo en la segunda entrada, que cambia la entrada parsedValuedirectamente. Observe que la entrada superior no actualizar). Esto puede suceder por una acción del controlador o por cargar datos de un servicio de datos.

¿Hay alguna manera más fácil de implementar este escenario usando AngularJS? ¿Me falta alguna funcionalidad en la documentación?

Jeremy Bell
fuente

Respuestas:

231

Resulta que hay una solución muy elegante para esto, pero no está bien documentado.

Los valores de modelo de formato para visualización pueden ser manejados por el |operador y un angular formatter. Resulta que el ngModel que no solo tiene una lista de formateadores sino también una lista de analizadores.

1. Use ng-modelpara crear el enlace de datos bidireccional

<input type="text" ng-model="foo.bar"></input>

2. Cree una directiva en su módulo angular que se aplicará al mismo elemento y que depende del ngModelcontrolador

module.directive('lowercase', function() {
    return {
        restrict: 'A',
        require: 'ngModel',
        link: function(scope, element, attr, ngModel) {
            ...
        }
    };
});

3. Dentro del linkmétodo, agregue sus convertidores personalizados al ngModelcontrolador

function fromUser(text) {
    return (text || '').toUpperCase();
}

function toUser(text) {
    return (text || '').toLowerCase();
}
ngModel.$parsers.push(fromUser);
ngModel.$formatters.push(toUser);

4. Agregue su nueva directiva al mismo elemento que ya tiene ngModel

<input type="text" lowercase ng-model="foo.bar"></input>

Aquí hay un ejemplo de trabajo que transforma el texto en minúsculas en inputy de nuevo en mayúsculas en el modelo

La documentación API para el controlador modelo también tiene una breve explicación y una descripción general de los otros métodos disponibles.

Phaas
fuente
¿Hay alguna razón por la que usó "ngModel" como nombre para el cuarto parámetro en su función de enlace? ¿No es solo un controlador genérico para la directiva que básicamente no tiene nada que ver con el atributo ngModel? (Todavía estoy aprendiendo angular aquí, así que podría estar totalmente equivocado.)
Drew Miller
77
Debido a "require: 'ngModel'", el cuarto parámetro de la función de enlace será el controlador de la directiva ngModel, es decir, el controlador de foo.bar, que es una instancia de ngModelController . Puedes nombrar el cuarto parámetro como quieras. (Lo nombraría ngModelCtrl)
Mark Rajcok
8
Esta técnica está documentada en docs.angularjs.org/guide/forms , en la sección Validación personalizada.
Nikhil Dabas
1
@Mark Rajcok en el violín proporcionado, al hacer clic en Cargar datos, todo en minúsculas, esperaba que el valor del modelo estuviera en TODAS LAS MAYÚSCULAS, pero el valor del modelo era pequeño. ¿Podrías por favor? explique por qué y cómo hacer que el modelo esté siempre EN MAYÚSCULAS
Rajkamal Subramanian
1
@rajkamal, dado que loadData2 () se modifica $scopedirectamente, eso es lo que se configurará el modelo ... hasta que el usuario interactúe con el cuadro de texto. En ese punto, cualquier analizador puede afectar el valor del modelo. Además de un analizador, puede agregar un $ watch a su controlador para transformar el valor del modelo.
Mark Rajcok