2013-07-13 4 views
9

В AngularJS прямолинейно, как $ смотреть переменную $ scope и использовать ее для обновления другого. Но какова наилучшая практика, если две переменные сферы должны смотреть друг на друга?Фаренгейт и Цельсия Двунаправленное преобразование в AngularJS

У меня есть пример двунаправленного конвертера, который преобразует Фаренгейта в цельном, и наоборот. Он работает хорошо, если вы наберете «1» в градусы Фаренгейта, но попробовать «1.1» и Угловое отскакивает вокруг немного перед перезаписью Фаренгейт, что вы только что ввели, чтобы быть немного другое значение (1,1000000000000014):

function TemperatureConverterCtrl($scope) { 
    $scope.$watch('fahrenheit', function(value) { 
    $scope.celsius = (value - 32) * 5.0/9.0; 
    }); 
    $scope.$watch('celsius', function(value) { 
    $scope.fahrenheit = value * 9.0/5.0 + 32; 
    }); 
} 

Вот плункер: http://plnkr.co/edit/1fULXjx7MyAHjvjHfV1j?p=preview

Каковы различные возможные способы остановки углового от «подпрыгивания» вокруг и заставить его использовать значение, введенное вами как есть, например используя форматирование или парсеры (или любой другой трюк)?

+1

Причина, по которой это хороший вопрос, состоит в том, что эта ситуация имеет бесконечный цикл во всем этом - если вычисления не были обратными друг другу ... +1. И я буду удивлен, увидев любые другие разные ответы. – rGil

+0

Из-за фона в DSP мне трудно противостоять математике, поэтому я просто отдам его: возможно, это перейдет в бесконечный цикл (за исключением того, что у Angular есть встроенный предел максимальных итераций). Вот отличное объяснение того, почему значения «отскок» вообще (из-за грязной проверки): http://stackoverflow.com/a/9693933/885163. И в сочетании с ограниченной точностью с плавающей запятой возможно, чтобы значения осциллировали 0, 1 или более раз, даже неопределенно, эффект, известный как «предельный цикл» в фильтрах DSP IIR. * phew * как это для комментария? –

ответ

9

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

Все, что вам нужно, это использовать директиву ng-change, чтобы установить флаг в редактируемом поле.

Working Plunk

Код необходимые изменения:

Изменить контроллер выглядеть следующим образом:

function TemperatureConverterCtrl($scope) { 
    // Keep track of who was last edited 
    $scope.edited = null; 
    $scope.markEdited = function(which) { 
    $scope.edited = which; 
    }; 

    // Only edit if the correct field is being modified 
    $scope.$watch('fahrenheit', function(value) { 
    if($scope.edited == 'F') { 
     $scope.celsius = (value - 32) * 5.0/9.0; 
    } 
    }); 
    $scope.$watch('celsius', function(value) { 
    if($scope.edited == 'C') { 
     $scope.fahrenheit = value * 9.0/5.0 + 32; 
    } 
    }); 
} 

А затем добавить эту простую директиву с полями ввода (с использованием F или C в зависимости от ситуации) :

<input ... ng-change="markEdited('F')/> 

Теперь только введенное поле может изменить другое.

Если вам нужна возможность изменять эти поля вне вход, вы можете добавить область или контроллера функцию, которая выглядит примерно так:

$scope.setFahrenheit = function(val) { 
    $scope.edited = 'F'; 
    $scope.fahrenheit = val; 
} 

Тогда поле Celsius будет обновляться на следующий $ digest.

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

+0

Если кто-то думает, что моя несвязанная нота внизу неуместна, я удалю ее. Просто дай мне знать. Благодарю. – OverZealous

+1

ваш ответ супер. Я принял ваше замечательное замечание и оценил его. Я отредактирую свой вопрос, чтобы вынуть (или изменить) дополнительный спрос. –

+0

Удивительный, рад, что вы не обиделись! :-) Теперь я уберу это, сам. – OverZealous

7

Это очень хороший вопрос, и я ответить на него, даже если он уже принял :)

В Угловом, то $ сфера является моделью. Модель - это место для хранения данных, которые вы, возможно, захотите сохранить или использовать в других частях приложения, и как таковой, она должна быть разработана с хорошей схемой так же, как и в базе данных, например.

Ваша модель имеет два избыточных поля для температуры, что не является хорошей идеей. Какая из них «настоящая» температура? Бывают случаи, когда вы хотите денормализовать модель, только для эффективности доступа, но это только вариант, когда значения являются идемпотентными, что, как вы обнаружили, не связано с точностью с плавающей запятой.

Если вы хотите продолжить использование модели, это будет выглядеть примерно так. Вы выбрали бы одну или другую как температуру «источника истины», а затем использовали бы другой вход в качестве удобного окна ввода с форматированием и парсером. Допустим, мы хотим Фаренгейта в модели:

<input type="number" ng-model="temperatureF"> 
<input type="number" ng-model="temperatureF" fahrenheit-to-celcius> 

И директивы преобразования:

someModule.directive('fahrenheitToCelcius', function() { 
    return { 
    require: 'ngModel', 
    link: function(scope, element, attrs, ngModel) { 
     ngModel.$formatters.push(function(f) { 
     return (value - 32) * 5.0/9.0; 
     }); 
     ngModel.$parsers.push(function(c) { 
     return c * 9.0/5.0 + 32; 
     }); 
    } 
    }; 
}); 

При этом, вы бы избежать «подпрыгивая», потому что $ парсеры работать только на действия пользователя не по изменению модели. У вас все еще будут длинные десятичные знаки, но это можно было бы исправить округлением.

Однако это звучит так, как будто вы не должны использовать модель для этого. Если все, что вам нужно, - «каждый ящик обновляет другое поле», вы можете сделать именно это и даже не иметь модель. Это предполагает, что поля ввода начинают пустым, и это простой инструмент для пользователей, чтобы преобразовывать температуры. Если это так, у вас нет модели, нет контроллера и даже вряд ли можно использовать Angular. В данный момент это чисто виджет-виджет, и в основном это jQuery или jQLite. Однако он имеет ограниченную полезность, поскольку без модели он не может воздействовать ни на что другое в Angular.

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

fahrenheitBox.bind('change', function() { 
    celciusBox.val((Number(fahrenheitBox.val()) - 32) * 5.0/9.0); 
}); 
+1

Этот ответ дает большое представление о разработке правильно нормированной схемы для $ scope; Лучше ли маркировка и защита (ответ @OverZealous) или лучший дизайн схемы, могут варьироваться в зависимости от приложения, и оба ответа потрясающие. –