Математические функции в привязках AngularJS

232

Есть ли способ использовать математические функции в привязках AngularJS?

например

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

Эта скрипка показывает проблему

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

ricick
источник
11
Другой вариант - избегать использования Math в своих шаблонах, используя вместо этого фильтр: <p>The percentage is {{(100*count/total)| number:0}}%</p>подробнее об этом ниже.
Эндрю Куклевич
2
Для Angular2 определите элемент компонента, private Math = Math;и вы можете использовать его.
Ондра Жижка

Ответы:

329

Вы должны ввести Mathв свой объем, если вам нужно использовать его, так как $scopeничего не знаете о математике.

Самый простой способ, вы можете сделать

$scope.Math = window.Math;

в вашем контроллере. Я думаю, что угловой способ сделать это правильно - создать математический сервис.

вздор
источник
1
Спасибо. У меня есть сценарий использования, когда у меня есть несколько шаблонов с совершенно другой логикой, и я хочу максимально изолировать их. Идеальный
Ябларго
48
Вы должны использовать фильтр, а не помещать объект Math в область видимости.
Совьют
4
Это хорошо для быстрых и грязных вещей; быстро что-то издеваться или хотеть посмотреть, как что-то работает. Это, вероятно, то, что хочет кто-то, кто хочет сделать математику в своих файлах HTML-шаблонов.
Лан
1
Использование функций в привязках вызовет функцию при обновлении каждой области и будет использовать слишком много ресурсов.
Десмати
Это решение неверно . Зачем? 1. Загрязнение глобального масштаба - худшая идея. 2- представления несут ответственность formatting, поэтому используйте фильтр вместо.
Афшин Мехрабани
304

Хотя принятый ответ является правильным, вы можете Mathиспользовать его в угловых значениях, но для этой конкретной проблемы более общепринятым / угловым способом является числовой фильтр:

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

Вы можете прочитать больше о numberфильтре здесь: http://docs.angularjs.org/api/ng/filter/number

Андрей Куклевич
источник
7
Это не только угловой путь, это правильный путь. Ответственность за «форматирование» значения лежит на представлении.
Люк
39
Эндрю, извините за засорение вашего поста этим комментарием. Ваш ответ хорош и полезен; Тем не менее, я хочу не согласиться с другими комментариями. Я часто съеживаюсь, когда люди рекламируют «угловой путь», как будто это какой-то образец идеального развития. Это не так. ОП в общем спросил, как включить математические функции в привязку, с округлением, представленным только в качестве примера. Хотя этот ответ может быть пушистским «угловым путем» для решения ровно одной математической задачи, он не дает полного ответа на этот вопрос.
Кбримингтон
1
Извините за археологию, но это неверно, когда вам нужен номер в качестве вывода. {{1234.567|number:2}}отображается как 1,234.57или 1 234,57. Если вы попытаетесь передать его <input type="number" ng-value="1234.567|number:2">вам, то очистите входные данные, так как значение НЕ является ПРАВИЛЬНЫМ НОМЕРОМ, а зависит от локали.
SWilk
61

Я думаю, что лучший способ сделать это - создать фильтр, например так:

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

тогда разметка выглядит так:

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

Обновленная скрипка: http://jsfiddle.net/BB4T4/

sabinstefanescu
источник
Числовой фильтр, приведенный в ответе Клаксона, уже будет работать, поэтому в этом нет необходимости.
Ким
Я сделал нечто подобное, только используя фильтр, чтобы создать сам таймер, как я хотел. Другими словами, я бы взял текущее время и отформатировал бы его в строку, как я счел нужным, используя пользовательский фильтр. Кроме того, числовой фильтр хорош для округления чисел, но для функций пола и потолка он бесполезен.
Габриэль Ловетро
37

На этот вопрос сложно ответить, потому что вы не дали полного контекста того, что вы делаете. Принятый ответ будет работать, но в некоторых случаях это приведет к снижению производительности. Это, и это будет сложнее проверить.

Если вы делаете это как часть статической формы, хорошо. Принятый ответ сработает, даже если его нелегко проверить, и он обманчив.

Если вы хотите быть "Angular" по этому поводу:

Вы захотите сохранить любую «бизнес-логику» (т. Е. Логику, которая изменяет отображаемые данные) за пределами ваших представлений. Это сделано для того, чтобы вы могли модульно протестировать свою логику, и чтобы в итоге вы не связали свой контроллер с вашим взглядом. Теоретически, вы должны иметь возможность указать свой контроллер на другое представление и использовать те же значения из областей. (если это имеет смысл).

