Funciones matemáticas en enlaces AngularJS

232

¿Hay alguna manera de usar funciones matemáticas en enlaces AngularJS?

p.ej

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

Este violín muestra el problema

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

ricick
fuente
11
Otra opción es evitar el uso de matemáticas en sus plantillas mediante el uso de un filtro: <p>The percentage is {{(100*count/total)| number:0}}%</p>más en el comentario a continuación.
Andrew Kuklewicz
2
Para Angular2, defina un miembro componente private Math = Math;y puede usarlo.
Ondra Žižka

Respuestas:

329

Debe inyectar Mathen su ámbito, si necesita usarlo, ya $scopeque no sabe nada sobre matemáticas.

De la manera más simple, puedes hacer

$scope.Math = window.Math;

en su controlador Supongo que una forma angular de hacer esto correctamente sería crear un servicio matemático.

Tosh
fuente
1
Gracias. Tengo un caso de uso en el que tengo varias plantillas con una lógica completamente diferente y quiero aislarlas tanto como sea posible. Perfecto
Yablargo
48
Debe usar un filtro, no poner el objeto matemático en el ámbito.
Soviut
44
Esto es bueno para cosas rápidas y sucias; burlándose rápidamente de algo o queriendo ver cómo funciona algo. Que es probablemente lo que quiere alguien que quiera hacer matemáticas en sus archivos de plantilla html.
Lan
1
El uso de funciones en enlaces hará que la función invoque la actualización de cada ámbito y usará demasiados recursos.
desmati
Esta solución está mal . ¿por qué? 1- Contaminar el alcance global es la peor idea de todas. 2- las vistas son responsables formatting, así que use filtro en su lugar
Afshin Mehrabani
304

Si bien la respuesta aceptada es correcta, puede inyectarla Mathpara usarla en angular, para este problema en particular, la forma más convencional / angular es el filtro de números:

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

Puede leer más sobre el numberfiltro aquí: http://docs.angularjs.org/api/ng/filter/number

Andrew Kuklewicz
fuente
77
No solo es esta la forma Angular, es la forma correcta. Es responsabilidad de la vista 'formatear' el valor.
Lucas
39
Andrew, lamento abarrotar tu publicación con este comentario. Su respuesta es buena y útil. Sin embargo, deseo estar en desacuerdo con los otros comentarios. A menudo me estremezco cuando la gente promociona la "forma angular" como si fuera un modelo de desarrollo perfecto. No lo es El OP preguntó en general cómo incluir funciones matemáticas en un enlace, con el redondeo presentado solo como un ejemplo. Si bien esta respuesta puede ser "la forma angular" de los puristas para resolver exactamente un problema matemático, no responde completamente la pregunta.
kbrimington
1
Perdón por la arqueología, pero esto es incorrecto cuando necesita un Número como salida. {{1234.567|number:2}}se representa como 1,234.57o 1 234,57. Si intenta pasarlo <input type="number" ng-value="1234.567|number:2">, vaciará la entrada, ya que el valor NO ES UN NÚMERO CORRECTO, sino una representación de cadena dependiente de la configuración regional.
SWilk
61

Creo que la mejor manera de hacerlo es creando un filtro, como este:

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

entonces el marcado se ve así:

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

Violín actualizado: http://jsfiddle.net/BB4T4/

sabinstefanescu
fuente
El filtro de números que figura en la respuesta de klaxon ya hará el tope, por lo que no es necesario.
Kim
Hice algo similar, solo usé el filtro para crear el temporizador como lo quería. En otras palabras, tomaría la hora actual y la formatearía en una cadena como mejor me parezca usando un filtro personalizado. Además, el filtro de números es bueno cuando desea redondear sus números, pero para las funciones de piso y techo es inútil.
Gabriel Lovetro
37

Esta es una pregunta difícil de responder, porque no dio el contexto completo de lo que está haciendo. La respuesta aceptada funcionará, pero en algunos casos causará un bajo rendimiento. Eso, y va a ser más difícil de probar.

Si está haciendo esto como parte de una forma estática, está bien. La respuesta aceptada funcionará, incluso si no es fácil de evaluar, y es extraña.

Si quieres ser "Angular" sobre esto:

Querrá mantener cualquier "lógica de negocios" (es decir, lógica que altere los datos que se mostrarán) fuera de sus vistas. Esto es para que pueda probar su lógica de forma unitaria, y para que no termine estrechamente acoplando su controlador y su vista. Teóricamente, debería poder apuntar su controlador a otra vista y usar los mismos valores de los ámbitos. (Si eso tiene sentido).

