Mathematische Funktionen in AngularJS-Bindungen

232

Gibt es eine Möglichkeit, mathematische Funktionen in AngularJS-Bindungen zu verwenden?

z.B

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

Diese Geige zeigt das Problem

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

Ricick
quelle
11
Eine andere Möglichkeit besteht darin, die Verwendung von Mathematik in Ihren Vorlagen zu vermeiden, indem Sie stattdessen einen Filter verwenden : <p>The percentage is {{(100*count/total)| number:0}}%</p>, mehr im Kommentar unten.
Andrew Kuklewicz
2
Definieren Sie für Angular2 ein Komponentenelement, private Math = Math;und Sie können es verwenden.
Ondra Žižka

Antworten:

329

Sie müssen Mathin Ihr Zielfernrohr injizieren , wenn Sie es verwenden müssen, da Sie $scopenichts über Mathematik wissen.

Am einfachsten geht das

$scope.Math = window.Math;

in Ihrem Controller. Ein eckiger Weg, dies richtig zu machen, wäre, einen Mathe-Dienst zu erstellen, denke ich.

Tosh
quelle
1
Vielen Dank. Ich habe einen Anwendungsfall, in dem ich mehrere Vorlagen mit völlig unterschiedlicher Logik habe und diese so weit wie möglich isolieren möchte. Perfekt
Yablargo
48
Sie sollten einen Filter verwenden und das Math-Objekt nicht in den Bereich einfügen.
Soviut
4
Dies ist gut für schnelle und schmutzige Dinge; schnell etwas verspotten oder sehen wollen, wie etwas funktioniert. Welches ist wahrscheinlich, was jemand, der Mathe in seinen HTML-Vorlagendateien machen will, will.
Lan
1
Durch die Verwendung von Funktionen in Bindungen wird die Funktion dazu gebracht, das Update jedes Bereichs aufzurufen, und es werden zu viele Ressourcen verwendet.
Desmati
Diese Lösung ist falsch . Warum? 1- Die Verschmutzung des globalen Geltungsbereichs ist die schlimmste Idee aller Zeiten. 2-Ansichten sind dafür verantwortlich formatting, verwenden Sie stattdessen Filter.
Afshin Mehrabani
304

Während die akzeptierte Antwort richtig ist, dass Sie injizieren können, Mathum sie im Winkel zu verwenden, ist für dieses spezielle Problem der Zahlenfilter der konventionellere / eckigere Weg:

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

Weitere Informationen zum numberFilter finden Sie hier: http://docs.angularjs.org/api/ng/filter/number

Andrew Kuklewicz
quelle
7
Dies ist nicht nur der Winkelweg, sondern auch der richtige Weg. Es liegt in der Verantwortung der Ansicht, den Wert zu "formatieren".
Luke
39
Andrew, tut mir leid, dass ich deinen Beitrag mit diesem Kommentar überladen habe. Ihre Antwort ist gut und hilfreich; Ich möchte jedoch den anderen Kommentaren nicht zustimmen. Ich erschrecke oft, wenn Leute den "eckigen Weg" anpreisen, als wäre er ein Inbegriff perfekter Entwicklung. Ist es nicht. Das OP fragte im Allgemeinen, wie mathematische Funktionen in eine Bindung aufgenommen werden sollen, wobei die Rundung nur als Beispiel dient. Während diese Antwort von Puristen "der eckige Weg" sein mag, genau ein mathematisches Problem zu lösen, beantwortet sie die Frage nicht vollständig.
Kbrimington
1
Entschuldigung für die Archäologie, aber dies ist falsch, wenn Sie eine Zahl als Ausgabe benötigen. {{1234.567|number:2}}rendert als 1,234.57oder 1 234,57. Wenn Sie versuchen, es an <input type="number" ng-value="1234.567|number:2">Sie zu übergeben, wird die Eingabe leer, da der Wert KEINE RICHTIGE NUMMER, sondern eine vom Gebietsschema abhängige Zeichenfolgendarstellung ist.
SWilk
61

