I'm building a weather app using Angular 1.5.8 and need to give users the ability to toggle back and forth between imperial and metric measurements for the temperature and wind speed.
The toggle option and all weather information (fetched from an external API) are located in separate directives, but I've thought about moving the temp and wind speed data to the same directive as the toggle option, then using either $broadcast or $emit to display the data and conversions in the weather directive. Is that the best way to go about doing this? If not, what would be?
Directive where the toggle is located:
app.directive('topBar', topBar);
function topBar() {
return {
template:
'<div class="changeTemp" ng-click="vm.changeTempUnit()">' +
'<span ng-class="vm.fahrClass">°F</span>' +
'<span>/</span>' +
'<span ng-class="vm.celsClass">°C</span>' +
'</div>',
restrict: 'E',
scope: {},
controller: TopBarController,
controllerAs: 'vm'
};
}
function TopBarController() {
var vm = this;
vm.celsClass = 'unselected';
vm.changeTempUnit = changeTempUnit;
vm.fahrClass = 'selected';
vm.temp;
vm.windSpeed;
function changeTempUnit() {
if (vm.fahrClass === "selected") {
vm.fahrClass = 'unselected'; //F unselected
vm.celsClass = 'selected'; //C selected
vm.temp = Math.round((vm.temp - 32) * 5 / 9); //Celsius
vm.windSpeed = (vm.speed * 0.44704).toFixed(0); // M/S
} else if (vm.celsClass === 'selected') {
vm.celsClass = 'unselected'; //C unselected
vm.fahrClass = 'selected'; //F selected
vm.temp = Math.round(vm.temp * 1.8 + 32); //Fahren
vm.windSpeed = (vm.speed / 0.44704).toFixed(0); //MPH
}
}
}
Directive where the weather is displayed
app.directive('weather', weather);
function weather() {
return {
template:
'<div>' +
'Temp: {{vm.temp}}°' + '<br>' +
'Wind Speed: {{vm.windSpeed}}' +
'</div>',
restrict: 'E',
scope: {},
controller: WeatherController,
controllerAs: 'vm'
};
}
WeatherController.$inject = ['weatherService'];
function WeatherController(weatherService) {
var vm = this;
vm.temp;
vm.windSpeed;
activate();
function activate() {
return weatherService.getWeather().then(function(data) {
weatherInfo(data);
});
}
function weatherInfo(data) {
vm.temp = Math.round(data.main.temp); //Fahren
vm.windSpeed = (data.wind.speed).toFixed(0); //MPH
}
}
My first recommendation is to use the AngularJs 1.5+ component
api. Components assume several of the directive definition object values you've already chosen.
If your answer is yes to these questions then you should be using the component
api instead.
Converting your <top-bar>
to a component would look like this
app.component('topBar', {
template:
'<div class="changeTemp" ng-click="$ctrl.changeTempUnit()">' +
'<span ng-class="$ctrl.fahrClass">°F</span>' +
'<span>/</span>' +
'<span ng-class="$ctrl.celsClass">°C</span>' +
'</div>',
controller: TopBarController,
bindings: {}
});
function TopBarController() {
...
}
Notice how the template uses $ctrl
to refer to the controller instead of vm
. With components, $ctrl
is the default.
emit
and broadcast
can be used and this is probably an ideal place to use them! but if you can avoid them, then don't rely on them.
Here is one option
Topbar
app.component('topBar', {
template:
'<div class="changeTemp" ng-click="$ctrl.changeTempUnit()">' +
'<span ng-class="$ctrl.fahrClass">°F</span>' +
'<span>/</span>' +
'<span ng-class="$ctrl.celsClass">°C</span>' +
'</div>',
controller: ['conversionService', TopBarController],
bindings: {
}
})
function TopBarController(conversionService) {
var vm = this;
vm.celsClass = 'unselected';
vm.changeTempUnit = changeTempUnit;
vm.fahrClass = 'selected';
function changeTempUnit() {
if (vm.fahrClass === "selected") {
vm.fahrClass = 'unselected'; //F unselected
vm.celsClass = 'selected'; //C selected
conversionService.selectedUnit = conversionService.tempUnits.celsius;
} else if (vm.celsClass === 'selected') {
vm.celsClass = 'unselected'; //C unselected
vm.fahrClass = 'selected'; //F selected
conversionService.selectedUnit = conversionService.tempUnits.farhenheit;
}
}
}
ConversionService
app.service('conversionService', function() {
var service = this;
service.tempUnits = {
farhenheit: 'farhenheit',
celsius: 'celsius'
};
service.selectedUnit = 'farhenheit';
service.convertTemperature = function(temp, tempUnit) {
if (service.selectedUnit === tempUnit) {
return temp;
} else if (service.selectedUnit === service.tempUnits.farhenheiht) {
return Math.round(temp * 1.8 + 32);
} else if (service.selectedUnit === service.tempUnits.celsius) {
return Math.round((temp - 32) * 5 / 9);
} else {
throw Error("Invalid unit");
}
}
});
Weather
app.component('weather', {
template:
'<div>' +
'Temp: {{ $ctrl.getTemp() }}°' +
'<br>' +
'Wind Speed: {{ $ctrl.windSpeed }}' +
'</div>',
controller: ['conversionService', 'weatherService', WeatherController],
bindings: {}
});
function WeatherController(conversionService, weatherService) {
var ctrl = this;
ctrl.temp;
ctrl.windSpeed;
ctrl.conversionService = conversionService;
activate();
function getTemp() {
return ctrl.conversionService.convertTemperature(ctrl.temp, ctrl.conversionService.tempUnits.farhenheit);
}
function activate() {
return weatherService.getWeather()
.then(weatherInfo);
}
function weatherInfo(data) {
ctrl.temp = Math.round(data.main.temp); //Fahren
ctrl.windSpeed = (data.wind.speed).toFixed(0); //MPH
}
}
Since Angular does dirty checking when directives like ng-click
evaluate their bound expressions the template of <weather>
will also be dirty checked and the expression
{{ $ctrl.conversionService.convertTemperature($ctrl.temp, $ctrl.conversionService.tempUnits.farhenheit) }}
will be evaluated.