Como fazer filtragem bidirecional no AngularJS?

Uma das coisas interessantes que o AngularJS pode fazer é aplicar um filtro a uma determinada expressão de binding de dados, que é uma maneira conveniente de aplicar, por exemplo, moeda específica da cultura ou formatação de data das propriedades de um modelo. Também é bom ter propriedades calculadas no escopo. O problema é que nenhum desses resources funciona com cenários de binding de dados bidirecionais – somente binding de dados unidirecional do escopo para a visão. Esta parece ser uma omissão gritante em uma excelente biblioteca – ou estou faltando alguma coisa?

No KnockoutJS , eu poderia criar uma propriedade computada de leitura / gravação, que me permitia especificar um par de funções, uma que é chamada para obter o valor da propriedade e uma que é chamada quando a propriedade é definida. Isso me permitiu implementar, por exemplo, input com reconhecimento de cultura – permitindo que o usuário digitasse “$ 1,24” e analisasse isso em um ponto flutuante no ViewModel, e fizesse alterações no ViewModel refletido na input.

A coisa mais próxima que eu poderia encontrar semelhante a isso é o uso de $scope.$watch(propertyName, functionOrNGExpression); Isso me permite ter uma function invocada quando uma propriedade no $scope muda. Mas isso não resolve, por exemplo, o problema de input com reconhecimento de cultura. Observe os problemas quando tento modificar a propriedade $watched no próprio método $watch :

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

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

O elemento de input fica muito confuso quando o usuário começa a digitar. Eu o aprimorei dividindo a propriedade em duas propriedades, uma para o valor não analisado e outra para o valor analisado:

 $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/ )

Isso foi uma melhoria em relação à primeira versão, mas é um pouco mais detalhado e observe que ainda há um problema na propriedade parsedValue das alterações no escopo (digite algo na segunda input, que altera o parsedValue diretamente. Observe a input superior não atualiza). Isso pode acontecer a partir de uma ação do controlador ou do carregamento de dados de um serviço de dados.

Existe alguma maneira mais fácil de implementar esse cenário usando o AngularJS? Estou faltando alguma funcionalidade na documentação?

Acontece que há uma solução muito elegante para isso, mas não está bem documentada.

Os valores do modelo de formatação para exibição podem ser manipulados pelo | operador e um formatter angular. Acontece que o ngModel que tem não apenas uma lista de formatadores, mas também uma lista de analisadores.

1. Use ng-model para criar a binding de dados bidirecional

  

2. Crie uma diretiva em seu módulo angular que será aplicada ao mesmo elemento e que depende do controlador ngModel

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

3. No método link , inclua seus conversores customizados no controlador ngModel

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

4. Adicione sua nova diretiva ao mesmo elemento que já possui o ngModel

  

Aqui está um exemplo de trabalho que transforma texto em letras minúsculas na input e de volta para maiúsculas no modelo

A documentação da API para o controlador de modelo também possui uma breve explicação e uma visão geral dos outros methods disponíveis.