Is it possible to use an ngClick function in one directive to toggle data in another?

Currently, I am in the process of developing a weather application using Angular 1.5.8 where users should have the option to switch between imperial and metric units for temperature and wind speed.

The toggle feature along with all the weather data fetched from an external API are placed in separate directives. However, I am considering consolidating the temperature and wind speed information into the same directive as the toggle feature, and then utilizing either $broadcast or $emit to showcase the data and conversions within the weather directive. Do you think this approach is the most effective way to achieve this? If not, what alternative method would you suggest?

Here is the directive containing the toggle:

app.directive('topBar', topBar);

function topBar() {
    return {
        template: 
        '<div class="changeTemp" ng-click="vm.changeTempUnit()">' +
            '<span ng-class="vm.fahrClass">&deg;F</span>' +
            '<span>/</span>' +
            '<span ng-class="vm.celsClass">&deg;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'; // Fahrenheit unselected
            vm.celsClass = 'selected'; // Celsius 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'; // Celsius unselected
            vm.fahrClass = 'selected'; // Fahrenheit selected
            vm.temp = Math.round(vm.temp * 1.8 + 32); // Fahrenheit
            vm.windSpeed = (vm.speed / 0.44704).toFixed(0); // MPH
        }
    }
}

Below is the directive responsible for displaying the weather:

app.directive('weather', weather);

function weather() {
    return {
        template:
  '<div>' +
      'Temp: {{vm.temp}}&deg;' + '<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); // Fahrenheit
        vm.windSpeed = (data.wind.speed).toFixed(0); // MPH
    }
}

Plunker link

Answer №1

Utilize Components for AngularJS Development

To enhance your AngularJs 1.5+ development, I suggest making use of the component api. With components, you can leverage various directive definition object values that have already been selected.

If the following questions receive a positive answer, then opting for the component api would be ideal:

  1. Does your directive include a template?
  2. Is an isolated scope part of your directive?
  3. Does your directive possess a controller?
  4. Is your directive restricted to elements?

Transforming your <top-bar> directive into a component can be done in the following manner:

app.component('topBar', {
    template: 
            '<div class="changeTemp" ng-click="$ctrl.changeTempUnit()">' +
            '<span ng-class="$ctrl.fahrClass">&deg;F</span>' +
            '<span>/</span>' +
            '<span ng-class="$ctrl.celsClass">&deg;C</span>' +
            '</div>',
    controller: TopBarController,
    bindings: {}
});

function TopBarController() {
...
}

Observe how the template uses $ctrl to reference the controller instead of vm. In components, $ctrl serves as the default.

Potential Resolution

The utilization of emit and broadcast is feasible, but it might be preferable to avoid them if possible. Here is one potential option:

Shift Calculation Logic to a Service

Topbar

app.component('topBar', {
    template: 
            '<div class="changeTemp" ng-click="$ctrl.changeTempUnit()">' +
            '<span ng-class="$ctrl.fahrClass">&deg;F</span>' +
            '<span>/</span>' +
            '<span ng-class="$ctrl.celsClass">&deg;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() }}&deg;' + 
            '<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
    }
}

Explore the updated version of your plunk here

Angular conducts dirty checking when directives like ng-click evaluate their bound expressions. Hence, the template of <weather> will undergo dirty checking, and the expression

{{ $ctrl.conversionService.convertTemperature($ctrl.temp, $ctrl.conversionService.tempUnits.farhenheit) }}

will be computed accordingly.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Passing properties from the parent component to the child component in Vue3JS using TypeScript

Today marks my inaugural experience with VueJS, as we delve into a class project utilizing TypeScript. The task at hand is to transfer the attributes of the tabsData variable from the parent component (the view) to the child (the view component). Allow me ...

How can I customize a default button in HTML to hide the selected option from the dropdown menu?

Hey there! I'm currently working on a website that needs to be bilingual, with Spanish as the default language. I want to include a dropdown button that allows users to translate the content into English. Here's what I've tried so far: ...

Identifying Elements Generated on-the-fly in JavaScript

Currently, I am tackling the challenge of creating a box that can expand and collapse using regular JavaScript (without relying on jQuery). My main roadblock lies in figuring out how to effectively detect dynamically added elements or classes to elements a ...

How can I conditionally disable a button in Vue.js using an if statement?

