The AngularJs formatter fails to trigger when the array undergoes modifications

Can someone help me understand two confusing behaviors in my code:

  1. I have a formatter in the directive array-to-string that is only triggered during initialization. When I update the array in my Controller with the push function, the formatter doesn't get invoked. Why is this happening and how can I fix it?
  2. In the formatter function of the array-to-string directive, I extract values from an array into a single string and return it. However, the ng-model of mydir doesn't change to the value returned by the formatter. This seems illogical because the formatter of mydir should receive the result of the array-to-string formatter.

angular.module("app", []);

angular.module("app").directive("arrayToString", function() {
  return {
    restrict: "A",
    require: "?ngModel",
    link: function(scope, element, attrs, ngModelCtrl) {
      ngModelCtrl.$formatters.push(function(array) {
        console.log("formatter called");
        var text = "";
        angular.forEach(array, function(e) {
          text += e.label + ", ";
        });
        text = text.substring(0, text.length - 2);
        console.log(text);
        return text;
      });
    }
  };
});

angular.module("app").directive("mydir", function() {
  return {
    restrict: "E",
    scope: {
      ngModel: "="
    },
    require: "?ngModel",
    link: function(scope, element, attrs, ngModelCtrl) {
      ngModelCtrl.$formatters.push(function(alreadyFormattedValue) {
        scope.myString = alreadyFormattedValue;
      });
    },
    template: "<div>{{myString}}</div><div>{{ngModel}}</div>"
  }
});

angular.module("app").controller("Controller", function() {
  var _this = this;
  
  _this.myValues = [
    { label: "apple" },
    { label: "lemon" },
    { label: "pear" }
  ];
  
  _this.add = function() {
    _this.myValues.push({ label: "strawberry" });
  }
});
<!DOCTYPE html>
<html>

  <head>
    <script src="https://code.angularjs.org/1.4.8/angular.js" data-semver="1.4.8" data-require="<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="dcbdb2bba9b0bdaef2b6af9cedf2eff2eeec">[email protected]</a>"></script>
    <link href="style.css" rel="stylesheet" />
    <script src="script.js"></script>
  </head>
  
  <body ng-app="app" ng-controller="Controller as ctrl">
    <mydir ng-model="ctrl.myValues" array-to-string=""></mydir>
    <button type="button" ng-click="ctrl.add()">Add</button>
  </body>

</html>

Answer №1

Upon further examination of the source code for the ngModel directive, it was discovered that a custom function is used for watching. This function includes an if statement with the following condition:

if (modelValue !== ctrl.$modelValue &&
       // checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator
       (ctrl.$modelValue === ctrl.$modelValue || modelValue === modelValue)
    )

Therefore, if the reference of modelValue remains unchanged, no formatters or validators are applied to the viewValue.

The simplest solution is to change the reference by using functions like concat instead of push.

_this.myValues = _this.myValues.concat({ label: "strawberry" });

For example,

Another option is to add your own $watch and manually apply formatters:

scope.$watchCollection(function() {
    return ngModelCtrl.$modelValue;
}, function(n) {
    var formatters = ngModelCtrl.$formatters,
        idx = formatters.length;

    var viewValue = n;
    while (idx--) {
        viewValue = formatters[idx](viewValue);
    }
    ngModelCtrl.$setViewValue(viewValue);
});

Sample:

angular.module("app", []);

angular.module("app").directive("arrayToString", function() {
  return {
    restrict: "A",
    require: "?ngModel",
    link: function(scope, element, attrs, ngModelCtrl) {
      scope.$watchCollection(function() {
        return ngModelCtrl.$modelValue;
      }, function(n) {
        var formatters = ngModelCtrl.$formatters,
          idx = formatters.length;

        var viewValue = n;
        while (idx--) {
          viewValue = formatters[idx](viewValue);
        }
        ngModelCtrl.$setViewValue(viewValue);
      });
      ngModelCtrl.$formatters.push(function(array) {
        console.log("formatter called");
        var text = "";
        angular.forEach(array, function(e) {
          text += e.label + ", ";
        });
        text = text.substring(0, text.length - 2);
        console.log(text);
        return text;
      });
    }
  };
});

angular.module("app").directive("mydir", function() {
  return {
    restrict: "E",
    scope: {
      ngModel: "="
    },
    require: "?ngModel",
    link: function(scope, element, attrs, ngModelCtrl) {
      ngModelCtrl.$formatters.push(function(alreadyFormattedValue) {
        scope.myString = alreadyFormattedValue;
      });
    },
    template: "<div>{{myString}}</div><div>{{ngModel}}</div>"
  }
});

angular.module("app").controller("Controller", function() {
  var _this = this;

  _this.myValues = [{
    label: "apple"
  }, {
    label: "lemon"
  }, {
    label: "pear"
  }];

  _this.add = function() {
    _this.myValues.push({
      label: "strawberry"
    });
  }
});
<!DOCTYPE html>
<html>

<head>
  <script src="https://code.angularjs.org/1.4.8/angular.js" data-semver="1.4.8" data-require="<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="dcbdb2bba9b0bdaef2b6af9cedf2eff2eeec">[email protected]</a>"></script>
  <link href="style.css" rel="stylesheet" />
  <script src="script.js"></script>
</head>

<body ng-app="app" ng-controller="Controller as ctrl">
  <mydir ng-model="ctrl.myValues" array-to-string=""></mydir>
  <button type="button" ng-click="ctrl.add()">Add</button>
