Funções matemáticas nas ligações AngularJS

232

Existe uma maneira de usar funções matemáticas nas ligações do AngularJS?

por exemplo

<p>The percentage is {{Math.round(100*count/total)}}%</p>

Este violino mostra o problema

http://jsfiddle.net/ricick/jtA99/1/

ricick
fonte
11
Outra opção é evitar o uso do Math em seus modelos, usando um filtro:, <p>The percentage is {{(100*count/total)| number:0}}%</p>mais nos comentários abaixo.
Andrew Kuklewicz
2
Para Angular2, defina um membro do componente private Math = Math;e você pode usá-lo.
Ondra Žižka 14/02

Respostas:

329

Você precisa injetar Mathno seu escopo, se não precisar usá-lo, pois $scopenão sabe nada sobre matemática.

Maneira mais simples, você pode fazer

$scope.Math = window.Math;

no seu controlador. Uma maneira angular de fazer isso corretamente seria criar um serviço de matemática, eu acho.

Tosh
fonte
1
Obrigado. Eu tenho um caso de uso em que tenho vários modelos com uma lógica completamente diferente e quero isolá-los o máximo possível. Perfeito
Yablargo
48
Você deve usar um filtro, não colocar o objeto Math no escopo.
Soviut 01/12/2014
4
Isso é bom para coisas rápidas e sujas; zombando rapidamente de algo ou querendo ver como algo funciona. Provavelmente é o que alguém que deseja fazer matemática em seus arquivos de modelo html deseja.
Lan
1
O uso de funções nas ligações fará com que a função chame a atualização de cada escopo e usará muitos recursos.
desmati 20/09/16
Esta solução está errada . porque? 1- Poluir o escopo global é a pior idéia de todos os tempos. As 2 visualizações são responsáveis ​​por formatting, então use o filtro.
Afshin Mehrabani
304

Enquanto a resposta aceita é correta, você pode injetar Mathpara usá-la em ângulo, para esse problema em particular, a maneira mais convencional / angular é o filtro numérico:

<p>The percentage is {{(100*count/total)| number:0}}%</p>

Você pode ler mais sobre o numberfiltro aqui: http://docs.angularjs.org/api/ng/filter/number

Andrew Kuklewicz
fonte
7
Não é apenas o caminho angular, é o caminho correto. É responsabilidade da visualização 'formatar' o valor.
Lucas
39
Andrew, desculpe-me por desorganizar sua postagem com este comentário. Sua resposta é boa e útil; no entanto, desejo discordar dos outros comentários. Costumo me encolher quando as pessoas veem o "caminho angular" como se fosse um exemplo de desenvolvimento perfeito. Não é. O OP perguntou em geral como incluir funções matemáticas em uma ligação, com o arredondamento apresentado apenas como exemplo. Embora essa resposta possa ser "o caminho angular" dos puristas para resolver exatamente um problema de matemática, ela não responde totalmente à pergunta.
precisa saber é o seguinte
1
Desculpe pela arqueologia, mas isso está incorreto quando você precisa de um número como saída. {{1234.567|number:2}}processa como 1,234.57ou 1 234,57. Se você tentar passá-lo, <input type="number" ng-value="1234.567|number:2">você esvaziará a entrada, pois o valor NÃO É UM NÚMERO CORRETO, mas a representação de sequência dependente da localidade.
#
61

Eu acho que a melhor maneira de fazer isso é criando um filtro, assim:

myModule.filter('ceil', function() {
    return function(input) {
        return Math.ceil(input);
    };
});

então a marcação fica assim:

<p>The percentage is {{ (100*count/total) | ceil }}%</p>

Violino atualizado: http://jsfiddle.net/BB4T4/

sabinstefanescu
fonte
O filtro numérico, conforme indicado na resposta da klaxon, já está no limite, portanto não há necessidade disso.
Kim
Fiz algo semelhante, apenas usando o filtro para criar o próprio cronômetro como eu queria. Em outras palavras, eu pegava o horário atual e o formatava em uma sequência que eu considerasse adequada usando um filtro personalizado. Além disso, o filtro numérico é bom quando você deseja arredondar seus números, mas para as funções de piso e teto é inútil.
Gabriel Lovetro
37

