Angular Animation: Page transition without style `position: absolute`? - animation

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

Related

How can I extend or overwrite component decorators in Angular Material 2?

I am nesting a <mat-nav-list> inside of a <mat-expansion-panel> component. At runtime, Angular Material 2 is creating html, in my specific case:
<div class="mat-expansion-panel-body">...</div>
Which also is getting styles assigned to it. I am unable to overwrite these styles using the component style sheet like I normally would.
Looking through the source, I am seeing this:
/node_modules/#angular/material/esm2015/expansion.js
MatExpansionPanel.decorators = [
{ type: Component, args: [{styles: [".mat-expansion-panel{transition:box-shadow 280ms cubic-bezier(.4,0,.2,1);box-sizing:content-box;display:block;margin:0;transition:margin 225ms cubic-bezier(.4,0,.2,1)}.mat-expansion-panel:not([class*=mat-elevation-z]){box-shadow:0 3px 1px -2px rgba(0,0,0,.2),0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12)}.mat-expansion-panel-content{overflow:hidden}.mat-expansion-panel-body{margin:0 24px 16px;overflow:auto}
...
I'm sure this isn't the exact code that is creating the component, but it does contain the css I would like to override:
...
.mat-expansion-panel-body{margin:0 24px 16px;overflow:auto}
...
How can I modify the decorator to remove the margin?
Here is what my .ts file looks like so far:
#Component({
selector: 'my-selector',
templateUrl: './my-selector.component.html',
styleUrls: ['./my-selector.component.scss']
})
export class MyComponent implements OnInit, OnDestroy {...}
Thank you for any suggestions!
I wish I could take credit for the solution that seems to work, but here is a thread with a similar problem.
I was able to remove the margin like this:
my-template.component.html
<mat-expansion-panel *ngFor="let foo of bar" class="my-sidenav">
<mat-expansion-panel-header>
...
</mat-expansion-panel-header>
<mat-nav-list>
<mat-list-item>...</mat-list-item>
</mat-nav-list>
</mat-expansion-panel>
my-component.component.scss
/deep/ .my-sidenav {
.mat-expansion-panel-body {
margin: 0;
}
}

Animate only new components

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.

Angular2 Animations :: Easings not working as expected

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

Styles in component for D3.js do not show in angular 2

I am using Angular 2 and D3.js. I want to show a red rectangle.
It only works if I put styles in the style.css file.
Check this plunkr
When I put my styles in the component styles: [], it does not work. Check this plunkr
How to let it work when I use the component styles: []? Thanks
UPDATE: #micronyks provides a solution, but it makes the styles in the component global, basically no difference with writing in style.css file. In this plunkr, it shows style in one component will override another component's styles, so cannot show green and red rectangles.
UPDATE 2: #Günter's way perfectly solve this problem!! Just a remind, for Günter's way: it needs at least Angular beta 10. (My other plunkrs use Angular beta 8) The working demo for green and one red rectangle using Angular beta 12 is here.
import {Component} from 'angular2/core'
#Component({
selector: 'my-app',
providers: [],
styles: [`
/*this does not work*/
.bar {
fill: red;
}
`],
template: `
<div>
<svg class="chart"></svg>
</div>
`,
directives: []
})
export class App {
constructor() {}
ngOnInit() {
this.draw();
}
draw() {
let data = [{name: 'A', value: 1}];
let width = 400, height = 200;
let x = d3.scale.ordinal().rangeRoundBands([0, width]);
let y = d3.scale.linear().range([height, 0]);
let chart = d3.select(".chart")
.attr("width", width)
.attr("height", height)
.append("g");
x.domain(data.map(function(d) { return d.name; }));
y.domain([0, d3.max(data, function(d) { return d.value; })]);
chart.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.name); })
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); })
.attr("width", x.rangeBand());
}
}
Update
Angular and SASS agreed on supporting ::ng-deep (instead of >>> or /deep/) a while ago until ::slotted or whatever makes it into browser standards becomes available in all browsers.
ViewEncapsulation.Emulated (default)
That's by design. Angular adds class names unique to components and rewrites the added styles to only apply to the components where they were added.
D3 generates HTML dynamically without Angulars knowledge and Angular can't apply the classes to make the styles apply on the generated HTML.
If you add the styles at the entry point HTML file, Angular also doesn't rewrite the styles and the added helper classes don't take effect.
ViewEncapsulation.None
With encapsulation: ViewEncapsulation.None Angular doesn't do this rewriting, therefore the result is similar to adding the HTML to the index.html.
"Shadow-piercing"
Alternatively you can use the recently introduced shadow piercing CSS combinators >>>, /deep/ and ::shadow (::shadow is just replaced by a and thus very limited). See also https://stackoverflow.com/a/36225709/217408 and the Plunker
:host /deep/ div {
color: red;
}
SASS
/deep/ works fine with SASS but the alias >>> doesn't.
The shadow-piersing CSS combinators are rewritten by Angular and they don't need to be supported by the browsers. Chrome supported them for a while but they are deprecated - but as said, that doesn't matter, because Angular rewrites them to use its encapsulation emulation.
ViewEncapsulation.Native
Angular doesn't support any way to style such components from the outside. Only if the browser provides support like CSS variables then these can be used.
ViewEncapsulation will fix your problem.
import {Component,ViewEncapsulation} from 'angular2/core'
#Component({
selector: 'my-app',
encapsulation: ViewEncapsulation.None,
providers: [],
styles: [`
.bar {
fill: red;
}
`],
template: `
<div>
<svg class="chart"></svg>
</div>
`,
directives: []
})
View Encapsulation
This is because of the view encapsulation in Angular 2. By default, all the HTML and CSS is transformed so that it's only applied locally. In other words, if you add this style in your component's CSS:
h2 { color: red; }
It will only affect h2 elements inside the component, not every h2 element in your whole app. You can read more about this mechanisms in Angular documentation on View Encapsulation.
Why does it affect you?
Angular transforms your styles but since the C3 graph is not yet drawn, it can't transform HTML/SVG too. Because of that, component styles won't match elements inside of C3 graph.
What should I do?
External stylesheet
External stylesheets are not transformed by the View Encapsulation mechanisms, so effectively they will affect your C3 chart (and any other element for that matter).
If you're using Angular CLI, adding external stylesheet is really simple.
Edit your angular-cli.json file and inside of apps property find styles array. Add another stylesheet here:
{
…
"apps": [
{
…
"styles": [
"styles.scss",
"c3.scss" // <---- add this or any other file
],
}
],
…
}
In case you're not using Angular CLI, there must be some way to add external stylesheets. Probably the simplest one is adding another <link …> inside of <head> in your index.html file.
ViewEncapsulation.None
Your first option is: Create a component with the chart (and only chart) and turn off View Encapsulation inside of it. It's a good idea to do that also because of obeying the Single Responsibility Principle. Your chart, by design, should be encapsulated in a separate component. Turning of View Encapsulation is as simple as adding another property to your #Component decorator:
#Component({
…
encapsulation: ViewEncapsulation.None
})
/deep/ CSS selector
If, for some reason, you don't want to do that, there's another possibility. You can try using /deep/ selector inside of your CSS which forces styles down into all child components views. Effectively, this breaks the encapsulation and should affect your C3 chart. So, for example, you can do that in your component's CSS file:
/deep/ .c3-chart-arc path {
stroke: white;
}
Either way, I recommend reading the aforementioned documentation on View Encapsulation in Angular 2 in order to understand why this happens and how it works. This feature is supposed to help you write code, not cause troubles :)
This article might help you understand how it works: View Encapsulation on blog.thoughtram.io
you can use
::ng-deep
.bar {
fill: red;
}
Here you can read perfect article explaining the approach.
And... information from the Angular documentation
...then I cannot show one red and one green rectangle... The problem
comes back
I think it's some override, I do not know how much of this is true, but I think this solves your problem.
add in child1-cmp, child1-cmp .bar for example:
#Component({
encapsulation: ViewEncapsulation.None,
selector: 'child1-cmp',
styles: [`
child1-cmp .bar {
fill: red;
}
`],
template: `
<div>
<svg class="chart1"></svg>
</div>
`,
directives: []
})
Note: in addition to encapsulation: ViewEncapsulation.None, as mentioned by micronyks.
Test
Plunker
or this:
#Component({
selector: 'my-app',
directives: [Child1Cmp, Child2Cmp],
encapsulation: ViewEncapsulation.None,
styles: [`
child1-cmp .bar {
fill: red;
}
child2-cmp .bar {
fill: yellow;
}
`],
..//
#Component({
//encapsulation: ViewEncapsulation.None,
selector: 'child1-cmp',
template: `
<div>
<svg class="chart1"></svg>
</div>
`,
directives: []
})
#Component({
//encapsulation: ViewEncapsulation.None,
selector: 'child2-cmp',
template: `
<div>
<svg class="chart2"></svg>
</div>
`,
directives: []
})
Test
Plunker
or this using class .chart1, .chart2, for example if you want.
#Component({
selector: 'my-app',
directives: [Child1Cmp, Child2Cmp],
encapsulation: ViewEncapsulation.None,
styles: [`
.chart1 .bar {
fill: red;
}
.chart2 .bar {
fill: yellow;
}
`],
..//
Test
Plunker
I found that * /deep/ .my-element-class works, but for some reason, ONLY if the svg parent element is present in the html template (not when the svg parent element is created on the fly by d3).
For instance, the following situation would work:
mycomponent.component.html
<svg id="mygraph"></svg> <!-- IMPORTANT!! -->
mycomponent.component.css
* /deep/ .my-element-class {
/* ... desired styles */
}
mycomponent.component.ts
d3.select("svg#mygraph").append("circle").classed("my-element-class", true)
...

