Skip to content

Commit e753278

Browse files
JeanMechealxhub
authored andcommitted
feat(animations): Add the possibility of lazy loading animations code. (#50738)
`provideLazyLoadedAnimations()` returns providers which allow the lazy loading of the animation module. Lazy loading of the animation code can shave off up to 16KB gzipped of the main bundle. PR Close #50738
1 parent e1728a2 commit e753278

File tree

24 files changed

+1020
-204
lines changed

24 files changed

+1020
-204
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
## API Report File for "@angular/platform-browser_animations_async"
2+
3+
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
4+
5+
```ts
6+
7+
import { Provider } from '@angular/core';
8+
9+
// @public
10+
export function provideAnimationsAsync(type?: 'animations' | 'noop'): Provider[];
11+
12+
// (No @packageDocumentation comment for this package)
13+
14+
```

packages.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ DOCS_ENTRYPOINTS = [
9393
"platform-browser-dynamic",
9494
"platform-browser-dynamic/testing",
9595
"platform-browser/animations",
96+
"platform-browser/animations/async",
9697
"platform-browser/testing",
9798
"platform-server",
9899
"platform-server/init",
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {NoopAnimationStyleNormalizer} from './dsl/style_normalization/animation_style_normalizer';
10+
import {WebAnimationsStyleNormalizer} from './dsl/style_normalization/web_animations_style_normalizer';
11+
import {NoopAnimationDriver} from './render/animation_driver';
12+
import {AnimationEngine} from './render/animation_engine_next';
13+
import {WebAnimationsDriver} from './render/web_animations/web_animations_driver';
14+
15+
export function createEngine(type: 'animations'|'noop', doc: Document): AnimationEngine {
16+
// TODO: find a way to make this tree shakable.
17+
if (type === 'noop') {
18+
return new AnimationEngine(doc, new NoopAnimationDriver(), new NoopAnimationStyleNormalizer());
19+
}
20+
21+
return new AnimationEngine(doc, new WebAnimationsDriver(), new WebAnimationsStyleNormalizer());
22+
}

packages/animations/browser/src/private_export.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8+
export {createEngine as ɵcreateEngine} from './create_engine';
89
export {Animation as ɵAnimation} from './dsl/animation';
910
export {AnimationStyleNormalizer as ɵAnimationStyleNormalizer, NoopAnimationStyleNormalizer as ɵNoopAnimationStyleNormalizer} from './dsl/style_normalization/animation_style_normalizer';
1011
export {WebAnimationsStyleNormalizer as ɵWebAnimationsStyleNormalizer} from './dsl/style_normalization/web_animations_style_normalizer';
1112
export {AnimationEngine as ɵAnimationEngine} from './render/animation_engine_next';
12-
export {AnimationRenderer as ɵAnimationRenderer, AnimationRendererFactory as ɵAnimationRendererFactory} from './render/animation_renderer';
13+
export {AnimationRendererFactory as ɵAnimationRendererFactory} from './render/animation_renderer';
14+
export {AnimationRenderer as ɵAnimationRenderer, BaseAnimationRenderer as ɵBaseAnimationRenderer} from './render/renderer';
1315
export {containsElement as ɵcontainsElement, getParentElement as ɵgetParentElement, invokeQuery as ɵinvokeQuery, validateStyleProperty as ɵvalidateStyleProperty, validateWebAnimatableStyleProperty as ɵvalidateWebAnimatableStyleProperty} from './render/shared';
1416
export {WebAnimationsDriver as ɵWebAnimationsDriver} from './render/web_animations/web_animations_driver';
1517
export {WebAnimationsPlayer as ɵWebAnimationsPlayer} from './render/web_animations/web_animations_player';

packages/animations/browser/src/render/animation_renderer.ts

Lines changed: 12 additions & 186 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,17 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88
import {AnimationTriggerMetadata} from '@angular/animations';
9-
import {Injectable, NgZone, Renderer2, RendererFactory2, RendererStyleFlags2, RendererType2} from '@angular/core';
9+
import type {NgZone, Renderer2, RendererFactory2, RendererType2} from '@angular/core';
1010

1111
import {AnimationEngine} from './animation_engine_next';
12-
13-
const ANIMATION_PREFIX = '@';
14-
const DISABLE_ANIMATIONS_FLAG = '@.disabled';
12+
import {AnimationRenderer, BaseAnimationRenderer} from './renderer';
1513

1614
// Define a recursive type to allow for nested arrays of `AnimationTriggerMetadata`. Note that an
1715
// interface declaration is used as TypeScript prior to 3.7 does not support recursive type
1816
// references, see https://github.com/microsoft/TypeScript/pull/33050 for details.
1917
type NestedAnimationTriggerMetadata = AnimationTriggerMetadata|RecursiveAnimationTriggerMetadata;
2018
interface RecursiveAnimationTriggerMetadata extends Array<NestedAnimationTriggerMetadata> {}
2119

22-
@Injectable()
2320
export class AnimationRendererFactory implements RendererFactory2 {
2421
private _currentId: number = 0;
2522
private _microtaskId: number = 1;
@@ -47,16 +44,17 @@ export class AnimationRendererFactory implements RendererFactory2 {
4744
// cache the delegates to find out which cached delegate can
4845
// be used by which cached renderer
4946
const delegate = this.delegate.createRenderer(hostElement, type);
50-
if (!hostElement || !type || !type.data || !type.data['animation']) {
51-
let renderer: BaseAnimationRenderer|undefined = this._rendererCache.get(delegate);
47+
if (!hostElement || !type?.data?.['animation']) {
48+
const cache = this._rendererCache;
49+
let renderer: BaseAnimationRenderer|undefined = cache.get(delegate);
5250
if (!renderer) {
5351
// Ensure that the renderer is removed from the cache on destroy
5452
// since it may contain references to detached DOM nodes.
55-
const onRendererDestroy = () => this._rendererCache.delete(delegate);
53+
const onRendererDestroy = () => cache.delete(delegate);
5654
renderer =
5755
new BaseAnimationRenderer(EMPTY_NAMESPACE_ID, delegate, this.engine, onRendererDestroy);
5856
// only cache this result when the base renderer is used
59-
this._rendererCache.set(delegate, renderer);
57+
cache.set(delegate, renderer);
6058
}
6159
return renderer;
6260
}
@@ -100,19 +98,19 @@ export class AnimationRendererFactory implements RendererFactory2 {
10098
return;
10199
}
102100

103-
if (this._animationCallbacksBuffer.length == 0) {
101+
const animationCallbacksBuffer = this._animationCallbacksBuffer;
102+
if (animationCallbacksBuffer.length == 0) {
104103
queueMicrotask(() => {
105104
this._zone.run(() => {
106-
this._animationCallbacksBuffer.forEach(tuple => {
105+
animationCallbacksBuffer.forEach(tuple => {
107106
const [fn, data] = tuple;
108107
fn(data);
109108
});
110109
this._animationCallbacksBuffer = [];
111110
});
112111
});
113112
}
114-
115-
this._animationCallbacksBuffer.push([fn, data]);
113+
animationCallbacksBuffer.push([fn, data]);
116114
}
117115

118116
end() {
@@ -134,176 +132,4 @@ export class AnimationRendererFactory implements RendererFactory2 {
134132
whenRenderingDone(): Promise<any> {
135133
return this.engine.whenRenderingDone();
136134
}
137-
}
138-
139-
export class BaseAnimationRenderer implements Renderer2 {
140-
constructor(
141-
protected namespaceId: string, public delegate: Renderer2, public engine: AnimationEngine,
142-
private _onDestroy?: () => void) {}
143-
144-
get data() {
145-
return this.delegate.data;
146-
}
147-
148-
destroyNode(node: any): void {
149-
this.delegate.destroyNode?.(node);
150-
}
151-
152-
destroy(): void {
153-
this.engine.destroy(this.namespaceId, this.delegate);
154-
this.engine.afterFlushAnimationsDone(() => {
155-
// Call the renderer destroy method after the animations has finished as otherwise
156-
// styles will be removed too early which will cause an unstyled animation.
157-
queueMicrotask(() => {
158-
this.delegate.destroy();
159-
});
160-
});
161-
162-
this._onDestroy?.();
163-
}
164-
165-
createElement(name: string, namespace?: string|null|undefined) {
166-
return this.delegate.createElement(name, namespace);
167-
}
168-
169-
createComment(value: string) {
170-
return this.delegate.createComment(value);
171-
}
172-
173-
createText(value: string) {
174-
return this.delegate.createText(value);
175-
}
176-
177-
appendChild(parent: any, newChild: any): void {
178-
this.delegate.appendChild(parent, newChild);
179-
this.engine.onInsert(this.namespaceId, newChild, parent, false);
180-
}
181-
182-
insertBefore(parent: any, newChild: any, refChild: any, isMove: boolean = true): void {
183-
this.delegate.insertBefore(parent, newChild, refChild);
184-
// If `isMove` true than we should animate this insert.
185-
this.engine.onInsert(this.namespaceId, newChild, parent, isMove);
186-
}
187-
188-
removeChild(parent: any, oldChild: any, isHostElement: boolean): void {
189-
this.engine.onRemove(this.namespaceId, oldChild, this.delegate);
190-
}
191-
192-
selectRootElement(selectorOrNode: any, preserveContent?: boolean) {
193-
return this.delegate.selectRootElement(selectorOrNode, preserveContent);
194-
}
195-
196-
parentNode(node: any) {
197-
return this.delegate.parentNode(node);
198-
}
199-
200-
nextSibling(node: any) {
201-
return this.delegate.nextSibling(node);
202-
}
203-
204-
setAttribute(el: any, name: string, value: string, namespace?: string|null|undefined): void {
205-
this.delegate.setAttribute(el, name, value, namespace);
206-
}
207-
208-
removeAttribute(el: any, name: string, namespace?: string|null|undefined): void {
209-
this.delegate.removeAttribute(el, name, namespace);
210-
}
211-
212-
addClass(el: any, name: string): void {
213-
this.delegate.addClass(el, name);
214-
}
215-
216-
removeClass(el: any, name: string): void {
217-
this.delegate.removeClass(el, name);
218-
}
219-
220-
setStyle(el: any, style: string, value: any, flags?: RendererStyleFlags2|undefined): void {
221-
this.delegate.setStyle(el, style, value, flags);
222-
}
223-
224-
removeStyle(el: any, style: string, flags?: RendererStyleFlags2|undefined): void {
225-
this.delegate.removeStyle(el, style, flags);
226-
}
227-
228-
setProperty(el: any, name: string, value: any): void {
229-
if (name.charAt(0) == ANIMATION_PREFIX && name == DISABLE_ANIMATIONS_FLAG) {
230-
this.disableAnimations(el, !!value);
231-
} else {
232-
this.delegate.setProperty(el, name, value);
233-
}
234-
}
235-
236-
setValue(node: any, value: string): void {
237-
this.delegate.setValue(node, value);
238-
}
239-
240-
listen(target: any, eventName: string, callback: (event: any) => boolean | void): () => void {
241-
return this.delegate.listen(target, eventName, callback);
242-
}
243-
244-
protected disableAnimations(element: any, value: boolean) {
245-
this.engine.disableAnimations(element, value);
246-
}
247-
}
248-
249-
export class AnimationRenderer extends BaseAnimationRenderer implements Renderer2 {
250-
constructor(
251-
public factory: AnimationRendererFactory, namespaceId: string, delegate: Renderer2,
252-
engine: AnimationEngine, onDestroy?: () => void) {
253-
super(namespaceId, delegate, engine, onDestroy);
254-
this.namespaceId = namespaceId;
255-
}
256-
257-
override setProperty(el: any, name: string, value: any): void {
258-
if (name.charAt(0) == ANIMATION_PREFIX) {
259-
if (name.charAt(1) == '.' && name == DISABLE_ANIMATIONS_FLAG) {
260-
value = value === undefined ? true : !!value;
261-
this.disableAnimations(el, value as boolean);
262-
} else {
263-
this.engine.process(this.namespaceId, el, name.slice(1), value);
264-
}
265-
} else {
266-
this.delegate.setProperty(el, name, value);
267-
}
268-
}
269-
270-
override listen(
271-
target: 'window'|'document'|'body'|any, eventName: string,
272-
callback: (event: any) => any): () => void {
273-
if (eventName.charAt(0) == ANIMATION_PREFIX) {
274-
const element = resolveElementFromTarget(target);
275-
let name = eventName.slice(1);
276-
let phase = '';
277-
// @listener.phase is for trigger animation callbacks
278-
// @@listener is for animation builder callbacks
279-
if (name.charAt(0) != ANIMATION_PREFIX) {
280-
[name, phase] = parseTriggerCallbackName(name);
281-
}
282-
return this.engine.listen(this.namespaceId, element, name, phase, event => {
283-
const countId = (event as any)['_data'] || -1;
284-
this.factory.scheduleListenerCallback(countId, callback, event);
285-
});
286-
}
287-
return this.delegate.listen(target, eventName, callback);
288-
}
289-
}
290-
291-
function resolveElementFromTarget(target: 'window'|'document'|'body'|any): any {
292-
switch (target) {
293-
case 'body':
294-
return document.body;
295-
case 'document':
296-
return document;
297-
case 'window':
298-
return window;
299-
default:
300-
return target;
301-
}
302-
}
303-
304-
function parseTriggerCallbackName(triggerName: string) {
305-
const dotIndex = triggerName.indexOf('.');
306-
const trigger = triggerName.substring(0, dotIndex);
307-
const phase = triggerName.slice(dotIndex + 1);
308-
return [trigger, phase];
309-
}
135+
}

0 commit comments

Comments
 (0)