How to access getter/setter accessors from angular 4 in template binding?

James picture James · Aug 12, 2017 · Viewed 26k times · Source

Lets say I have the following getter/setter methods

and I want to call this in the following way:

<ng-template ngFor let-person="$implicit" [ngForOf]="people" let-i=index let-last=last>
  <app-card [cardItem]="people[i]" [nextCard]="next(i)"></app-card>
</ng-template>

PS: Think of this as circular array. Where by I need prev, current, and next items.

However I get the following Error

Angular: Member 'next' in not callable

Why is that? And whats the solution?

Thanks

Edit

Thank you guys for your help and explanation. With your help i managed to make it work:

<app-card [currentCard]="people[i]" [nextCard]="people[i === people.length - 1 ? 0: i + 1]" [prevCard]="i == 0 ? people[people.length - 1] : people[i - 1]"></app-card>

So its pretty much circular array. Lets assume we have the following:

people["James Dan", "Aluan Haddad", "Jota Toledo"]

So few conditions:

  1. If I stand in the beginning of the array (i.e. index = 0) - then my prev will be people[people.length - 1] which is the last element in the array. And if my current is on index 1, then my prev will be index 0 and next will be index 2.

Answer

Aluan Haddad picture Aluan Haddad · Aug 12, 2017

The Angular template syntax is, in general, a subset of JavaScript syntax with some notable differences and with many restrictions.

However what you have here is actually invalid in JavaScript as well. It is not valid to call a property accessor. Ever.

Given the following property

get p() {
  console.info('read p');
  return this.wrapped;
}
set p(value) {
  console.info('wrote p');
  this.wrapped = value;
}

The get accessor is invoked implicitly when the property thusly named is read.

For example:

console.log(o.p); // read p

The set accessor is invoked implicitly when the property thusly named is written.

For example:

o.p = x; // wrote p;

The same rules apply in Angular templates.

However, your example of

<app-card [cardItem]="people[i]" [nextCard]="next(i)">

suggests that a property is not what you want here.

The correct usage of a property implies the following syntax

<app-card [cardItem]="people[i]" [nextCard]="next = i">

Which I do not believe is supported by the Angular template syntax and even if it is doesn't make a lot of sense and would be hard to read.

Instead you should create a method that returns a value

getNext(i: number) {
  this._index = i + 1;
  this._index = i % this.people.length;
  return this.people[this._index];
}

Then used in your template as

<app-card [cardItem]="people[i]" [nextCard]="getNext(i)">

Having said that, I think the entire design is questionable. You seem to be going through contortions to store excess mutable state independently of the array that naturally maintains it.

I believe you would be much better served by removing the method and the property entirely and using

<app-card
  *ngFor="let person of people; let i = index"
  [previousCard]="people[i === 0 ? people.length - 1 : i - 1]" 
  [cardItem]="person"
  [nextCard]="people[i === people.length - 1 ? 0 : i + 1]">

If you want a cleaner syntax, you might define a property, with a get accessor only, that returns a view of your array as objects with previous, current, and next properties.

get peopleAsPreviousCurrentAndNextTriplets() {
  return this.people.map((person, i) => ({
    previous: this.people[i === 0 ? this.people.length - 1 : i - 1],
    current: person,
    next: this.people[i === this.people.length - 1 ? 0 : i + 1]
  }));
}

This can be more readable in complex code because its abstracts away the index for more semantic properties that we can use directly. Perhaps more importantly, it enables TypeScript's world-class tooling to validate the computation.

<app-card
  *ngFor="let item of peopleAsPreviousCurrentAndNextTriplets"
  [previousCard]="item.previous" 
  [cardItem]="item.current"
  [nextCard]="item.next">

And thus we come full circle. Note how we define the get accessor and how we read the property it defines without (), implicitly invoking that accessor.

The last example is probably overkill for this scenario but I think it's useful nonetheless.