Can someone help me figure out why all my buttons are getting disabled when I only want one to be disabled? Here is the code where I created a counter with vue.js: <body> <div id="app"> <button @click="co ...

What is the procedure for assigning an element's background-color to match its class name?

Is there a way to use jQuery to make the background color of a span element match its class? $(function() { $("span").css("background-color") }); span { display: inline-block; width: 5px; height: 5px; border: solid #0a0a0a 1px; } <script src= ...

Select either one checkbox out of two available options

My form includes two checkboxes, allowing users to click on both of them. I'm wondering if it's possible to set it up so that only one checkbox can be selected at a time. For example, clicking on the first checkbox would turn it on while turning ...

Is it acceptable to designate the db instance as a global variable so that it is always accessible without requiring the "require" statement in Node.js?

I am just starting my journey with "node js" and I am currently working on a program that requires a database model similar to "wpdb" in WordPress. Should I create it as a global variable or use the "require" statement only when needed? Your help in provi ...

Choosing a recently inserted row in jqGrid

After reloading the grid, I am trying to select the newly added row, which is always added at the end. However, it seems impossible to do so after the reload. Is there a reliable way to select the last row after reloading the grid? The current code I have ...

Utilizing Jquery for ASP.NET, an AJAX call dynamically populates a list

My user interface is designed to populate a select dropdown menu using data retrieved from a database through an AJAX call. The C# web method responsible for this operation is structured as follows: private static List<List<string>> componentT ...

Replicate the form to a new one while concealing the elements and then submit it

Initially, I was working with just one form. Now, I find myself in a situation where I need to utilize a different form which contains the same inputs. This is necessary because depending on the action taken upon submission, different processes will be tri ...

What method can I use to ensure that the sidebar stays fixed at a particular div as the user continues to scroll down the

Is there a way to automatically fix the sidebar once the user scrolls down and hits the top of the .Section2? Currently, I have to manually enter a threshold number which can be problematic due to varying positions across browsers and systems. Fiddle htt ...

Tips for ensuring a NodeJS/connect middleware runs after response.end() has been called?

I'm trying to create a setup like this: var c = require('connect'); var app = c(); app.use("/api", function(req, res, next){ console.log("request filter 1"); next(); }); app.use("/api", function(req, res, next){ console.log("r ...

Angular2 Service Failing to Return Expected Value

It's frustrating that my services are not functioning properly. Despite spending the last two days scouring Stack Overflow for solutions, I haven't been able to find a solution that matches my specific issue. Here is a snippet of my Service.ts c ...

Send a parameter to the modal that represents the component

Is there a way to pass data to a modal without passing all of $scope? The modal is located within a component. angular.module('app').component('testModal', { templateUrl: '/test-modal', bindings: { close: '&& ...

The HTML must be loaded prior to the execution of my JavaScript function

In the controller, there is a function that sets the true value to a variable. function setShow() { return this.isShow === 'Foo'; } The value of this.isShow is set to 'Foo' Within the template, there is <div ng-if = "vm.setShow( ...

Using Radio button to access local HTML pages - A step-by-step guide

I am currently working on a project that involves the use of radio buttons and their corresponding options. My goal is to have each radio button selection lead to a specific HTML page being displayed. I've come across solutions involving external URLs ...

Expressjs Error- ReferenceError: cors has not been defined in this context

While working on creating a backend using ExpressJs, I encountered an error when running the backend. app.use(cors()) ^ ReferenceError: cors is not defined at Object.<anonymous> (C:\Users\hp\Desktop\Entri\kanba\ ...

How can I retrieve the PHP response once a successful upload has occurred using DropzoneJS?

I am currently in the process of integrating Dropzone into my website. My goal is to capture the "success" event and extract specific information from the server response to add to a form on the same page as the DropZone once the upload is finished. The k ...

Experiencing memory issues while attempting to slice an extensive buffer in Node.js

Seeking a solution for efficiently processing a very large base64 encoded string by reading it into a byte (Uint8) array, splitting the array into chunks of a specified size, and then encoding those chunks separately. The current function in use works but ...

Display only one dropdown menu at a time

Hey there, I'm currently working on a dropdown menu and struggling to figure out how to keep only one item open at a time. I've tried using an array with useState for all my dropdowns but haven't been able to find a solution yet: code co ...