How come AngularJS $onChanges isn't able to detect the modification in the array?

What is the reason behind Angular's failure to detect changes in an array, but successfully does so after changing it to null first?

In my AngularJS component, I utilize a two-way data binding to pass an array. The parent component contains a button with an ng-click event that calls this function:

onClickAddValue()
{ 
    this.listOfValues.push(this.valueInput);
}

The value of this.listOfValues remains unchanged even after adding a new value to the array, resulting in no updates to the child component's view. Understanding this behavior, I attempted to overcome it by resetting the array as shown below:

onClickAddValue()
{ 
    var oldList = this.listOfValues;
    this.listOfValues = [];
    for (let value of oldList)
    {
        this.listOfValues.push(value);
    }
    this.listOfValues.push(this.valueInput);
}

However, this approach did not yield the desired outcome. I also experimented with Object.assign and angular.copy, but to no avail. Interestingly, the following code snippet proves effective:

onClickAddValue()
{ 
    var oldList = this.listOfValues;
    this.listOfValues = null;
    setTimeout(
        () =>
        {
            this.listOfValues = oldList;
            this.listOfValues.push(this.valueInput);
            this.$scope.$digest();
        }
    );  
}

This implementation works because setting listOfValues to null triggers a digest cycle, prompting Angular to recognize the change and update the view. Subsequently, updating the array again within the same context ensures another digest cycle and subsequent view update. On the contrary, why doesn't the initial approach work? What makes altering the object differ from resetting it to null first, followed by making modifications?

Answer №1

Listening for changes in the bound listOfValues in $onChanges

To ensure changes are detected by $onChanges, use Array.concat to create a new object:

  onClickAddValue()
  { 
    this.listOfValues.push(this.valueInput);
    this.listOfValues = this.listOfValues.concat();
  }

The $onChanges Life-Cycle Hook only checks for changes in reference, not contents. After modifying the contents with Array.push, using Array.concat will create a new object that can be detected by the change detector.

View DEMO

angular.module("app",[])
.controller("ctrl", class {
  constructor () {
    this.listOfValues = [3,5];
  }
  onClickAddValue()
  { 
    this.listOfValues.push(this.valueInput);
    this.listOfValues = this.listOfValues.concat();
  }
})
.component("myComponent",{
  bindings: {values: "<"},
  template: `
      <fieldset>
        {{$ctrl.values}}<br>
        changes={{$ctrl.changes}}
      </fieldset>
  `,
  controller: class {
    constructor () {
      this.changes=0;
    }
    $onChanges(ch) {
      this.changes++;
      //console.log(ch);
    }
  }
})
<script src="//unpkg.com/angular/angular.js"></script>
<body ng-app="app" ng-controller="ctrl as $ctrl">
    <input ng-model="$ctrl.valueInput" /><br>
    <button ng-click="$ctrl.onClickAddValue()">Add value</button>
    <br>
    {{$ctrl.listOfValues}}
    <my-component values="$ctrl.listOfValues"></my-component>
</body>


Update

After further exploration, it appears that the issue lies in binding listOfValues to the child component with a = instead of a < binding - switching to a < binding resolves the problem.

The documentation for $ngChanges specifies that it works with attribute ('@') and one-way ('<') bindings.

From the Docs:

Life-cycle hooks

  • $onChanges(changesObj) - Triggered when one-way (<) or interpolation (@) bindings are updated. Use this hook to manage updates within a component, such as cloning the bound value to prevent unintended changes. This also triggers on initialization of bindings.

AngularJS Comprehensive Directive API Reference - Life-Cycle Hooks

It's advised to avoid two-way binding ("=") due to added watchers and difficulties in transitioning to Angular 2+.

For more details, visit AngularJS Developer Guide - Component-based Application Architecture.

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

When trying to add a react-bootstrap modal, the react app crashes abruptly

Encountering errors while attempting to integrate react-bootstrap Modal component into my react project: ERROR in ./~/react-dom/lib/ReactDOMUnknownPropertyHook.js Module not found: Error: Cannot resolve module 'react/lib/ReactComponentTreeHook' ...

Attempting to modify a JSON file within a React application

I recently obtained a JSON file named 'DealerList.json' which I managed to import into my app using the following code snippet: import DealerList from './json/DealerList' let tasks = DealerList; The tasks now store the JSON data and I ...

Mongoose: When encountering a duplicate key error (E11000), consider altering the type of return message for better error handling

