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

1

5

Lets say I have the following getter/setter methods

get next() {
  console.log(this.people[this._index], this._index);
  return this.people[this._index];
}

set next(i: any) {
  this._index = (+i) + 1;
  this._index = (+i) % this.people.length;
}

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.
Ice answered 12/8, 2017 at 9:33 Comment(1)
I added one more example to my answer. This shows how you would actually use a property correctly and demonstrates building a display structure while keeping the complexity out of the template.Stabler
S
20

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.

Stabler answered 12/8, 2017 at 10:0 Comment(11)
the approach with the getNext method is necessary, as op wants to get the item at index 0 for the item at index array.length apparentlyHaul
@Haul I still don't think you need a get accessor. It is never read by his example code. A method might be necessary.Stabler
Sorry, expressed my idea in a wrong way. I meant what you just said :DHaul
Lol, apologies, didnt give too much attention to your answer. Thank you for your thorough explanation. So I decided to opt my whol answer based on your review. and now Im planning to get next and prev directly. As for next: we have people[(i + 1) % people.length] which works fineIce
however, as for prev, not sure why it gives me undefinedIce
@Ice so you want both the person before and the person after as well as the current person then? In a circular structure?Stabler
@Ice your question doesnt mention anything about prev. Update your question if thats part of it...Haul
Correct @AluanHaddad Yes, thats what i wantIce
@Ice take a look at the code at the end of my answer I think that will do the job.Stabler
Thank you, you've won this question :)Ice
@Ice glad to helpStabler

© 2022 - 2024 — McMap. All rights reserved.