</body>

</html>

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

Finding the smallest value using multiple conditions in Excel

I am currently facing an issue with devising a formula to find the minimum value of a specific product on a particular date (located in Sheet 2, Column C), and then returning the name of the store from (Sheet 2, Column D). On Sheet2 https://i.sstatic.net ...

Limiting input to specific characters is effective on Chrome, but unfortunately does not function on Firefox

This code snippet is designed to restrict user input to lowercase letters from a-z, numbers from 0-9, and the dash character "-". While it functions correctly in Chrome, it does not allow any input in Firefox. What could be causing this issue? $('#sl ...

Unavailability of dates in Json ajax DatePicker

After retrieving database records of dates and converting them into a JSON Object, I am attempting to pass the JSON to javascript. The goal is to make the DatePicker UI dynamic by marking the dates in the JSON as unavailable on the calendar. Unfortunately ...

How can I add a group of numbers to an array?

I am trying to populate an array with a list of numbers from 1 to 10: var topics = []; var obj = {one:1, two:2, three:3, four:4, five:5, six:6, seven:7, eight:8, nine:9, ten:10}; Currently, I am using jQuery's $.each() function to loop through the ...

AngularJS scope watch isn't firing as expected

In the scope, I store a filtering string for dates. $scope.myModel = {date:""}; Utilizing a jQuery datepicker, <input date-value="myModel.date" date-picker /> This directive updates the string through AngularJS - Attribute directive input value c ...

After attempting to publish my NPM package, I encountered an issue where I received an empty object. This occurred despite utilizing a setup that includes ES6, Babel,

Is there anyone out there who can assist me with this issue? I am attempting to publish an npm package with the following configuration: webpack: production: { entry: [ './src', './src/app.scss', 'draft- ...

Experiencing sluggish performance with Vuetifyjs when typing in multiple v-text-field components?

I'm experiencing slow response when using multiple v-text-field in Vuetifyjs, especially during keypress. Check out the Codepen here: Any suggestions on how to improve performance? ...

How can Express Validator be used to create conditional validations that depend on other fields?

Can I create custom validations for express-validator based on other fields? Scenario: I developed a wishlist application where users can purchase items for the wishlist owner and leave a note along with the item. The character count of the note should be ...

Node.js application experiencing bug with End of Line (EOL) not displaying correctly

I've encountered an issue with my node.js application that involves writing the following code: word_meaning = 'line 1' + os.EOL +'line 2'; When attempting to render this in an HTML file using the following code: <p> <% ...

Horizontal Scrolling Menu for Dynamic Page Navigation

My goal was to create a smooth-scrolling one-page website that scrolls horizontally. One feature I wanted was a menu that smoothly slides into view whenever a new page is in focus along with the horizontal scrolling. I found an example of a scrolling scr ...

Guide to creating scroll-based animations for a Div element

I've been brainstorming ways to rotate a div as I scroll. My goal is to recreate the effect shown in this codepen. However, I'm struggling to implement scrolling functionality. What I envision is scrolling down causing the word Data to rotate in ...

The link in Next.js is updating the URL but the page is not refreshing

I am facing a peculiar issue where a dynamic link within a component functions correctly in most areas of the site, but fails to work inside a specific React Component - Algolia InstantSearch (which is very similar in functionality to this example componen ...

PHP - Printing out various embedded HTML quotes

My goal is to display the following line of HTML using PHP echo "<a class='fa fa-ban fa-2x cancelClass' onClick='cancelClass('$id', '$formattedDate', '$time')'></a><p class='text-center ...

Switching Languages in react-simple-keyboard: Explained

import React, { useRef, useState } from "react"; import Keyboard from "react-simple-keyboard"; import "react-simple-keyboard/build/css/index.css"; function App() { const [input, setInput] = useState(""); const [ ...

Convert JSON data into a Google chart with a dynamic number of columns and arrays

Modify my variable chart which currently holds this JSON: [{ "month": "January", "values": [0, 0, 0, 0, 0, 0, 0, 0, 0] }, { "month": "February", "values": [0, 0, 0, 0, 0, 0, 0, 0, 0] }, { "month": "March", "values": [35, 3, 8, 18, ...

Steps to Initiate the Uber Eats Application from a Tailored Android App

I'm currently working on integrating the Uber Eats app with my own application. My app runs on Cordova and Angular. Here is the code I am using: var url = 'https://www.ubereats.com/'; if (isPhoneGap()) { if (isAndroid()) { url = &apo ...

Tips on attaining the desired CSS impact

I'm curious about the method used by the creators of carv.ai to achieve the text masking effect seen on the paragraph "Carv connects wirelessly ...". I'm aware of how background images can be masked on text. ...

having trouble accessing a JavaScript array in AngularJS

Hey there, I'm currently working on a web application using AngularJS and I'm facing an issue with querying arrays. Check out the code snippet below: var angulararray = []; bindleaselistings.bindleaselistingsmethod().then(function(response) { ...

Setting the z-index for a JavaScript plugin

I need help with embedding the LinkedIn plugin into a website that has stacked images using z-index for layout. I have tried assigning the plugin to a CSS class with a z-index and position properties, but it hasn't worked as expected. Can anyone sugge ...

Detecting which item is selected when the tab key is pressed using Javascript and jQuery

I'm having trouble figuring out which element is currently selected or focused when using the tab key. I want to trigger a function when the enter key is pressed on the currently selected item. The code should be pretty straightforward, but my proble ...