navbar-fixed-top show content behind Navbar

How do you prevent the content from floating behind the Navbar when scrolling?
<Navbar className="navbar-form navbar-fixed-top" responsive collapseable brand='x' inverse toggleNavKey={1} onClick={this.handleMouseDown}>
Or is it in:
<Nav className="navigation"..
Thanks
Add a custom class to your navbar component, say, sticky-nav. Define the following CSS properties on it:
.sticky-nav {
position: sticky;
top: 0;
}
This will make your navbar stick to the top and will automatically adjust the height of its following DOM elements. You can read more about the sticky property on MDN.
As Scrotch said adding:
<Navbar style={{backgroundColor: "#071740", position: "sticky"}} variant="dark" fixed="top">
worked for me, I did it inline but putting in a separate CSS file should work as well. This is the only thing that's worked so far for me.
NB: I'm using "react-bootstrap": "^1.0.0-beta.16"
I was running into this too. I found the following (baseline bootstrap) page that shows a fixed navbar and has the main page showing up properly below it. It seems to be a function of the css that they are using. Specifically:
padding-top: 70px;
I added
body {
padding-top: 70px;
}
to my css file, and it seems to be working. Obviously mileage may vary, not applicable in all territories, etc. I am going to need to test it further for myself, but that might get you started.
In order to get responsive padding-top for body you may use sth. like this (ES6 example):
import ReactDOM from 'react-dom';
import React from 'react';
import { Navbar, Nav, NavItem } from 'react-bootstrap';
export default class Template extends React.Component {
constructor(props) {
super(props);
this.state = { navHeight: 50 };
this.handleResize = this.handleResize.bind(this);
}
handleResize(e = null) {
this.setState({ navHeight: ReactDOM.findDOMNode(this._navbar).offsetHeight });
}
componentDidMount() {
window.addEventListener('resize', this.handleResize);
this.handleResize();
}
componentWillUnmount() {
window.removeEventListener('resize', this.handleResize);
}
render() {
return (
<body style={{paddingTop: this.state.navHeight}}>
<Navbar ref={(e) => this._navbar = e} fixedTop>
<Navbar.Header>
<Navbar.Brand>
<Link to="/">Some title</Link>
</Navbar.Brand>
</Navbar.Header>
<Nav>
<NavItem eventKey={1} href="/link1">Link1</NavItem>
<NavItem eventKey={2} href="/link2">Link2</NavItem>
</Nav>
</Navbar>
<main>
{this.props.children}
</main>
</body>
);
}
}
Template.propTypes = {
navHeight: React.PropTypes.number,
children: React.PropTypes.object,
};
This way your <body> padding-top will always fit your navbar height even after adding more links in mobile view.
I also assume that base height is 50px (see the constructor) but it shouldn't matter as long as you call this.handleResize() in componentDidMount().
There is also the option to specify sticky-top which basically implements the solution of using position:sticky that others have suggested (see documentation).
So in your example you could just specify sticky-top instead of fixed-top as a className, and it satisfies your requirements.
react-bootstrap provides position utilities.
<Navbar sticky="top">
react-bootstrap documentation
However in-page anchor does not work properly, and position can be under the navbar after jump from a link.
I also use this custom css to fix the issue.
*[id]:before {
display: block;
content: " ";
margin-top: -75px;
height: 75px;
visibility: hidden;
}
Reference
I came across the same problem. Wanting to stay within bootstrap and avoid custom css I tried adding class 'position-sticky', which gave me a left-shifted navbar. Apparently, for whatever reason, the navbar has a negative padding, so adding 'ps-0' to the class list fixed it.
<Navbar
fixed={'top'}
className={'position-sticky ps-0'}
>
for anyone using tailwind use sticky as a property in the top div of your navbar
<div className="sticky"> navbar-content </div>

Resources