Bindings

Note: If you skipped ahead to this section, click here to download a zip of the tutorial at this point.

In this step, we will implement the party details view, which is displayed when a user clicks on a party in the parties list. The user will also be able to change the party's details.

To implement the party details view we will use helpers.

We used helpers in the previous Component we implemented, but now we will demonstrate how to use it with a single object instead of a Mongo.Collection cursor.

Implement the component

We'll expand the PartyDetails by using helpers method, and we will use findOne method from the Mongo.Collection, which returns a single object.

3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import uiRouter from 'angular-ui-router';
 
import template from './partyDetails.html';
import { Parties } from '../../../api/parties';
 
class PartyDetails {
  constructor($stateParams, $scope, $reactive) {
    'ngInject';
 
    $reactive(this).attach($scope);
 
    this.partyId = $stateParams.partyId;
 
    this.helpers({
      party() {
        return Parties.findOne({
          _id: $stateParams.partyId
        });
      }
    });
  }
}
 

In our example we find our relevant party by its id, and used a regular MongoDB syntax to create our findOne query, which is explained in Meteor's collection.findOne documentation.

So after declaring this helper, we can just use this.party in our Component's Controller, or partyDetails.party in our HTML view.

Component template

In partyDetails.html let's replace the binding to the partyDetails.partyId with a binding to partyDetails.party.name and partyDetails.party.description:

6.2 Add form with the party details to the party details page imports/ui/components/partyDetails/partyDetails.html
1
2
3
4
5
The party you selected is:
<form>
  Party name: <input type="text" ng-model="partyDetails.party.name" />
  Description: <input type="text" ng-model="partyDetails.party.description" />
</form>

We used ng-model and created a form with the party details, now we just missing the "Save" button!

Add Save logic

First, let's add a button, and we will use ng-click with the name of the method that we will later implement:

6.3 Add save and back buttons to the view imports/ui/components/partyDetails/partyDetails.html
2
3
4
5
6
7
8
<form>
  Party name: <input type="text" ng-model="partyDetails.party.name" />
  Description: <input type="text" ng-model="partyDetails.party.description" />
  <button ng-click="partyDetails.save()">Save</button>
</form>
 
<button ui-sref="parties">Back</button>

We also added a "Back" button with uses ui-sref attribute, which is a shorthand for creating a link for a state.

And now let's implement the logic of the "Save" button on the controller:

6.4 Implement save button on the component logic imports/ui/components/partyDetails/partyDetails.js
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
      }
    });
  }
 
  save() {
    Parties.update({
      _id: this.party._id
    }, {
      $set: {
        name: this.party.name,
        description: this.party.description
      }
    });
  }
}
 
const name = 'partyDetails';

We used Parties.update method which is a method that comes from the Mongo.Collection object.

The first parameter is the parties we want to update, in this case, we send the specific party's id, just like we did with findOne.

In the second parameter we specify the action we want to perform, in our case we used the $set operator to update the actual relevant fields.

We can also handle success or fail when using Parties.update by adding a callback as the third argument, for example:

6.5 Handle success and fail for data actions imports/ui/components/partyDetails/partyDetails.js
30
31
32
33
34
35
36
37
38
39
40
41
        name: this.party.name,
        description: this.party.description
      }
    }, (error) => {
      if (error) {
        console.log('Oops, unable to update the party...');
      } else {
        console.log('Done!');
      }
    });
  }
}

Testing

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import { name as PartyDetails } from '../partyDetails';
import { Parties } from '../../../../api/parties';
import 'angular-mocks';
 
describe('PartyDetails', () => {
  beforeEach(() => {
    window.module(PartyDetails);
  });
 
  describe('controller', () => {
    let controller;
    const party = {
      _id: 'partyId',
      name: 'Foo',
      description: 'Birthday of Foo'
    };
 
    beforeEach(() => {
      inject(($rootScope, $componentController) => {
        controller = $componentController(PartyDetails, {
          $scope: $rootScope.$new(true)
        });
      });
    });
 
    describe('save()', () => {
      beforeEach(() => {
        spyOn(Parties, 'update');
        controller.party = party;
        controller.save();
      });
 
      it('should update a proper party', () => {
        expect(Parties.update.calls.mostRecent().args[0]).toEqual({
          _id: party._id
        });
      });
 
      it('should update with proper modifier', () => {
        expect(Parties.update.calls.mostRecent().args[1]).toEqual({
          $set: {
            name: party.name,
            description: party.description
          }
        });
      });
    });
  });
});

Summary

We've seen the power of using Meteor.Collection API and how we can get single object from the collections.

We also learned how to update an object with the user's data!

Let's move on to provide some order and structure in our application.