Fonctions mathématiques dans les liaisons AngularJS

232

Existe-t-il un moyen d'utiliser les fonctions mathématiques dans les liaisons AngularJS?

par exemple

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

Ce violon montre le problème

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

ricick
la source
11
Une autre option consiste à éviter d'utiliser Math dans vos modèles en utilisant un filtre à la place:, <p>The percentage is {{(100*count/total)| number:0}}%</p>plus dans les commentaires ci-dessous.
Andrew Kuklewicz
2
Pour Angular2, définissez un membre de composant private Math = Math;et vous pouvez l'utiliser.
Ondra Žižka

Réponses:

329

Vous devez injecter Mathdans votre portée, si vous avez besoin de l'utiliser car vous $scopene savez rien des mathématiques.

Le moyen le plus simple, vous pouvez le faire

$scope.Math = window.Math;

dans votre contrôleur. Une manière angulaire de le faire correctement serait de créer un service Math, je suppose.

Tosh
la source
1
Merci. J'ai un cas d'utilisation où j'ai plusieurs modèles avec une logique complètement différente et que je souhaite les isoler autant que possible. Parfait
Yablargo
48
Vous devez utiliser un filtre et non placer l'objet Math sur la portée.
Soviut
4
C'est bon pour les choses rapides et sales; se moquer rapidement de quelque chose ou vouloir voir comment quelque chose fonctionne. C'est probablement ce que veut quelqu'un qui veut faire des calculs dans ses fichiers de modèle html.
Lan
1
L'utilisation de fonctions dans les liaisons obligera la fonction à appeler dans la mise à jour de chaque étendue et utilisera trop de ressources.
desmati
Cette solution est fausse . Pourquoi? 1- Polluer la portée mondiale est la pire idée qui soit. 2- Les vues sont responsables formatting, utilisez plutôt le filtre.
Afshin Mehrabani
304

Alors que la réponse acceptée est juste que vous pouvez injecter Mathpour l'utiliser en angulaire, pour ce problème particulier, la manière la plus conventionnelle / angulaire est le filtre numérique:

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

Vous pouvez en savoir plus sur le numberfiltre ici: http://docs.angularjs.org/api/ng/filter/number

Andrew Kuklewicz
la source
7
Non seulement c'est la voie angulaire, c'est la bonne façon. C'est la responsabilité de la vue de «formater» la valeur.
Luke
39
Andrew, désolé d'encombrer votre message avec ce commentaire. Votre réponse est bonne et utile; cependant, je désapprouve les autres commentaires. Je grince souvent des dents quand les gens vantent la "voie angulaire" comme si c'était un modèle de développement parfait. Ce n'est pas le cas. Le PO a demandé en général comment inclure des fonctions mathématiques dans une reliure, l'arrondi n'étant présenté qu'à titre d'exemple. Bien que cette réponse puisse être "la voie angulaire" des puristes pour résoudre exactement un problème mathématique, elle ne répond pas complètement à la question.
kbrimington
1
Désolé pour l'archéologie, mais cela est incorrect lorsque vous avez besoin d'un nombre en sortie. {{1234.567|number:2}}s'affiche comme 1,234.57ou 1 234,57. Si vous essayez de le transmettre, <input type="number" ng-value="1234.567|number:2">vous videz l'entrée, car la valeur N'EST PAS UN NUMÉRO CORRECT, mais une représentation sous forme de chaîne dépendante des paramètres régionaux.
SWilk
61

Je pense que la meilleure façon de le faire est de créer un filtre, comme ceci:

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

alors le balisage ressemble à ceci:

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

Violon mis à jour: http://jsfiddle.net/BB4T4/

sabinstefanescu
la source
Le filtre numérique comme indiqué dans la réponse de klaxon fera déjà le plafond, donc pas besoin de ça.
Kim
J'ai fait quelque chose de similaire, en utilisant uniquement le filtre pour créer le minuteur lui-même comme je le voulais. En d'autres termes, je prendrais l'heure actuelle et la formaterais en une chaîne comme bon me semble en utilisant un filtre personnalisé. De plus, le filtre numérique est bon lorsque vous souhaitez arrondir vos chiffres, mais pour les fonctions de plancher et de plafond, il est inutile.
Gabriel Lovetro
37

C'est une question velue à laquelle répondre, car vous n'avez pas donné le contexte complet de ce que vous faites. La réponse acceptée fonctionnera, mais dans certains cas, entraînera de mauvaises performances. Ça, et ça va être plus difficile à tester.

Si vous faites cela dans le cadre d'une forme statique, très bien. La réponse acceptée fonctionnera, même si elle n'est pas facile à tester et qu'elle est hinky.

Si vous voulez être "angulaire" à ce sujet:

Vous voudrez garder toute "logique métier" (c'est-à-dire la logique qui modifie les données à afficher) hors de vos vues. Cela vous permet de tester votre logique à l'unité et de ne pas coupler étroitement votre contrôleur et votre vue. Théoriquement, vous devriez pouvoir pointer votre contrôleur vers une autre vue et utiliser les mêmes valeurs des étendues. (si ça a du sens).

Vous voudrez également considérer que tout appel de fonction à l'intérieur d'une liaison (comme {{}}ou ng-bindou ng-bind-html) devra être évalué à chaque résumé , car angular n'a aucun moyen de savoir si la valeur a changé ou non comme avec une propriété sur la portée.

La manière "angulaire" de procéder serait de mettre en cache la valeur d'une propriété sur la portée lors du changement en utilisant un événement ng-change ou même un $ watch.

Par exemple avec un formulaire statique:

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>

Et maintenant, vous pouvez le tester!

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
la source
vous pouvez compiler l'élément, puis tester la valeur trouvée dans l'élément dom. c'est en fait préférable car vous utilisez peut-être aussi des filtres de localisation.
FlavorScape
Bonne réponse. Mais vous vous demandez pourquoi ajouter avant $windowcar il semble fonctionner avec juste un plan Math.round()?
Neel
3
@Neel - $ window vous permet de vous moquer plus facilement des Mathtests. Alternativement, vous pouvez créer un service Math et l'injecter.
Ben Lesh
8

Pourquoi ne pas envelopper tout l'objet mathématique dans un filtre?

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


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

        return 0;
    }
}

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

et utiliser:

{{number_var | math: 'ceil'}}

Marcos Ammon
la source
8

La meilleure option consiste à utiliser:

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

Il définit la valeur par défaut de l'équation à 0 dans le cas où les valeurs ne sont pas initialisées.

klaxon
la source
6

La façon la plus simple de faire des calculs simples avec Angular est directement dans le balisage HTML pour les liaisons individuelles selon les besoins, en supposant que vous n'avez pas besoin de faire des calculs de masse sur votre page. Voici un exemple:

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

Dans ce cas, il vous suffit de faire le calcul dans le () puis d'utiliser un filtre | pour obtenir une réponse numérique. Voici plus d'informations sur la mise en forme des nombres angulaires sous forme de texte:

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

Sara Inés Calderón
la source
4

Soit liez l'objet Math global sur la portée (n'oubliez pas d'utiliser $ window not window)

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

Utilisez la liaison dans votre HTML:

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

Ou créez un filtre pour la fonction mathématique spécifique que vous recherchez:

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

Utilisez le filtre dans votre HTML:

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

Cela ne ressemble pas à une façon très angulaire de le faire. Je ne sais pas exactement pourquoi cela ne fonctionne pas, mais vous auriez probablement besoin d'accéder à la portée afin d'utiliser une fonction comme celle-ci.

Ma suggestion serait de créer un filtre. C'est la voie angulaire.

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

Ensuite, dans votre code HTML, procédez comme suit:

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

Le numberfiltre formate le nombre avec des milliers de séparateurs, ce n'est donc pas strictement une fonction mathématique.

De plus, son «limiteur» décimal n'a pas ceilde décimales hachées (comme certaines autres réponses le laisseraient croire), mais plutôt roundles.

Donc, pour n'importe quelle fonction mathématique que vous voulez, vous pouvez l'injecter (plus facile à simuler que d'injecter tout l'objet Math) comme ceci:

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

Pas besoin non plus de l'envelopper dans une autre fonction.


la source
0

Il s'agit plus ou moins d'un résumé de trois réponses (par Sara Inés Calderón, klaxon et Gothburz), mais comme elles ont toutes ajouté quelque chose d'important, je considère qu'il vaut la peine de rejoindre les solutions et d'ajouter quelques explications.

Compte tenu de votre exemple, vous pouvez effectuer des calculs dans votre modèle en utilisant:

{{ 100 * (count/total) }}

Cependant, cela peut entraîner un grand nombre de décimales, donc l' utilisation de filtres est une bonne façon de procéder:

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

Par défaut, le filtre numérique laisse jusqu'à trois chiffres fractionnaires , c'est là que l' argument fractionSize est assez pratique ( {{ 100 * (count/total) | number:fractionSize }}), ce qui dans votre cas serait:

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

Cela arrondira également déjà le résultat:

Dernière chose à mentionner, si vous comptez sur une source de données externe, il est probablement judicieux de fournir une valeur de secours appropriée (sinon vous pouvez voir NaN ou rien sur votre site):

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

Sidenote: En fonction de vos spécifications, vous pouvez même être plus précis avec vos solutions de secours / définir des solutions de secours à des niveaux inférieurs déjà (par exemple {{(100 * (count || 10)/ (total || 100) | number:2)}}). Cependant, cela peut ne pas toujours avoir de sens ..

Kim
la source
0

Exemple de typographie angulaire utilisant un tuyau.

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

Ajouter aux déclarations @NgModule

@NgModule({
  declarations: [
    MathPipe,

puis utilisez dans votre modèle comme ceci:

{{(100*count/total) | math:'round'}}
Joel Davey
la source
TypeError: Math [args] n'est pas une fonction
MattEnth