2013-07-13 13 views
9

W AngularJS można łatwo obserwować zmienną $ scope i używać jej do aktualizacji innej. Ale jaka jest najlepsza praktyka, jeśli dwie zmienne zakresu muszą oglądać się nawzajem?Konwersja dwukierunkowa Fahrenheita i Celsjusza w AngularJS

Mam jako przykład dwukierunkowy konwerter, który konwertuje Fahrenheita na stopnie Celsjusza i na odwrót. To działa dobrze, jeśli wpiszesz „1” w skali Fahrenheita, ale spróbuj „1.1” i kątowe odbije się trochę przed zastąpieniem Fahrenheita, który właśnie wszedł do być nieco inna wartość (+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; 
    }); 
} 

Oto plunker: http://plnkr.co/edit/1fULXjx7MyAHjvjHfV1j?p=preview

Jakie są różne możliwe sposoby zatrzymania Angulara od "odbijania się" i zmuszania go do użycia wartości wpisanej jako-jest, np. za pomocą formaterów lub analizatorów składni (lub jakiejkolwiek innej sztuczki)?

+1

Powodem tego jest dobre pytanie, ponieważ ta sytuacja ma nieskończoną pętlę - jeśli obliczenia nie były odwrotne względem siebie ... +1. I będę zaskoczony, widząc inne, różniące się odpowiedzi. – rGil

+0

Ze względu na tło w DSP trudno mi się oprzeć wchodzącemu w to w matematyce, więc po prostu się poddam: Jest możliwe, że to wejdzie w nieskończoną pętlę (z wyjątkiem tego, że Angular ma wbudowany limit max. iteracji). Oto wspaniałe wyjaśnienie, dlaczego wartości "odbijają się" w ogóle (ze względu na brudne sprawdzanie): http://stackoverflow.com/a/9693933/885163. A w połączeniu z ograniczoną precyzją punktu zmiennoprzecinkowego możliwe jest, aby wartości oscylowały 0, 1 lub więcej razy, nawet w nieskończoność, efekt znany jako "cykl graniczny" w filtrach DSR IIR. * uff * jak to dla komentarza? –

Odpowiedz

9

Myślę, że najprostszym, najszybszym i najbardziej poprawnym rozwiązaniem jest posiadanie flagi do śledzenia, które pole jest edytowane, i dopuszczanie tylko aktualizacji z tego pola.

Wystarczy użyć dyrektywy ng-change, aby ustawić flagę na edytowanym polu.

Working Plunk

Kod zmienia konieczne:

Modyfikowanie kontroler wyglądać następująco:

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

a następnie dodać ten prosty dyrektywy do pól wejściowych (z wykorzystaniem F lub C odpowiednio) :

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

Teraz tylko wpisywane pole może zmienić drugie.

Jeśli potrzebujesz zdolność do modyfikowania tych dziedzinach poza wejście, można dodać zakresu lub kontrolera funkcję, która wygląda mniej więcej tak:

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

Następnie pole Celsjusza będzie aktualizowana na następny cykl $ digest.

To rozwiązanie ma minimum dodatkowe kodowanie, eliminuje szansę na wielokrotne aktualizacje na cykl i nie powoduje żadnych problemów z wydajnością.

+0

Jeśli ktokolwiek myśli, że moja niepowiązana notatka na dole jest nieodpowiednia, usunę ją. Po prostu daj mi znać. Dzięki. – OverZealous

+1

Twoja odpowiedź jest super. Wziąłem twoją dodatkową uwagę w dobrym nastroju i doceniam to. Zmienię moje pytanie, aby wykupić (lub przerobić) dodatkowe zapotrzebowanie. –

+0

Niesamowite, cieszę się, że się nie obraziłeś! :-) Teraz sam się rozbiorę. – OverZealous

7

Jest to całkiem dobre pytanie, a ja odpowiadałem, mimo że już zaakceptowane :)

W kątowy zakres $ jest model. Model jest miejscem przechowywania danych, które możesz chcieć utrwalić lub wykorzystać w innych częściach aplikacji, i jako taki powinien być zaprojektowany z dobrym schematem, tak jak na przykład w bazie danych.

Twój model ma dwa pola redundantne dla temperatury, co nie jest dobrym pomysłem. Który z nich jest "prawdziwą" temperaturą? Są chwile, kiedy chcesz zdenormalizować model, tylko dla wydajności dostępu, ale jest to naprawdę tylko opcja, gdy wartości są idempotentne, co jak zauważyłeś, nie wynikają one z precyzji zmiennoprzecinkowej.

Jeśli chcesz nadal korzystać z modelu, wyglądałoby to tak. Wybrałbyś jedną lub drugą jako temperaturę "źródła prawdy", a następnie wprowadziłeś inne dane jako pole wprowadzania wygody za pomocą formatera i analizatora składni. Powiedzmy, że chcemy Fahrenheita w modelu

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

i dyrektywa konwersji:

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

Dzięki temu, można uniknąć „podskakują wokół”, ponieważ $ parser uruchomić tylko na działania użytkownika, a nie w sprawie zmian modelu. Wciąż masz długie miejsca dziesiętne, ale można to naprawić zaokrąglając.

Jednak dla mnie brzmi to tak, jakby nie używać tego modelu. Jeśli wszystko, czego chcesz, to "każde pudełko aktualizuje drugie pudełko", możesz zrobić dokładnie to, a nawet nie mieć modelu. Zakłada to, że pola wejściowe zaczynają się puste i jest to po prostu proste narzędzie do konwersji temperatury przez użytkowników. Jeśli tak jest, nie masz modelu, nie ma kontrolera, a nawet nie używasz Angulara w ogóle. W tym momencie jest to widżet oparty wyłącznie na widoku i jest to w zasadzie po prostu jQuery lub jQLite. Ma jednak ograniczoną przydatność, ponieważ bez modelu nie może wpływać na nic innego w Angular.

Aby to zrobić, wystarczy wprowadzić dyrektywę temperatureConverter, która ma szablon z kilkoma polami wprowadzania danych, i obserwuje oba pola i ustawia ich wartości. Coś jak:

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

Ta odpowiedź jest świetnym pomysłem na zaprojektowanie prawidłowo znormalizowanego schematu dla $ scope; Czy oznakowanie i ochrona (odpowiedź @OverZealous) lub lepszy projekt schematu jest najlepszy, może się różnić w zależności od aplikacji i obie odpowiedzi są wspaniałe. –