Updating the ngModel within a directive using another directive

Struggling to grasp why modifications to an ngModel don't transfer between directives. Take a look at this plunker for a simplified example of the issue.

Essentially, there's a directive I've defined that utilizes ngModel with an isolate scope:

.directive('echo', function() {
    var link = function(scope, element, attrs, ngModel) {
        // --- 8< ---
        scope.$watch(attrs['ngModel'], function() {
            scope.model = ngModel.$modelValue;
            console.log("***** Echo model updated: ", scope.model);
        });
    };

    return {
        restrict: 'E',
        require: 'ngModel',
        link: link,
        scope: {
            id: "="
        }
    }
})

This directive is then enclosed within another directive, which also depends on ngModel and has an isolate scope:

.directive('wrapper', function() {
    var link = function(scope, element, attrs, ngModel) {
        scope.$watch(attrs['ngModel'], function() {
            var model = ngModel.$modelValue;
            console.log("----- Wrapper model updated", model);

            scope.model = model;
        })
    };

    return {
        restrict: 'E',
        require: 'ngModel',
        link: link,
        scope: {
        },

        template: "<div><h2>Echo:</h2> <echo id='myEcho' ng-model='model'></echo></div><div><h2>Model text:</h2>{{ model.text }}</div>"
    }
})

The "wrapper" directive requires an ngModel, just like the echo directive.

After using the "wrapper" directive in my HTML and updating the ngModel value, the "wrapper" directive correctly detects changes (as shown in the console.log). However, at this point, the wrapper directive updates the model on its scope, which should theoretically pass that update onto the "echo" directive.

Yet, upon monitoring the console, the "echo" directive doesn't pick up on the model update.

Q: What prevents the "echo" directive from recognizing the updated model from the "wrapper" directive?

Note: The situation may be slightly complicated by the fact that the "echo" is sometimes accessed directly, not solely through the "wrapper" directive.

Answer №1

Here's an updated response:

No, the issue is not related to timing; watches will still function regardless of whether they are added before or after the value being watched is set.

I suggest inserting breakpoints in your echo directive and stepping through to observe how the watchers are being established.

You can view a working plunker with updates here: http://plnkr.co/edit/bbv2vpZ7KaDiblVcoaNX?p=preview

.directive('echo', function() {
    var link = function(scope, element, attrs, ngModel) {
        console.log("***** Linking echo");

        var render = function (val) {
            var htmlText = val || 'n/t';
            element.html(htmlText);
        };
        scope.$watch("model.text", render);
    };

    return {
        restrict: 'E',
        link: link,
        scope: {
            id: "=",
            model: '=echoModel'
        }
    }
})

.directive('wrapper', function() {
    var link = function(scope, element, attrs, ngModel) {
        console.log("---- Linking Wrapper");
    };

    return {
        restrict: 'E',
        require: 'ngModel',
        link: link,
        scope: {
          wrapperModel: '=ngModel'
        },

        template: "<div><h2>Echo:</h2> <echo id='myEcho' echo-model='wrapperModel'></echo></div><div><h2>Model text:</h2>{{ wrapperModel.text }}</div>"
    }
})

The reason it's not functioning as expected is due to how the attrs and watchers operate, which may be unexpected.

Essentially, you're attempting to watch the scope.model property on your scope rather than the evaluated value of the ngModel attribute:

.directive('echo', function() {
    var link = function(scope, element, attrs, ngModel) {
        // Your HTML is `<echo ng-model='model'></echo>`
        // which means this `scopePropertyToWatch` will have the value 'model'.
        var scopePropertyToWatch = attrs['ngModel'];

        // This means it will try to watch the value
        // of `scope.model`, which isn't right because
        // it hasn't been set.
        scope.$watch(scopePropertyToWatch, function() {
            scope.model = ngModel.$modelValue;
            console.log("***** Echo model updated: ", scope.model);
        });
    };

    // ...
})

Two straightforward solutions are available.

1. Establish two-way binding on the ngModel attribute:

.directive('echo', function() {
    var link = function(scope, element, attrs, ngModelCtrl) {
        // Watch the `scope.ngModel` property on the scope.
        // NOT the attr['ngModel'] value which will still
        // be 'model'.
        scope.$watch('ngModel', function() {
            scope.model = ngModelCtrl.$modelValue;
            console.log("***** Echo model updated: ", scope.model);
        });
    };

    return {
        restrict: 'E',
        require: 'ngModel',
        link: link,
        scope: {
            id: "=",
            ngModel: "=" // This will make the `ngModel` property available on the scope.
        }
    }
});

Using ngModel in the current manner can become complex - I recommend viewing this video on utilizing ngModel in custom components for clarification: Jason Aden - Using ngModelController to Make Sexy Custom Components

2. Watch the property on the $parent scope:

.directive('echo', function() {
    var link = function(scope, element, attrs, ngModelCtrl) {
        // Add a watch on the **parent** scope for the attribute value.
        // NOTE that we use the attrs['ngModel'] value because the property
        // on the parent scope **is**: `scope.$parent.model`
        scope.$parent.$watch(attrs['ngModel'], function() {
            scope.model = ngModelCtrl.$modelValue;
            console.log("***** Echo model updated: ", scope.model);
        });
    };

    return {
        restrict: 'E',
        require: 'ngModel',
        link: link,
        scope: {
            id: "="
        }
    }
});