También querrá tener en cuenta que cualquier función llamada dentro de un enlace (como {{}}o ng-bindo ng-bind-html) tendrá que evaluarse en cada resumen , porque angular no tiene forma de saber si el valor ha cambiado o no como lo haría con una propiedad en el alcance.

La forma "angular" de hacer esto sería almacenar en caché el valor de una propiedad en el alcance del cambio utilizando un evento ng-change o incluso un $ watch.

Por ejemplo con una forma estática:

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>

¡Y ahora puedes probarlo!

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
fuente
podría compilar el elemento y luego probar el valor encontrado en el elemento dom. eso es realmente preferido ya que también podrías estar usando filtros de localización.
FlavorScape
Buena respuesta. ¿Pero se pregunta por qué anteponer $windowya que parece funcionar solo con el plan Math.round()?
Neel
3
@Neel - $ window le permite burlarse más fácilmente de las Mathpruebas. Alternativamente, puede crear un servicio matemático e inyectarlo.
Ben Lesh
8

¿Por qué no envolver todo el obj matemático en un 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]);

y para usar:

{{number_var | matemática: 'ceil'}}

Marcos Ammon
fuente
8

La mejor opción es usar:

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

Establece el valor predeterminado de la ecuación en 0 en el caso de que los valores no se inicialicen.

claxon
fuente
6

La forma más fácil de hacer cálculos matemáticos simples con Angular es directamente en el marcado HTML para enlaces individuales según sea necesario, suponiendo que no necesite hacer cálculos masivos en su página. Aquí hay un ejemplo:

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

En este caso, solo hace los cálculos en () y luego usa un filtro | para obtener una respuesta numérica Aquí hay más información sobre cómo formatear números angulares como texto:

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

Sara Inés Calderón
fuente
4

Puede vincular el objeto matemático global al ámbito (recuerde usar $ window, no window)

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

Use el enlace en su HTML:

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

O cree un filtro para la función matemática específica que busca:

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

Usa el filtro en tu HTML:

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

Eso no parece una forma muy angular de hacerlo. No estoy completamente seguro de por qué no funciona, pero es probable que necesite acceder al alcance para usar una función como esa.

Mi sugerencia sería crear un filtro. Esa es la forma angular.

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

Luego en tu HTML haz esto:

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

El numberfiltro formatea el número con miles de separadores, por lo que no es estrictamente una función matemática.

Además, su 'limitador' decimal no tiene ceildecimales cortados (como algunas otras respuestas lo llevarían a creer), sino más bien su respuesta round.

Entonces, para cualquier función matemática que desee, puede inyectarla (más fácil de burlarse que inyectar todo el objeto matemático) de esta manera:

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

No es necesario envolverlo en otra función tampoco.


fuente
0

Esto es más o menos un resumen de tres respuestas (por Sara Inés Calderón, klaxon y Gothburz), pero como todos agregaron algo importante, considero que vale la pena unir las soluciones y agregar más explicaciones.

Considerando su ejemplo, puede hacer cálculos en su plantilla usando:

{{ 100 * (count/total) }}

Sin embargo, esto puede resultar en una gran cantidad de decimales, por lo que usar filtros es una buena manera de hacerlo:

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

Por defecto, el filtro de números dejará hasta tres dígitos fraccionarios , aquí es donde el argumento fraccionSize resulta bastante útil ( {{ 100 * (count/total) | number:fractionSize }}), que en su caso sería:

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

Esto también redondeará el resultado ya:

Lo último que hay que mencionar, si confía en una fuente de datos externa, probablemente sea una buena práctica proporcionar un valor de reserva adecuado (de lo contrario, puede ver NaN o nada en su sitio):

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

Nota al margen: Dependiendo de sus especificaciones, incluso puede ser más preciso con sus retrocesos / definir retrocesos en niveles más bajos (por ejemplo {{(100 * (count || 10)/ (total || 100) | number:2)}}). Sin embargo, esto no siempre tiene sentido.

Kim
fuente
0

Ejemplo de mecanografía angular usando una tubería.

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;
  }
}

Agregar a las declaraciones de @NgModule

@NgModule({
  declarations: [
    MathPipe,

luego use en su plantilla de la siguiente manera:

{{(100*count/total) | math:'round'}}
Joel Davey
fuente
TypeError: Math [args] no es una función
MattEnth