Funzioni matematiche negli attacchi AngularJS


232

Esiste un modo per utilizzare le funzioni matematiche negli attacchi AngularJS?

per esempio

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

Questo violino mostra il problema

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


11
Un'altra opzione è quella di evitare di usare la matematica nei tuoi modelli usando invece un filtro:, <p>The percentage is {{(100*count/total)| number:0}}%</p>più nei commenti qui sotto.
Andrew Kuklewicz,

2
Per Angular2, definisci un membro componente private Math = Math;e puoi usarlo.
Ondra Žižka,

Risposte:


329

Devi iniettarti Mathnel tuo ambito, se hai bisogno di usarlo perché non $scopesai nulla di matematica.

Il modo più semplice, puoi farlo

$scope.Math = window.Math;

nel tuo controller. Immagino che un modo angolare per farlo correttamente sarebbe creare un servizio di matematica.


1
Grazie. Ho un caso d'uso in cui ho più modelli con logica completamente diversa e voglio isolarli il più possibile. Perfetto
Yablargo il

48
È necessario utilizzare un filtro, non posizionare l'oggetto Math sull'ambito.
Soviet

4
Questo è buono per cose veloci e sporche; deridere rapidamente qualcosa o voler vedere come funziona qualcosa. Questo è probabilmente ciò che vuole qualcuno che vuole fare matematica nei propri file modello html.
Lan

1
L'uso delle funzioni nei collegamenti renderà la funzione da chiamare nell'aggiornamento di ciascun ambito e utilizzerà troppe risorse.
Desmati,

Questa soluzione è sbagliata . perché? 1- L'inquinamento dell'ambito globale è l'idea peggiore di sempre. 2- le visualizzazioni sono responsabili formatting, quindi utilizzare il filtro.
Afshin Mehrabani,

304

Mentre la risposta accettata è corretta, è possibile iniettare Mathper usarla in angolare, per questo particolare problema, il modo più convenzionale / angolare è il filtro numerico:

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

Puoi leggere di più sul numberfiltro qui: http://docs.angularjs.org/api/ng/filter/number


7
Questo non è solo il modo angolare, è anche il modo corretto. È responsabilità della vista "formattare" il valore.
Luca

39
Andrew, mi dispiace ingombrare il tuo post con questo commento. La tua risposta è buona e utile; tuttavia, non sono d'accordo con gli altri commenti. Spesso mi arrabbio quando le persone propongono la "via angolare" come se fosse un esempio di sviluppo perfetto. Non lo è. L'OP ha chiesto in generale come includere le funzioni matematiche in un'associazione, con arrotondamenti presentati solo come esempio. Sebbene questa risposta possa essere "la via angolare" dei puristi per risolvere esattamente un problema di matematica, non risponde pienamente alla domanda.
Kbrimington,

1
Ci scusiamo per l'archeologia, ma questo non è corretto quando è necessario un numero come output. {{1234.567|number:2}}viene visualizzato come 1,234.57o 1 234,57. Se si tenta di passarlo, <input type="number" ng-value="1234.567|number:2">si vuota l'input, poiché il valore NON È UN NUMERO CORRETTO, ma una rappresentazione di stringa dipendente dalla locale.
SWilk,

61

Penso che il modo migliore per farlo sia quello di creare un filtro, in questo modo:

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

quindi il markup è simile al seguente:

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

Fiddle aggiornato: http://jsfiddle.net/BB4T4/


Il filtro numerico come indicato nella risposta di klaxon farà già il ceil, quindi non è necessario.
Kim,

Ho fatto qualcosa di simile, usando solo il filtro per creare il timer stesso come lo desideravo. In altre parole, prenderei l'ora corrente e la formatterei in una stringa come ho ritenuto opportuno utilizzando un filtro personalizzato. Anche il filtro numerico è buono quando vuoi arrotondare i numeri, ma per le funzioni del pavimento e del soffitto è inutile.
Gabriel Lovetro,

37

È una domanda pelosa a cui rispondere, perché non hai dato il contesto completo di ciò che stai facendo. La risposta accettata funzionerà, ma in alcuni casi causerà scarse prestazioni. Quello, e sarà più difficile testarlo.

Se lo stai facendo come parte di una forma statica, va bene. La risposta accettata funzionerà, anche se non è facile da testare ed è viziosa.

Se vuoi essere "Angolare" a riguardo:

Ti consigliamo di mantenere qualsiasi "logica aziendale" (ovvero logica che altera i dati da visualizzare) fuori dalle tue viste. Questo è così che puoi testare l'unità della tua logica e quindi non finisci per accoppiare strettamente il tuo controller e la tua vista. Teoricamente, dovresti essere in grado di puntare il controller verso un'altra vista e utilizzare gli stessi valori degli ambiti. (se questo ha senso).