Once more, using ngModelCtrl.$modelValue might seem intricate, but it'll ensure your watchers trigger correctly.

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

Tips for optimizing Slider Code to showcase an expanded selection of slides

I am currently troubleshooting a slider code on my ASP.NET website. After examining the HTML, JS, and CSS from the page theme, I noticed that only two slides are being displayed. However, when attempting to add a new slider HTML and adjusting the CSS for t ...

Operating PHP program from html on Beaglebone Black Rev C Debian

I've created a JavaScript program that utilizes node.js to manage GPIO from an IO.html page to the beaglebone. I simply initiate the JavaScript with "node myscript.js". Now, I'm looking to incorporate a PHP file that will store data from the IO. ...

I'm experiencing an issue where my drum app element does not refresh after performing an action dispatch

Struggling with my React/Redux drum app, I'm at a roadblock with the final component that should update with the selected object's ID through an action dispatch. It baffles me why the element won't reflect the changes even though I've c ...

Looking for a way to choose an Object using the key value in VueJS?

In my Vuejs setup, there is an object containing various fields including an email value which looks like this: "[email protected]". { "fields": [ { "label": "email", "value": "<a href="/cdn-cgi/l/email-protection ...

Determine in JavaScript whether a character is 32-bit or not

Is there a way to determine if a specific character is 32 bits using JavaScript? I attempted to use charCodeAt() but it was unsuccessful for identifying 32-bit characters. Any guidance or assistance on this matter would be greatly valued. ...

Add the file retrieved from Firestore to an array using JavaScript

Trying to push an array retrieved from firestore, but encountering issues where the array appears undefined. Here is the code snippet in question: const temp = []; const reference = firestore.collection("users").doc(user?.uid); firestore .collec ...

Directive removing input field value from

I'm facing a straightforward Angular issue - it seems like I can't see the solution clearly. The problem arises when I attach a directive to an input field. The intention is to compare new data with old data and display a popup. However, once I ...

Exploring the Dynamic Styling Power of Angular's ng-class Directive Alongside the Transition from

I recently updated my app from Fontawesome version 4 to 5 by following the guidelines provided HERE. Everything looks great and appears to be working smoothly, except for the dynamic icons... In my Angular-based app, the icon to display is often dynamic: ...

Pass the ASP.NET MVC model onto the AngularJS scope

Here is the code snippet from my view with temporary JavaScript code for testing: I am trying to assign the ASP.NET MVC model (@Model) to the AngularJS scope ($scope.person). Any suggestions on how to accomplish this? Thank you, The View @model MyApp. ...

Is there a way to trigger the second function once the first one has been completed?

When the change event occurs, I am invoking two functions. function1(); function2(); The function1() makes an ajax call. The function2() is running before function1() for some reason. Can anyone explain why this is happening? Any assistance would be ...

angular and node: troubleshooting the $http.get error

I have encountered an issue with the $http.get instruction. Without it on the main page, I receive a result of "random 46" which is correct. However, when I include $http.get, I get a result of "random {{ number }}". How can this problem be resolved? -se ...

Obtaining information from a intricate string input

{JSON.stringify(walletData.transactions, null, 2)} My goal is to extract backend data and display it as a table. The response has been converted into an array as shown below. [ { "date": "Nov 07, 2023", "description" ...

Gruntjs Live Reload is actively monitoring for changes, yet fails to refresh the page

Check out my Gruntfile.js here Also, take a look at my package.json here I ran npm install in the main directory of my workspace (using WAMP) and it created a node_modules folder along with several subfolders. After navigating to the directory c:\w ...

Sharing methods between two components on the same page in Angular can greatly improve code efficiency

On a single page, I have two components sharing some methods and properties. How can I utilize a service to achieve this? See the sample code snippet below... @Component({ selector: 'app', template: '<h1>AppComponent1</h1>' ...

Console is displaying an error message stating that the $http.post function is returning

Just diving into angular and I've set up a controller to fetch data from a factory that's loaded with an $http.get method connecting to a RESTful API: videoModule.factory('myFactory', function($http){ var factory = {}; facto ...

Using Express with Angular and EJS to pass variables to a template

After successfully logging in, I am attempting to pass a message variable to my ejs template: app.post('/login', passport.authenticate('local-login', { successRedirect : '/profile', failureRedirect : '/l ...

Leveraging functions in a Node.js module

I am struggling with using a function inside a Node.js module. I have implemented a custom sorting function to sort an array of objects based on the value of a specific property. exports.getResult = function(cards) { cards.sort(sortByField('suit& ...

Utilizing JQuery for a smooth animation effect with the slide down feature

I have a question about my top navigation bar animation. While scrolling down, it seems to be working fine but the animation comes with a fade effect. I would like to achieve a slide-down effect for the background instead. Since scrolling doesn't trig ...

Executing a javascript file inside a jade document

I need help incorporating a JavaScript file into my Jade file so that it runs every time the page is accessed. How can I achieve this? Here is my current code snippet: doctype html html head title= title body h2 Bus Driver Locati ...

Does the language setting on a browser always stay consistent?

Using javascript, I am able to identify the language of my browser function detectLanguage(){ return navigator.language || navigator.userLanguage; } This code snippet returns 'en-EN' as the language. I'm curious if this i ...