9

I have a list of hero-buttons with a custom animation created in button.component.ts. At the beginning, they are inactive. When I press one of then, the selected one should turn active. For this, I created a field in hero.ts called state and a function called toggleState() where I change the state. But when I press the button, I receive the error:

EXCEPTION: Error in http://localhost:3000/app/button.component.js class ButtonComponent - inline template:4:10 caused by: self.context.$implicit.toggleState is not a function

My guess is that I can't create a custom method like I did here. But I'm new in Angular2 so I can't really tell it. What did I do wrong? I played enough "Where's Wally?" with my code and still I can't find anything.

button.component.ts:

import { Component, Input, OnInit, trigger, state, style, transition, animate
} from '@angular/core';

import { Hero } from './hero';
import { HeroService } from './hero.service';

@Component({
    moduleId: module.id,
    selector: 'button-collection',
    template: `
      <button *ngFor="let hero of heroes"
          [@heroState]="hero.state"
          (click)="hero.toggleState()">
        {{hero.name}}
      </button>
    `,
    styleUrls: ['heroes.component.css'],
    animations: [
        trigger('heroState', [
            state('inactive', style({
                backgroundColor: '#e1e1e1',
                transform: 'scale(1)'
            })),
            state('active', style({
                backgroundColor: '#dd1600',
                transform: 'scale(1.1)'
            })),
            transition('inactive => active', animate('100ms ease-in')),
            transition('active => inactive', animate('100ms ease-out'))
        ])
    ],
})
export class ButtonComponent implements OnInit {
    heroes: Hero[];

    constructor(private heroService: HeroService) {

    }

    ngOnInit(): void {
        this.heroService.getHeroes()
        .then(heroes => this.heroes = heroes);
    }
}

hero.ts:

export class Hero {
    id: number;
    name: string;
    state: string;

    constructor() {
        this.state = 'inactive';
    }

    public toggleState(): void{
        this.state = (this.state === 'active' ? 'inactive' : 'active');
    }
}
SuffPanda
  • 348
  • 2
  • 17
  • 1
    I think you left out too much code. What is `class Hero`? A component, a service, something else? What is "and so on"? Does it contain a `hero` reference? – Günter Zöchbauer Nov 01 '16 at 09:40
  • @GünterZöchbauer: short explanation: class `hero.ts` is a model I use for the heroes list (which is a Hero[]). `//and so on` contains the field `heroes` and the method to get the list of heroes. I'll add it for you. – SuffPanda Nov 01 '16 at 09:48
  • You don't by any chance cast JSON to `Hero` in `getHeroes()`? – Günter Zöchbauer Nov 01 '16 at 09:52
  • @GünterZöchbauer That's the case. I make an HTTP request to the server to receive the heroes and afterward I cast it from JSON to `Hero[]`. – SuffPanda Nov 01 '16 at 10:01
  • @SovietPanda then you have your answer. The JSON unmarshaller doesn't know about your class, and creates objects containing the fields from the JSON. But these objects are not instances of your Hero class. They don't have any method. You need to go through the JSON and convert each object into a Hero. Or you need to put this toggle() feature into your component, and make it take a Hero as argument. Credit goes to Gunter, so I won't post this as an answer. – JB Nizet Nov 01 '16 at 10:08

2 Answers2

12

If you cast JSON to a TypeScript class, all is happening is that you indicate to the static analysis it can safely assume the value is of that class; that doesn't actually change the JSON value at all (i.e it doesn't make it an instance of that class).

What you want is probably How do I cast a JSON object to a typescript class or Cast JSON object to TypeScript class instance

RJFalconer
  • 10,890
  • 5
  • 51
  • 66
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
5

Your service most probably only returns plain objects, meaning they don't have the member methods of the Hero class.

You need to explicitly create new Hero() objects to have the toggleState() method available on the hero object.

Alexander Ciesielski
  • 10,506
  • 5
  • 45
  • 66
  • Can you elaborate a little bit on this? I don't understand why the service would only return a plain object. – mad_fox Jul 01 '17 at 23:59