Dovresti anche considerare che qualsiasi chiamata di funzione all'interno di un binding (come {{}}o ng-bindo ng-bind-html) dovrà essere valutata su ogni digest , perché angolare non ha modo di sapere se il valore è cambiato o no come sarebbe con una proprietà sul campo di applicazione.

Il modo "angolare" per farlo sarebbe quello di memorizzare nella cache il valore in una proprietà nell'ambito della modifica mediante un evento ng-change o persino un $ watch.

Ad esempio con un modulo statico:

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 ora puoi provarlo!

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

è possibile compilare l'elemento e quindi testare il valore trovato nell'elemento dom. questo è in realtà preferito dal momento che potresti utilizzare anche i filtri di localizzazione.
FlavorScape

Buona risposta. Ma ti chiedi perché anteporre $windowdal momento che sembra funzionare solo con un piano Math.round()?
Neel,

3
@Neel - $ window ti permette di deridere più facilmente i Mathtest. In alternativa, è possibile creare un servizio matematico e iniettarlo.
Ben Lesh,

8

Perché non avvolgere l'intero oggetto matematico in 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]);

e usare:

{{number_var | matematica: 'ceil'}}


8

L'opzione migliore è usare:

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

Imposta il valore predefinito dell'equazione su 0 nel caso in cui i valori non siano inizializzati.



6

Il modo più semplice per eseguire semplici calcoli con Angular è direttamente nel markup HTML per i singoli binding, se necessario, supponendo che non sia necessario eseguire calcoli di massa sulla pagina. Ecco un esempio:

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

In questo caso, fai semplicemente i calcoli in () e poi usa un filtro | per ottenere una risposta numerica. Ecco ulteriori informazioni sulla formattazione di numeri angolari come testo:

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


4

Associare l'oggetto Math globale all'ambito (ricordarsi di usare $ window, non finestra)

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

Usa l'associazione nel tuo HTML:

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

Oppure crea un filtro per la specifica funzione matematica che stai cercando:

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

Usa il filtro nel tuo HTML:

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

2

Non sembra un modo molto angolare di farlo. Non sono del tutto sicuro del motivo per cui non funziona, ma probabilmente avrai bisogno di accedere all'ambito per utilizzare una funzione del genere.

Il mio consiglio sarebbe di creare un filtro. Questo è il modo angolare.

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

Quindi nel tuo HTML fai questo:

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

1

Il numberfiltro formatta il numero con migliaia di separatori, quindi non è strettamente una funzione matematica.

Inoltre, il suo "limitatore" decimale non ha ceildecimali tritati (come alcune altre risposte ti inducono a credere), ma piuttosto roundli ingigantisce.

Quindi, per qualsiasi funzione matematica che desideri, puoi iniettarla (più facile da deridere che iniettare l'intero oggetto Math) in questo modo:

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

Non è necessario avvolgerlo in un'altra funzione.


0

Questo è più o meno un riassunto di tre risposte (di Sara Inés Calderón, klaxon e Gothburz), ma poiché tutti hanno aggiunto qualcosa di importante, ritengo che valga la pena unire le soluzioni e aggiungere qualche spiegazione in più.

Considerando il tuo esempio, puoi fare calcoli nel tuo modello usando:

{{ 100 * (count/total) }}

Tuttavia, ciò può comportare un sacco di cifre decimali, quindi usare i filtri è un buon modo per andare:

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

Per impostazione predefinita, il filtro numerico lascerà fino a tre cifre frazionarie , è qui che l' argomento fractionSize è abbastanza utile ( {{ 100 * (count/total) | number:fractionSize }}), che nel tuo caso sarebbe:

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

Questo arrotonderà anche il risultato già:

L'ultima cosa da menzionare, se si fa affidamento su un'origine dati esterna, è probabilmente buona norma fornire un valore di fallback adeguato (altrimenti si potrebbe vedere NaN o nulla sul proprio sito):

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

Sidenote: a seconda delle specifiche, potresti anche essere in grado di essere più preciso con i tuoi fallback / definire già i fallback su livelli inferiori (ad es {{(100 * (count || 10)/ (total || 100) | number:2)}}.). Tuttavia, questo potrebbe non avere sempre senso.


0

Esempio di dattiloscritto angolare usando una pipa.

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

Aggiungi alle dichiarazioni @NgModule

@NgModule({
  declarations: [
    MathPipe,

quindi utilizzare nel modello in questo modo:

{{(100*count/total) | math:'round'}}

TypeError: Math [args] non è una funzione
MattEnth,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.