What could be the reason for one AngularJS component displaying the ng-model value correctly while the other one fails to do so?

I am currently working on creating a set of dynamic components that can be imported into a main component. It is much more convenient to nest components inside one another when I can pass all objects into a single binding instead of creating multiple bindings for each object being passed. To demonstrate what I am trying to achieve, I have set up a plunkr. My goal is to pass an object from a parent component into a child component's ng-model without the need for a separate binding.

I am wondering if this is possible and if anyone can provide suggestions or explanations as to why the nested component only updates the model locally and not in the entire view?

In essence, I would like the cNestedComponent to function in the same manner as the cDirectComponent, where the databinding updates from both inside and outside of the component's template.

http://plnkr.co/edit/vusx9rm1DnkbBlNBGyZG?p=preview

MARKUP:

<h1> Data Comment => {{ data.comment }} </h1>
<c-direct-input plplaceholder="direct component" plmodel="data.comment" pltype="text"></c-direct-input>
<c-nested-input input-bindings="{type: 'text', model: 'data.comment', placeholder: 'nested component'}"></c-nested-input>

COMPONENTS:

app.component('cNestedInput', {
  template: '\
    <h2> Nested Component </h2>\
    <p style="display: block;"> {{ $ctrl.inputBindings.model }} </p>\
    <input type="{{ $ctrl.inputBindings.type }}" placeholder="{{$ctrl.inputBindings.placeholder}}" ng-model="$ctrl.inputBindings.model" />\
  ',
  bindings: {
    inputBindings: '='
  },
  controller: function($scope) {}
});

app.component('cDirectInput', {
  template: '\
    <h2> Direct Component </h2>\
    <p style="display: block;"> {{ $ctrl.plmodel }} </p>\
    <input type="{{ $ctrl.pltype }}" placeholder="{{ $ctrl.plplaceholder }}" ng-model="$ctrl.plmodel" />\
  ',
  bindings: {
    plplaceholder: '@',
    plmodel: '=',
    pltype: '@'
  },
  controller: function($scope) {}
});

========================================================

UPDATE

Following feedback from user Julien Tassin, I have updated the plunker to be cleaner and to better demonstrate what I am aiming for:

https://plnkr.co/edit/cvYAdB?p=preview

The Direct Component examples show a clear path to achieving my goal, but I prefer not having to list out every single binding as components get nested. For instance:

<c-nested-input input-bindings="$ctrl.input.inputBindings"/>

is much simpler to type out than having to write out this

<c-direct-input input-placeholder="{{$ctrl.inputPlaceholder}}" input-type="{{$ctrl.inputType}}" input-model="$ctrl.inputModel"/>\

each time I want to nest the input component within a parent component.

This update should provide further clarification on what I am looking to achieve.

Answer №1

There are several issues causing your example to not work correctly:

First issue: the non-assignable property

An issue arises with a "non-assignable" property. When you declare

<c-nested-input input-bindings="{type: 'text', model: 'data.comment', placeholder: 'nested component'}></c-nested-input>
, you create a non-assignable property
{type: 'text', model: 'data.comment', placeholder: 'nested component'}
(there is also a mistake in 'data.comment' that should be corrected as data.comment). Attempting to assign a value to it in ngModel will fail because non-assignable expressions cannot be assigned to, even non-assignable expression properties.

The solution is to set an assignable object inputBindings in your main controller and pass it to your component.

Second issue: the data.comment reference

Another issue arises when you try:

$scope.data = {}
$scope.inputBindings = {
  type: 'text',
  model: $scope.data.comment,
  placeholder: 'nested component'
}

And pass it to your nested component:

<c-nested-input input-bindings="inputBindings"></c-nested-input>

It will not work as expected because when your nested component modifies inputBindings.model, it will not refer to the same values as data.comment. The = binding refers to inputBindings, not its properties.

This issue cannot be avoided.

The solution is to remove data.comment and work as follows:

MARKUP:

<body ng-controller="MainCtrl">
  <h1> Data Comment => {{ inputBindings.model }} </h1>
  <hr/>
  <c-direct-input plplaceholder="direct component" plmodel="inputBindings.model" pltype="text"></c-direct-input>
  <c-nested-input input-bindings="inputBindings"></c-nested-input>
</body>

JS:

var app = angular.module('plunker', []);

app.controller('MainCtrl', function($scope) {
  $scope.inputBindings = {
    type: 'text',
    placeholder: 'nested component'
  }
});

app.component('cNestedInput', {
  template: '\
    <h2> Nested Component </h2>\
    <p style="display: block;"> {{ $ctrl.data.comment }} </p>\
    <input type="{{ $ctrl.inputBindings.type }}" placeholder="{{ $ctrl.inputBindings.placeholder}}" ng-model="$ctrl.inputBindings.model" />\
  ',
  bindings: {
    inputBindings: '='
  },
  controller: function() {}
});

app.component('cDirectInput', {
  template: '\
    <h2> Direct Component </h2>\
    <p style="display: block;"> {{ $ctrl.plmodel }} </p>\
    <input type="{{ $ctrl.pltype }}" placeholder="{{ $ctrl.plplaceholder }}" ng-model="$ctrl.plmodel" />\
  ',
  bindings: {
    plplaceholder: '@',
    plmodel: '=',
    pltype: '@'
  },
  controller: function() {

  }
});

The example can be found in the plunker.

My advice

I suggest a cleaner way to create your component using an hybrid approach:

Something like:

JS:

app.component('cHybridInput', {
  template: '\
    <h2> Nested Component </h2>\
    <p style="display: block;"> {{ $ctrl.data.comment }} </p>\
    <input type="{{ $ctrl.options.type }}" placeholder="{{ $ctrl.options.placeholder}}" ng-model="$ctrl.ngModel" />\
  ',
  bindings: {
    ngModel: '=',
    options: '<'
  },
  controller: function() {}
});

HTML:

<c-hybrid-input ng-model="inputBindings.model" options="inputBindings"></c-hybrid-input>

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

What could be the reason for v-model not functioning properly?

Embarking on my Vue.js coding journey, I am currently following a straightforward online tutorial. Utilizing the vue-cli, I kickstarted my application and crafted a basic component named Test.vue. Within this component lies a simplistic input control conne ...

Refreshing the Angular resource under observation

My brain feels a bit fried today, so pardon what might seem like an obvious question. I have a useful resource at my disposal: Book = $resource("/books/:id", {id: "@id"}); book = Book.get(1); When I want to refresh the object to receive any updates from ...

Encountering issues while trying to run npm install for an Angular 7 application, specifically receiving an error stating: "Module not found: @angular-devkit/build-ng-packagr." This error is hindering

I don't have much experience with JavaScript, node, npm, Angular, etc. My expertise lies in TypeScript as I am still a beginner. However, I recently inherited an application that requires maintenance to resolve a cross-site cookie issue. As I attempt ...

Custom AngularJS directive: dynamic template rendering using scope value (ng-bind-html)

I am currently working on a directive which looks like this: ... template: function(element, attrs) { var htmlTemplate = '<div class="start-it" ng-if="isVisible">\ <p ng-bind-html="\'{{customDynamicText}}\&a ...

JavaScript can be used to create a fullscreen experience without any toolbars, scrollbars, and the like, without having

Is there a way for me to expand the current window I am using to fullscreen mode and eliminate all toolbars? ...

Leveraging global variables within Vuex state management strategy

I have successfully added custom global variables into Vue by injecting them. Here is the code snippet: export default function (props, inject) { inject('models', { register(name) { const model = require(`@/models/${name}. ...

designating the initial item as the chosen selection for the hash category

When working with AngularJS and creating a select element, I can easily set the first value as selected using ng-init="item.state = stateArray[0]" But how do I set the first item selected for key/value type array? var States = { "AL": "Alabama", "AK": ...

Looking for a way to detect changes in a select menu using Angular?

How can I determine with the openedChange event if there have been any changes to the select box items when the mat select panel is closed or opened? Currently, I am only able to detect if the panel is open or closed. I would like to be able to detect any ...

Adjust the color of an SVG icon depending on its 'liked' status

In my React/TypeScript app, I have implemented an Upvote component that allows users to upvote a post or remove their upvote. The icon used for the upvote is sourced from the Grommet-Icons section of the react-icons package. When a user clicks on the icon ...

Vector indicating the direction of a rotation in Three.js

I am in the process of rotating an arrow on the surface of a planet to align with the direction of its travel. I have the direction vector and the up vector from the surface normal. How can I convert this into a quaternion for the rotation of my arrow? I a ...

Utilizing a PHP-scripted multidimensional array in an AJAX success function

I've encountered an issue while trying to access a multidimensional array passed to my AJAX success function. Here's how I'm sending the data: $response = array(); $response['message']['name'] = $name; $response['m ...

Refresh the Content of a Page Using AJAX by Forcing a Full Reload

I have a webpage that automatically updates a section using jQuery AJAX every 10 seconds. Whenever I modify the CSS or JavaScript of that page, I would like to include a script in the content fetched via AJAX to trigger a full page reload. The page is ac ...

Tips for assigning custom values using CSS and jQuery for a standalone look

I am facing a challenge with my HTML markup: <div> <figure></figure> <figure></figure> <figure></figure> </div> Alongside some CSS styling: div { position: relative; } figure { position: absolu ...

Ways to retrieve and update the state of a reactjs component

I'm facing an issue with modifying a React JS component attribute using an event handler. export default interface WordInputProps { onWordChange:(word:string) => void firstLetter:string disabled?:boolean } These are the props for my co ...

Guide on implementing ng-change in a select dropdown for dynamically populating data

As a newcomer to AngularJS, I am currently working on an Ionic app where I am facing an issue with populating select options based on the selection of the first option. The variable selected in the first select option is not being processed correctly to re ...

What is the best way to nest _app.js within several providers?

Is there a way to wrap my top-level page _app.js in both Redux provider and Next-auth provider? Currently, I have already wrapped it in the Next-auth provider like this: import React from "react" import { Provider } from 'next-auth/client&ap ...

"Adding a grid panel to the final node of a tree-grid in extjs: A step-by-step guide

Does anyone have a suggestion on how to add a grid panel to the last node/children of a treepanel dynamically? I would like to append the gridpanel dynamically and for reference, I am providing a link: Jsfiddle I also need to ensure that the gridpanel is ...

How can I stop iOS mobile browsers from automatically opening apps when I click on links?

Recently, I discovered an issue with my website when accessed on mobile (iOS). The links to external websites, such as Amazon product links, are causing the Amazon app to open instead of simply opening a new tab in the browser. The HTML code for these lin ...

Exploring the behavior of Object.assign in for loops and forEach loops

I've come across an interesting anomaly with Object.assign in the following scenario. function sampleFunction(index, myList) { myList.forEach((element, i) => { if (i === index) { console.log(Object.assign({}, {"newKey": " ...

Guide to using Angular $resource to pass query parameter array

My goal is to implement a feature that allows users to be searched based on multiple tags (an array of tags) using the specified structure: GET '/tags/users?tag[]=test&tag[]=sample' I have managed to make this work on my node server and hav ...