Troubles arise when attempting to bind or watch a service variable that is shared between two

I'm really struggling to understand what's happening here. I grasp the basics of Angular's $digest cycle, and according to this Stack Overflow post, I seem to be correctly assigning a scoped variable to a service's property (an array in this case). However, the only way I can update CtrlA's 'things' is by re-assigning it after updating my service's property with a reference to a new array.

Check out this fiddle that showcases my problem:

http://jsfiddle.net/tehsuck/Mujun/

(function () {
angular.module('testApp', [])
    .factory('TestService', function ($http) {
    var service = {
        things: [],
        setThings: function (newThings) {
            service.things = newThings;
        }
    };
    return service;
})
    .controller('CtrlA', function ($scope, $timeout, TestService) {
    $scope.things = TestService.things;
    $scope.$watch('things.length', function (n, o) {
        if (n !== o) {

            alert('Things have changed in CtrlA');
        }
    });
    $timeout(function () {
        TestService.setThings(['a', 'b', 'c']);

        // Without the next line, CtrlA acts like CtrlB in that
        // it's $scope.things doesn't receive an update
        $scope.things = TestService.things;
    }, 2000);
})
    .controller('CtrlB', function ($scope, TestService) {
    $scope.things = TestService.things;
    $scope.$watch('things.length', function (n, o) {
        if (n !== o) {
            // never alerts
            alert('Things have changed in CtrlB');
        }
    });
})

})();

Answer №1

Here are some important points to consider regarding your code:

  1. It's crucial to note that arrays do not possess a count property; the appropriate property to use is length instead.

    $scope.$watch('things.length', ...);
    

    However, it should be mentioned that if you add or remove elements from the things array and end up with a different list of the same length, the watcher callback will not be triggered.

  2. The setThings method within the TestService class actually replaces the reference to the things array with a new one, causing TestService.things to point to a new array in memory while CtrlA.$scope.things and CtrlB.$scope.things still point to the old, now empty array. The following code snippet shows this behavior:

    var a = [];
    var b = a;
    
    a = [1, 2, 3];
    console.log(a); // outputs [1, 2, 3];
    console.log(b); // outputs [];
    

    In order for your code to function as intended, it is necessary to modify the way in which the TestService.setThings method updates the things array. Here's a suggestion:

    setThings: function (newThings) {
        service.things.length = 0; // clears the array
        newThings.forEach(function(thing) { 
            service.things.push(thing);
        });                
    

    }

Furthermore, a functional version of your jsFiddle can be accessed through the provided link.

Answer №2

For some reason, it appears that using a function to return the data in your service and then watching that function instead of the property fixes the issue. If this explanation is unclear, you can view an example here: http://jsfiddle.net/UniqueUser123/Example/15/
I have included a getter in your service:

var service = {
        items: [],
        setItems: function (newItems) {
            service.items = newItems;
        },
        getItems:function(){
             return service.items;   
        }
    };

Then, I have made modifications to your code in both controllers as follows:

    $scope.items = TestService.getItems();
    $scope.getItems=function(){return TestService.getItems();};
    $scope.$watch('getItems()', function (newVal, oldVal) {
        if (newVal !== oldVal) {
            // alert never displays
            alert('Items have been updated in CtrlA');
        }
    }, true);

And in the HTML:

<li ng-repeat="item in getItems()">{{item}}</li>

This defines a function getItems, which simply retrieves the property from your service. The function is then watched using $watch (which evaluates the parameter, allowing functions to be watched) with deep inspection (using the true parameter at the end). The same approach is applied to your other controller. Now, when you modify the value of your service, both $watchers detect the change, ensuring that the data is bound correctly.

Although I cannot guarantee that this is the best method, it appears to work in your specific example. I recommend exploring this approach further.

Enjoy experimenting :)

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

Is it possible to verify if each value satisfies a condition within a Javascript function?

I am currently working on a project using Vue.js and Laravel where I have a data list named "questions." My goal is to iterate through this list and check if the answer value for each question is not null. If any question has a null answer, I want to preve ...

I am currently utilizing react-admin, however, I am encountering an issue with the heavy build causing errors

Currently, I am working on developing the frontend using react-admin. Initially, I intended to utilize NextJS and found a helpful reference article. Reference: When attempting to run next dev, everything worked smoothly without any errors. However, when ...

Issue when Updating Component State: React child cannot be an object

I'm currently in the process of familiarizing myself with React and how to manage component state. My goal is to build a component, set up the initial state using a constructor, make a GET request to the server to fetch an array of objects, and then ...