Вы также захотите учесть, что любые вызовы функций внутри привязки (например, {{}}или ng-bindили ng-bind-html) должны оцениваться при каждом дайджесте , потому что angular не может узнать, изменилось ли значение или нет, как это было бы для свойства по объему.

«Угловой» способ сделать это состоял бы в том, чтобы кэшировать значение в свойстве в области изменения при помощи события ng-change или даже $ watch.

Например со статической формой:

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>

И теперь вы можете проверить это!

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.
  });
});
Бен Леш
источник
Вы можете скомпилировать элемент и затем проверить значение, найденное в элементе dom. это на самом деле предпочтительнее, так как вы также можете использовать фильтры локализации.
FlavorScape
Хороший ответ. Но интересно, зачем начинать с того, $windowчто, кажется, работает только с планом Math.round()?
Нил
3
@Neel - $ window позволяет вам легко смоделировать Mathтесты. С другой стороны, вы можете создать математический сервис и внедрить его.
Бен Леш,
8

Почему бы не обернуть весь математический объект в фильтр?

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


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

        return 0;
    }
}

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

и использовать:

{{number_var | математика: 'CEIL'}}

Маркос Аммон
источник
8

Лучшим вариантом является использование:

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

Он устанавливает значение по умолчанию уравнения в 0 в случае, когда значения не инициализированы.

клаксон
источник
6

Самый простой способ сделать простую математику с Angular - это непосредственно в HTML-разметке для отдельных привязок по мере необходимости, при условии, что вам не нужно делать массовые вычисления на своей странице. Вот пример:

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

В этом случае вы просто делаете математику в (), а затем используете фильтр | чтобы получить числовой ответ. Вот больше информации о форматировании угловых чисел как текста:

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

Сара Инес Кальдерон
источник
4

Либо привяжите глобальный объект Math к области (не забудьте использовать $ window, а не window)

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

Используйте привязку в своем HTML:

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

Или создайте фильтр для конкретной математической функции, которую вы ищете:

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

Используйте фильтр в вашем HTML:

<p>Distance from zero: {{distance | abs}}</p>
craigsnyders
источник
2

Это не похоже на очень угловой способ сделать это. Я не совсем уверен, почему это не работает, но вам, вероятно, потребуется доступ к области действия, чтобы использовать такую ​​функцию.

Мое предложение было бы создать фильтр. Это угловой путь.

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

Затем в вашем HTML сделайте это:

<p>The percentage is {{ (100*count/total) | ceil }}%</p>
7ahid
источник
1

numberФильтр форматирует число с тысячами разделителей, так что это не строго математическая функция.

Более того, его десятичный «ограничитель» не содержит ceilдробные десятичные дроби (как некоторые другие ответы заставят вас поверить), а скорее использует roundих.

Таким образом, для любой математической функции, которую вы хотите, вы можете внедрить ее (проще имитировать, чем внедрить весь объект Math) следующим образом:

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

Не нужно также оборачивать его в другую функцию.


источник
0

Это более или менее краткое изложение трех ответов (Сара Инес Кальдерон, Клаксон и Готбурс), но, поскольку все они добавили что-то важное, я считаю, что стоит объединить решения и добавить еще несколько объяснений.

Рассматривая ваш пример, вы можете выполнять вычисления в своем шаблоне, используя:

{{ 100 * (count/total) }}

Однако это может привести к большому количеству десятичных разрядов, поэтому использование фильтров - хороший способ:

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

По умолчанию числовой фильтр будет содержать до трех дробных цифр , здесь аргумент fraSize довольно удобно ( {{ 100 * (count/total) | number:fractionSize }}), что в вашем случае будет:

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

Это также будет округлять результат уже:

И последнее, что следует упомянуть, если вы полагаетесь на внешний источник данных, вероятно, хорошей практикой является предоставление правильного запасного значения (в противном случае вы можете увидеть NaN или ничего на вашем сайте):

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

Sidenote: В зависимости от ваших спецификаций, вы можете даже быть более точными с вашими запасными вариантами / определить запасные варианты уже на более низких уровнях (например {{(100 * (count || 10)/ (total || 100) | number:2)}}). Хотя, это не всегда имеет смысл ..

Ким
источник
0

Пример Angular Typescript с использованием канала.

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

Добавить в объявления @NgModule

@NgModule({
  declarations: [
    MathPipe,

затем используйте в своем шаблоне так:

{{(100*count/total) | math:'round'}}
Джоэл Дэйви
источник
TypeError: Math [args] не является функцией
MattEnth