Como fazer a filtragem bidirecional no AngularJS?

124

Uma das coisas interessantes que o AngularJS pode fazer é aplicar um filtro a uma expressão de ligação de dados específica, 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 computadas no escopo. O problema é que nenhum desses recursos funciona com cenários de ligação de dados bidirecionais - apenas ligação de dados unidirecional do escopo para a visualização. Parece uma omissão flagrante em uma biblioteca excelente - ou estou perdendo alguma coisa?

No KnockoutJS , eu poderia criar uma propriedade calculada de leitura / gravação, o que me permitiu especificar um par de funções, uma que é chamada para obter o valor da propriedade e outra que é chamada quando a propriedade é definida. Isso me permitiu implementar, por exemplo, entrada com reconhecimento de cultura - deixando o usuário digitar "$ 1,24" e analisá-lo em um carro alegórico no ViewModel e ter alterações no ViewModel refletidas na entrada.

A coisa mais próxima que eu poderia achar semelhante é o uso de $scope.$watch(propertyName, functionOrNGExpression);Isso me permite ter uma função invocada quando uma propriedade nas $scopealterações. Mas isso não resolve, por exemplo, o problema de entrada que reconhece a cultura. Observe os problemas quando tento modificar a $watchedpropriedade dentro do $watchpróprio método:

$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 entrada fica muito confuso quando o usuário começa a digitar. Eu a 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 detalhada, e observe que ainda há um problema na parsedValuepropriedade das alterações do escopo (digite algo na segunda entrada, que altera parsedValuediretamente. Observe que a entrada superior não atualizar). 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?

Jeremy Bell
fonte

Respostas:

231

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

A formatação dos valores do modelo para exibição pode ser manipulada pelo |operador e por um angular formatter. Acontece que o ngModel que possui não apenas uma lista de formatadores, mas também uma lista de analisadores.

1. Use ng-modelpara criar a ligação de dados bidirecional

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

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

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

3. Dentro do linkmétodo, adicione seus conversores personalizados ao ngModelcontrolador

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

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

Aqui está um exemplo de trabalho que transforma texto em minúsculas no inpute de volta em maiúsculas no modelo

A documentação da API para o Model Controller também possui uma breve explicação e uma visão geral dos outros métodos disponíveis.

phaas
fonte
Existe algum motivo para você usar "ngModel" como o nome do quarto parâmetro na sua função de vinculação? Não é apenas um controlador genérico para a diretiva que basicamente não tem nada a ver com o atributo ngModel? (Ainda aprendendo angular aqui, então eu poderia ser totalmente errado.)
Drew Miller
7
Por causa de "require: 'ngModel'", o quarto parâmetro da função de ligação será o controlador da diretiva ngModel - ou seja, o controlador da foo.bar , que é uma instância do ngModelController . Você pode nomear o quarto parâmetro como desejar. (Eu ngModelCtrl
nomearia
8
Essa técnica está documentada em docs.angularjs.org/guide/forms , na seção Validação personalizada.
Nikhil Dabas
1
@ Mark Rajcok no violino fornecido, ao clicar em Carregar dados - tudo em minúsculas, esperava que o valor do modelo estivesse em TODOS OS CAPS, mas o valor do modelo era pequeno. Você poderia pls. explicar por que, e como fazer o modelo sempre no CAPS
Rajkamal Subramanian
1
@rajkamal, como loadData2 () modifica $scopediretamente, é para isso que o modelo será definido ... até que o usuário interaja com a caixa de texto. Nesse ponto, qualquer analisador pode afetar o valor do modelo. Além de um analisador, você pode adicionar um $ watch ao seu controlador para transformar o valor do modelo.
precisa saber é o seguinte