Ich denke, der beste Weg, dies zu tun, besteht darin, einen Filter wie folgt zu erstellen:

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

dann sieht das Markup folgendermaßen aus:

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

Aktualisierte Geige: http://jsfiddle.net/BB4T4/

sabinstefanescu
quelle
Der in der Antwort von klaxon angegebene Zahlenfilter erfüllt bereits die Obergrenze, sodass dies nicht erforderlich ist.
Kim
Ich habe etwas Ähnliches gemacht und nur den Filter verwendet, um den Timer selbst so zu erstellen, wie ich es wollte. Mit anderen Worten, ich würde die aktuelle Zeit nehmen und sie nach eigenem Ermessen mithilfe eines benutzerdefinierten Filters in eine Zeichenfolge formatieren. Auch der Zahlenfilter ist gut, wenn Sie Ihre Zahlen runden möchten, aber für Boden- und Deckenfunktionen ist er nutzlos.
Gabriel Lovetro
37

Dies ist eine haarige Frage, weil Sie nicht den vollständigen Kontext Ihrer Arbeit angegeben haben. Die akzeptierte Antwort funktioniert, führt jedoch in einigen Fällen zu einer schlechten Leistung. Das und es wird schwieriger zu testen sein.

Wenn Sie dies als Teil eines statischen Formulars tun, ist das in Ordnung. Die akzeptierte Antwort wird funktionieren, auch wenn es nicht einfach zu testen ist und es schwierig ist.

Wenn Sie diesbezüglich "Angular" sein möchten:

Sie sollten jede "Geschäftslogik" (dh Logik, die die anzuzeigenden Daten ändert) aus Ihren Ansichten heraushalten. Auf diese Weise können Sie Ihre Logik einem Unit-Test unterziehen und Ihren Controller und Ihre Ansicht nicht eng miteinander verbinden. Theoretisch sollten Sie in der Lage sein, Ihren Controller auf eine andere Ansicht zu richten und dieselben Werte aus den Bereichen zu verwenden. (wenn das Sinn macht).

Sie sollten auch berücksichtigen, dass alle Funktionsaufrufe innerhalb einer Bindung (wie {{}}oder oder ng-bindoder ng-bind-html) bei jedem Digest ausgewertet werden müssen , da Angular nicht wissen kann, ob sich der Wert geändert hat oder nicht wie bei einer Eigenschaft auf den Umfang.

Die "eckige" Möglichkeit, dies zu tun, besteht darin, den Wert in einer Eigenschaft des Bereichs bei Änderung mithilfe eines ng-change-Ereignisses oder sogar einer $ watch zwischenzuspeichern.

Zum Beispiel mit einer statischen Form:

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>

Und jetzt können Sie es testen!

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
quelle
Sie können das Element kompilieren und dann den im dom-Element gefundenen Wert testen. Dies wird tatsächlich bevorzugt, da Sie möglicherweise auch Lokalisierungsfilter verwenden.
FlavorScape
Gute Antwort. Aber fragen Sie sich, warum Sie mit voranstellen, $windowda es nur mit Plan zu funktionieren scheint Math.round()?
Neel
3
Mit @Neel - $ window können Sie das Mathfür Tests einfacher verspotten . Alternativ können Sie einen Mathedienst erstellen und einfügen.
Ben Lesh
8

Warum nicht das ganze Matheobjekt in einen Filter einwickeln?

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


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

        return 0;
    }
}

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

und zu verwenden:

{{number_var | Mathe: 'Ceil'}}

Marcos Ammon
quelle
8

Bessere Option ist zu verwenden:

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

Der Standardwert der Gleichung wird auf 0 gesetzt, wenn die Werte nicht initialisiert werden.

Klaxon
quelle
6