Essa é uma pergunta difícil de responder, porque você não deu o contexto completo do que está fazendo. A resposta aceita funcionará, mas em alguns casos causará baixo desempenho. Isso, e será mais difícil de testar.

Se você estiver fazendo isso como parte de um formulário estático, tudo bem. A resposta aceita funcionará, mesmo que não seja fácil de testar, e seja hinky.

Se você deseja ser "angular" sobre isso:

Você deseja manter qualquer "lógica comercial" (isto é, lógica que altera os dados a serem exibidos) fora de suas visualizações. Isso é para que você possa testar sua lógica de unidade e não acabar acoplando firmemente seu controlador e sua visão. Teoricamente, você deve poder apontar seu controlador para outra visão e usar os mesmos valores dos escopos. (se isso faz sentido).

Você também deve considerar que todas as chamadas de função dentro de uma ligação (como {{}}ou ng-bindou ng-bind-html) terão que ser avaliadas em todos os resumos , porque o angular não tem como saber se o valor foi alterado ou não como uma propriedade no escopo.

A maneira "angular" de fazer isso seria armazenar em cache o valor em uma propriedade no escopo da mudança usando um evento ng-change ou mesmo um $ watch.

Por exemplo, com um formulário estático:

angular.controller('MainCtrl', function($scope, $window) {
   $scope.count = 0;
   $scope.total = 1;

   $scope.updatePercentage = function () {
      $scope.percentage = $window.Math.round((100 * $scope.count) / $scope.total);
   };
});
<form name="calcForm">
   <label>Count <input name="count" ng-model="count" 
                  ng-change="updatePercentage()"
                  type="number" min="0" required/></label><br/>
   <label>Total <input name="total" ng-model="total"
                  ng-change="updatePercentage()"
                  type="number" min="1" required/></label><br/>
   <hr/>
   Percentage: {{percentage}}
</form>

E agora você pode testá-lo!

describe('Testing percentage controller', function() {
  var $scope = null;
  var ctrl = null;

  //you need to indicate your module in a test
  beforeEach(module('plunker'));

  beforeEach(inject(function($rootScope, $controller) {
    $scope = $rootScope.$new();

    ctrl = $controller('MainCtrl', {
      $scope: $scope
    });
  }));

  it('should calculate percentages properly', function() {
    $scope.count = 1;
    $scope.total = 1;
    $scope.updatePercentage();
    expect($scope.percentage).toEqual(100);

    $scope.count = 1;
    $scope.total = 2;
    $scope.updatePercentage();
    expect($scope.percentage).toEqual(50);

    $scope.count = 497;
    $scope.total = 10000;
    $scope.updatePercentage();
    expect($scope.percentage).toEqual(5); //4.97% rounded up.

    $scope.count = 231;
    $scope.total = 10000;
    $scope.updatePercentage();
    expect($scope.percentage).toEqual(2); //2.31% rounded down.
  });
});
Ben Lesh
fonte
você pode compilar o elemento e testar o valor encontrado no elemento dom. isso é realmente preferido, pois você também pode usar filtros de localização.
FlavorScape
Boa resposta. Mas querendo saber por que acrescentar uma $windowvez que parece funcionar apenas com um plano Math.round()?
Neel
3
A janela @Neel - $ permite zombar mais facilmente dos Mathtestes. Como alternativa, você pode criar um serviço de matemática e injetá-lo.
Ben Lesh
8

Por que não agrupar todo o objeto de matemática em um filtro?

var app = angular.module('fMathFilters',[]);


function math() {
    return function(input,arg) {
        if(input) {
            return Math[arg](input);
        }

        return 0;
    }
}

return app.filter('math',[math]);

e usar:

{{number_var | math: 'ceil'}}

Marcos Ammon
fonte
8

Melhor opção é usar:

{{(100*score/questionCounter) || 0 | number:0}}

