2

I have an angular control that I use to display a set of data looking roughly like the following example. Apart from a unique block, it has a number of repeating (but discrete) blocks of the same structure.

{
  "person": {
    "lastName": "Bettertester",
    "firstName": "Fester",
    "address": "Out in the woods 17",
    "zipCode": "10666",
    "place": "Back of beyond"
  },
  "contact1": {
    "firstName": "Jane",
    "lastName": "Doe",
    "phone": "555-987-654",
    "relationship": "Aunt"
  },
  "contact2": {
    "firstName": "Kherumple",
    "lastName": "Whaduffle",
    "phone": "555-666-000",
    "relationship": "Imaginary friend"
  },
  "contact3": {
    "firstName": "Kherumple",
    "lastName": "Whaduffle",
    "phone": "555-666-000",
    "relationship": "Imaginary friend"
  }
}

I have written an angularjs component to retrieve and display this structure but want to hand the repeating block off to another component.

angular.module('myModule').component('mainComponent', {
  templateUrl : 'person.template.html',
  controller : [ '$scope', '$http', function mainController($scope, $http) {
    var self = this;
    self.data = null;
    $http.get(url).then(function(response) {
      self.data = response.data;
    }, function(response, status) {
      console.warn("Error while loading data");
      console.warn(" - response=", response);
      console.warn(" - status=", status);
      self.data = null;
    });
  } ]
});

The corresponding template:

<div>
  <h1>Person information</h1>
  <table>
  <tr>
    <th class="label-column">First & last name</th>
    <td class="data">{{$ctrl.data.person.firstName}} {{$ctrl.data.person.lastName}}</td>
  </tr>
  <tr>
    <th class="label-column">Address</th>
    <td class="data">{{$ctrl.data.person.address}}</td>
  </tr>
  <tr>
    <th class="label-column">ZIP code & Place</th>
    <td class="data">{{$ctrl.data.person.zipCode}} {{$ctrl.data.person.place}}</td>
  </tr>
  </table>

  <contact details="{{$ctrl.data.contact1}}"></contact> <!-- passing the details like this sort of works -->
  <contact details="{{$ctrl.data.contact2}}"></contact>
  <contact details="$ctrl.data.contact3"></contact>     <!-- passing the details like this does not work at all -->
</div>

The controller for the contact details looks as follows:

angular.module('myModule').component('contact', {
  templateUrl : 'contact.template.html',
  bindings : {
    details : '@'
  },
  controller : [ '$scope', '$http', function contactController($scope, $http) {
    var self = this;
    console.log("- details=", self.details);
  } ]
});

And the corresponding template:

<div>
  <h2>Contact</h2>
  <table>
  <!-- this works -->
  <tr>
    <th class="label-column">Everything</th>
    <td class="data">{{$ctrl.details}}</td>
  </tr>
  <tr>
    <th class="label-column">First & last name</th>
    <td class="data">{{$ctrl.details.firstName}} {{$ctrl.details.lastName}}</td>
  </tr>
  <tr>
    <th class="label-column">Phone</th>
    <td class="data">{{$ctrl.details.phone}}</td>
  </tr>
  <tr>
    <th class="label-column">Relationship</th>
    <td class="data">{{$ctrl.details.relationship}}</td>
  </tr>
  </table>

  <contact details="{{$ctrl.data.contact1}}"></contact>
  <contact details="{{$ctrl.data.contact2}}"></contact>
  <contact details="{{$ctrl.data.contact3}}"></contact>
  <contact details="{{$ctrl.data.contact4}}"></contact>
</div>

My questions is how to correctly pass the contact details that are part of the mainComponent to the contactComponent in a way that lets me access its fields in the corresponding template. If I pass them without the curly braces, the contact component does not seem to get any data at all. If I pass them with the curly braces, the contact component seems to get them in a way, but not as correct json object as I am unable to access fields within the contact block. I'm sure I'm missing something trivial but did not manage to find out where I go wrong.

2
  • 1
    (1) use details="$ctrl.data.contact1" and details : '=' (2) use the $scope object to access the data passed to component, it should be $scope.details not self.details Commented May 28, 2017 at 21:51
  • @Abdo Adel: I think we don't need to use $scope and self is assigned with this. so self.details would be enough Commented May 29, 2017 at 6:39

