I am experimenting with the new animation API in Angular 2, and have the following challenge:
I have a parent component that displays a set of child components by using a *ngFor. The ngFor references a simple data array in the parent component.
Requirements:
When the parent component is rendered with its initial children, the parent and children should be rendered instantly (without animation)
When new child components are added (because of a new object appended to the data array), the new child should be animated (e.g. bounce in from the left).
How can I configure the animation to handle this?
The basic question is: How can a child component know if it is rendered as part of the initialization of its parent or later?
Some possible solutions:
- I can set a boolean variable directly on the data object that says that the new object is created after the view and should be animated. This property could be checked by the component. However, I don't want to introduce this kind of view logic in the data model.
- I can use the lifecycle hooks in the parent component to set a property in the parent that says that the parent is rendered and that all subsequent (new) children should be animated. However, I haven't been able to do this as it seems that all lifecycle hooks are executed before the children components are instantiated.
Other solutions?
Br
Anders
You can use a property in parent
initialLoad = true;
ngAfterViewInit() {
this.initialLoad = false;
}
https://github.com/angular/angular/issues/7239#issuecomment-227369239 contains an example (with Plunker):
#Component({
selector: 'app',
template: `
<button (click)="items.push(items.length)">Add</button>
<ul>
<template ngFor let-item [ngForOf]="items" let-i="index">
<li style="display: block;" #flyInOut="'in'" (click)="onClick(i)" *ngIf="!!item">{{ i }} - {{ item }}</li>
</template>
</ul>
`,
animations: [
trigger('flyInOut', [
state('in', style({ opacity: 1, transform: 'translateX(0) scaleY(1)' })),
transition('void => *', [
style({
opacity: 0,
transform: 'translateX(-100%)'
}),
animate('1s ease-in')
]),
transition('* => void', [
animate('1s 10 ease-out', style({
opacity: .5,
transform: 'scaleY(0.8)'
}))
])
])
]
})
export class App {
private items = [];
constructor() { }
onClick(index) {
//this.items.splice(index,1);
delete this.items[index];
}
}
There is currently an issue with *ngFor that was fixed in https://github.com/angular/angular/pull/10287 but the fix has yet to land.
Related
I would like to implement elements with a draggable property. You get what you wanted in part. I have several blocks that are built from the response of a web-service.
Use ng-repeat to represent the elements on the screen:
<div class = "declareContainer" ng-repeat = "item in group">
<!-- My Draggable Boxes -->
<div id="{{$index}}" title="{{$index}}" class="col-lg-6" style=" border radius: 15px; padding: 1%; ">
</div>
The loop is controlled by "group" object, however it is obtained by AJAX:
$Http.get (path + "getgroup"). Then (function (response) {
$ Scope.group = response.data;
}, Function (error) {
});
I use the following excerpt in my controller to apply a draggable property to the elements that are in the declareContainer div:
Angular.element (document) .ready (function () {
$ (".declareContainer"). Draggable ();
});
Elements are drawn normally, but lose a draggable property.
I ran tests and noticed that when defining the group object in static or exploit code (ng-repat already gets as object of the object during the construction of the app).
I tried to initialize "group" as null for the loop not toenter code here run while a response is not, but still giving the same problem.
Does anyone know of any way to solve this problem?
Purpose: I want to add a nice transition effect when click from one page to another
I tried lots of solution online, including:
Angular 2 — Animating Router transitions
Page transition animations with Angular 2.0 router and component interface promises
....
one thing in common is that they all have style like position: absolute or position: fixed added, which breaks my existing app layout.
I remember when I using Angular 1, there's no need to add position: absolute to <div ui-view><div>
Is it possible in Angular 2 or 4?
You can add the absolute positioning exclusively to the leaving animation.
transition(':enter', [
style({transform: 'translateX(100%)'}),
animate('0.3s ease-in-out', style({transform: 'translateX(0%)'}))
]),
transition(':leave', [
style({transform: 'translateX(0%)', position: 'absolute', left: 0, right: 0, top: 0}),
animate('0.3s ease-in-out', style({transform: 'translateX(-100%)'}))
])
So only the leaving route is positioned absolutely, while the entering route is positioned staticly.
If it doesn't work, make sure that your router-outlet is wrapped by position: relative
<div style="position: relative;">
<router-outlet></router-outlet>
</div>
And that your route-components have display: block
#Component({
styles:[':host {display: block;}']
Talking about Angular version 4.3.x. Reading the router documentation, they explain how to add animation between routes. Here is a resume for the lazy ones (including myself).
You want to import animations libraries from #angular/core (and not #angular/animations):
import {
AnimationEntryMetadata,
animate,
state,
style,
trigger
} from '#angular/core';
export const fadeInAnimation: AnimationEntryMetadata = trigger('fadeInAnimation', [
transition(':enter', [
style({
opacity: 0,
transform: 'translateY(20px)'
}),
animate(
'.3s',
style({
opacity: 1,
transform: 'translateY(0)'
})
)
])
]);
Then in your component, use the HostBinding decorator in order to specify the component's layout css properties (you don't need use a fixed or absolute position):
import { Component, OnInit, HostBinding } from '#angular/core';
import { fadeInAnimation } from './../animations/fadein';
#Component({
animations: [fadeInAnimation],
selector: 'app-posts',
templateUrl: './posts.component.html'
})
export class DemandsComponent implements OnInit {
#HostBinding('#fadeInAnimation') fadeInAnimation = true;
#HostBinding('style.display') display = 'block';
#HostBinding('style.position') position = 'relative';
// Rest of the code
}
Adding this to each routed component can be cumbersome. The documentation suggest, and I quote:
Applying route animations to individual components works for a simple demo, but in a real life app, it is better to animate routes based on route paths.
This article from Matias Niemelä can help https://www.yearofmoo.com/2017/06/new-wave-of-animation-features.html#routable-animations
So I am trying to display a transition with ReactCSSTransitionGroup on load of the home page. It is to display a list of items below the jumbotron. This items come from Redux store. They are passed from a container component called home_index.js that is aware of Redux Store. The component itself 'poll-list.js' contains poll-links, the items to be transitioned in; it (and its child) is a DUMB component. Meaning, it has no state nor is it aware of redux store. (Can these components even use ReactCSSTransitionGroup?)
In any case, I cannot, for the life of me, get this fade in transition to work. I know I must be doing something wrong...but cannot figure it out.
const appearTransition = {
transitionName: "fade",
transitionLeave: false,
transitionEnter: false,
transitionAppear: true,
transitionAppearTimeout: 2500
};
let polls;
if (props.pollsList) {
polls = props.pollsList.map((poll, ind) => {
return (
<PollLink
className='poll-link'
title={poll.title}
index={ind}
id={poll.id}
key={ind}
/>
)
});
}
return (
<div className='poll-list'>
<ReactCSSTransitionGroup {...appearTransition}>
{props.pollsList.length > 0 ? polls : null}
</ReactCSSTransitionGroup>
</div>
)
}
And here is the CSS (note I am not doing a Leave, and I have set Appear to true, i just want this to happen on initial load of the home page)
.fade-appear {
opacity: 0.01;
#include prefix(transition, (opacity 2500ms), webkit ms moz o);
}
.fade-appear.fade-appear-active {
opacity: 1;
}
Also, putting ReactCSSTransitionGroup at this level, in this element, makes my flexbox get all weird. The components are no longer in columns of 4 (or however many based on screen width), they are in one single column down the center...
Where do I put the transition group HOC?
Hierarchy:
<HomeIndex>
<Poll-List>
<Poll-Links>
You can simply add this style in your css file, try to remove the hyphen between class name and make it in camelcasing:
.fadeAppear {
opacity: 1;
transition: opacity 0.3s ease-in;-webkit-transition: opacity 0.3s ease-in;
}
import your css file in your redux code,
#import mycss from '../path/style.css'
and add the given class in css in wherever needed.
as:
return (
<div className='poll-list'>
<ReactCSSTransitionGroup className={mycss.fadeAppear} >
{props.pollsList.length > 0 ? polls : null}
</ReactCSSTransitionGroup>
</div>
)
Hope this works!
Angular 1 handles enter, leave and move animations. The Angular 2 documentation describes how to do enter and leave animations (void => * and * => void), but how can one implement move animations in Angular 2?
Read Angular's official guide for animations if you haven't already.
You define animation states and the transitions between them. For instance:
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'))
])
]
inactive and active can be replaced with any arbitrary strings and you can have as many unique states as you wish, but there must be a valid transition to each one or else the animation won't happen. void is a special case for when elements aren't yet attached to the view and * is a wildcard, applying to any of the defined states.
EDIT:
Hmm... well, for one thing, you might be able to use this Sortable library. It claims to support Angular 2 and is pure Javascript (no jQuery) so theoretically, it should work well but I have not used it myself.
Otherwise, I am certain it would be possible purely inside Angular 2, but it would probably require some fairly clever code. Relative motion (irrespective of a component or element's particular position) is easy with transform: translateY() property. The problem is that Angular 2 animation states only apply if the component is in that state so if you give it a translateY(-20px) to move an element up a position, it's not going to keep that position if you want to want to move it up again.
See this plunker for the solution I have come up with.
template: `
<div #thisElement>
<div class="div-box" #moveState="state">Click buttons to move <div>
</div>
<button (click)="moveUp()">Up</button>
<button (click)="moveDown()">Down</button>
`,
I defined animation states for 'moveUp' and 'moveDown' that ONLY apply during the actual animation and a 'static' state that is applied when the component isn't moving.
animations: [
trigger('moveState', [
state('moveUp', style({
transform: 'translateY(-30px)';
})),
state('moveDown', style({
transform: 'translateY(30px)';
})),
state('static', style({
transform: 'translateY(0)';
})),
transition('* => moveUp', animate('100ms ease-in')),
transition('* => moveDown', animate('100ms ease-out')),
transition('* => static', animate('0ms linear'))
])
]
For the function that actually initiates the animation, it applies the 'moveUp' or 'moveDown' state and then starts a timeout that triggers a callback after an amount of time equal to the length of the transition. In the callback, it sets the animation state to 'static' (the transition to the 'static' state is set to 0 ms so we don't actually animate it moving back to a static position). Then we use Renderer to apply a translation for where we want it to ultimately end up (calculated using a position property that would define it's position relative to where it was initially, not it's position in the array). The Renderer applies its styles separately from the animation so we can apply both without them conflicting with each other.
export class MyComponent {
state = 'static';
#ViewChild('thisElement') thisBox: ElementRef;
position: number = 0;
//...
moveUp() {
this.state = 'moveUp';
this.position--;
setTimeout(() => {
this.state = 'static';
this.renderer.setElementStyle(this.thisBox.nativeElement, 'transform', 'translateY(' + String(this.position * 30) + 'px)');
}, 100)
}
moveDown() {
this.state = 'moveDown';
this.position++;
setTimeout(() => {
this.state = 'static';
this.renderer.setElementStyle(this.thisBox.nativeElement, 'transform', 'translateY(' + String(this.position * 30) + 'px)');
}, 100)
}
//...
}
This is only an example of how you can animate moves without having to define states for each possible position it could be in. As far as triggering the animations on array manipulation, you'll have to figure that out for yourself. I would use some kind of implementation with EventEmitters or Subjects to send events to the components that would then decide on whether or not they need to animate or not.
I'm working on a collapsable component, one that you can click to roll-up/down to show/hide details. The component is as follows:
// component.ts
import {Component, Directive, Input} from '#angular/core';
import {trigger, state, style, transition, animate} from '#angular/core'
#Component({
selector: 'expandable',
template: '<div *ngIf="isExpanded" #animate="'slide'"><ng-content></ng-content></div>',
animations: [
trigger('animate', [
state('slide', style({ overflow: 'hidden', height: '*' })),
transition('slide => void', [animate('200ms ease-out', style({ height: 0 }))]),
transition('void => slide', [style({ height: 0 }), animate('200ms ease-out', style({ height: '*' }))])
])
]
})
export class SimExpandable {
private _expanded: boolean = false;
#Input('expanded') set expanded(value: string | boolean) {
this._expanded = (value === 'true' || value === true);
}
get isExpanded(): boolean { return this._expanded }
}
The component works fine, partially. The animations, however, are not perfect. I've configured the component to use ease-out animation but in reality, the component animates linearly.
I've tried using ease-out, easeOut, ease-out-cubic, easeOutCubic, ease-in-out, easeInOut, bounce, and a lot of other permutations but the component still animates linearly. I REALLY need to use ease-out for my component. Any help would be really appreciated.
CSS properties transition and animation allow you to pick the easing
function. Unfortunately, they don’t support all easings and you must
specify the desired easing function yourself (as a Bezier curve).
Easings.net
It would seem that there are 4 default types of easing that should work.
linear
ease
ease-in
ease-out
ease-in-out
These work directly, however the differences are subtle
For more effective types of easing, you have to use a bezier-curve which allows you to create your own easing. For example the below is "easeInOutBack"
cubic-bezier(0.680, -0.550, 0.265, 1.550)
When using with Angular animate function
animate("1500ms 2000ms cubic-bezier(0.680, -0.550, 0.265, 1.550)", style({transform: "translateX(-100%)"}))
You can navigate to this bezier curver generator which should provide you with the ability to create your own easings.
Animate is no longer a part in angular/core.. so you have to import it, its part of the module ngAnimate .. so to use the $animate service you need to import js/lib/angular-animate.js libs.
bower install --save angular-animate