When trying to insert a duplicate key in the collection, an error message similar to E11000 duplicate key error collection ... is returned. If one of the attributes is set as unique: true, it is possible to customize this error message like so: {error: ...

Learn how to insert JavaScript code into the head of an iframe using jQuery

My goal is to inject javascript code into the head of an iframe using jquery with the code provided below. var snippets_js='<?php echo $snippets_javascript;?>'; var scriptjs = document.createElement("script"); scriptjs.type = "text/j ...

What is the best way to implement this design using CSS or JavaScript?

I would like to enhance the appearance of my school website during these challenging times of the pandemic by adding some CSS or JavaScript elements. However, I am unsure how to go about it. ...

Developing a Secondary User within Meteor.JS after Establishing the Primary User

Is it possible to automatically create a secondary user upon registration of the primary user using a form generated with the useraccounts:core package? An issue arises when attempting to run Accounts.createUser within Accounts.onCreateUser, resulting in ...

What is the most effective way to populate an element using jQuery?

When you click a button with jQuery, I am generating a modal (Bootstrap modal) as a string. This modal has two option classes: Call Today and Call Tomorrow, which seems fine so far. Upon clicking the button, the modal is created, prompting me to add two a ...

Removing double double quotes for Javascript

My problem involves a string that represents longitude/latitude in the format of dd°mm'ss''W (note 2 single quotes after ss). To convert this string into its decimal representation, I am using the following code snippet: function dmsTodeg ...

The system is unable to retrieve the value of the property which is set as null

I'm trying to figure out how to store the input value of name_enter into the variable userName. However, every time I attempt this, I encounter the following console error: Uncaught TypeError: Cannot read property 'value' of null function ...

Problems with Atom's ternjs autocomplete feature

My project structure is as follows: https://i.sstatic.net/J9Pk4.png The content of .tern-project is: { "ecmaVersion": 6, "libs": [ "browser", "jquery" ], "loadEagerly": [ "/bower-components/d3/d3.js" ] } I attempted to change d3.j ...

Adjustable height for text input field

Is it possible to adjust the height of a textarea dynamically based on user input, rather than relying on scrollbars when content exceeds the width and height of the field? <textarea class="form-control"></textarea> For instance, if a user ty ...

What could be the reason for the lack of controller updates despite changes made to the service

Could someone please help me solve the issue with my code? I expected that after clicking the button, the text would be updated. However, it seems to not be working as intended. Any assistance you can provide would be greatly appreciated. main.js x = a ...

Effortlessly fill dropdown menus with data from JSON files

I've been using this jQuery code successfully, but I recently needed to add 3 more field columns to the JSON. My goal is to have the HTML select drop-downs dynamically change based on the data from the previous drop-down. Additionally, my current jQue ...

load a file with a client-side variable

Is there a way to load a file inside a container while utilizing an argument to fetch data from the database initially? $('#story').load('test.php'); test.php $st = $db->query("select * from users where id = " . $id); ... proce ...

Next.js: Generating static sites only at runtime due to getStaticProps having no data during the build phase, skipping build time generation

I am looking to customize the application for individual customers, with a separate database for each customer (potentially on-premise). This means that I do not have access to any data during the build phase, such as in a CI/CD process, which I could use ...

Utilize the class or interface method's data type

In the context of a child component calling a callback provided by its parent, this situation is commonly seen in AngularJS. As I am utilizing TypeScript, I aim to implement strong typing for the callback in the child component. Here is the initial stat ...

Issue with Angular ng-src not able to load picture when using --livereload (REVISITED)

My Goal: My objective is to enable users to download images from the server to their device when they click on an image placeholder. The downloaded image path will then be stored in the $scope and shown to the user. Below is my controller: .controller(&a ...

Invoke a codebehind function using jQuery

I am encountering an issue while trying to complete a simple task. I am attempting to pass values to a code behind method, perform some calculations, and then return the result. Initially, I started with a test method but have not been successful in making ...

Issues with Angular authentication: HTTP POST request not being sent

UPDATE: I had a small oversight with my submit button placement, but it's all sorted out now (turns out the request wasn't sent because my function wasn't called, a classic mistake). Furthermore, the reason why authentication always succ ...

Is the useNavigate() function failing to work consistently?

Currently facing an issue while working on a MERN Web App. When logging in with an existing account, the backend API call returns user properties and a JWT Token. Upon saving it, I use the navigate function to redirect the User to the homepage. Everything ...