Reputation: 24462
I've been checking the following angular 2 animate docs: https://angular.io/docs/ts/latest/guide/animations.html
and started playing with it. I went for the simplest example, and did the following:
Added to my component:
animations: [
trigger('dataState', [
state('inactive', style({
backgroundColor: '#eee',
transform: 'scale(1)'
})),
state('active', style({
backgroundColor: '#cfd8dc',
transform: 'scale(1.1)'
})),
transition('inactive => active', animate('100ms ease-in')),
transition('active => inactive', animate('100ms ease-out'))
])
]
added to my template:
<div class="well"
[@dataState]="data.active"
(click)="data.toggleState()" style="cursor:pointer">
SOME TEXT
</div>
Surely imported everything..
import {Component,OnInit,
trigger,
state,
style,
transition,
animate} from '@angular/core';
but when I click my object I get:
browser_adapter.js:86 TypeError: self.context.$implicit.toggleState is not a function
Since toggleState is not in the docs I tried to remove it, but there was no effect of animation after all (although no error)
What I am missing?
Upvotes: 1
Views: 2597
Reputation: 375
I also found the animations doc on angular.io to be incomplete. I used the example code and made a couple minor changes to make it work with the code from their TOH app tutorial.
TL;DR
Add a
state="inactive"
property to the hero.ts hero class to track each hero's animation state.In HTML, change their
(click)="hero.toggleState()"
method binding to(click)="toggleState(hero)"
and write that method in the HeroesComponent class:
toggleState(hero: Hero) { hero.state = (hero.state === 'active' ? 'inactive' : 'active'); }
Rewire
onSelect()
method so thatgotoDetail()
navigation works.
Here is the plunker they provide with everything complete through section 5 - routing. Use it to follow along, if you'd like.
I'm going to walk through how to modify that plunk to achieve the first animation in their animations doc.
The first code they partially walk you through in the animations docs is to add animated active/inactive states to selected heroes in the Heroes view (as opposed to the dashboard view):
import { Component, Input, trigger, state, animate } from '@angular/core';
import { Heroes } from './hero.service';
@Component({
moduleId: module.id,
selector: 'hero-list-basic',
template: `
<ul>
<li *ngFor="let hero of heroes"
[@heroState]="hero.state"
(click)="hero.toggleState()">
{{hero.name}}
</li>
</ul>
`,
styleUrls: ['hero-list.component.css'],
animations: [
trigger('heroState', [
state('inactive', style({
backgroundColor: '#eee',
transform: 'scale(1)'
})),
state('active', style({
backgroundColor: '#cfd8dc',
transform: 'scale(1.1)'
})),
transition('inactive => active', animate('100ms ease-in')),
transition('active => inactive', animate('100ms ease-out'))
])
]
})
export class HeroListBasicComponent {
@Input() heroes: Heroes;
}
Above, their (animations example) code parallels the code in app/heroes.component.ts (from the plnkr) and note that the html/css have been extracted into separate files on plnkr. I presume most who read this have followed the tutorial and are familiar with this code.
heroes.component.html
The new animations will basically replicate the existing bindings on each hero <li>
, so delete these two lines -- because they would conflict if we kept them -- we're going to bring that functionality back with animation states.
<ul class="heroes">
<li *ngFor="let hero of heroes">
--------->[class.selected]="hero === selectedHero"
--------->(click)="onSelect(hero)">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</li>
</ul>
new html from animations example:
<ul class="heroes">
<li *ngFor="let hero of heroes"
[@heroState]="hero.state"
(click)="hero.toggleState()">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</li>
</ul>
I didn't want to add the toggleState method to the hero class, I wanted it in the component that calls it. So I changed the click binding to
(click)="toggleState(hero)"
and simply pass the clicked hero to the method we still need to write.
A hero
doesn't have the property state
yet so let's add that in app/hero.ts:
add state:string = "inactive";
to the list of properties.
Now let's get back to heroes.component.ts, import our animations dependencies, add the animations metadata in @Component, and create the toggleState()
method. We want to keep the onSelect()
method we removed from html, we'll alter it and reuse it in a moment.
Up top, replace
import { Component, OnInit } from '@angular/core';
with
import { Component, OnInit, trigger, state, style, transition, animate } from '@angular/core';
Append the animations
metadata after styleUrls: [ ... ],
:
animations: [
trigger('heroState', [
state('inactive', style({
backgroundColor: '#eee',
transform: 'scale(1)'
})),
state('active', style({
backgroundColor: '#cfd8dc',
transform: 'scale(1.1)'
})),
transition('inactive => active', animate('100ms ease-in')),
transition('active => inactive', animate('100ms ease-out'))
])
]
In the HeroesComponent class add the following method:
toggleState(hero: Hero) {
hero.state = (hero.state === 'active' ? 'inactive' : 'active');
}
So that all works. Now let's unbreak the hero detail. The hero detail was a little blurb after the list that shows which hero is selected accompanied by a button that navigates to the detail/:id route. But now it's gone. The onSelect()
method we detached was launching that.
Let's rename onSelect()
to updateSelectedHero()
and then call it from inside toggleState()
:
updateSelectedHero(hero: Hero): void {
this.selectedHero = hero;
}
toggleState(hero: Hero) {
hero.state = (hero.state === 'active' ? 'inactive' : 'active');
this.updateSelectedHero(hero);
}
aaand we're back in business. The hero detail shows up and it's View Details button calls gotoDetail(). There are obnoxious UI flaws that need ironed out, but you get the idea.
Upvotes: 1
Reputation: 1312
I ran into this as well. I found most of the docs walk you through each step needed but here it skips over adding some of the setup. I'm guessing their assuming that by this point in the docs a few things are self-explanatory.
If you look at the live plunker example and review the code you can see how they've set it up.
In the app/hero.service.ts they've added a constructor that adds a hero.state string and a toggleState() method to the Hero class.
class Hero {
constructor(public name: string,
public state = 'inactive') {
}
toggleState() {
this.state = (this.state === 'active' ? 'inactive' : 'active');
}
}
Based on your error and question it seems you haven't added either of these in yet which is why toggleState() is not a function and nothing can be toggled without the state attribute.
For your example you could (depending on your class) do something like this:
import { Component, Input, trigger, state, style, transition, animate } from '@angular/core';
@Component({
selector: 'basic-component',
template: `
<div class="well" [@dataState]="active" (click)="toggleState()" style="cursor:pointer">SOME TEXT</div>
`,
animations: [
trigger('dataState', [
state('inactive', style({
backgroundColor: '#eee',
transform: 'scale(1)'
})),
state('active', style({
backgroundColor: '#cfd8dc',
transform: 'scale(1.5)'
})),
transition('inactive => active', animate('100ms ease-in')),
transition('active => inactive', animate('100ms ease-out'))
])
]
})
export class BasicComponent {
active = 'inactive';
toggleState() {
this.state = (this.state === 'active' ? 'inactive' : 'active');
}
}
Upvotes: 1
Reputation: 7050
The value you assign to @dataState through [@dataState]="data.active"
could either be 'active'
or 'inactive'
in your example. (well, the value could be different, but there wouldn't be any effect then unless you specify it)
So your component.ts needs a property data.active that has either one as its value. What triggers the animation is a change between them:
[@dataState]="'active'"
to
[@dataState]="'inactive'"
or vice-versa
data.active
is just a variable and data.toggleState()
is a function that assigns 'active'
or 'inactive'
to it.
Upvotes: 3