3 Answers 3

1
  1. Use one-way < binding instead of interpolation @ binding because @ watches for interpolation and if there is no interpolation just passes the raw attribute string as the value itself. < or = binding watches for expression change and updates the template with the expression value correspondingly. In this particular case < is more convenient than = because we only need to watch input changes for component, we don't need that additional watch to affect parent back by changing the same expression.

  2. After applying < use attribute values without curly braces safely.

  3. Initialise your component logic in $onInit controller lifecycle hook, because this hook is called after the bindings have been initialised, which was the exact cause of why your console.log("- details=", this.details); was giving - details=undefined in contact controller.

Code Details:

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

app.component('mainComponent', {
  template: `
  <div>
  <h1>Person information</h1>
  <table>
  <tr>
    <th class="label-column">First & last name</th>
    <td class="data">{{$ctrl.data.person.firstName}} {{$ctrl.data.person.lastName}}</td>
  </tr>
  <tr>
    <th class="label-column">Address</th>
    <td class="data">{{$ctrl.data.person.address}}</td>
  </tr>
  <tr>
    <th class="label-column">ZIP code & Place</th>
    <td class="data">{{$ctrl.data.person.zipCode}} {{$ctrl.data.person.place}}</td>
  </tr>
  </table>

  <contact details="$ctrl.data.contact1"></contact> <!-- passing the details like this sort of works -->
  <contact details="$ctrl.data.contact2"></contact>
  <contact details="$ctrl.data.contact3"></contact>     <!-- passing the details like this does not work at all -->
</div>
  `,
  controller: ['$scope', '$http', function mainController($scope, $http) {
  
    var self = this;
    
    self.$onInit = () => {
      self.data = {
        "person": {
          "lastName": "Bettertester",
          "firstName": "Fester",
          "address": "Out in the woods 17",
          "zipCode": "10666",
          "place": "Back of beyond"
        },
        "contact1": {
          "firstName": "Jane",
          "lastName": "Doe",
          "phone": "555-987-654",
          "relationship": "Aunt"
        },
        "contact2": {
          "firstName": "Kherumple",
          "lastName": "Whaduffle",
          "phone": "555-666-000",
          "relationship": "Imaginary friend"
        },
        "contact3": {
          "firstName": "Kherumple",
          "lastName": "Whaduffle",
          "phone": "555-666-000",
          "relationship": "Imaginary friend"
        }
      };
    };
    
  }]
});

app.component('contact', {
  template: `
    <div>
  <h2>Contact</h2>
  <table>
  <!-- this works -->
  <tr>
    <th class="label-column">Everything</th>
    <td class="data">{{$ctrl.details}}</td>
  </tr>
  <tr>
    <th class="label-column">First & last name</th>
    <td class="data">{{$ctrl.details.firstName}} {{$ctrl.details.lastName}}</td>
  </tr>
  <tr>
    <th class="label-column">Phone</th>
    <td class="data">{{$ctrl.details.phone}}</td>
  </tr>
  <tr>
    <th class="label-column">Relationship</th>
    <td class="data">{{$ctrl.details.relationship}}</td>
  </tr>
  </table>
</div>
  `,
  bindings: {
    details: '<'
  },
  controller: ['$scope', '$http', function jassMonitorHandController($scope, $http) {
    this.$onInit = () => {
      console.log("- details=", this.details);
    };
  }]
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.min.js"></script>

<div ng-app="app">
    <main-component></main-component>
</div>

Sign up to request clarification or add additional context in comments.

Comments

1

The problem is the details binding in your contact component.

It should be details: '<' (one-way binding) instead of details: '@' (one time string binding). When using this type of binding, you won't need the curly braces in your template.

Comments

0

I think that you could rewrite your component to receive the array of contacts and use the ng-repeat directive

<div ng-repeat='item in contactList'>
  <table>//your stuff</table>

Pass the data with bindings using the equal operator to receive the ng-model

bindings: '='

The last thing that I could suggest is to make a service to retrieve and store the data and inject in the corresponding component

Useful resources

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.