Why is one of these AngularJS components showing the ng model value successfully and the other not?

I am trying to create a series of dynamic components that can be imported into one main component. Nested components inside components are much easier when I can pass all objects into one binding and create multiple bindings for each object passed. So I created a plunkr to demonstrate what I would like to achieve. Ideally I would like to pass an object from the parent component to the ng-model child component without creating a separate binding.

Is it possible, can anyone please offer me any suggestions or explanations as to why the nested component only updates the model locally and not across the whole view?

Basically, if you look at the plunkr below, I would like the cNestedComponent to function in the same way as the cDirectComponent, where the data binding is updated both inside the component template and outside of its 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

Based on feedback from user Julien Tassin, I created an updated plunker which is clean and I think it shows better what I will be doing:

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

The Direct Component examples are a clear way to accomplish my goal, but I would rather not list every binding as the components are nested within each other. For example:

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

      

much easier to print and then print this

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

      

every time i want to nest an input component in a parent component.

Hopefully this update adds some additional clarification as to what I'm looking for.

+3


source to share


1 answer


There are several problems that are causing your example to fail:

First problem: non-assignable property

You have a non-assignment problem. When you declare <c-nested-input input-bindings="{type: 'text', model: 'data.comment', placeholder: 'nested component'}"></c-nested-input>

, you are creating a non-assignable property {type: 'text', model: 'data.comment', placeholder: 'nested component'}

(by the way, you have a bug in 'data.comment'

that should be data.comment

). When you try to assign a value to it in ngModel, it will fail because you cannot affect an invalid expression, not even a non-transferable property of an expression

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

Second problem: link data.comment

This is not all. If 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 won't work the way you want it to. Because when your nested will modify inputBindings.model it won't be the same link as data.comment. The binding string =

for inputBindings is not its properties.

There is no way to avoid this.

So, you need to abandon your data.com site and work like this:



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() {

  }
});

      

An example in plunker .

My advice

I think a cleaner way of creating your component is a 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>

      

0


source







All Articles