Ele define o valor padrão da equação como 0 no caso em que os valores não são inicializados.

klaxon
fonte
6

A maneira mais fácil de fazer contas simples com o Angular é diretamente na marcação HTML para ligações individuais, conforme necessário, supondo que você não precise fazer cálculos em massa na sua página. Aqui está um exemplo:

{{(data.input/data.input2)| number}} 

Nesse caso, basta fazer as contas em () e usar um filtro | para obter uma resposta numérica. Veja mais informações sobre a formatação de números angulares como texto:

https://docs.angularjs.org/api/ng/filter

Sara Inés Calderón
fonte
4

Ligue o objeto Math global ao escopo (lembre-se de usar $ window not window)

$scope.abs = $window.Math.abs;

Use a ligação no seu HTML:

<p>Distance from zero: {{abs(distance)}}</p>

Ou crie um filtro para a função matemática específica que você procura:

module.filter('abs', ['$window', function($window) {
  return function(n) {
    return $window.Math.abs($window.parseInt(n));
  };
});

Use o filtro no seu HTML:

<p>Distance from zero: {{distance | abs}}</p>
craigsnyders
fonte
2

Isso não parece uma maneira muito angular de fazer isso. Não sei ao certo por que isso não funciona, mas você provavelmente precisará acessar o escopo para usar uma função como essa.

Minha sugestão seria criar um filtro. Essa é a maneira angular.

myModule.filter('ceil', function() {
    return function(input) {
        return Math.ceil(input);
    };
});

Então, no seu HTML, faça o seguinte:

<p>The percentage is {{ (100*count/total) | ceil }}%</p>
Zahid Mahmood
fonte
1

O numberfiltro formata o número com separadores de milhares, portanto, não é estritamente uma função matemática.

Além disso, o seu 'limitador' decimal não ceildecimais cortados (como algumas outras respostas levariam você a acreditar), mas sim roundpreenchê-los.

Portanto, para qualquer função matemática que você desejar, você pode injetá-la (mais fácil de zombar do que injetar todo o objeto Math) da seguinte maneira:

myModule.filter('ceil', function () {
  return Math.ceil;
});

Também não é necessário envolvê-lo em outra função.


fonte
0

Este é mais ou menos um resumo de três respostas (de Sara Inés Calderón, klaxon e Gothburz), mas como todas adicionaram algo importante, considero que vale a pena juntar as soluções e acrescentar mais explicações.

Considerando o seu exemplo, você pode fazer cálculos no seu modelo usando:

{{ 100 * (count/total) }}

No entanto, isso pode resultar em muitas casas decimais, portanto, usar filtros é um bom caminho a seguir:

{{ 100 * (count/total) | number }}

Por padrão, o filtro numérico deixará até três dígitos fracionários ; é nesse ponto que o argumento da fraçãoSize é bastante útil ( {{ 100 * (count/total) | number:fractionSize }}), que no seu caso seria:

{{ 100 * (count/total) | number:0 }}

Isso também arredondará o resultado já:

A última coisa a mencionar, se você confiar em uma fonte de dados externa, provavelmente é uma boa prática fornecer um valor de fallback adequado (caso contrário, você poderá ver NaN ou nada no seu site):

{{ (100 * (count/total) | number:0) || 0 }}

Nota: Dependendo das suas especificações, você pode até ser mais preciso com seus fallbacks / definir fallbacks em níveis mais baixos (por exemplo {{(100 * (count || 10)/ (total || 100) | number:2)}}). No entanto, isso nem sempre faz sentido ..

Kim
fonte
0

Exemplo de texto datilografado angular usando um pipe.

math.pipe.ts

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'math',
})

export class MathPipe implements PipeTransform {

  transform(value: number, args: any = null):any {
      if(value) {
        return Math[args](value);
      }
      return 0;
  }
}

Adicionar às declarações @NgModule

@NgModule({
  declarations: [
    MathPipe,

use no seu modelo da seguinte maneira:

{{(100*count/total) | math:'round'}}
Joel Davey
fonte
TypeError: Math [args] não é uma função
MattEnth 11/11/19