Here is some working code:
Stackblitz: https://stackblitz.com/edit/angular-ivy-tt9vjd?file=src/app/app.component.ts
app.component.html
<button (click)='swap()'>Swap object</button>
<div @div *ngIf='object'>{{ object.data }}</div>
app.component.css
div {
width: 100px;
height: 100px;
background: purple;
display: flex;
align-items: center;
justify-content: center;
color: antiquewhite;
}
app.component.ts
import { animate, style, transition, trigger } from "@angular/animations";
import { Component } from "@angular/core";
import { interval } from "rxjs";
import { first } from "rxjs/operators";
@Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"],
animations: [
trigger("div", [
transition(":enter", [
style({ transform: "scale(0)" }),
animate("250ms 0ms ease-out", style({ transform: "scale(1)" }))
]),
transition(":leave", [
style({ transform: "scale(1)" }),
animate("250ms 0ms ease-in", style({ transform: "scale(0)" }))
])
])
]
})
export class AppComponent {
object: any = { data: "DIV_1" };
swap() {
this.object = null;
interval(0)
.pipe(first())
.subscribe(() => {
this.object = { data: "DIV_2" };
});
// DESIRED CODE INSTEAD OF THE ABOVE
// this.object = { data: "DIV_2" };
}
}
The issue with this code is that an intermediary null
state had to be introduced. Thus I am mixing presentation code with logic in order "to make it work". This is violating good encapsulation practices and adds unnecessary complexity to the code.
How can the same results be achieved using code exclusively in the animations
property in the decorator?
requirements
:enter
and :leave
work; First animate DIV_1
out, then animate DIV_2
in.animations
code. So the swap function should be: swap(){this.object = { data: "DIV_2" };}
So, I spent a bit to solve, but I found a possible solution that can help you. You should create a custom directive to use instead of the classic ngIf
.
import {
Directive,
Input,
TemplateRef,
ViewContainerRef
} from '@angular/core';
@Directive({
selector: '[ngIfAnimation]'
})
export class NgIfAnimationDirective {
private value: any;
private hasView = false;
constructor(
private view: ViewContainerRef,
private tmpl: TemplateRef<any>
) { }
@Input() set ngIfAnimation(val: any) {
if (!this.hasView) {
this.view.createEmbeddedView(this.tmpl);
this.hasView = true;
} else if (val !== this.value) {
this.view.clear();
this.view.createEmbeddedView(this.tmpl);
this.value = val;
}
}
}
Quickly: we clear the current view, instantiate an embedded view and inserts it into the div each time there's a change. About your component your animations will be like this (you can change them of course):
animations: [
trigger('div', [
state('void', style({ transform: 'scale(0)' })),
state('*', style({transform: 'scale(1)' })),
transition('void => *', [animate('0.2s 0.2s ease-in')]),
transition('* => void', [animate('0.2s ease-in')])
])
],
And your template:
<div [@div] *ngIfAnimation="object">{{ object.data }}</div>
Remember to remove your *ngIf
because you can't have multiple template bindings on one element.
Stackblitz: https://stackblitz.com/edit/angular-ivy-hc8snt?file=src/app/app.component.html