Funkcje matematyczne w powiązaniach AngularJS

232

Czy istnieje sposób używania funkcji matematycznych w powiązaniach AngularJS?

na przykład

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

To skrzypce pokazuje problem

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

Ricka
źródło
11
Inną opcją jest unikanie korzystania z matematyki w szablonach za pomocą filtra zamiast: <p>The percentage is {{(100*count/total)| number:0}}%</p>więcej w komentarzu poniżej.
Andrew Kuklewicz
2
W przypadku Angular2 zdefiniuj element składowy private Math = Math;i możesz go użyć.
Ondra Žižka

Odpowiedzi:

329

Musisz wstrzyknąć Mathdo swojego zakresu, jeśli musisz go użyć, ponieważ $scopenic nie wiesz o matematyce.

Najprościej możesz to zrobić

$scope.Math = window.Math;

w twoim kontrolerze. Myślę, że dobrym sposobem na zrobienie tego poprawnie byłoby stworzenie usługi matematycznej.

Bzdury
źródło
1
Dzięki. Mam przypadek użycia, w którym mam wiele szablonów o zupełnie innej logice i chcę je w jak największym stopniu odizolować. Idealny
Yablargo,
48
Powinieneś użyć filtra, a nie umieszczać obiektu Math w zakresie.
Soviut
4
Jest to dobre dla szybkich i brudnych rzeczy; szybko wyśmiewając coś lub chcąc zobaczyć, jak coś działa. Prawdopodobnie tego chce ktoś, kto chce robić matematykę w plikach szablonów HTML.
Lan
1
Użycie funkcji w powiązaniach sprawi, że funkcja będzie wywoływała aktualizację każdego zakresu i zużyje zbyt dużo zasobów.
desmati,
To rozwiązanie jest złe . czemu? 1- Zanieczyszczenie globalnego zasięgu jest najgorszym pomysłem na świecie. Odpowiedzialne są za 2 widoki formatting, więc użyj filtru.
Afshin Mehrabani
304

Chociaż przyjęta odpowiedź jest słuszna, że ​​można wstrzyknąć, Mathaby użyć jej w kanale kątowym, w przypadku tego konkretnego problemu bardziej konwencjonalny / kątowy jest filtr liczb:

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

Możesz przeczytać więcej o numberfiltrze tutaj: http://docs.angularjs.org/api/ng/filter/number

Andrzej Kuklewicz
źródło
7
Jest to nie tylko sposób kątowy, ale także poprawny. Obowiązkiem widoku jest „sformatowanie” wartości.
Łukasz
39
Andrew, przykro mi, że ten komentarz jest zaśmiecony. Twoja odpowiedź jest dobra i pomocna; chcę jednak nie zgadzać się z innymi komentarzami. Często kulę się, gdy ludzie reklamują „kanciastą drogę”, jakby to był wzór doskonałego rozwoju. To nie jest PO zapytał ogólnie, jak włączyć funkcje matematyczne do wiązania, a zaokrąglanie przedstawiono jedynie jako przykład. Chociaż odpowiedź ta może być „kanciasta” dla purystów, aby rozwiązać dokładnie jeden problem matematyczny, nie w pełni odpowiada na pytanie.
kbrimington
1
Przepraszamy za archeologię, ale jest to niepoprawne, gdy potrzebujesz liczby jako wyniku. {{1234.567|number:2}}renderuje jako 1,234.57lub 1 234,57. Jeśli spróbujesz przekazać to <input type="number" ng-value="1234.567|number:2">, puste dane wejściowe, ponieważ NIE JEST PRAWIDŁOWA LICZBA, ale zależna od ustawień regionalnych reprezentacja ciągu.
SWilk
61

Myślę, że najlepszym sposobem na to jest utworzenie filtra, takiego jak ten:

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

znaczniki wyglądają następująco:

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

Zaktualizowano skrzypce: http://jsfiddle.net/BB4T4/

sabinstefanescu
źródło
Filtr liczbowy podany w odpowiedzi Klaxona zrobi już pułap, więc nie ma takiej potrzeby.
Kim,
Zrobiłem coś podobnego, używając filtra tylko do stworzenia timera, tak jak tego chciałem. Innymi słowy, wykorzystałbym bieżący czas i sformatowałem go w ciąg znaków, gdy uznałem za stosowny, używając niestandardowego filtra. Filtr liczb jest również dobry, gdy chcesz zaokrąglić liczby, ale w przypadku funkcji podłogi i sufitu jest bezużyteczny.
Gabriel Lovetro
37

Jest to owłosiona odpowiedź, ponieważ nie podałeś pełnego kontekstu tego, co robisz. Przyjęta odpowiedź będzie działać, ale w niektórych przypadkach spowoduje niską wydajność. To będzie trudniejsze do przetestowania.

Jeśli robisz to jako część statycznej formy, dobrze. Zaakceptowana odpowiedź zadziała, nawet jeśli nie jest to łatwe do przetestowania, i jest pokręcona.

Jeśli chcesz być w tym „Angularny”:

Będziesz chciał ukryć jakąkolwiek „logikę biznesową” (tj. Logikę, która zmienia dane, które mają być wyświetlane) z dala od twoich widoków. Jest to możliwe, abyś mógł przetestować logikę jednostkową, aby nie skończyć ściśle połączeniem kontrolera i widoku. Teoretycznie powinieneś być w stanie skierować kontroler na inny widok i używać tych samych wartości z zakresów. (Jeśli to ma sens).