Der einfachste Weg, mit Angular einfache Berechnungen durchzuführen, ist das HTML-Markup für einzelne Bindungen nach Bedarf, vorausgesetzt, Sie müssen keine Massenberechnungen auf Ihrer Seite durchführen. Hier ist ein Beispiel:

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

In diesem Fall rechnen Sie einfach in () und verwenden dann einen Filter | um eine Antwort zu erhalten. Weitere Informationen zum Formatieren von Winkelnummern als Text:

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

Sara Inés Calderón
quelle
4

Binden Sie entweder das globale Math-Objekt an den Bereich (denken Sie daran, $ window not window zu verwenden).

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

Verwenden Sie die Bindung in Ihrem HTML:

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

Oder erstellen Sie einen Filter für die spezifische Math-Funktion, nach der Sie suchen:

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

Verwenden Sie den Filter in Ihrem HTML:

<p>Distance from zero: {{distance | abs}}</p>
Craigsnyders
quelle
2

Das sieht nicht nach einer sehr eckigen Art aus, es zu tun. Ich bin mir nicht ganz sicher, warum es nicht funktioniert, aber Sie müssten wahrscheinlich auf den Bereich zugreifen, um eine solche Funktion verwenden zu können.

Mein Vorschlag wäre, einen Filter zu erstellen. Das ist der Winkelweg.

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

Dann machen Sie in Ihrem HTML Folgendes:

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

Der numberFilter formatiert die Zahl mit Tausenden von Trennzeichen, sodass es sich nicht ausschließlich um eine mathematische Funktion handelt.

Darüber hinaus enthält der Dezimalbegrenzer ceilkeine abgeschnittenen Dezimalstellen (wie einige andere Antworten Sie glauben machen würden), sondern roundsie.

Für jede gewünschte mathematische Funktion können Sie sie wie folgt injizieren (einfacher zu verspotten als das gesamte mathematische Objekt zu injizieren):

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

Sie müssen es auch nicht in eine andere Funktion einbinden.


quelle
0

Dies ist mehr oder weniger eine Zusammenfassung von drei Antworten (von Sara Inés Calderón, Klaxon und Gothburz), aber da sie alle etwas Wichtiges hinzugefügt haben, halte ich es für sinnvoll, sich den Lösungen anzuschließen und weitere Erklärungen hinzuzufügen.

Anhand Ihres Beispiels können Sie Berechnungen in Ihrer Vorlage durchführen, indem Sie:

{{ 100 * (count/total) }}

Dies kann jedoch zu einer ganzen Reihe von Dezimalstellen führen. Daher ist die Verwendung von Filtern ein guter Weg:

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

Standardmäßig hinterlässt der Zahlenfilter bis zu drei Bruchstellen . Hier ist das Argument fraktionSize sehr nützlich ( {{ 100 * (count/total) | number:fractionSize }}). In Ihrem Fall wäre dies:

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

Dies wird auch das Ergebnis bereits abrunden:

Wenn Sie sich auf eine externe Datenquelle verlassen, ist es wahrscheinlich empfehlenswert , einen geeigneten Fallback-Wert anzugeben (andernfalls wird auf Ihrer Website möglicherweise NaN oder nichts angezeigt):

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

Nebenbemerkung: Abhängig von Ihren Spezifikationen können Sie möglicherweise sogar präziser mit Ihren Fallbacks umgehen / Fallbacks bereits auf niedrigeren Ebenen definieren (z {{(100 * (count || 10)/ (total || 100) | number:2)}}. B. ). Dies ist jedoch möglicherweise nicht immer sinnvoll.

Kim
quelle
0

Angular Typescript Beispiel mit einer 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;
  }
}

Zu @ NgModule-Deklarationen hinzufügen

@NgModule({
  declarations: [
    MathPipe,

Verwenden Sie dann in Ihrer Vorlage wie folgt:

{{(100*count/total) | math:'round'}}
Joel Davey
quelle
TypeError: Math [args] ist keine Funktion
MattEnth