2013-07-13 12 views
9

In AngularJS, è semplice come $ guardare una variabile $ scope e usarla per aggiornarne un'altra. Ma qual è la migliore pratica se due variabili di ambito devono guardarsi l'un l'altro?Conversione bidirezionale Fahrenheit e Celsius in AngularJS

Ho come esempio un convertitore bidirezionale che convertirà Fahrenheit in Celsius e viceversa. Funziona bene se si digita "1" in Fahrenheit, ma provate "1.1" e angolare si rimbalzare un po 'intorno prima di sovrascrivere Fahrenheit che appena immessa per essere un valore leggermente diverso (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; 
    }); 
} 

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

Quali sono i diversi modi possibili per interrompere Angular da "rimbalzare" e forzarlo a utilizzare il valore digitato così com'è, ad es. usando formattatori o parser (o qualsiasi altro trucco)?

+1

Il motivo per cui questa è una buona domanda è perché questa situazione ha un ciclo infinito dappertutto - se i calcoli non erano inversi l'uno dell'altro ... +1. E sarò sorpreso di vedere altre risposte diverse. – rGil

+0

A causa di uno sfondo in DSP, è difficile per me resistere alla matematica, quindi mi limiterò a rinunciare: è possibile che questo entri in un loop infinito (eccetto che Angular ha un built-in limite di iterazioni massime). Ecco una grande spiegazione del perché i valori "rimbalzano" a tutti (a causa del controllo sporco): http://stackoverflow.com/a/9693933/885163. E quando accoppiato con la precisione limitata del punto mobile, è possibile che i valori oscillino 0, 1 o più volte, anche indefinitamente, un effetto noto come "ciclo limite" nei filtri DSP IIR. * phew * com'è questo per un commento? –

risposta

9

Penso che la soluzione più semplice, più veloce e più corretta sia quella di avere un flag per tracciare il campo in corso di modifica e consentire solo gli aggiornamenti da quel campo.

Tutto ciò che serve è utilizzare la direttiva ng-change per impostare il flag sul campo che si sta modificando. modifiche necessarie

Working Plunk

Codice:

Modificare il controller per assomigliare a questo:

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

e quindi aggiungere questa semplice direttiva per i campi di input (utilizzando F o C a seconda dei casi) :

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

Ora solo il campo da digitare può cambiare l'altro.

Se è necessario la possibilità di modificare questi campi fuori un ingresso, è possibile aggiungere una funzione di ambito o controller che sembra qualcosa di simile:

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

Poi il campo Celsius sarà aggiornato sul prossimo $ ciclo di digestione.

Questa soluzione ha un minimo di codice aggiuntivo, elimina ogni possibilità di aggiornamenti multipli per ciclo e non causa alcun problema di prestazioni.

+0

Se qualcuno pensa che la mia nota non correlata in fondo sia inappropriata, la cancellerò. Fammelo sapere. Grazie. – OverZealous

+1

la tua risposta è super. Ho preso la tua nota in più con buon passo e l'ho apprezzata. Modificherò la mia domanda per eliminare (o riformulare) la domanda aggiuntiva. –

+0

Fantastico, felice che tu non ti abbia offeso! :-) Ora spoglierò quel pezzo, me stesso. – OverZealous

7

Questa è una buona domanda e sto rispondendo che anche se è già accettata :)

Nel angolare, il $ ambito è il modello. Il modello è un luogo in cui archiviare i dati che potresti voler conservare o utilizzare in altre parti dell'app e, come tale, dovrebbe essere progettato con uno schema valido, proprio come faresti per esempio in un database.

Il modello ha due campi ridondanti per la temperatura, che non è una buona idea. Qual è la temperatura "reale"? Ci sono volte in cui vuoi denormalizzare un modello, solo per l'efficienza dell'accesso, ma questa è solo un'opzione quando i valori sono idempotenti, che come hai trovato, non sono dovuti alla precisione in virgola mobile.

Se si desidera continuare a utilizzare il modello, sarebbe simile a questo. Sceglieresti l'uno o l'altro come la "fonte di verità" della temperatura, poi avrai l'altro input come una comoda entry box con un formattatore e un parser. Diciamo che vogliamo Fahrenheit nel modello:

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

e la direttiva di conversione:

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

Con questo, devi evitare il "rimbalza", perché $ parser eseguiti solo su azione dell'utente, non sui cambiamenti del modello. Avresti ancora lunghi decimali, ma questo potrebbe essere risolto con arrotondamenti.

Tuttavia, mi sembra che non dovresti usare un modello per questo. Se tutto ciò che vuoi è "ogni casella aggiorna l'altra casella", puoi fare esattamente questo e non avere nemmeno un modello. Ciò presuppone che le caselle di input inizino in bianco e che sia solo uno strumento semplice per convertire le temperature. Se questo è il caso, non hai alcun modello, nessun controller e non usi nemmeno quasi nulla. A quel punto è un widget basato esclusivamente sulla vista, ed è fondamentalmente solo jQuery o jQLite. È tuttavia di utilità limitata, poiché senza modello non può effettuare alcun effetto su Angular.

Per fare ciò, è sufficiente creare una direttiva temperatureConverter che abbia un modello con un paio di caselle di input e che guardi entrambe le caselle e ne imposti i valori. Qualcosa del tipo:

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

Questa risposta ha un grande vantaggio nella progettazione di uno schema correttamente normalizzato per $ scope; Se la marcatura e la protezione (la risposta di @OverZealous) o la migliore progettazione dello schema sono le migliori, possono variare in base all'applicazione per applicazione, ed entrambe le risposte sono eccezionali. –

Problemi correlati