Należy również wziąć pod uwagę, że wszelkie wywołania funkcji w powiązaniu (takie jak {{}}lub ng-bindlub ng-bind-html) będą musiały być oceniane przy każdym skrócie , ponieważ kątowy nie ma możliwości dowiedzenia się, czy wartość się zmieniła, czy nie, jak w przypadku właściwości w zakresie.

„Kątowym” sposobem na wykonanie tego byłoby buforowanie wartości we właściwości w zakresie przy zmianie za pomocą zdarzenia ng-change lub nawet $ watch.

Na przykład w formie statycznej:

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>

A teraz możesz to przetestować!

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
źródło
możesz skompilować element, a następnie przetestować wartość znalezioną w elemencie dom. jest to właściwie preferowane, ponieważ możesz także używać filtrów lokalizacji.
FlavorScape,
Dobra odpowiedź. Ale zastanawiasz się, $windowpo co dalej, skoro wydaje się, że działa to z właściwym planem Math.round()?
Neel,
3
@Neel - $ window pozwala łatwiej kpić z Mathtestów. Alternatywnie możesz utworzyć usługę matematyczną i wstrzyknąć ją.
Ben Lesh
8

Dlaczego nie owinąć całego obiektu matematyki w filtr?

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


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

        return 0;
    }
}

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

i użyć:

{{number_var | matematyka: „ceil”}}

Marcos Ammon
źródło
8

Lepszą opcją jest użycie:

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

Ustawia domyślną wartość równania na 0 w przypadku, gdy wartości nie są inicjowane.

klakson
źródło
6

Najprostszym sposobem na wykonanie prostej matematyki za pomocą Angulara jest użycie znaczników HTML dla poszczególnych powiązań w razie potrzeby, zakładając, że nie musisz wykonywać obliczeń masy na swojej stronie. Oto przykład:

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

W takim przypadku wystarczy wykonać obliczenia matematyczne w (), a następnie użyć filtru | aby uzyskać odpowiedź liczbową. Oto więcej informacji na temat formatowania liczb kątowych jako tekstu:

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

Sara Inés Calderón
źródło
4

Powiąż globalny obiekt Math z zasięgiem (pamiętaj, aby użyć $ window nie window)

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

Użyj powiązania w swoim HTML:

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

Lub utwórz filtr dla określonej funkcji matematycznej, której szukasz:

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

Użyj filtra w swoim HTML:

<p>Distance from zero: {{distance | abs}}</p>
craigsnyders
źródło
2

To nie wygląda na bardzo Angularny sposób. Nie jestem do końca pewien, dlaczego to nie działa, ale prawdopodobnie potrzebujesz dostępu do zakresu, aby użyć takiej funkcji.

Moją sugestią byłoby utworzenie filtra. To jest sposób Angularny.

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

Następnie w swoim HTML zrób to:

<p>The percentage is {{ (100*count/total) | ceil }}%</p>
Zahid Mahmood
źródło
1

numberFiltr formatuje numer z tysiącami separatorów, więc nie jest to ściśle funkcją matematyki.

Co więcej, jego dziesiętny „ogranicznik” nie ma ceilżadnych posiekanych miejsc po przecinku (jak w niektórych innych odpowiedziach można by w to uwierzyć), a raczej roundich odmierzanie.

Tak więc dla dowolnej funkcji matematycznej możesz ją wstrzyknąć (łatwiej kpić niż wstrzyknąć cały obiekt Math) w następujący sposób:

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

Nie trzeba też owijać go w inną funkcję.


źródło
0

To mniej więcej podsumowanie trzech odpowiedzi (Sary Inés Calderón, Klaxona i Gothburza), ale ponieważ wszystkie one dodały coś ważnego, uważam, że warto przyłączyć się do rozwiązań i dodać więcej wyjaśnień.

Biorąc pod uwagę twój przykład, możesz wykonać obliczenia w swoim szablonie, używając:

{{ 100 * (count/total) }}

Może to jednak skutkować dużą liczbą miejsc po przecinku, dlatego dobrym rozwiązaniem jest użycie filtrów :

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

Domyślnie filtr liczb pozostawia do trzech cyfr ułamkowych , w tym przypadku argument fractionSize jest bardzo przydatny ( {{ 100 * (count/total) | number:fractionSize }}), co w twoim przypadku byłoby:

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

Zaokrągli to również wynik:

Ostatnią rzeczą do wspomnienia, jeśli korzystasz z zewnętrznego źródła danych, prawdopodobnie dobrą praktyką jest zapewnienie właściwej wartości zastępczej (w przeciwnym razie możesz zobaczyć NaN lub nic na swojej stronie):

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

Sidenote: W zależności od specyfikacji możesz być w stanie być bardziej precyzyjnym dzięki swoim awariom / definiować już awarie na niższych poziomach (np {{(100 * (count || 10)/ (total || 100) | number:2)}}.). Chociaż nie zawsze może to mieć sens.

Kim
źródło
0

Przykład Anges Typescript przy użyciu potoku.

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

Dodaj do deklaracji @NgModule

@NgModule({
  declarations: [
    MathPipe,

następnie użyj w swoim szablonie tak:

{{(100*count/total) | math:'round'}}
Joel Davey
źródło
TypeError: Math [args] nie jest funkcją
MattEnth