What steps can be taken to enhance cleanliness and efficiency, and what are some recommended practices to adhere to?

Currently, I am in the process of developing a user authentication system on the backend. Specifically, I have implemented a POST method for registering new users. userRouter.post("/", expressAsyncHandler(async (req, res) => { try { const { na ...

The lower text box on the page being covered by the virtual keyboard on IOS

Our website features a fixed header and footer with scrollable content. We have 20 text boxes on the page, but the ones at the bottom, like Zip and Telephone, are obscured by the iOS virtual keyboard that appears when a text box is clicked. If we could d ...

Steps for referencing a custom JavaScript file instead of the default one:

Currently, I am utilizing webpack and typescript in my single page application in combination with the oidc-client npm package. The structure of the oidc-client package that I am working with is as follows: oidc-client.d.ts oidc-client.js oidc-client.rs ...

What is an effective method for coordinating JQuery animations simultaneously?

My current understanding of $("#someElement").animate() is that it will run asynchronously in relation to other JavaScript statements. For example: $("#anotherElement").css("display", "none"); $("#someElement").animate(); //The CSS display may change a ...

Why is my React app opening in Google Chrome instead of the API?

I have a link in my React app that is supposed to open a PDF in another page, but for some reason Google Chrome is redirecting me to the React application instead of opening the API endpoint that renders the PDF. The URL for the PDF is /api/file/:_id and m ...

Creating a sequence of dependent HTTP requests in Angular

Is it possible to execute multiple http get requests sequentially in Angular, where the endpoint URL for the second request depends on the response of the first request? I attempted to nest the requests using the following code snippet: this.http.get(end ...

Arrangement of div elements tailored to fit the size of the viewport

I am working on creating a grid of divs that will cover the entire viewport. To start, I want to have a grid that is 7 divs wide and 10 divs high. Below is the code snippet I've written so far to set the size of the div elements: function adjustHeig ...

Glass-pane or angular/HTML overlay on a designated element

Is there a way to create a glass-pane effect, like an hourglass symbol on a semi-transparent layer, within the boundaries of an HTML element E? The goal is to have the glass-pane only overlap the area within E's boundaries. When the glass-pane is ac ...

Retrieve the current time of day based on the user's timezone

Currently, I am working with a firebase cloud function that is responsible for sending push notifications to my app. My main requirement is to send notifications only during the day time. To achieve this, I have integrated moment-timezone library into my p ...

Converting the information retrieved from Firebase into a different format

My Project: In my Angular SPA, I am trying to retrieve data from Firebase and display specific values on the screen. Approach Taken: The data returned from Firebase looks like this: Returned Data from Firebase Error Encountered: In order to display the ...

stop automatic resizing of windows

My CSS is written using percentages. I need to maintain the percentages intact. Is there a way to stop the layout from changing when the browser window is resized? I want the percentages to remain unaffected, even when the browser window is being resized ...

`Angular Image Upload: A Comprehensive Guide`

I'm currently facing a challenge while attempting to upload an image using Angular to a Google storage bucket. Interestingly, everything works perfectly with Postman, but I've hit a roadblock with Angular Typescript. Does anyone have any suggesti ...

What is the right rendering strategy to use for shouldComponentUpdate in React?

Provide an exhaustive list of all props required for rendering shouldComponentUpdate(nextProps, nextState) { if (this.props.color !== nextProps.color) { return true; } if (this.state.count !== nextState.count) { return true; ...

Trouble getting ng-change and ng-checked to function simultaneously with radio buttons in AngularJS

Visit this link for the code Here is a piece of Javascript code: angular.module("sampleapp", []).controller('samplecontroller', function($scope,$rootScope) { $scope.radii = [ {id:.25, checked:false, name:"1/4 Mile"}, {id:.5, checked:f ...

What is the best way to select multiple items using mongoose?

For instance, consider this list: [ { name: "John" }, { name: "Mike" }, { name: "Homer" }, { name: "Bart" }, { name: "Dmitry" }, { name: "Dan" } ] If I want to select specific objects ...

Updating website content dynamically using Javascript and JSON-encoded data

My programming code seems to be acting oddly. I have structured my data in a JSON object as follows: injectJson = { "title": "Champion Challenge Questions", "rules": [ { "idChrono": "chrono-minute", "text": "Top is missing!", ...

Trigger the Modal once the navigation step is completed

When navigating to a new page, I need to wait for the navigation process to complete in order for the modal template to be properly displayed on the destination page. public onOpenModal(item) { this.router.navigate([item.link]).then(() => { this. ...