如何在对象引用更改时制作类似于 :enter 和 :leave 的动画
How to animate akin to :enter and :leave when object reference changes
这是一些工作代码:
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" };
}
}
此代码的问题在于必须引入中间 null
状态。因此,我将表示代码与逻辑混合在一起以“使其工作”。这违反了良好的封装做法,并给代码增加了不必要的复杂性。
如何使用装饰器中 animations
属性 中的代码实现相同的结果?
要求
- 检测到对象引用的变化。
- 对对象引用更改的反应类似于
:enter
和 :leave
的工作方式;首先动画 DIV_1
out,然后动画 DIV_2
in.
- 在
animations
代码中封装有关动画的任何内容。所以swap函数应该是:swap(){this.object = { data: "DIV_2" };}
所以,我花了一点时间来解决,但我找到了一个可以帮助你的可能的解决方案。您应该创建一个自定义指令来代替经典的 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;
}
}
}
快速:我们清除当前视图,实例化一个嵌入视图,并在每次发生变化时将其插入到 div 中。
关于您的组件,您的动画将是这样的(您当然可以更改它们):
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')])
])
],
你的模板:
<div [@div] *ngIfAnimation="object">{{ object.data }}</div>
记得删除您的 *ngIf
,因为您不能在一个元素上绑定多个模板。
Stackblitz:https://stackblitz.com/edit/angular-ivy-hc8snt?file=src/app/app.component.html
只是想分享我的结果。该解决方案的灵感主要来自 Francesco Lisandro 的回答。
- 已创建结构指令
- 该指令采用具有 2 个属性的对象
- data: 引用改变的对象
- leaveTransitionDuration: 延迟插入空状态的持续时间
- 该指令在旧对象之后和新对象创建之前插入一个空状态。在 leaveTransitionDuration 之后插入 void 状态。
- 当引用从虚值变为对象时,指令不应用 leaveTransitionDuration。所以正常的
:enter
动画没有延迟。
app.component.html
<div @div *ngIfAnimation="{data: object, leaveTransitionDuration: 2000}">{{ object.data }}</div>
app.component.ts
import { Component } from "@angular/core";
import { animate, style, transition, trigger } from "@angular/animations";
@Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"],
animations: [
trigger("div", [
transition(":enter", [
style({ transform: "scale(0)" }),
animate("500ms 0s ease-in", style({ transform: "scale(1)" }))
]),
transition(":leave", [
style({ transform: "scale(1)" }),
animate("2s ease-in", style({ transform: "scale(0)" }))
])
])
]
})
export class AppComponent {
object: any = { data: "DIV_1" };
swap() {
this.object = { data: "DIV_2" };
}
}
ngIfAnimation.diretive.ts
import { Directive, Input, TemplateRef, ViewContainerRef } from "@angular/core";
import { interval } from "rxjs";
import { first } from "rxjs/operators";
@Directive({
selector: "[ngIfAnimation]"
})
export class NgIfAnimationDirective {
constructor(private view: ViewContainerRef, private tmpl: TemplateRef<any>) {}
private old_data = null;
private leaveTransitionDuration: number;
@Input() set ngIfAnimation(object: {
data: any;
leaveTransitionDuration: number;
}) {
this.leaveTransitionDuration = object.leaveTransitionDuration;
this.view.clear();
if (object.data) this.add_new_view();
this.old_data = object.data;
}
private get delay(): number {
return this.old_data ? this.leaveTransitionDuration : 0;
}
private add_new_view(): void {
interval(this.delay) // timeout until `:leave` animation is ran
.pipe(first())
.subscribe(() => {
this.view.createEmbeddedView(this.tmpl);
});
}
}
查看 Stackblitz 的演示:https://stackblitz.com/edit/angular-ivy-t7ea53?file=src/app/ngIfAnimation.directive.ts
注意:确保 leaveTransitionDuration 等于 :leave 过渡的持续时间。
这是一些工作代码:
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" };
}
}
此代码的问题在于必须引入中间 null
状态。因此,我将表示代码与逻辑混合在一起以“使其工作”。这违反了良好的封装做法,并给代码增加了不必要的复杂性。
如何使用装饰器中 animations
属性 中的代码实现相同的结果?
要求
- 检测到对象引用的变化。
- 对对象引用更改的反应类似于
:enter
和:leave
的工作方式;首先动画DIV_1
out,然后动画DIV_2
in. - 在
animations
代码中封装有关动画的任何内容。所以swap函数应该是:swap(){this.object = { data: "DIV_2" };}
所以,我花了一点时间来解决,但我找到了一个可以帮助你的可能的解决方案。您应该创建一个自定义指令来代替经典的 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;
}
}
}
快速:我们清除当前视图,实例化一个嵌入视图,并在每次发生变化时将其插入到 div 中。 关于您的组件,您的动画将是这样的(您当然可以更改它们):
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')])
])
],
你的模板:
<div [@div] *ngIfAnimation="object">{{ object.data }}</div>
记得删除您的 *ngIf
,因为您不能在一个元素上绑定多个模板。
Stackblitz:https://stackblitz.com/edit/angular-ivy-hc8snt?file=src/app/app.component.html
只是想分享我的结果。该解决方案的灵感主要来自 Francesco Lisandro 的回答。
- 已创建结构指令
- 该指令采用具有 2 个属性的对象
- data: 引用改变的对象
- leaveTransitionDuration: 延迟插入空状态的持续时间
- 该指令在旧对象之后和新对象创建之前插入一个空状态。在 leaveTransitionDuration 之后插入 void 状态。
- 当引用从虚值变为对象时,指令不应用 leaveTransitionDuration。所以正常的
:enter
动画没有延迟。
app.component.html
<div @div *ngIfAnimation="{data: object, leaveTransitionDuration: 2000}">{{ object.data }}</div>
app.component.ts
import { Component } from "@angular/core";
import { animate, style, transition, trigger } from "@angular/animations";
@Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"],
animations: [
trigger("div", [
transition(":enter", [
style({ transform: "scale(0)" }),
animate("500ms 0s ease-in", style({ transform: "scale(1)" }))
]),
transition(":leave", [
style({ transform: "scale(1)" }),
animate("2s ease-in", style({ transform: "scale(0)" }))
])
])
]
})
export class AppComponent {
object: any = { data: "DIV_1" };
swap() {
this.object = { data: "DIV_2" };
}
}
ngIfAnimation.diretive.ts
import { Directive, Input, TemplateRef, ViewContainerRef } from "@angular/core";
import { interval } from "rxjs";
import { first } from "rxjs/operators";
@Directive({
selector: "[ngIfAnimation]"
})
export class NgIfAnimationDirective {
constructor(private view: ViewContainerRef, private tmpl: TemplateRef<any>) {}
private old_data = null;
private leaveTransitionDuration: number;
@Input() set ngIfAnimation(object: {
data: any;
leaveTransitionDuration: number;
}) {
this.leaveTransitionDuration = object.leaveTransitionDuration;
this.view.clear();
if (object.data) this.add_new_view();
this.old_data = object.data;
}
private get delay(): number {
return this.old_data ? this.leaveTransitionDuration : 0;
}
private add_new_view(): void {
interval(this.delay) // timeout until `:leave` animation is ran
.pipe(first())
.subscribe(() => {
this.view.createEmbeddedView(this.tmpl);
});
}
}
查看 Stackblitz 的演示:https://stackblitz.com/edit/angular-ivy-t7ea53?file=src/app/ngIfAnimation.directive.ts
注意:确保 leaveTransitionDuration 等于 :leave 过渡的持续时间。