From 77669648a1e4bf75ea745ea358e308c67eef20fe Mon Sep 17 00:00:00 2001 From: Matthieu Riegler Date: Fri, 16 Jun 2023 01:37:13 +0300 Subject: [PATCH] feat(animations): Add a provider to lazy load the animations In order to support Lazy loading of Animations `@angular/animations/browser` must be lazy loaded. Due to the dependencies of the exising providers a new entry point in `platform-browser` had to be created. Usage: use `provideLazyLoadedAnimation()` in the list of providers in ``bootstrapApplication`. --- WORKSPACE | 10 +- .../public-api/animations/browser/index.md | 6 + .../platform-browser/animations/index.md | 15 + .../lazy-animations/errors.md | 15 + .../platform-browser/lazy-animations/index.md | 14 + packages.bzl | 1 + packages/animations/browser/BUILD.bazel | 1 + .../animations/browser/src/createEngine.ts | 24 + .../browser/src/dsl/animation_ast_builder.ts | 2 +- ...lizer.ts => animation_style_normalizer.ts} | 36 +- .../src/dsl/animation_transition_factory.ts | 2 +- .../browser/src/dsl/animation_trigger.ts | 2 +- .../animation_style_normalizer.ts | 32 - .../animations/browser/src/private_export.ts | 7 +- .../browser/src/render/animation_driver.ts | 79 +- .../src/render/animation_engine_next.ts | 23 +- .../animations/browser/src/render/shared.ts | 2 +- .../src/render/timeline_animation_engine.ts | 2 +- .../src/render/transition_animation_engine.ts | 2 +- .../web_animations/web_animations_driver.ts | 81 - packages/animations/browser/src/util.ts | 4 +- .../web_animations_style_normalizer_spec.ts | 2 +- .../render/timeline_animation_engine_spec.ts | 2 +- .../transition_animation_engine_spec.ts | 4 +- .../web_animations_driver_spec.ts | 2 +- packages/animations/browser/test/shared.ts | 2 +- .../test/acceptance/renderer_factory_spec.ts | 20 +- .../animation/animation_integration_spec.ts | 7 +- .../animation_query_integration_spec.ts | 6 +- ...ns_with_web_animations_integration_spec.ts | 7 +- .../animations-standalone/BUILD.bazel | 1 - .../bundle.golden_symbols.json | 1642 +++++++++++++++-- .../bundling/animations-standalone/index.ts | 24 +- .../animations/bundle.golden_symbols.json | 1640 ++++++++++++++-- .../core/test/render3/imported_renderer2.ts | 12 - packages/platform-browser/BUILD.bazel | 1 + .../platform-browser/animations/BUILD.bazel | 1 + .../animations/src/animation_builder.ts | 11 +- .../animations/src/animation_renderer.ts | 268 +-- .../animations/src/animations.ts | 1 + .../src/lazy/abstract_animation_renderer.ts | 277 +++ .../src/lazy/async_animation_renderer.ts | 216 +++ .../animations/src/lazy/errors.ts | 14 + .../animations/src/lazy/lazy_providers.ts | 54 + .../platform-browser/animations/src/module.ts | 8 + .../animations/src/private_export.ts | 7 +- .../animations/src/providers.ts | 35 +- .../test/animation_renderer_spec.ts | 33 +- .../test/browser_animation_builder_spec.ts | 9 +- .../test/noop_animations_module_spec.ts | 6 +- .../lazy-animations/BUILD.bazel | 46 + .../lazy-animations/PACKAGE.md | 1 + .../platform-browser/lazy-animations/index.ts | 14 + .../lazy-animations/public_api.ts | 14 + .../src/abstract_animation_renderer.ts | 277 +++ .../src/async_animation_renderer.ts | 216 +++ .../lazy-animations/src/errors.ts | 14 + .../lazy-animations/src/lazy-animations.ts | 16 + .../lazy-animations/src/lazy_providers.ts | 54 + .../lazy-animations/src/private_export.ts | 9 + .../lazy-animations/test/BUILD.bazel | 46 + .../test/animation_renderer_spec.ts | 379 ++++ .../platform-browser/src/dom/dom_renderer.ts | 10 +- packages/platform-browser/src/errors.ts | 4 + .../platform-browser/src/private_export.ts | 2 +- 65 files changed, 4966 insertions(+), 808 deletions(-) create mode 100644 goldens/public-api/platform-browser/lazy-animations/errors.md create mode 100644 goldens/public-api/platform-browser/lazy-animations/index.md create mode 100644 packages/animations/browser/src/createEngine.ts rename packages/animations/browser/src/dsl/{style_normalization/web_animations_style_normalizer.ts => animation_style_normalizer.ts} (56%) delete mode 100644 packages/animations/browser/src/dsl/style_normalization/animation_style_normalizer.ts delete mode 100644 packages/animations/browser/src/render/web_animations/web_animations_driver.ts create mode 100644 packages/platform-browser/animations/src/lazy/abstract_animation_renderer.ts create mode 100644 packages/platform-browser/animations/src/lazy/async_animation_renderer.ts create mode 100644 packages/platform-browser/animations/src/lazy/errors.ts create mode 100644 packages/platform-browser/animations/src/lazy/lazy_providers.ts create mode 100644 packages/platform-browser/lazy-animations/BUILD.bazel create mode 100644 packages/platform-browser/lazy-animations/PACKAGE.md create mode 100644 packages/platform-browser/lazy-animations/index.ts create mode 100644 packages/platform-browser/lazy-animations/public_api.ts create mode 100644 packages/platform-browser/lazy-animations/src/abstract_animation_renderer.ts create mode 100644 packages/platform-browser/lazy-animations/src/async_animation_renderer.ts create mode 100644 packages/platform-browser/lazy-animations/src/errors.ts create mode 100644 packages/platform-browser/lazy-animations/src/lazy-animations.ts create mode 100644 packages/platform-browser/lazy-animations/src/lazy_providers.ts create mode 100644 packages/platform-browser/lazy-animations/src/private_export.ts create mode 100644 packages/platform-browser/lazy-animations/test/BUILD.bazel create mode 100644 packages/platform-browser/lazy-animations/test/animation_renderer_spec.ts diff --git a/WORKSPACE b/WORKSPACE index 80310d6dc7d536..1195e40bba47e0 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -139,11 +139,11 @@ yarn_install( # with bin symlinks in the external repository. This is needed to link the shared # set of deps for example e2es. exports_directories_only = False, - manual_build_file_contents = """\ -filegroup( - name = "node_modules_files", - srcs = ["node_modules"], -) + manual_build_file_contents = """\\\r +filegroup(\r + name = "node_modules_files",\r + srcs = ["node_modules"],\r +)\r """, package_json = "//aio/tools/examples/shared:package.json", yarn = YARN_LABEL, diff --git a/goldens/public-api/animations/browser/index.md b/goldens/public-api/animations/browser/index.md index cf895239c77031..77f8f8fa71a412 100644 --- a/goldens/public-api/animations/browser/index.md +++ b/goldens/public-api/animations/browser/index.md @@ -4,6 +4,8 @@ ```ts +import * as i0 from '@angular/core'; + // @public (undocumented) export abstract class AnimationDriver { // (undocumented) @@ -23,6 +25,10 @@ export abstract class AnimationDriver { abstract validateAnimatableStyleProperty?: (prop: string) => boolean; // (undocumented) abstract validateStyleProperty(prop: string): boolean; + // (undocumented) + static ɵfac: i0.ɵɵFactoryDeclaration; + // (undocumented) + static ɵprov: i0.ɵɵInjectableDeclaration; } // (No @packageDocumentation comment for this package) diff --git a/goldens/public-api/platform-browser/animations/index.md b/goldens/public-api/platform-browser/animations/index.md index 8480695cd761d7..bfd0f1f221e07c 100644 --- a/goldens/public-api/platform-browser/animations/index.md +++ b/goldens/public-api/platform-browser/animations/index.md @@ -5,13 +5,28 @@ ```ts import { ANIMATION_MODULE_TYPE } from '@angular/core'; +import { AnimationBuilder } from '@angular/animations'; +import { AnimationFactory } from '@angular/animations'; +import { AnimationMetadata } from '@angular/animations'; import * as i0 from '@angular/core'; import * as i1 from '@angular/platform-browser'; import { ModuleWithProviders } from '@angular/core'; import { Provider } from '@angular/core'; +import { RendererFactory2 } from '@angular/core'; export { ANIMATION_MODULE_TYPE } +// @public (undocumented) +export class BrowserAnimationBuilder extends AnimationBuilder { + constructor(rootRenderer: RendererFactory2, doc: Document); + // (undocumented) + build(animation: AnimationMetadata | AnimationMetadata[]): AnimationFactory; + // (undocumented) + static ɵfac: i0.ɵɵFactoryDeclaration; + // (undocumented) + static ɵprov: i0.ɵɵInjectableDeclaration; +} + // @public export class BrowserAnimationsModule { static withConfig(config: BrowserAnimationsModuleConfig): ModuleWithProviders; diff --git a/goldens/public-api/platform-browser/lazy-animations/errors.md b/goldens/public-api/platform-browser/lazy-animations/errors.md new file mode 100644 index 00000000000000..b538e21c524b39 --- /dev/null +++ b/goldens/public-api/platform-browser/lazy-animations/errors.md @@ -0,0 +1,15 @@ +## API Report File for "angular-srcs" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +// @public +export const enum RuntimeErrorCode { + // (undocumented) + MODULE_LOADING_FAILED = 5950 +} + +// (No @packageDocumentation comment for this package) + +``` diff --git a/goldens/public-api/platform-browser/lazy-animations/index.md b/goldens/public-api/platform-browser/lazy-animations/index.md new file mode 100644 index 00000000000000..8f10fd99f81662 --- /dev/null +++ b/goldens/public-api/platform-browser/lazy-animations/index.md @@ -0,0 +1,14 @@ +## API Report File for "@angular/platform-browser_lazy-animations" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { Provider } from '@angular/core'; + +// @public +export function provideLazyLoadedAnimations(type?: 'animations' | 'noop'): Provider[]; + +// (No @packageDocumentation comment for this package) + +``` diff --git a/packages.bzl b/packages.bzl index 47a73433819ef9..847b796cd086d5 100644 --- a/packages.bzl +++ b/packages.bzl @@ -93,6 +93,7 @@ DOCS_ENTRYPOINTS = [ "platform-browser-dynamic", "platform-browser-dynamic/testing", "platform-browser/animations", + "platform-browser/lazy-animations", "platform-browser/testing", "platform-server", "platform-server/init", diff --git a/packages/animations/browser/BUILD.bazel b/packages/animations/browser/BUILD.bazel index 2e83c13b407c55..4a15088a1a41ca 100644 --- a/packages/animations/browser/BUILD.bazel +++ b/packages/animations/browser/BUILD.bazel @@ -14,6 +14,7 @@ ng_module( ), deps = [ "//packages/animations", + "//packages/common", "//packages/core", ], ) diff --git a/packages/animations/browser/src/createEngine.ts b/packages/animations/browser/src/createEngine.ts new file mode 100644 index 00000000000000..23a60bab003578 --- /dev/null +++ b/packages/animations/browser/src/createEngine.ts @@ -0,0 +1,24 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {ApplicationRef} from '@angular/core'; + +import {NoopAnimationStyleNormalizer, WebAnimationsStyleNormalizer} from './dsl/animation_style_normalizer'; +import {NoopAnimationDriver, WebAnimationsDriver} from './render/animation_driver'; +import {AnimationEngine} from './render/animation_engine_next'; + +export function createEngine( + type: 'animations'|'noop', applicationRef: ApplicationRef, doc: Document): AnimationEngine { + if (type === 'noop') { + return new AnimationEngine( + doc, new NoopAnimationDriver(), new NoopAnimationStyleNormalizer(), applicationRef); + } + + return new AnimationEngine( + doc, new WebAnimationsDriver(), new WebAnimationsStyleNormalizer(), applicationRef); +} diff --git a/packages/animations/browser/src/dsl/animation_ast_builder.ts b/packages/animations/browser/src/dsl/animation_ast_builder.ts index 1ffb46bdf6f6da..5ca7b531dfa0ed 100644 --- a/packages/animations/browser/src/dsl/animation_ast_builder.ts +++ b/packages/animations/browser/src/dsl/animation_ast_builder.ts @@ -7,7 +7,7 @@ */ import {AnimateTimings, AnimationAnimateChildMetadata, AnimationAnimateMetadata, AnimationAnimateRefMetadata, AnimationGroupMetadata, AnimationKeyframesSequenceMetadata, AnimationMetadata, AnimationMetadataType, AnimationOptions, AnimationQueryMetadata, AnimationQueryOptions, AnimationReferenceMetadata, AnimationSequenceMetadata, AnimationStaggerMetadata, AnimationStateMetadata, AnimationStyleMetadata, AnimationTransitionMetadata, AnimationTriggerMetadata, AUTO_STYLE, style, ɵStyleDataMap} from '@angular/animations'; -import {invalidDefinition, invalidKeyframes, invalidOffset, invalidParallelAnimation, invalidProperty, invalidStagger, invalidState, invalidStyleValue, invalidTrigger, keyframeOffsetsOutOfOrder, keyframesMissingOffsets} from '../error_helpers'; +import {invalidDefinition, invalidKeyframes, invalidOffset, invalidParallelAnimation, invalidStagger, invalidState, invalidStyleValue, invalidTrigger, keyframeOffsetsOutOfOrder, keyframesMissingOffsets} from '../error_helpers'; import {AnimationDriver} from '../render/animation_driver'; import {getOrSetDefaultValue} from '../render/shared'; import {convertToMap, copyObj, extractStyleParams, iteratorToArray, NG_ANIMATING_SELECTOR, NG_TRIGGER_SELECTOR, normalizeAnimationEntry, resolveTiming, SUBSTITUTION_EXPR_START, validateStyleParams, visitDslNode} from '../util'; diff --git a/packages/animations/browser/src/dsl/style_normalization/web_animations_style_normalizer.ts b/packages/animations/browser/src/dsl/animation_style_normalizer.ts similarity index 56% rename from packages/animations/browser/src/dsl/style_normalization/web_animations_style_normalizer.ts rename to packages/animations/browser/src/dsl/animation_style_normalizer.ts index 94457f6f5faf34..2deb082558fbbf 100644 --- a/packages/animations/browser/src/dsl/style_normalization/web_animations_style_normalizer.ts +++ b/packages/animations/browser/src/dsl/animation_style_normalizer.ts @@ -5,10 +5,39 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import {invalidCssUnitValue} from '../../error_helpers'; -import {dashCaseToCamelCase} from '../../util'; -import {AnimationStyleNormalizer} from './animation_style_normalizer'; +import {forwardRef, Injectable} from '@angular/core'; + +import {invalidCssUnitValue} from '../error_helpers'; +import {dashCaseToCamelCase} from '../util'; + + +/** + * @publicApi + */ +// Keep in mind that all browser test environments are init with the NoopAnimationsModule +// Effectively overwriting this providedIn:'root'. See /tools/browser_tests.init.ts +// So everytime the WebAnimationsStyleNormalizer is expected, it must me provided explicitly. +@Injectable({providedIn: 'root', useClass: forwardRef(() => NoopAnimationStyleNormalizer)}) +export abstract class AnimationStyleNormalizer { + abstract normalizePropertyName(propertyName: string, errors: Error[]): string; + abstract normalizeStyleValue( + userProvidedProperty: string, normalizedProperty: string, value: string|number, + errors: Error[]): string; +} + +@Injectable() +export class NoopAnimationStyleNormalizer implements AnimationStyleNormalizer { + normalizePropertyName(propertyName: string, errors: Error[]): string { + return propertyName; + } + + normalizeStyleValue( + userProvidedProperty: string, normalizedProperty: string, value: string|number, + errors: Error[]): string { + return value; + } +} const DIMENSIONAL_PROP_SET = new Set([ 'width', @@ -42,6 +71,7 @@ const DIMENSIONAL_PROP_SET = new Set([ 'perspective' ]); +@Injectable() export class WebAnimationsStyleNormalizer extends AnimationStyleNormalizer { override normalizePropertyName(propertyName: string, errors: Error[]): string { return dashCaseToCamelCase(propertyName); diff --git a/packages/animations/browser/src/dsl/animation_transition_factory.ts b/packages/animations/browser/src/dsl/animation_transition_factory.ts index d66700da1b70ba..1e36be4b0ebfc4 100644 --- a/packages/animations/browser/src/dsl/animation_transition_factory.ts +++ b/packages/animations/browser/src/dsl/animation_transition_factory.ts @@ -12,12 +12,12 @@ import {getOrSetDefaultValue} from '../render/shared'; import {copyObj, interpolateParams, iteratorToArray} from '../util'; import {StyleAst, TransitionAst} from './animation_ast'; +import {AnimationStyleNormalizer} from './animation_style_normalizer'; import {buildAnimationTimelines} from './animation_timeline_builder'; import {AnimationTimelineInstruction} from './animation_timeline_instruction'; import {TransitionMatcherFn} from './animation_transition_expr'; import {AnimationTransitionInstruction, createTransitionInstruction} from './animation_transition_instruction'; import {ElementInstructionMap} from './element_instruction_map'; -import {AnimationStyleNormalizer} from './style_normalization/animation_style_normalizer'; const EMPTY_OBJECT = {}; diff --git a/packages/animations/browser/src/dsl/animation_trigger.ts b/packages/animations/browser/src/dsl/animation_trigger.ts index 8279d6f50ea671..c4b5793b1802cd 100644 --- a/packages/animations/browser/src/dsl/animation_trigger.ts +++ b/packages/animations/browser/src/dsl/animation_trigger.ts @@ -8,8 +8,8 @@ import {AnimationMetadataType, ɵStyleDataMap} from '@angular/animations'; import {SequenceAst, TransitionAst, TriggerAst} from './animation_ast'; +import {AnimationStyleNormalizer} from './animation_style_normalizer'; import {AnimationStateStyles, AnimationTransitionFactory} from './animation_transition_factory'; -import {AnimationStyleNormalizer} from './style_normalization/animation_style_normalizer'; diff --git a/packages/animations/browser/src/dsl/style_normalization/animation_style_normalizer.ts b/packages/animations/browser/src/dsl/style_normalization/animation_style_normalizer.ts deleted file mode 100644 index d8e719f7285d16..00000000000000 --- a/packages/animations/browser/src/dsl/style_normalization/animation_style_normalizer.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -/** - * @publicApi - */ -export abstract class AnimationStyleNormalizer { - abstract normalizePropertyName(propertyName: string, errors: Error[]): string; - abstract normalizeStyleValue( - userProvidedProperty: string, normalizedProperty: string, value: string|number, - errors: Error[]): string; -} - -/** - * @publicApi - */ -export class NoopAnimationStyleNormalizer { - normalizePropertyName(propertyName: string, errors: Error[]): string { - return propertyName; - } - - normalizeStyleValue( - userProvidedProperty: string, normalizedProperty: string, value: string|number, - errors: Error[]): string { - return value; - } -} diff --git a/packages/animations/browser/src/private_export.ts b/packages/animations/browser/src/private_export.ts index 5f1bb28806f989..3b8c76191dcf95 100644 --- a/packages/animations/browser/src/private_export.ts +++ b/packages/animations/browser/src/private_export.ts @@ -5,12 +5,11 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ +export {createEngine as ɵcreateEngine} from './createEngine'; export {Animation as ɵAnimation} from './dsl/animation'; -export {AnimationStyleNormalizer as ɵAnimationStyleNormalizer, NoopAnimationStyleNormalizer as ɵNoopAnimationStyleNormalizer} from './dsl/style_normalization/animation_style_normalizer'; -export {WebAnimationsStyleNormalizer as ɵWebAnimationsStyleNormalizer} from './dsl/style_normalization/web_animations_style_normalizer'; -export {NoopAnimationDriver as ɵNoopAnimationDriver} from './render/animation_driver'; +export {AnimationStyleNormalizer as ɵAnimationStyleNormalizer, NoopAnimationStyleNormalizer as ɵNoopAnimationStyleNormalizer, WebAnimationsStyleNormalizer as ɵWebAnimationsStyleNormalizer} from './dsl/animation_style_normalizer'; +export {NoopAnimationDriver as ɵNoopAnimationDriver, WebAnimationsDriver as ɵWebAnimationsDriver} from './render/animation_driver'; export {AnimationEngine as ɵAnimationEngine} from './render/animation_engine_next'; export {containsElement as ɵcontainsElement, getParentElement as ɵgetParentElement, invokeQuery as ɵinvokeQuery, validateStyleProperty as ɵvalidateStyleProperty} from './render/shared'; -export {WebAnimationsDriver as ɵWebAnimationsDriver} from './render/web_animations/web_animations_driver'; export {WebAnimationsPlayer as ɵWebAnimationsPlayer} from './render/web_animations/web_animations_player'; export {allowPreviousPlayerStylesMerge as ɵallowPreviousPlayerStylesMerge, normalizeKeyframes as ɵnormalizeKeyframes} from './util'; diff --git a/packages/animations/browser/src/render/animation_driver.ts b/packages/animations/browser/src/render/animation_driver.ts index 71aa51a9a5790c..23d35db9ec2525 100644 --- a/packages/animations/browser/src/render/animation_driver.ts +++ b/packages/animations/browser/src/render/animation_driver.ts @@ -5,15 +5,15 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import {AnimationPlayer, NoopAnimationPlayer} from '@angular/animations'; +import {AnimationPlayer, NoopAnimationPlayer, ɵStyleDataMap} from '@angular/animations'; import {Injectable} from '@angular/core'; -import {containsElement, getParentElement, invokeQuery, validateStyleProperty} from './shared'; +import {allowPreviousPlayerStylesMerge, balancePreviousStylesIntoKeyframes, camelCaseToDashCase, copyStyles, normalizeKeyframes} from '../util'; + +import {containsElement, getParentElement, invokeQuery, validateStyleProperty, validateWebAnimatableStyleProperty} from './shared'; +import {packageNonAnimatableStyles} from './special_cased_styles'; +import {WebAnimationsPlayer} from './web_animations/web_animations_player'; -/** - * @publicApi - */ -@Injectable() export class NoopAnimationDriver implements AnimationDriver { validateStyleProperty(prop: string): boolean { return validateStyleProperty(prop); @@ -48,9 +48,76 @@ export class NoopAnimationDriver implements AnimationDriver { } } +export class WebAnimationsDriver implements AnimationDriver { + validateStyleProperty(prop: string): boolean { + // Perform actual validation in dev mode only, in prod mode this check is a noop. + if (typeof ngDevMode === 'undefined' || ngDevMode) { + return validateStyleProperty(prop); + } + return true; + } + + validateAnimatableStyleProperty(prop: string): boolean { + // Perform actual validation in dev mode only, in prod mode this check is a noop. + if (typeof ngDevMode === 'undefined' || ngDevMode) { + const cssProp = camelCaseToDashCase(prop); + return validateWebAnimatableStyleProperty(cssProp); + } + return true; + } + + matchesElement(_element: any, _selector: string): boolean { + // This method is deprecated and no longer in use so we return false. + return false; + } + + containsElement(elm1: any, elm2: any): boolean { + return containsElement(elm1, elm2); + } + + getParentElement(element: unknown): unknown { + return getParentElement(element); + } + + query(element: any, selector: string, multi: boolean): any[] { + return invokeQuery(element, selector, multi); + } + + computeStyle(element: any, prop: string, defaultValue?: string): string { + return (window.getComputedStyle(element) as any)[prop] as string; + } + + animate( + element: any, keyframes: Array>, duration: number, delay: number, + easing: string, previousPlayers: AnimationPlayer[] = []): AnimationPlayer { + const fill = delay == 0 ? 'both' : 'forwards'; + const playerOptions: {[key: string]: string|number} = {duration, delay, fill}; + // we check for this to avoid having a null|undefined value be present + // for the easing (which results in an error for certain browsers #9752) + if (easing) { + playerOptions['easing'] = easing; + } + + const previousStyles: ɵStyleDataMap = new Map(); + const previousWebAnimationPlayers = previousPlayers.filter( + player => player instanceof WebAnimationsPlayer); + if (allowPreviousPlayerStylesMerge(duration, delay)) { + previousWebAnimationPlayers.forEach(player => { + player.currentSnapshot.forEach((val, prop) => previousStyles.set(prop, val)); + }); + } + + let _keyframes = normalizeKeyframes(keyframes).map(styles => copyStyles(styles)); + _keyframes = balancePreviousStylesIntoKeyframes(element, _keyframes, previousStyles); + const specialStyles = packageNonAnimatableStyles(element, _keyframes); + return new WebAnimationsPlayer(element, _keyframes, playerOptions, specialStyles); + } +} + /** * @publicApi */ +@Injectable({providedIn: 'root', useFactory: () => new NoopAnimationDriver()}) export abstract class AnimationDriver { static NOOP: AnimationDriver = (/* @__PURE__ */ new NoopAnimationDriver()); diff --git a/packages/animations/browser/src/render/animation_engine_next.ts b/packages/animations/browser/src/render/animation_engine_next.ts index 080c7c04a1f7b6..9a7afc8d513d5f 100644 --- a/packages/animations/browser/src/render/animation_engine_next.ts +++ b/packages/animations/browser/src/render/animation_engine_next.ts @@ -6,11 +6,13 @@ * found in the LICENSE file at https://angular.io/license */ import {AnimationMetadata, AnimationPlayer, AnimationTriggerMetadata} from '@angular/animations'; +import {DOCUMENT} from '@angular/common'; +import {ApplicationRef, Inject, Injectable, OnDestroy} from '@angular/core'; import {TriggerAst} from '../dsl/animation_ast'; import {buildAnimationAst} from '../dsl/animation_ast_builder'; +import {AnimationStyleNormalizer} from '../dsl/animation_style_normalizer'; import {AnimationTrigger, buildTrigger} from '../dsl/animation_trigger'; -import {AnimationStyleNormalizer} from '../dsl/style_normalization/animation_style_normalizer'; import {triggerBuildFailed} from '../error_helpers'; import {warnTriggerBuild} from '../warning_helpers'; @@ -19,7 +21,9 @@ import {parseTimelineCommand} from './shared'; import {TimelineAnimationEngine} from './timeline_animation_engine'; import {TransitionAnimationEngine} from './transition_animation_engine'; -export class AnimationEngine { + +@Injectable({providedIn: 'root'}) +export class AnimationEngine implements OnDestroy { private _transitionEngine: TransitionAnimationEngine; private _timelineEngine: TimelineAnimationEngine; @@ -28,16 +32,23 @@ export class AnimationEngine { // this method is designed to be overridden by the code that uses this engine public onRemovalComplete = (element: any, context: any) => {}; + // The `ApplicationRef` is injected here explicitly to force the dependency ordering. + // Since the `ApplicationRef` should be created earlier before the `AnimationEngine`, they + // both have `ngOnDestroy` hooks and `flush()` must be called after all views are destroyed. constructor( - private bodyNode: any, private _driver: AnimationDriver, - private _normalizer: AnimationStyleNormalizer) { - this._transitionEngine = new TransitionAnimationEngine(bodyNode, _driver, _normalizer); - this._timelineEngine = new TimelineAnimationEngine(bodyNode, _driver, _normalizer); + @Inject(DOCUMENT) doc: Document, private _driver: AnimationDriver, + private _normalizer: AnimationStyleNormalizer, appRef: ApplicationRef) { + this._transitionEngine = new TransitionAnimationEngine(doc.body, _driver, _normalizer); + this._timelineEngine = new TimelineAnimationEngine(doc.body, _driver, _normalizer); this._transitionEngine.onRemovalComplete = (element: any, context: any) => this.onRemovalComplete(element, context); } + ngOnDestroy(): void { + this.flush(); + } + registerTrigger( componentId: string, namespaceId: string, hostElement: any, name: string, metadata: AnimationTriggerMetadata): void { diff --git a/packages/animations/browser/src/render/shared.ts b/packages/animations/browser/src/render/shared.ts index e7353ff282fdf1..32c0510557e00a 100644 --- a/packages/animations/browser/src/render/shared.ts +++ b/packages/animations/browser/src/render/shared.ts @@ -7,7 +7,7 @@ */ import {AnimationEvent, AnimationPlayer, AUTO_STYLE, NoopAnimationPlayer, ɵAnimationGroupPlayer, ɵPRE_STYLE as PRE_STYLE, ɵStyleDataMap} from '@angular/animations'; -import {AnimationStyleNormalizer} from '../../src/dsl/style_normalization/animation_style_normalizer'; +import {AnimationStyleNormalizer} from '../../src/dsl/animation_style_normalizer'; import {animationFailed} from '../error_helpers'; import {ANIMATABLE_PROP_SET} from './web_animations/animatable_props_set'; diff --git a/packages/animations/browser/src/render/timeline_animation_engine.ts b/packages/animations/browser/src/render/timeline_animation_engine.ts index 71fed6c31274d4..834309cc1540b1 100644 --- a/packages/animations/browser/src/render/timeline_animation_engine.ts +++ b/packages/animations/browser/src/render/timeline_animation_engine.ts @@ -9,10 +9,10 @@ import {AnimationMetadata, AnimationMetadataType, AnimationOptions, AnimationPla import {Ast} from '../dsl/animation_ast'; import {buildAnimationAst} from '../dsl/animation_ast_builder'; +import {AnimationStyleNormalizer} from '../dsl/animation_style_normalizer'; import {buildAnimationTimelines} from '../dsl/animation_timeline_builder'; import {AnimationTimelineInstruction} from '../dsl/animation_timeline_instruction'; import {ElementInstructionMap} from '../dsl/element_instruction_map'; -import {AnimationStyleNormalizer} from '../dsl/style_normalization/animation_style_normalizer'; import {createAnimationFailed, missingOrDestroyedAnimation, missingPlayer, registerFailed} from '../error_helpers'; import {ENTER_CLASSNAME, LEAVE_CLASSNAME} from '../util'; import {warnRegister} from '../warning_helpers'; diff --git a/packages/animations/browser/src/render/transition_animation_engine.ts b/packages/animations/browser/src/render/transition_animation_engine.ts index e30b43277f6ca3..13e73e354d3a07 100644 --- a/packages/animations/browser/src/render/transition_animation_engine.ts +++ b/packages/animations/browser/src/render/transition_animation_engine.ts @@ -7,12 +7,12 @@ */ import {AnimationOptions, AnimationPlayer, AUTO_STYLE, NoopAnimationPlayer, ɵAnimationGroupPlayer as AnimationGroupPlayer, ɵPRE_STYLE as PRE_STYLE, ɵStyleDataMap} from '@angular/animations'; +import {AnimationStyleNormalizer} from '../dsl/animation_style_normalizer'; import {AnimationTimelineInstruction} from '../dsl/animation_timeline_instruction'; import {AnimationTransitionFactory} from '../dsl/animation_transition_factory'; import {AnimationTransitionInstruction} from '../dsl/animation_transition_instruction'; import {AnimationTrigger} from '../dsl/animation_trigger'; import {ElementInstructionMap} from '../dsl/element_instruction_map'; -import {AnimationStyleNormalizer} from '../dsl/style_normalization/animation_style_normalizer'; import {missingEvent, missingTrigger, transitionFailed, triggerTransitionsFailed, unregisteredTrigger, unsupportedTriggerEvent} from '../error_helpers'; import {copyObj, ENTER_CLASSNAME, eraseStyles, LEAVE_CLASSNAME, NG_ANIMATING_CLASSNAME, NG_ANIMATING_SELECTOR, NG_TRIGGER_CLASSNAME, NG_TRIGGER_SELECTOR, setStyles} from '../util'; diff --git a/packages/animations/browser/src/render/web_animations/web_animations_driver.ts b/packages/animations/browser/src/render/web_animations/web_animations_driver.ts deleted file mode 100644 index 069500c6c3947d..00000000000000 --- a/packages/animations/browser/src/render/web_animations/web_animations_driver.ts +++ /dev/null @@ -1,81 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {AnimationPlayer, ɵStyleDataMap} from '@angular/animations'; - -import {allowPreviousPlayerStylesMerge, balancePreviousStylesIntoKeyframes, camelCaseToDashCase, copyStyles, normalizeKeyframes} from '../../util'; -import {AnimationDriver} from '../animation_driver'; -import {containsElement, getParentElement, invokeQuery, validateStyleProperty, validateWebAnimatableStyleProperty} from '../shared'; -import {packageNonAnimatableStyles} from '../special_cased_styles'; - -import {WebAnimationsPlayer} from './web_animations_player'; - -export class WebAnimationsDriver implements AnimationDriver { - validateStyleProperty(prop: string): boolean { - // Perform actual validation in dev mode only, in prod mode this check is a noop. - if (typeof ngDevMode === 'undefined' || ngDevMode) { - return validateStyleProperty(prop); - } - return true; - } - - validateAnimatableStyleProperty(prop: string): boolean { - // Perform actual validation in dev mode only, in prod mode this check is a noop. - if (typeof ngDevMode === 'undefined' || ngDevMode) { - const cssProp = camelCaseToDashCase(prop); - return validateWebAnimatableStyleProperty(cssProp); - } - return true; - } - - matchesElement(_element: any, _selector: string): boolean { - // This method is deprecated and no longer in use so we return false. - return false; - } - - containsElement(elm1: any, elm2: any): boolean { - return containsElement(elm1, elm2); - } - - getParentElement(element: unknown): unknown { - return getParentElement(element); - } - - query(element: any, selector: string, multi: boolean): any[] { - return invokeQuery(element, selector, multi); - } - - computeStyle(element: any, prop: string, defaultValue?: string): string { - return (window.getComputedStyle(element) as any)[prop] as string; - } - - animate( - element: any, keyframes: Array>, duration: number, delay: number, - easing: string, previousPlayers: AnimationPlayer[] = []): AnimationPlayer { - const fill = delay == 0 ? 'both' : 'forwards'; - const playerOptions: {[key: string]: string|number} = {duration, delay, fill}; - // we check for this to avoid having a null|undefined value be present - // for the easing (which results in an error for certain browsers #9752) - if (easing) { - playerOptions['easing'] = easing; - } - - const previousStyles: ɵStyleDataMap = new Map(); - const previousWebAnimationPlayers = previousPlayers.filter( - player => player instanceof WebAnimationsPlayer); - if (allowPreviousPlayerStylesMerge(duration, delay)) { - previousWebAnimationPlayers.forEach(player => { - player.currentSnapshot.forEach((val, prop) => previousStyles.set(prop, val)); - }); - } - - let _keyframes = normalizeKeyframes(keyframes).map(styles => copyStyles(styles)); - _keyframes = balancePreviousStylesIntoKeyframes(element, _keyframes, previousStyles); - const specialStyles = packageNonAnimatableStyles(element, _keyframes); - return new WebAnimationsPlayer(element, _keyframes, playerOptions, specialStyles); - } -} diff --git a/packages/animations/browser/src/util.ts b/packages/animations/browser/src/util.ts index 09f4b76ebfaff4..9b566345345e2a 100644 --- a/packages/animations/browser/src/util.ts +++ b/packages/animations/browser/src/util.ts @@ -7,8 +7,8 @@ */ import {AnimateTimings, AnimationMetadata, AnimationMetadataType, AnimationOptions, sequence, ɵStyleData, ɵStyleDataMap} from '@angular/animations'; -import {Ast as AnimationAst, AstVisitor as AnimationAstVisitor} from './dsl/animation_ast'; -import {AnimationDslVisitor} from './dsl/animation_dsl_visitor'; +import type {Ast as AnimationAst, AstVisitor as AnimationAstVisitor} from './dsl/animation_ast'; +import type {AnimationDslVisitor} from './dsl/animation_dsl_visitor'; import {invalidNodeType, invalidParamValue, invalidStyleParams, invalidTimingValue, negativeDelayValue, negativeStepValue} from './error_helpers'; export const ONE_SECOND = 1000; diff --git a/packages/animations/browser/test/dsl/style_normalizer/web_animations_style_normalizer_spec.ts b/packages/animations/browser/test/dsl/style_normalizer/web_animations_style_normalizer_spec.ts index a8b136cffcf2c8..4891ac13960457 100644 --- a/packages/animations/browser/test/dsl/style_normalizer/web_animations_style_normalizer_spec.ts +++ b/packages/animations/browser/test/dsl/style_normalizer/web_animations_style_normalizer_spec.ts @@ -5,7 +5,7 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import {WebAnimationsStyleNormalizer} from '../../../src/dsl/style_normalization/web_animations_style_normalizer'; +import {WebAnimationsStyleNormalizer} from '../../../src/dsl/animation_style_normalizer'; { describe('WebAnimationsStyleNormalizer', () => { diff --git a/packages/animations/browser/test/render/timeline_animation_engine_spec.ts b/packages/animations/browser/test/render/timeline_animation_engine_spec.ts index 0f23c0fbee3da0..e9f227f8e5ceec 100644 --- a/packages/animations/browser/test/render/timeline_animation_engine_spec.ts +++ b/packages/animations/browser/test/render/timeline_animation_engine_spec.ts @@ -7,7 +7,7 @@ */ import {animate, AnimationMetadata, style} from '@angular/animations'; -import {AnimationStyleNormalizer, NoopAnimationStyleNormalizer} from '../../src/dsl/style_normalization/animation_style_normalizer'; +import {AnimationStyleNormalizer, NoopAnimationStyleNormalizer} from '../../src/dsl/animation_style_normalizer'; import {AnimationDriver} from '../../src/render/animation_driver'; import {getBodyNode} from '../../src/render/shared'; import {TimelineAnimationEngine} from '../../src/render/timeline_animation_engine'; diff --git a/packages/animations/browser/test/render/transition_animation_engine_spec.ts b/packages/animations/browser/test/render/transition_animation_engine_spec.ts index cbe5817c18d81e..8f5d7d37dbc758 100644 --- a/packages/animations/browser/test/render/transition_animation_engine_spec.ts +++ b/packages/animations/browser/test/render/transition_animation_engine_spec.ts @@ -5,12 +5,12 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import {animate, AnimationEvent, AnimationMetadata, AnimationTriggerMetadata, NoopAnimationPlayer, state, style, transition, trigger} from '@angular/animations'; +import {animate, AnimationEvent, AnimationMetadata, AnimationTriggerMetadata, state, style, transition, trigger} from '@angular/animations'; import {TriggerAst} from '../../src/dsl/animation_ast'; import {buildAnimationAst} from '../../src/dsl/animation_ast_builder'; +import {AnimationStyleNormalizer, NoopAnimationStyleNormalizer} from '../../src/dsl/animation_style_normalizer'; import {buildTrigger} from '../../src/dsl/animation_trigger'; -import {AnimationStyleNormalizer, NoopAnimationStyleNormalizer} from '../../src/dsl/style_normalization/animation_style_normalizer'; import {getBodyNode} from '../../src/render/shared'; import {TransitionAnimationEngine, TransitionAnimationPlayer} from '../../src/render/transition_animation_engine'; import {MockAnimationDriver, MockAnimationPlayer} from '../../testing/src/mock_animation_driver'; diff --git a/packages/animations/browser/test/render/web_animations/web_animations_driver_spec.ts b/packages/animations/browser/test/render/web_animations/web_animations_driver_spec.ts index aeef781a4c681a..81737f93f3b991 100644 --- a/packages/animations/browser/test/render/web_animations/web_animations_driver_spec.ts +++ b/packages/animations/browser/test/render/web_animations/web_animations_driver_spec.ts @@ -5,7 +5,7 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import {WebAnimationsDriver} from '../../../src/render/web_animations/web_animations_driver'; +import {WebAnimationsDriver} from '../../../src/render/animation_driver'; import {WebAnimationsPlayer} from '../../../src/render/web_animations/web_animations_player'; { diff --git a/packages/animations/browser/test/shared.ts b/packages/animations/browser/test/shared.ts index e699c243dbccda..9a3ef565115929 100644 --- a/packages/animations/browser/test/shared.ts +++ b/packages/animations/browser/test/shared.ts @@ -10,8 +10,8 @@ import {trigger} from '@angular/animations'; import {TriggerAst} from '../src/dsl/animation_ast'; import {buildAnimationAst} from '../src/dsl/animation_ast_builder'; +import {NoopAnimationStyleNormalizer} from '../src/dsl/animation_style_normalizer'; import {AnimationTrigger, buildTrigger} from '../src/dsl/animation_trigger'; -import {NoopAnimationStyleNormalizer} from '../src/dsl/style_normalization/animation_style_normalizer'; import {triggerParsingFailed} from '../src/error_helpers'; import {triggerParsingWarnings} from '../src/warning_helpers'; import {MockAnimationDriver} from '../testing/src/mock_animation_driver'; diff --git a/packages/core/test/acceptance/renderer_factory_spec.ts b/packages/core/test/acceptance/renderer_factory_spec.ts index 1a0e6b1f174299..1f63a237765759 100644 --- a/packages/core/test/acceptance/renderer_factory_spec.ts +++ b/packages/core/test/acceptance/renderer_factory_spec.ts @@ -11,7 +11,7 @@ import {ɵAnimationEngine, ɵNoopAnimationStyleNormalizer} from '@angular/animat import {MockAnimationDriver, MockAnimationPlayer} from '@angular/animations/browser/testing'; import {CommonModule, DOCUMENT} from '@angular/common'; import {PLATFORM_BROWSER_ID, PLATFORM_SERVER_ID} from '@angular/common/src/platform_id'; -import {Component, DoCheck, NgZone, Renderer2, RendererFactory2, RendererStyleFlags2, RendererType2, ViewEncapsulation} from '@angular/core'; +import {ApplicationRef, Component, DoCheck, NgZone, Renderer2, RendererFactory2, RendererStyleFlags2, RendererType2, ViewEncapsulation} from '@angular/core'; import {RElement} from '@angular/core/src/render3/interfaces/renderer_dom'; import {ngDevModeResetPerfCounters} from '@angular/core/src/util/ng_dev_mode'; import {NoopNgZone} from '@angular/core/src/zone/ng_zone'; @@ -249,8 +249,9 @@ describe('animation renderer factory', () => { declarations: [SomeComponentWithAnimation, SomeComponent], providers: [{ provide: RendererFactory2, - useFactory: (d: any) => rendererFactory = getAnimationRendererFactory2(d), - deps: [DOCUMENT] + useFactory: (d: Document, appRef: ApplicationRef) => rendererFactory = + getAnimationRendererFactory2(d, appRef), + deps: [DOCUMENT, ApplicationRef] }] }); }); @@ -319,7 +320,7 @@ describe('animation renderer factory', () => { }); }); -function getRendererFactory2(document: any): RendererFactory2 { +function getRendererFactory2(document: Document): RendererFactory2 { const fakeNgZone: NgZone = new NoopNgZone(); const eventManager = new EventManager([], fakeNgZone); const appId = 'app-id'; @@ -335,12 +336,13 @@ function getRendererFactory2(document: any): RendererFactory2 { return rendererFactory; } -function getAnimationRendererFactory2(document: any): RendererFactory2 { +function getAnimationRendererFactory2( + document: Document, appRef: ApplicationRef): RendererFactory2 { const fakeNgZone: NgZone = new NoopNgZone(); return new ɵAnimationRendererFactory( getRendererFactory2(document), new ɵAnimationEngine( - document.body, new MockAnimationDriver(), new ɵNoopAnimationStyleNormalizer()), + document, new MockAnimationDriver(), new ɵNoopAnimationStyleNormalizer(), appRef), fakeNgZone); } @@ -355,7 +357,7 @@ describe('custom renderer', () => { /** * Creates a patched renderer factory that creates elements with a shape different than DOM node */ - function createPatchedRendererFactory(document: any) { + function createPatchedRendererFactory(document: Document) { let rendererFactory = getRendererFactory2(document); const origCreateRenderer = rendererFactory.createRenderer; rendererFactory.createRenderer = function(element: any, type: RendererType2|null) { @@ -376,7 +378,7 @@ describe('custom renderer', () => { declarations: [SomeComponent], providers: [{ provide: RendererFactory2, - useFactory: (document: any) => createPatchedRendererFactory(document), + useFactory: (document: Document) => createPatchedRendererFactory(document), deps: [DOCUMENT] }] }); @@ -431,7 +433,7 @@ describe('Renderer2 destruction hooks', () => { declarations: [SimpleApp, AppWithComponents, BasicComponent], providers: [{ provide: RendererFactory2, - useFactory: (document: any) => getRendererFactory2(document), + useFactory: (document: Document) => getRendererFactory2(document), deps: [DOCUMENT] }] }); diff --git a/packages/core/test/animation/animation_integration_spec.ts b/packages/core/test/animation/animation_integration_spec.ts index f5c8b29e31bca9..7a67be2491954e 100644 --- a/packages/core/test/animation/animation_integration_spec.ts +++ b/packages/core/test/animation/animation_integration_spec.ts @@ -7,6 +7,7 @@ */ import {animate, animateChild, animation, AnimationEvent, AnimationMetadata, AnimationOptions, AUTO_STYLE, group, keyframes, query, sequence, state, style, transition, trigger, useAnimation, ɵPRE_STYLE as PRE_STYLE} from '@angular/animations'; import {AnimationDriver, ɵAnimationEngine, ɵNoopAnimationDriver as NoopAnimationDriver} from '@angular/animations/browser'; +import {AnimationStyleNormalizer, WebAnimationsStyleNormalizer} from '@angular/animations/browser/src/dsl/animation_style_normalizer'; import {MockAnimationDriver, MockAnimationPlayer} from '@angular/animations/browser/testing'; import {ChangeDetectorRef, Component, HostBinding, HostListener, Inject, RendererFactory2, ViewChild, ViewContainerRef} from '@angular/core'; import {fakeAsync, flushMicrotasks, TestBed} from '@angular/core/testing'; @@ -33,7 +34,10 @@ describe('animation tests', function() { beforeEach(() => { resetLog(); TestBed.configureTestingModule({ - providers: [{provide: AnimationDriver, useClass: MockAnimationDriver}], + providers: [ + {provide: AnimationDriver, useClass: MockAnimationDriver}, + {provide: AnimationStyleNormalizer, useClass: WebAnimationsStyleNormalizer} + ], imports: [BrowserAnimationsModule] }); }); @@ -67,6 +71,7 @@ describe('animation tests', function() { const fixture = TestBed.createComponent(SharedAnimationCmp); expect(fixture.componentInstance.animationType).toEqual('NoopAnimations'); + TestBed.resetTestingModule(); }); }); diff --git a/packages/core/test/animation/animation_query_integration_spec.ts b/packages/core/test/animation/animation_query_integration_spec.ts index 6913cb5f585f2e..114d56bfa5fb9b 100644 --- a/packages/core/test/animation/animation_query_integration_spec.ts +++ b/packages/core/test/animation/animation_query_integration_spec.ts @@ -7,6 +7,7 @@ */ import {animate, animateChild, AnimationPlayer, AUTO_STYLE, group, query, sequence, stagger, state, style, transition, trigger, ɵAnimationGroupPlayer as AnimationGroupPlayer} from '@angular/animations'; import {AnimationDriver, ɵAnimationEngine, ɵnormalizeKeyframes as normalizeKeyframes} from '@angular/animations/browser'; +import {AnimationStyleNormalizer, WebAnimationsStyleNormalizer} from '@angular/animations/browser/src/dsl/animation_style_normalizer'; import {TransitionAnimationPlayer} from '@angular/animations/browser/src/render/transition_animation_engine'; import {ENTER_CLASSNAME, LEAVE_CLASSNAME} from '@angular/animations/browser/src/util'; import {MockAnimationDriver, MockAnimationPlayer} from '@angular/animations/browser/testing'; @@ -33,7 +34,10 @@ describe('animation query tests', function() { beforeEach(() => { resetLog(); TestBed.configureTestingModule({ - providers: [{provide: AnimationDriver, useClass: MockAnimationDriver}], + providers: [ + {provide: AnimationDriver, useClass: MockAnimationDriver}, + {provide: AnimationStyleNormalizer, useClass: WebAnimationsStyleNormalizer} + ], imports: [BrowserAnimationsModule, CommonModule] }); }); diff --git a/packages/core/test/animation/animations_with_web_animations_integration_spec.ts b/packages/core/test/animation/animations_with_web_animations_integration_spec.ts index 37003cc5a1f114..04a74aaaf1d863 100644 --- a/packages/core/test/animation/animations_with_web_animations_integration_spec.ts +++ b/packages/core/test/animation/animations_with_web_animations_integration_spec.ts @@ -6,7 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ import {animate, query, state, style, transition, trigger} from '@angular/animations'; -import {AnimationDriver, ɵAnimationEngine, ɵWebAnimationsDriver, ɵWebAnimationsPlayer} from '@angular/animations/browser'; +import {ɵAnimationEngine, ɵWebAnimationsPlayer} from '@angular/animations/browser'; +import {AnimationStyleNormalizer, WebAnimationsStyleNormalizer} from '@angular/animations/browser/src/dsl/animation_style_normalizer'; import {TransitionAnimationPlayer} from '@angular/animations/browser/src/render/transition_animation_engine'; import {AnimationGroupPlayer} from '@angular/animations/src/players/animation_group_player'; import {Component, ViewChild} from '@angular/core'; @@ -21,8 +22,8 @@ if (isNode) return; describe('animation integration tests using web animations', function() { beforeEach(() => { TestBed.configureTestingModule({ - providers: [{provide: AnimationDriver, useClass: ɵWebAnimationsDriver}], - imports: [BrowserAnimationsModule] + imports: [BrowserAnimationsModule], + providers: [{provide: AnimationStyleNormalizer, useClass: WebAnimationsStyleNormalizer}] }); }); diff --git a/packages/core/test/bundling/animations-standalone/BUILD.bazel b/packages/core/test/bundling/animations-standalone/BUILD.bazel index c70c6df9c85716..0d76edda26c2bb 100644 --- a/packages/core/test/bundling/animations-standalone/BUILD.bazel +++ b/packages/core/test/bundling/animations-standalone/BUILD.bazel @@ -8,7 +8,6 @@ ng_module( name = "animations_standalone", srcs = ["index.ts"], deps = [ - "//packages/animations", "//packages/core", "//packages/platform-browser", "//packages/platform-browser/animations", diff --git a/packages/core/test/bundling/animations-standalone/bundle.golden_symbols.json b/packages/core/test/bundling/animations-standalone/bundle.golden_symbols.json index c33202ab95c14a..d3718bdff6dc68 100644 --- a/packages/core/test/bundling/animations-standalone/bundle.golden_symbols.json +++ b/packages/core/test/bundling/animations-standalone/bundle.golden_symbols.json @@ -14,6 +14,12 @@ { "name": "APP_INITIALIZER" }, + { + "name": "AUTO_STYLE" + }, + { + "name": "AbstractAnimationRendererFactory" + }, { "name": "AnimationAstBuilderContext" }, @@ -41,6 +47,9 @@ { "name": "AnimationRendererFactory" }, + { + "name": "AnimationStateStyles" + }, { "name": "AnimationStyleNormalizer" }, @@ -53,6 +62,12 @@ { "name": "AnimationTransitionFactory" }, + { + "name": "AnimationTransitionNamespace" + }, + { + "name": "AnimationTrigger" + }, { "name": "AnimationsComponent" }, @@ -107,6 +122,9 @@ { "name": "CSP_NONCE" }, + { + "name": "ChainedInjector" + }, { "name": "ChangeDetectionStrategy" }, @@ -140,6 +158,9 @@ { "name": "DEFAULT_APP_ID" }, + { + "name": "DEFAULT_LOCALE_ID" + }, { "name": "DEFAULT_NOOP_PREVIOUS_NODE" }, @@ -149,6 +170,9 @@ { "name": "DIMENSIONAL_PROP_SET" }, + { + "name": "DI_DECORATOR_FLAG" + }, { "name": "DOCUMENT" }, @@ -158,12 +182,18 @@ { "name": "DefaultDomRenderer2" }, + { + "name": "DistinctUntilChangedOperator" + }, { "name": "DistinctUntilChangedSubscriber" }, { "name": "DomAdapter" }, + { + "name": "DomEventsPlugin" + }, { "name": "DomRendererFactory2" }, @@ -185,12 +215,18 @@ { "name": "EMPTY_PLAYER_ARRAY" }, + { + "name": "ENTER_CLASSNAME" + }, { "name": "ENTER_TOKEN_REGEX" }, { "name": "ENVIRONMENT_INITIALIZER" }, + { + "name": "ERROR_ORIGINAL_ERROR" + }, { "name": "EVENT_MANAGER_PLUGINS" }, @@ -260,15 +296,27 @@ { "name": "Injector" }, + { + "name": "KeyEventsPlugin" + }, + { + "name": "LEAVE_CLASSNAME" + }, { "name": "LEAVE_TOKEN_REGEX" }, { "name": "LOCALE_ID2" }, + { + "name": "LeakyRef" + }, { "name": "LifecycleHooksFeature" }, + { + "name": "MATH_ML_NAMESPACE" + }, { "name": "MODIFIER_KEYS" }, @@ -302,6 +350,12 @@ { "name": "NEW_LINE" }, + { + "name": "NG_ANIMATING_CLASSNAME" + }, + { + "name": "NG_ANIMATING_SELECTOR" + }, { "name": "NG_COMP_DEF" }, @@ -335,6 +389,18 @@ { "name": "NG_TEMPLATE_SELECTOR" }, + { + "name": "NG_TEMP_TOKEN_PATH" + }, + { + "name": "NG_TOKEN_PATH" + }, + { + "name": "NG_TRIGGER_CLASSNAME" + }, + { + "name": "NG_TRIGGER_SELECTOR" + }, { "name": "NOOP_CLEANUP_FN" }, @@ -350,6 +416,9 @@ { "name": "NO_CHANGE" }, + { + "name": "NO_NEW_LINE" + }, { "name": "NO_PARENT_INJECTOR" }, @@ -365,6 +434,9 @@ { "name": "NgModuleRef" }, + { + "name": "NgOnChangesFeatureImpl" + }, { "name": "NgZone" }, @@ -386,6 +458,9 @@ { "name": "NoopAnimationPlayer" }, + { + "name": "NoopAnimationStyleNormalizer" + }, { "name": "NullInjector" }, @@ -401,6 +476,9 @@ { "name": "PARAM_REGEX" }, + { + "name": "PLATFORM_BROWSER_ID" + }, { "name": "PLATFORM_DESTROY_LISTENERS" }, @@ -410,12 +488,21 @@ { "name": "PLATFORM_INITIALIZER" }, + { + "name": "PLATFORM_SERVER_ID" + }, { "name": "PRESERVE_HOST_CONTENT" }, + { + "name": "PRESERVE_HOST_CONTENT_DEFAULT" + }, { "name": "R3Injector" }, + { + "name": "REMOVAL_FLAG" + }, { "name": "REMOVE_STYLES_ON_COMPONENT_DESTROY" }, @@ -452,6 +539,15 @@ { "name": "SIMPLE_CHANGES_STORE" }, + { + "name": "SOURCE" + }, + { + "name": "SUBSTITUTION_EXPR_START" + }, + { + "name": "SVG_NAMESPACE" + }, { "name": "SafeSubscriber" }, @@ -521,9 +617,15 @@ { "name": "TYPE" }, + { + "name": "TimelineAnimationEngine" + }, { "name": "TimelineBuilder" }, + { + "name": "TransitionAnimationEngine" + }, { "name": "TransitionAnimationPlayer" }, @@ -536,6 +638,9 @@ { "name": "VERSION" }, + { + "name": "VIEW_REFS" + }, { "name": "ViewEncapsulation" }, @@ -548,12 +653,18 @@ { "name": "WeakRefImpl" }, + { + "name": "WebAnimationsDriver" + }, { "name": "WebAnimationsPlayer" }, { "name": "WebAnimationsStyleNormalizer" }, + { + "name": "XhrFactory" + }, { "name": "ZONE_IS_STABLE_OBSERVABLE" }, @@ -569,9 +680,15 @@ { "name": "_NullComponentFactoryResolver" }, + { + "name": "__esm" + }, { "name": "__forward_ref__" }, + { + "name": "__getOwnPropNames" + }, { "name": "_applyRootElementTransformImpl" }, @@ -818,9 +935,6 @@ { "name": "empty" }, - { - "name": "empty2" - }, { "name": "enterDI" }, @@ -860,6 +974,9 @@ { "name": "findAttrIndexInNode" }, + { + "name": "flattenGroupPlayers" + }, { "name": "flattenUnsubscriptionErrors" }, @@ -914,6 +1031,9 @@ { "name": "getFactoryDef" }, + { + "name": "getFactoryOf" + }, { "name": "getFirstLContainer" }, @@ -926,6 +1046,9 @@ { "name": "getInjectorIndex" }, + { + "name": "getInsertInFrontOfRNodeWithNoI18n" + }, { "name": "getLView" }, @@ -1035,442 +1158,1663 @@ "name": "incrementInitPhaseFlags" }, { - "name": "initializeDirectives" + "name": "init_BehaviorSubject" }, { - "name": "inject" + "name": "init_ConnectableObservable" }, { - "name": "injectArgs" + "name": "init_ObjectUnsubscribedError" }, { - "name": "injectElementRef" + "name": "init_Observable" }, { - "name": "injectInjectorOnly" + "name": "init_Observer" }, { - "name": "injectRootLimpMode" + "name": "init_Subject" }, { - "name": "injectableDefOrInjectorDefFactory" + "name": "init_SubjectSubscription" }, { - "name": "innerSubscribe" + "name": "init_Subscriber" }, { - "name": "insertBloom" + "name": "init_Subscription" }, { - "name": "instructionState" + "name": "init_UnsubscriptionError" }, { - "name": "internalImportProvidersFrom" + "name": "init_advance" }, { - "name": "interpolateParams" + "name": "init_all" }, { - "name": "invalidTimingValue" + "name": "init_animatable_props_set" }, { - "name": "invertObject" + "name": "init_animation" }, { - "name": "invokeDirectivesHostBindings" + "name": "init_animation_ast_builder" }, { - "name": "invokeHostBindingsInCreationMode" + "name": "init_animation_builder" }, { - "name": "invokeQuery" + "name": "init_animation_driver" }, { - "name": "isArray" + "name": "init_animation_engine_next" }, { - "name": "isArrayLike" + "name": "init_animation_group_player" }, { - "name": "isComponentDef" + "name": "init_animation_metadata" }, { - "name": "isComponentHost" + "name": "init_animation_player" }, { - "name": "isContentQueryHost" + "name": "init_animation_style_normalizer" }, { - "name": "isCssClassMatching" + "name": "init_animation_timeline_builder" }, { - "name": "isCurrentTNodeParent" + "name": "init_animation_timeline_instruction" }, { - "name": "isElementNode" + "name": "init_animation_transition_expr" }, { - "name": "isEnvironmentProviders" + "name": "init_animation_transition_factory" }, { - "name": "isFunction" + "name": "init_animation_transition_instruction" }, { - "name": "isInlineTemplate" + "name": "init_animation_trigger" }, { - "name": "isLContainer" + "name": "init_animations" }, { - "name": "isLView" + "name": "init_animations2" }, { - "name": "isNodeMatchingSelector" + "name": "init_annotate" }, { - "name": "isNodeMatchingSelectorList" + "name": "init_api" }, { - "name": "isObject" + "name": "init_api2" }, { - "name": "isPlatformServer" + "name": "init_api3" }, { - "name": "isPositive" + "name": "init_api_flags" }, { - "name": "isPromise" + "name": "init_application_config" }, { - "name": "isPromise2" + "name": "init_application_init" }, { - "name": "isScheduler" + "name": "init_application_module" }, { - "name": "isStableFactory" + "name": "init_application_ref" }, { - "name": "isTemplateNode" + "name": "init_application_tokens" }, { - "name": "isTypeProvider" + "name": "init_array_utils" }, { - "name": "isValueProvider" + "name": "init_assert" }, { - "name": "issueAnimationCommand" + "name": "init_assert2" }, { - "name": "iterator" + "name": "init_asserts" }, { - "name": "iteratorToArray" + "name": "init_async_pipe" }, { - "name": "leaveDI" + "name": "init_async_stack_tagging" }, { - "name": "leaveView" + "name": "init_attribute" }, { - "name": "leaveViewLight" + "name": "init_attribute_interpolation" }, { - "name": "listenOnPlayer" + "name": "init_attrs_utils" }, { - "name": "lookupTokenUsingModuleInjector" + "name": "init_bindings" }, { - "name": "lookupTokenUsingNodeInjector" + "name": "init_browser" }, { - "name": "makeAnimationEvent" + "name": "init_browser2" }, { - "name": "makeLambdaFromStates" + "name": "init_bypass" }, { - "name": "makeRecord" + "name": "init_canReportError" }, { - "name": "makeTimingAst" + "name": "init_case_conversion_pipes" }, { - "name": "map" + "name": "init_change_detection" }, { - "name": "markAsComponentHost" + "name": "init_change_detection2" }, { - "name": "markViewDirty" + "name": "init_change_detection3" }, { - "name": "markViewForRefresh" + "name": "init_change_detection_utils" }, { - "name": "maybeWrapInNotSelector" + "name": "init_change_detector_ref" }, { - "name": "mergeAll" + "name": "init_class_differ" }, { - "name": "mergeHostAttribute" + "name": "init_class_map_interpolation" }, { - "name": "mergeHostAttrs" + "name": "init_cleanup" }, { - "name": "mergeMap" + "name": "init_closure" }, { - "name": "nativeAppendChild" + "name": "init_cloudflare_loader" }, { - "name": "nativeAppendOrInsertBefore" + "name": "init_cloudinary_loader" }, { - "name": "nativeInsertBefore" + "name": "init_coercion" }, { - "name": "nextNgElementId" + "name": "init_collect_native_nodes" }, { - "name": "ngOnChangesSetInput" + "name": "init_common" }, { - "name": "ngZoneApplicationErrorHandlerFactory" + "name": "init_common2" }, { - "name": "nonNull" + "name": "init_common_module" }, { - "name": "noop" + "name": "init_comparison" }, { - "name": "normalizeAnimationEntry" + "name": "init_compiler" }, { - "name": "normalizeAnimationOptions" + "name": "init_compiler_facade" }, { - "name": "normalizeKeyframes" + "name": "init_compiler_facade_interface" }, { - "name": "notFoundValueOrThrow" + "name": "init_component" }, { - "name": "observable" + "name": "init_component_factory" }, { - "name": "onEnter" + "name": "init_component_factory_resolver" }, { - "name": "onLeave" + "name": "init_component_ref" }, { - "name": "optimizeGroupPlayer" + "name": "init_compression" }, { - "name": "options" + "name": "init_computed" }, { - "name": "parseTimelineCommand" + "name": "init_config" }, { - "name": "parseTransitionExpr" + "name": "init_console" }, { - "name": "processInjectorTypesWithProviders" + "name": "init_constants" }, { - "name": "profiler" + "name": "init_container" }, { - "name": "provideZoneChangeDetection" + "name": "init_context" }, { - "name": "refCount" + "name": "init_context_discovery" }, { - "name": "refreshContentQueries" + "name": "init_contextual" }, { - "name": "refreshView" + "name": "init_cookie" }, { - "name": "registerPostOrderHooks" + "name": "init_copy_definition_feature" }, { - "name": "rememberChangeHistoryAndInvokeOnChangesHook" + "name": "init_core" }, { - "name": "remove" + "name": "init_core2" }, { - "name": "removeClass" + "name": "init_core_private_export" }, { - "name": "removeFromArray" + "name": "init_core_reactivity_export" }, { - "name": "removeNodesAfterAnimationDone" + "name": "init_core_reactivity_export_internal" }, { - "name": "renderComponent" + "name": "init_core_render3_private_export" }, { - "name": "renderView" + "name": "init_createEngine" }, { - "name": "replacePostStylesAsPre" + "name": "init_create_injector" }, { - "name": "resetPreOrderHookFlags" + "name": "init_currencies" }, { - "name": "resolveForwardRef" + "name": "init_date_pipe" }, { - "name": "resolveTiming" + "name": "init_date_pipe_config" }, { - "name": "resolveTimingValue" + "name": "init_debug_node" }, { - "name": "retrieveHydrationInfo" + "name": "init_decorators" }, { - "name": "roundOffset" + "name": "init_default_iterable_differ" }, { - "name": "rxSubscriber" + "name": "init_default_keyvalue_differ" }, { - "name": "saveNameToExportMap" + "name": "init_definition" }, { - "name": "scheduleArray" + "name": "init_definition_factory" }, { - "name": "searchTokensOnInjector" + "name": "init_defs" }, { - "name": "sequence" + "name": "init_destroy_ref" }, { - "name": "setActiveConsumer" + "name": "init_di" }, { - "name": "setBindingRootForHostBindings" + "name": "init_di2" }, { - "name": "setCurrentDirectiveIndex" + "name": "init_di3" }, { - "name": "setCurrentInjector" + "name": "init_di4" }, { - "name": "setCurrentQueryIndex" + "name": "init_di5" }, { - "name": "setCurrentTNode" + "name": "init_di_attr" }, { - "name": "setDirectiveInputsWhichShadowsStyling" + "name": "init_di_setup" }, { - "name": "setIncludeViewProviders" + "name": "init_directive" }, { - "name": "setInjectImplementation" + "name": "init_directives" }, { - "name": "setInputsForProperty" + "name": "init_directives2" }, { - "name": "setInputsFromAttrs" + "name": "init_discovery_utils" }, { - "name": "setSelectedIndex" + "name": "init_distinctUntilChanged" }, { - "name": "setStyles" + "name": "init_document" }, { - "name": "setUpAttributes" + "name": "init_dom" }, { - "name": "setupStaticAttributes" + "name": "init_dom_adapter" }, { - "name": "share" + "name": "init_dom_tokens" }, { - "name": "shareSubjectFactory" + "name": "init_effect" }, { - "name": "shimStylesContent" + "name": "init_element" }, { - "name": "shouldSearchParent" + "name": "init_element_container" }, { - "name": "stringify" + "name": "init_element_instruction_map" }, { - "name": "stringifyCSSSelector" + "name": "init_element_ref" }, { - "name": "style" + "name": "init_element_validation" }, { - "name": "subscribeTo" + "name": "init_empty" }, { - "name": "subscribeToArray" + "name": "init_environment" }, { - "name": "switchMap" + "name": "init_environment2" }, { - "name": "throwProviderNotFoundError" + "name": "init_error_details_base_url" }, { - "name": "toRefArray" + "name": "init_error_handler" }, { - "name": "transition" + "name": "init_error_handling" }, { - "name": "uniqueIdCounter" + "name": "init_error_helper" }, { - "name": "unwrapRNode" + "name": "init_error_helpers" }, { - "name": "updateMicroTaskStatus" + "name": "init_errors" }, { - "name": "updateViewsToRefresh" + "name": "init_errors2" }, { - "name": "urlParsingNode" + "name": "init_errors3" }, { - "name": "visitDslNode" + "name": "init_errors4" }, { - "name": "walkProviderTree" + "name": "init_errors_di" }, { - "name": "writeDirectClass" + "name": "init_esm2015" }, { - "name": "writeToDirectiveInput" + "name": "init_event_emitter" + }, + { + "name": "init_fields" + }, + { + "name": "init_format_date" + }, + { + "name": "init_format_number" + }, + { + "name": "init_forward_ref" + }, + { + "name": "init_from" + }, + { + "name": "init_fromArray" + }, + { + "name": "init_get_current_view" + }, + { + "name": "init_global" + }, + { + "name": "init_global_utils" + }, + { + "name": "init_graph" + }, + { + "name": "init_hash_location_strategy" + }, + { + "name": "init_hooks" + }, + { + "name": "init_hostReportError" + }, + { + "name": "init_host_directives_feature" + }, + { + "name": "init_host_property" + }, + { + "name": "init_html_sanitizer" + }, + { + "name": "init_i18n" + }, + { + "name": "init_i18n2" + }, + { + "name": "init_i18n_apply" + }, + { + "name": "init_i18n_debug" + }, + { + "name": "init_i18n_icu_container_visitor" + }, + { + "name": "init_i18n_insert_before_index" + }, + { + "name": "init_i18n_locale_id" + }, + { + "name": "init_i18n_parse" + }, + { + "name": "init_i18n_plural_pipe" + }, + { + "name": "init_i18n_postprocess" + }, + { + "name": "init_i18n_select_pipe" + }, + { + "name": "init_i18n_tree_shaking" + }, + { + "name": "init_i18n_util" + }, + { + "name": "init_identity" + }, + { + "name": "init_iframe_attrs_validation" + }, + { + "name": "init_image_loader" + }, + { + "name": "init_imagekit_loader" + }, + { + "name": "init_imgix_loader" + }, + { + "name": "init_inert_body" + }, + { + "name": "init_inherit_definition_feature" + }, + { + "name": "init_initial_render_pending_tasks" + }, + { + "name": "init_initializer_token" + }, + { + "name": "init_inject_switch" + }, + { + "name": "init_injectable" + }, + { + "name": "init_injectable2" + }, + { + "name": "init_injection_token" + }, + { + "name": "init_injector" + }, + { + "name": "init_injector2" + }, + { + "name": "init_injector3" + }, + { + "name": "init_injector_compatibility" + }, + { + "name": "init_injector_token" + }, + { + "name": "init_injector_utils" + }, + { + "name": "init_innerSubscribe" + }, + { + "name": "init_input_transforms_feature" + }, + { + "name": "init_interfaces" + }, + { + "name": "init_internal_tokens" + }, + { + "name": "init_interpolation" + }, + { + "name": "init_invalid_pipe_argument_error" + }, + { + "name": "init_isArray" + }, + { + "name": "init_isArrayLike" + }, + { + "name": "init_isFunction" + }, + { + "name": "init_isInteropObservable" + }, + { + "name": "init_isIterable" + }, + { + "name": "init_isObject" + }, + { + "name": "init_isPromise" + }, + { + "name": "init_isScheduler" + }, + { + "name": "init_is_dev_mode" + }, + { + "name": "init_iterable" + }, + { + "name": "init_iterable_differs" + }, + { + "name": "init_iterator" + }, + { + "name": "init_jit_options" + }, + { + "name": "init_json_pipe" + }, + { + "name": "init_keyvalue_differs" + }, + { + "name": "init_keyvalue_pipe" + }, + { + "name": "init_lang" + }, + { + "name": "init_lcp_image_observer" + }, + { + "name": "init_linker" + }, + { + "name": "init_listener" + }, + { + "name": "init_locale_data" + }, + { + "name": "init_locale_data_api" + }, + { + "name": "init_locale_data_api2" + }, + { + "name": "init_locale_en" + }, + { + "name": "init_localization" + }, + { + "name": "init_localization2" + }, + { + "name": "init_location" + }, + { + "name": "init_location2" + }, + { + "name": "init_location_strategy" + }, + { + "name": "init_lview_tracking" + }, + { + "name": "init_map" + }, + { + "name": "init_mark_view_dirty" + }, + { + "name": "init_merge" + }, + { + "name": "init_mergeAll" + }, + { + "name": "init_mergeMap" + }, + { + "name": "init_metadata" + }, + { + "name": "init_metadata2" + }, + { + "name": "init_metadata3" + }, + { + "name": "init_metadata_attr" + }, + { + "name": "init_misc_utils" + }, + { + "name": "init_module" + }, + { + "name": "init_module_patch" + }, + { + "name": "init_multicast" + }, + { + "name": "init_namespace" + }, + { + "name": "init_namespaces" + }, + { + "name": "init_next_context" + }, + { + "name": "init_ng_class" + }, + { + "name": "init_ng_component_outlet" + }, + { + "name": "init_ng_dev_mode" + }, + { + "name": "init_ng_for_of" + }, + { + "name": "init_ng_i18n_closure_mode" + }, + { + "name": "init_ng_if" + }, + { + "name": "init_ng_jit_mode" + }, + { + "name": "init_ng_module" + }, + { + "name": "init_ng_module_factory" + }, + { + "name": "init_ng_module_factory_loader" + }, + { + "name": "init_ng_module_factory_loader_impl" + }, + { + "name": "init_ng_module_ref" + }, + { + "name": "init_ng_module_registration" + }, + { + "name": "init_ng_onchanges_feature" + }, + { + "name": "init_ng_optimized_image" + }, + { + "name": "init_ng_optimized_image2" + }, + { + "name": "init_ng_plural" + }, + { + "name": "init_ng_reflect" + }, + { + "name": "init_ng_style" + }, + { + "name": "init_ng_switch" + }, + { + "name": "init_ng_template_outlet" + }, + { + "name": "init_ng_zone" + }, + { + "name": "init_node" + }, + { + "name": "init_node_assert" + }, + { + "name": "init_node_lookup_utils" + }, + { + "name": "init_node_manipulation" + }, + { + "name": "init_node_manipulation_i18n" + }, + { + "name": "init_node_selector_matcher" + }, + { + "name": "init_noop" + }, + { + "name": "init_null_injector" + }, + { + "name": "init_number_pipe" + }, + { + "name": "init_observable" + }, + { + "name": "init_of" + }, + { + "name": "init_operators" + }, + { + "name": "init_partial" + }, + { + "name": "init_pipe" + }, + { + "name": "init_pipe2" + }, + { + "name": "init_pipe3" + }, + { + "name": "init_pipes" + }, + { + "name": "init_platform_core_providers" + }, + { + "name": "init_platform_id" + }, + { + "name": "init_platform_location" + }, + { + "name": "init_preconnect_link_checker" + }, + { + "name": "init_preload_link_creator" + }, + { + "name": "init_private_export" + }, + { + "name": "init_private_export2" + }, + { + "name": "init_private_export3" + }, + { + "name": "init_profiler" + }, + { + "name": "init_projection" + }, + { + "name": "init_property" + }, + { + "name": "init_property2" + }, + { + "name": "init_property_interpolation" + }, + { + "name": "init_provider" + }, + { + "name": "init_provider_collection" + }, + { + "name": "init_provider_flags" + }, + { + "name": "init_providers_feature" + }, + { + "name": "init_public_api" + }, + { + "name": "init_public_api2" + }, + { + "name": "init_public_api3" + }, + { + "name": "init_public_api4" + }, + { + "name": "init_pure_function" + }, + { + "name": "init_query" + }, + { + "name": "init_query_list" + }, + { + "name": "init_r3_injector" + }, + { + "name": "init_r3_symbols" + }, + { + "name": "init_raf" + }, + { + "name": "init_reactive_lview_consumer" + }, + { + "name": "init_refCount" + }, + { + "name": "init_reflection_capabilities" + }, + { + "name": "init_render" + }, + { + "name": "init_render2" + }, + { + "name": "init_render3" + }, + { + "name": "init_resource_loading" + }, + { + "name": "init_rxSubscriber" + }, + { + "name": "init_sanitization" + }, + { + "name": "init_sanitizer" + }, + { + "name": "init_scheduleArray" + }, + { + "name": "init_scheduleIterable" + }, + { + "name": "init_scheduleObservable" + }, + { + "name": "init_schedulePromise" + }, + { + "name": "init_scheduled" + }, + { + "name": "init_schema" + }, + { + "name": "init_scope" + }, + { + "name": "init_security" + }, + { + "name": "init_share" + }, + { + "name": "init_shared" + }, + { + "name": "init_shared2" + }, + { + "name": "init_signal" + }, + { + "name": "init_signals" + }, + { + "name": "init_simple_change" + }, + { + "name": "init_skip_hydration" + }, + { + "name": "init_slice_pipe" + }, + { + "name": "init_special_cased_styles" + }, + { + "name": "init_standalone_feature" + }, + { + "name": "init_state" + }, + { + "name": "init_static_styling" + }, + { + "name": "init_storage" + }, + { + "name": "init_stringify" + }, + { + "name": "init_stringify_utils" + }, + { + "name": "init_style_binding_list" + }, + { + "name": "init_style_map_interpolation" + }, + { + "name": "init_style_prop_interpolation" + }, + { + "name": "init_styling" + }, + { + "name": "init_styling2" + }, + { + "name": "init_styling_parser" + }, + { + "name": "init_subscribeTo" + }, + { + "name": "init_subscribeToArray" + }, + { + "name": "init_subscribeToIterable" + }, + { + "name": "init_subscribeToObservable" + }, + { + "name": "init_subscribeToPromise" + }, + { + "name": "init_switchMap" + }, + { + "name": "init_template" + }, + { + "name": "init_template_ref" + }, + { + "name": "init_testability" + }, + { + "name": "init_text" + }, + { + "name": "init_text_interpolation" + }, + { + "name": "init_timeline_animation_engine" + }, + { + "name": "init_toSubscriber" + }, + { + "name": "init_tokens" + }, + { + "name": "init_tokens2" + }, + { + "name": "init_tokens3" + }, + { + "name": "init_tokens4" + }, + { + "name": "init_transfer_state" + }, + { + "name": "init_transition_animation_engine" + }, + { + "name": "init_trusted_types" + }, + { + "name": "init_trusted_types_bypass" + }, + { + "name": "init_type" + }, + { + "name": "init_type_checks" + }, + { + "name": "init_untracked" + }, + { + "name": "init_url" + }, + { + "name": "init_url_sanitizer" + }, + { + "name": "init_util" + }, + { + "name": "init_util2" + }, + { + "name": "init_util3" + }, + { + "name": "init_util4" + }, + { + "name": "init_utils" + }, + { + "name": "init_version" + }, + { + "name": "init_version2" + }, + { + "name": "init_view" + }, + { + "name": "init_view2" + }, + { + "name": "init_view_container_ref" + }, + { + "name": "init_view_engine_compatibility_prebound" + }, + { + "name": "init_view_ref" + }, + { + "name": "init_view_ref2" + }, + { + "name": "init_view_traversal_utils" + }, + { + "name": "init_view_utils" + }, + { + "name": "init_viewport_scroller" + }, + { + "name": "init_views" + }, + { + "name": "init_warning_helpers" + }, + { + "name": "init_watch" + }, + { + "name": "init_weak_ref" + }, + { + "name": "init_web_animations_player" + }, + { + "name": "init_xhr" + }, + { + "name": "init_zone" + }, + { + "name": "initializeDirectives" + }, + { + "name": "inject" + }, + { + "name": "injectArgs" + }, + { + "name": "injectElementRef" + }, + { + "name": "injectInjectorOnly" + }, + { + "name": "injectRootLimpMode" + }, + { + "name": "injectableDefOrInjectorDefFactory" + }, + { + "name": "innerSubscribe" + }, + { + "name": "insertBloom" + }, + { + "name": "instructionState" + }, + { + "name": "internalImportProvidersFrom" + }, + { + "name": "interpolateParams" + }, + { + "name": "invalidTimingValue" + }, + { + "name": "invertObject" + }, + { + "name": "invokeDirectivesHostBindings" + }, + { + "name": "invokeHostBindingsInCreationMode" + }, + { + "name": "invokeQuery" + }, + { + "name": "isArray" + }, + { + "name": "isArrayLike" + }, + { + "name": "isComponentDef" + }, + { + "name": "isComponentHost" + }, + { + "name": "isContentQueryHost" + }, + { + "name": "isCssClassMatching" + }, + { + "name": "isCurrentTNodeParent" + }, + { + "name": "isElementNode" + }, + { + "name": "isEnvironmentProviders" + }, + { + "name": "isForwardRef" + }, + { + "name": "isFunction" + }, + { + "name": "isInlineTemplate" + }, + { + "name": "isLContainer" + }, + { + "name": "isLView" + }, + { + "name": "isNodeMatchingSelector" + }, + { + "name": "isNodeMatchingSelectorList" + }, + { + "name": "isObject" + }, + { + "name": "isPlatformServer" + }, + { + "name": "isPositive" + }, + { + "name": "isPromise" + }, + { + "name": "isPromise2" + }, + { + "name": "isScheduler" + }, + { + "name": "isStableFactory" + }, + { + "name": "isTemplateNode" + }, + { + "name": "isTypeProvider" + }, + { + "name": "isValueProvider" + }, + { + "name": "issueAnimationCommand" + }, + { + "name": "iterator" + }, + { + "name": "iteratorToArray" + }, + { + "name": "leaveDI" + }, + { + "name": "leaveView" + }, + { + "name": "leaveViewLight" + }, + { + "name": "listenOnPlayer" + }, + { + "name": "lookupTokenUsingModuleInjector" + }, + { + "name": "lookupTokenUsingNodeInjector" + }, + { + "name": "makeAnimationEvent" + }, + { + "name": "makeLambdaFromStates" + }, + { + "name": "makeRecord" + }, + { + "name": "makeTimingAst" + }, + { + "name": "map" + }, + { + "name": "markAsComponentHost" + }, + { + "name": "markViewDirty" + }, + { + "name": "markViewForRefresh" + }, + { + "name": "maybeWrapInNotSelector" + }, + { + "name": "mergeAll" + }, + { + "name": "mergeHostAttribute" + }, + { + "name": "mergeHostAttrs" + }, + { + "name": "mergeMap" + }, + { + "name": "nativeAppendChild" + }, + { + "name": "nativeAppendOrInsertBefore" + }, + { + "name": "nativeInsertBefore" + }, + { + "name": "nextNgElementId" + }, + { + "name": "ngOnChangesSetInput" + }, + { + "name": "ngZoneApplicationErrorHandlerFactory" + }, + { + "name": "noSideEffects" + }, + { + "name": "nonNull" + }, + { + "name": "noop" + }, + { + "name": "normalizeAnimationEntry" + }, + { + "name": "normalizeAnimationOptions" + }, + { + "name": "normalizeKeyframes2" + }, + { + "name": "notFoundValueOrThrow" + }, + { + "name": "observable" + }, + { + "name": "onEnter" + }, + { + "name": "onLeave" + }, + { + "name": "optimizeGroupPlayer" + }, + { + "name": "options" + }, + { + "name": "parseTimelineCommand" + }, + { + "name": "parseTransitionExpr" + }, + { + "name": "processInjectorTypesWithProviders" + }, + { + "name": "profiler" + }, + { + "name": "provideZoneChangeDetection" + }, + { + "name": "refCount" + }, + { + "name": "refreshContentQueries" + }, + { + "name": "refreshView" + }, + { + "name": "registerPostOrderHooks" + }, + { + "name": "rememberChangeHistoryAndInvokeOnChangesHook" + }, + { + "name": "remove" + }, + { + "name": "removeClass" + }, + { + "name": "removeFromArray" + }, + { + "name": "removeNodesAfterAnimationDone" + }, + { + "name": "renderComponent" + }, + { + "name": "renderView" + }, + { + "name": "replacePostStylesAsPre" + }, + { + "name": "resetPreOrderHookFlags" + }, + { + "name": "resolveForwardRef" + }, + { + "name": "resolveTiming" + }, + { + "name": "resolveTimingValue" + }, + { + "name": "retrieveHydrationInfo" + }, + { + "name": "roundOffset" + }, + { + "name": "rxSubscriber" + }, + { + "name": "saveNameToExportMap" + }, + { + "name": "scheduleArray" + }, + { + "name": "searchTokensOnInjector" + }, + { + "name": "sequence" + }, + { + "name": "setActiveConsumer" + }, + { + "name": "setBindingRootForHostBindings" + }, + { + "name": "setCurrentDirectiveIndex" + }, + { + "name": "setCurrentInjector" + }, + { + "name": "setCurrentQueryIndex" + }, + { + "name": "setCurrentTNode" + }, + { + "name": "setDirectiveInputsWhichShadowsStyling" + }, + { + "name": "setIncludeViewProviders" + }, + { + "name": "setInjectImplementation" + }, + { + "name": "setInputsForProperty" + }, + { + "name": "setInputsFromAttrs" + }, + { + "name": "setSelectedIndex" + }, + { + "name": "setStyles" + }, + { + "name": "setUpAttributes" + }, + { + "name": "setupStaticAttributes" + }, + { + "name": "share" + }, + { + "name": "shareSubjectFactory" + }, + { + "name": "shimStylesContent" + }, + { + "name": "shouldSearchParent" + }, + { + "name": "stringify" + }, + { + "name": "stringifyCSSSelector" + }, + { + "name": "style" + }, + { + "name": "subscribeTo" + }, + { + "name": "subscribeToArray" + }, + { + "name": "subscribeToIterable" + }, + { + "name": "subscribeToObservable" + }, + { + "name": "subscribeToPromise" + }, + { + "name": "switchMap" + }, + { + "name": "throwProviderNotFoundError" + }, + { + "name": "toRefArray" + }, + { + "name": "transition" + }, + { + "name": "uniqueIdCounter" + }, + { + "name": "unwrapRNode" + }, + { + "name": "updateMicroTaskStatus" + }, + { + "name": "updateViewsToRefresh" + }, + { + "name": "urlParsingNode" + }, + { + "name": "visitDslNode" + }, + { + "name": "walkProviderTree" + }, + { + "name": "writeDirectClass" + }, + { + "name": "writeToDirectiveInput" + }, + { + "name": "ɵPRE_STYLE" + }, + { + "name": "ɵɵNgOnChangesFeature" }, { "name": "ɵɵStandaloneFeature" diff --git a/packages/core/test/bundling/animations-standalone/index.ts b/packages/core/test/bundling/animations-standalone/index.ts index b7606c2ae9be1e..413d87391be61d 100644 --- a/packages/core/test/bundling/animations-standalone/index.ts +++ b/packages/core/test/bundling/animations-standalone/index.ts @@ -6,33 +6,35 @@ * found in the LICENSE file at https://angular.io/license */ import {animate, style, transition, trigger} from '@angular/animations'; -import {Component, NgModule, ɵNgModuleFactory as NgModuleFactory} from '@angular/core'; -import {bootstrapApplication, BrowserModule, platformBrowser} from '@angular/platform-browser'; -import {BrowserAnimationsModule, provideAnimations} from '@angular/platform-browser/animations'; +import {Component} from '@angular/core'; +import {bootstrapApplication} from '@angular/platform-browser'; +import {provideAnimations} from '@angular/platform-browser/animations'; @Component({ selector: 'app-animations', template: ` -
- `, +
+ `, animations: [trigger('myAnimation', [transition('* => on', [animate(1000, style({opacity: 1}))])])], standalone: true, }) class AnimationsComponent { - exp: any = false; + exp = false; } @Component({ selector: 'app-root', - template: ` - - `, + template: ``, standalone: true, - imports: [AnimationsComponent], + imports: [AnimationsComponent] }) class RootComponent { } -(window as any).waitForApp = bootstrapApplication(RootComponent, {providers: provideAnimations()}); +(window as any).waitForApp = bootstrapApplication(RootComponent, { + providers: [ + provideAnimations(), + ] +}); diff --git a/packages/core/test/bundling/animations/bundle.golden_symbols.json b/packages/core/test/bundling/animations/bundle.golden_symbols.json index 2919a639badb62..bcc71b1fb58629 100644 --- a/packages/core/test/bundling/animations/bundle.golden_symbols.json +++ b/packages/core/test/bundling/animations/bundle.golden_symbols.json @@ -17,6 +17,12 @@ { "name": "APP_INITIALIZER" }, + { + "name": "AUTO_STYLE" + }, + { + "name": "AbstractAnimationRendererFactory" + }, { "name": "AnimationAstBuilderContext" }, @@ -44,6 +50,9 @@ { "name": "AnimationRendererFactory" }, + { + "name": "AnimationStateStyles" + }, { "name": "AnimationStyleNormalizer" }, @@ -56,6 +65,12 @@ { "name": "AnimationTransitionFactory" }, + { + "name": "AnimationTransitionNamespace" + }, + { + "name": "AnimationTrigger" + }, { "name": "AnimationsComponent" }, @@ -128,6 +143,9 @@ { "name": "CSP_NONCE" }, + { + "name": "ChainedInjector" + }, { "name": "ChangeDetectionStrategy" }, @@ -164,6 +182,9 @@ { "name": "DEFAULT_APP_ID" }, + { + "name": "DEFAULT_LOCALE_ID" + }, { "name": "DEFAULT_NOOP_PREVIOUS_NODE" }, @@ -173,6 +194,9 @@ { "name": "DIMENSIONAL_PROP_SET" }, + { + "name": "DI_DECORATOR_FLAG" + }, { "name": "DOCUMENT" }, @@ -182,6 +206,9 @@ { "name": "DefaultDomRenderer2" }, + { + "name": "DistinctUntilChangedOperator" + }, { "name": "DistinctUntilChangedSubscriber" }, @@ -212,12 +239,18 @@ { "name": "EMPTY_PLAYER_ARRAY" }, + { + "name": "ENTER_CLASSNAME" + }, { "name": "ENTER_TOKEN_REGEX" }, { "name": "ENVIRONMENT_INITIALIZER" }, + { + "name": "ERROR_ORIGINAL_ERROR" + }, { "name": "EVENT_MANAGER_PLUGINS" }, @@ -284,15 +317,24 @@ { "name": "KeyEventsPlugin" }, + { + "name": "LEAVE_CLASSNAME" + }, { "name": "LEAVE_TOKEN_REGEX" }, { "name": "LOCALE_ID2" }, + { + "name": "LeakyRef" + }, { "name": "LifecycleHooksFeature" }, + { + "name": "MATH_ML_NAMESPACE" + }, { "name": "MODIFIER_KEYS" }, @@ -326,6 +368,12 @@ { "name": "NEW_LINE" }, + { + "name": "NG_ANIMATING_CLASSNAME" + }, + { + "name": "NG_ANIMATING_SELECTOR" + }, { "name": "NG_COMP_DEF" }, @@ -362,6 +410,18 @@ { "name": "NG_TEMPLATE_SELECTOR" }, + { + "name": "NG_TEMP_TOKEN_PATH" + }, + { + "name": "NG_TOKEN_PATH" + }, + { + "name": "NG_TRIGGER_CLASSNAME" + }, + { + "name": "NG_TRIGGER_SELECTOR" + }, { "name": "NOOP_CLEANUP_FN" }, @@ -377,6 +437,9 @@ { "name": "NO_CHANGE" }, + { + "name": "NO_NEW_LINE" + }, { "name": "NO_PARENT_INJECTOR" }, @@ -401,6 +464,9 @@ { "name": "NgModuleRef2" }, + { + "name": "NgOnChangesFeatureImpl" + }, { "name": "NgZone" }, @@ -422,6 +488,12 @@ { "name": "NoopAnimationPlayer" }, + { + "name": "NoopAnimationStyleNormalizer" + }, + { + "name": "NoopNgZone" + }, { "name": "NullInjector" }, @@ -437,6 +509,9 @@ { "name": "PARAM_REGEX" }, + { + "name": "PLATFORM_BROWSER_ID" + }, { "name": "PLATFORM_DESTROY_LISTENERS" }, @@ -446,15 +521,24 @@ { "name": "PLATFORM_INITIALIZER" }, + { + "name": "PLATFORM_SERVER_ID" + }, { "name": "PRESERVE_HOST_CONTENT" }, + { + "name": "PRESERVE_HOST_CONTENT_DEFAULT" + }, { "name": "PlatformRef" }, { "name": "R3Injector" }, + { + "name": "REMOVAL_FLAG" + }, { "name": "REMOVE_STYLES_ON_COMPONENT_DESTROY" }, @@ -497,6 +581,15 @@ { "name": "SIMPLE_CHANGES_STORE" }, + { + "name": "SOURCE" + }, + { + "name": "SUBSTITUTION_EXPR_START" + }, + { + "name": "SVG_NAMESPACE" + }, { "name": "SafeSubscriber" }, @@ -575,9 +668,15 @@ { "name": "TestabilityRegistry" }, + { + "name": "TimelineAnimationEngine" + }, { "name": "TimelineBuilder" }, + { + "name": "TransitionAnimationEngine" + }, { "name": "TransitionAnimationPlayer" }, @@ -590,6 +689,9 @@ { "name": "VERSION" }, + { + "name": "VIEW_REFS" + }, { "name": "ViewEncapsulation" }, @@ -602,12 +704,18 @@ { "name": "WeakRefImpl" }, + { + "name": "WebAnimationsDriver" + }, { "name": "WebAnimationsPlayer" }, { "name": "WebAnimationsStyleNormalizer" }, + { + "name": "XhrFactory" + }, { "name": "ZONE_IS_STABLE_OBSERVABLE" }, @@ -623,9 +731,15 @@ { "name": "_NullComponentFactoryResolver" }, + { + "name": "__esm" + }, { "name": "__forward_ref__" }, + { + "name": "__getOwnPropNames" + }, { "name": "_applyRootElementTransformImpl" }, @@ -881,9 +995,6 @@ { "name": "empty" }, - { - "name": "empty2" - }, { "name": "enterDI" }, @@ -923,6 +1034,9 @@ { "name": "findAttrIndexInNode" }, + { + "name": "flattenGroupPlayers" + }, { "name": "flattenUnsubscriptionErrors" }, @@ -977,6 +1091,9 @@ { "name": "getFactoryDef" }, + { + "name": "getFactoryOf" + }, { "name": "getFirstLContainer" }, @@ -989,6 +1106,9 @@ { "name": "getInjectorIndex" }, + { + "name": "getInsertInFrontOfRNodeWithNoI18n" + }, { "name": "getLView" }, @@ -1101,448 +1221,1666 @@ "name": "incrementInitPhaseFlags" }, { - "name": "initializeDirectives" + "name": "init_BehaviorSubject" }, { - "name": "inject" + "name": "init_ConnectableObservable" }, { - "name": "injectArgs" + "name": "init_ObjectUnsubscribedError" }, { - "name": "injectElementRef" + "name": "init_Observable" }, { - "name": "injectInjectorOnly" + "name": "init_Observer" }, { - "name": "injectRootLimpMode" + "name": "init_Subject" }, { - "name": "injectableDefOrInjectorDefFactory" + "name": "init_SubjectSubscription" }, { - "name": "innerSubscribe" + "name": "init_Subscriber" }, { - "name": "insertBloom" + "name": "init_Subscription" }, { - "name": "instructionState" + "name": "init_UnsubscriptionError" }, { - "name": "internalImportProvidersFrom" + "name": "init_advance" }, { - "name": "interpolateParams" + "name": "init_all" }, { - "name": "invalidTimingValue" + "name": "init_animatable_props_set" }, { - "name": "invertObject" + "name": "init_animation" }, { - "name": "invokeDirectivesHostBindings" + "name": "init_animation_ast_builder" }, { - "name": "invokeHostBindingsInCreationMode" + "name": "init_animation_builder" }, { - "name": "invokeQuery" + "name": "init_animation_driver" }, { - "name": "isArray" + "name": "init_animation_engine_next" }, { - "name": "isArrayLike" + "name": "init_animation_group_player" }, { - "name": "isComponentDef" + "name": "init_animation_metadata" }, { - "name": "isComponentHost" + "name": "init_animation_player" }, { - "name": "isContentQueryHost" + "name": "init_animation_style_normalizer" }, { - "name": "isCssClassMatching" + "name": "init_animation_timeline_builder" }, { - "name": "isCurrentTNodeParent" + "name": "init_animation_timeline_instruction" }, { - "name": "isElementNode" + "name": "init_animation_transition_expr" }, { - "name": "isEnvironmentProviders" + "name": "init_animation_transition_factory" }, { - "name": "isFunction" + "name": "init_animation_transition_instruction" }, { - "name": "isInlineTemplate" + "name": "init_animation_trigger" }, { - "name": "isLContainer" + "name": "init_animations" }, { - "name": "isLView" + "name": "init_animations2" }, { - "name": "isNodeMatchingSelector" + "name": "init_annotate" }, { - "name": "isNodeMatchingSelectorList" + "name": "init_api" }, { - "name": "isObject" + "name": "init_api2" }, { - "name": "isPlatformServer" + "name": "init_api3" }, { - "name": "isPositive" + "name": "init_api_flags" }, { - "name": "isPromise" + "name": "init_application_config" }, { - "name": "isPromise2" + "name": "init_application_init" }, { - "name": "isScheduler" + "name": "init_application_module" }, { - "name": "isStableFactory" + "name": "init_application_ref" }, { - "name": "isTemplateNode" + "name": "init_application_tokens" }, { - "name": "isTypeProvider" + "name": "init_array_utils" }, { - "name": "isValueProvider" + "name": "init_assert" }, { - "name": "issueAnimationCommand" + "name": "init_assert2" }, { - "name": "iterator" + "name": "init_asserts" }, { - "name": "iteratorToArray" + "name": "init_async_pipe" }, { - "name": "leaveDI" + "name": "init_async_stack_tagging" }, { - "name": "leaveView" + "name": "init_attribute" }, { - "name": "leaveViewLight" + "name": "init_attribute_interpolation" }, { - "name": "listenOnPlayer" + "name": "init_attrs_utils" }, { - "name": "lookupTokenUsingModuleInjector" + "name": "init_bindings" }, { - "name": "lookupTokenUsingNodeInjector" + "name": "init_browser" }, { - "name": "makeAnimationEvent" + "name": "init_browser2" }, { - "name": "makeLambdaFromStates" + "name": "init_bypass" }, { - "name": "makeRecord" + "name": "init_canReportError" }, { - "name": "makeTimingAst" + "name": "init_case_conversion_pipes" }, { - "name": "map" + "name": "init_change_detection" }, { - "name": "markAsComponentHost" + "name": "init_change_detection2" }, { - "name": "markViewDirty" + "name": "init_change_detection3" }, { - "name": "markViewForRefresh" + "name": "init_change_detection_utils" }, { - "name": "maybeWrapInNotSelector" + "name": "init_change_detector_ref" }, { - "name": "mergeAll" + "name": "init_class_differ" }, { - "name": "mergeHostAttribute" + "name": "init_class_map_interpolation" }, { - "name": "mergeHostAttrs" + "name": "init_cleanup" }, { - "name": "mergeMap" + "name": "init_closure" }, { - "name": "nativeAppendChild" + "name": "init_cloudflare_loader" }, { - "name": "nativeAppendOrInsertBefore" + "name": "init_cloudinary_loader" }, { - "name": "nativeInsertBefore" + "name": "init_coercion" }, { - "name": "nextNgElementId" + "name": "init_collect_native_nodes" }, { - "name": "ngOnChangesSetInput" + "name": "init_common" }, { - "name": "ngZoneApplicationErrorHandlerFactory" + "name": "init_common2" }, { - "name": "noSideEffects" + "name": "init_common_module" }, { - "name": "nonNull" + "name": "init_comparison" }, { - "name": "noop" + "name": "init_compiler" }, { - "name": "normalizeAnimationEntry" + "name": "init_compiler_facade" }, { - "name": "normalizeAnimationOptions" + "name": "init_compiler_facade_interface" }, { - "name": "normalizeKeyframes" + "name": "init_component" }, { - "name": "notFoundValueOrThrow" + "name": "init_component_factory" }, { - "name": "observable" + "name": "init_component_factory_resolver" }, { - "name": "onEnter" + "name": "init_component_ref" }, { - "name": "onLeave" + "name": "init_compression" }, { - "name": "optimizeGroupPlayer" + "name": "init_computed" }, { - "name": "optionsReducer" + "name": "init_config" }, { - "name": "parseTimelineCommand" + "name": "init_console" }, { - "name": "parseTransitionExpr" + "name": "init_constants" }, { - "name": "platformBrowser" + "name": "init_container" }, { - "name": "platformCore" + "name": "init_context" }, { - "name": "processInjectorTypesWithProviders" + "name": "init_context_discovery" }, { - "name": "profiler" + "name": "init_contextual" }, { - "name": "refCount" + "name": "init_cookie" }, { - "name": "refreshContentQueries" + "name": "init_copy_definition_feature" }, { - "name": "refreshView" + "name": "init_core" }, { - "name": "registerPostOrderHooks" + "name": "init_core2" }, { - "name": "rememberChangeHistoryAndInvokeOnChangesHook" + "name": "init_core_private_export" }, { - "name": "remove" + "name": "init_core_reactivity_export" }, { - "name": "removeClass" + "name": "init_core_reactivity_export_internal" }, { - "name": "removeFromArray" + "name": "init_core_render3_private_export" }, { - "name": "removeNodesAfterAnimationDone" + "name": "init_createEngine" }, { - "name": "renderComponent" + "name": "init_create_injector" }, { - "name": "renderView" + "name": "init_currencies" }, { - "name": "replacePostStylesAsPre" + "name": "init_date_pipe" }, { - "name": "resetPreOrderHookFlags" + "name": "init_date_pipe_config" }, { - "name": "resolveForwardRef" + "name": "init_debug_node" }, { - "name": "resolveTiming" + "name": "init_decorators" }, { - "name": "resolveTimingValue" + "name": "init_default_iterable_differ" }, { - "name": "retrieveHydrationInfo" + "name": "init_default_keyvalue_differ" }, { - "name": "roundOffset" + "name": "init_definition" }, { - "name": "rxSubscriber" + "name": "init_definition_factory" }, { - "name": "saveNameToExportMap" + "name": "init_defs" }, { - "name": "scheduleArray" + "name": "init_destroy_ref" }, { - "name": "searchTokensOnInjector" + "name": "init_di" }, { - "name": "sequence" + "name": "init_di2" }, { - "name": "setActiveConsumer" + "name": "init_di3" }, { - "name": "setBindingRootForHostBindings" + "name": "init_di4" }, { - "name": "setCurrentDirectiveIndex" + "name": "init_di5" }, { - "name": "setCurrentInjector" + "name": "init_di_attr" }, { - "name": "setCurrentQueryIndex" + "name": "init_di_setup" }, { - "name": "setCurrentTNode" + "name": "init_directive" }, { - "name": "setDirectiveInputsWhichShadowsStyling" + "name": "init_directives" }, { - "name": "setIncludeViewProviders" + "name": "init_directives2" }, { - "name": "setInjectImplementation" + "name": "init_discovery_utils" }, { - "name": "setInputsForProperty" + "name": "init_distinctUntilChanged" }, { - "name": "setInputsFromAttrs" + "name": "init_document" }, { - "name": "setSelectedIndex" + "name": "init_dom" }, { - "name": "setStyles" + "name": "init_dom_adapter" }, { - "name": "setUpAttributes" + "name": "init_dom_tokens" }, { - "name": "setupStaticAttributes" + "name": "init_effect" }, { - "name": "share" + "name": "init_element" }, { - "name": "shareSubjectFactory" + "name": "init_element_container" }, { - "name": "shimStylesContent" + "name": "init_element_instruction_map" }, { - "name": "shouldSearchParent" + "name": "init_element_ref" }, { - "name": "stringify" + "name": "init_element_validation" }, { - "name": "stringifyCSSSelector" + "name": "init_empty" }, { - "name": "style" + "name": "init_environment" }, { - "name": "subscribeTo" + "name": "init_environment2" }, { - "name": "subscribeToArray" + "name": "init_error_details_base_url" }, { - "name": "switchMap" + "name": "init_error_handler" }, { - "name": "throwProviderNotFoundError" + "name": "init_error_handling" }, { - "name": "toRefArray" + "name": "init_error_helper" }, { - "name": "transition" + "name": "init_error_helpers" }, { - "name": "uniqueIdCounter" + "name": "init_errors" }, { - "name": "unwrapRNode" + "name": "init_errors2" }, { - "name": "updateMicroTaskStatus" + "name": "init_errors3" }, { - "name": "updateViewsToRefresh" + "name": "init_errors4" }, { - "name": "urlParsingNode" + "name": "init_errors_di" }, { - "name": "visitDslNode" + "name": "init_esm2015" }, { - "name": "walkProviderTree" + "name": "init_event_emitter" }, { - "name": "writeDirectClass" + "name": "init_fields" }, { - "name": "writeToDirectiveInput" + "name": "init_format_date" + }, + { + "name": "init_format_number" + }, + { + "name": "init_forward_ref" + }, + { + "name": "init_from" + }, + { + "name": "init_fromArray" + }, + { + "name": "init_get_current_view" + }, + { + "name": "init_global" + }, + { + "name": "init_global_utils" + }, + { + "name": "init_graph" + }, + { + "name": "init_hash_location_strategy" + }, + { + "name": "init_hooks" + }, + { + "name": "init_hostReportError" + }, + { + "name": "init_host_directives_feature" + }, + { + "name": "init_host_property" + }, + { + "name": "init_html_sanitizer" + }, + { + "name": "init_i18n" + }, + { + "name": "init_i18n2" + }, + { + "name": "init_i18n_apply" + }, + { + "name": "init_i18n_debug" + }, + { + "name": "init_i18n_icu_container_visitor" + }, + { + "name": "init_i18n_insert_before_index" + }, + { + "name": "init_i18n_locale_id" + }, + { + "name": "init_i18n_parse" + }, + { + "name": "init_i18n_plural_pipe" + }, + { + "name": "init_i18n_postprocess" + }, + { + "name": "init_i18n_select_pipe" + }, + { + "name": "init_i18n_tree_shaking" + }, + { + "name": "init_i18n_util" + }, + { + "name": "init_identity" + }, + { + "name": "init_iframe_attrs_validation" + }, + { + "name": "init_image_loader" + }, + { + "name": "init_imagekit_loader" + }, + { + "name": "init_imgix_loader" + }, + { + "name": "init_inert_body" + }, + { + "name": "init_inherit_definition_feature" + }, + { + "name": "init_initial_render_pending_tasks" + }, + { + "name": "init_initializer_token" + }, + { + "name": "init_inject_switch" + }, + { + "name": "init_injectable" + }, + { + "name": "init_injectable2" + }, + { + "name": "init_injection_token" + }, + { + "name": "init_injector" + }, + { + "name": "init_injector2" + }, + { + "name": "init_injector3" + }, + { + "name": "init_injector_compatibility" + }, + { + "name": "init_injector_token" + }, + { + "name": "init_injector_utils" + }, + { + "name": "init_innerSubscribe" + }, + { + "name": "init_input_transforms_feature" + }, + { + "name": "init_interfaces" + }, + { + "name": "init_internal_tokens" + }, + { + "name": "init_interpolation" + }, + { + "name": "init_invalid_pipe_argument_error" + }, + { + "name": "init_isArray" + }, + { + "name": "init_isArrayLike" + }, + { + "name": "init_isFunction" + }, + { + "name": "init_isInteropObservable" + }, + { + "name": "init_isIterable" + }, + { + "name": "init_isObject" + }, + { + "name": "init_isPromise" + }, + { + "name": "init_isScheduler" + }, + { + "name": "init_is_dev_mode" + }, + { + "name": "init_iterable" + }, + { + "name": "init_iterable_differs" + }, + { + "name": "init_iterator" + }, + { + "name": "init_jit_options" + }, + { + "name": "init_json_pipe" + }, + { + "name": "init_keyvalue_differs" + }, + { + "name": "init_keyvalue_pipe" + }, + { + "name": "init_lang" + }, + { + "name": "init_lcp_image_observer" + }, + { + "name": "init_linker" + }, + { + "name": "init_listener" + }, + { + "name": "init_locale_data" + }, + { + "name": "init_locale_data_api" + }, + { + "name": "init_locale_data_api2" + }, + { + "name": "init_locale_en" + }, + { + "name": "init_localization" + }, + { + "name": "init_localization2" + }, + { + "name": "init_location" + }, + { + "name": "init_location2" + }, + { + "name": "init_location_strategy" + }, + { + "name": "init_lview_tracking" + }, + { + "name": "init_map" + }, + { + "name": "init_mark_view_dirty" + }, + { + "name": "init_merge" + }, + { + "name": "init_mergeAll" + }, + { + "name": "init_mergeMap" + }, + { + "name": "init_metadata" + }, + { + "name": "init_metadata2" + }, + { + "name": "init_metadata3" + }, + { + "name": "init_metadata_attr" + }, + { + "name": "init_misc_utils" + }, + { + "name": "init_module" + }, + { + "name": "init_module_patch" + }, + { + "name": "init_multicast" + }, + { + "name": "init_namespace" + }, + { + "name": "init_namespaces" + }, + { + "name": "init_next_context" + }, + { + "name": "init_ng_class" + }, + { + "name": "init_ng_component_outlet" + }, + { + "name": "init_ng_dev_mode" + }, + { + "name": "init_ng_for_of" + }, + { + "name": "init_ng_i18n_closure_mode" + }, + { + "name": "init_ng_if" + }, + { + "name": "init_ng_jit_mode" + }, + { + "name": "init_ng_module" + }, + { + "name": "init_ng_module_factory" + }, + { + "name": "init_ng_module_factory_loader" + }, + { + "name": "init_ng_module_factory_loader_impl" + }, + { + "name": "init_ng_module_ref" + }, + { + "name": "init_ng_module_registration" + }, + { + "name": "init_ng_onchanges_feature" + }, + { + "name": "init_ng_optimized_image" + }, + { + "name": "init_ng_optimized_image2" + }, + { + "name": "init_ng_plural" + }, + { + "name": "init_ng_reflect" + }, + { + "name": "init_ng_style" + }, + { + "name": "init_ng_switch" + }, + { + "name": "init_ng_template_outlet" + }, + { + "name": "init_ng_zone" + }, + { + "name": "init_node" + }, + { + "name": "init_node_assert" + }, + { + "name": "init_node_lookup_utils" + }, + { + "name": "init_node_manipulation" + }, + { + "name": "init_node_manipulation_i18n" + }, + { + "name": "init_node_selector_matcher" + }, + { + "name": "init_noop" + }, + { + "name": "init_null_injector" + }, + { + "name": "init_number_pipe" + }, + { + "name": "init_observable" + }, + { + "name": "init_of" + }, + { + "name": "init_operators" + }, + { + "name": "init_partial" + }, + { + "name": "init_pipe" + }, + { + "name": "init_pipe2" + }, + { + "name": "init_pipe3" + }, + { + "name": "init_pipes" + }, + { + "name": "init_platform_core_providers" + }, + { + "name": "init_platform_id" + }, + { + "name": "init_platform_location" + }, + { + "name": "init_preconnect_link_checker" + }, + { + "name": "init_preload_link_creator" + }, + { + "name": "init_private_export" + }, + { + "name": "init_private_export2" + }, + { + "name": "init_private_export3" + }, + { + "name": "init_profiler" + }, + { + "name": "init_projection" + }, + { + "name": "init_property" + }, + { + "name": "init_property2" + }, + { + "name": "init_property_interpolation" + }, + { + "name": "init_provider" + }, + { + "name": "init_provider_collection" + }, + { + "name": "init_provider_flags" + }, + { + "name": "init_providers_feature" + }, + { + "name": "init_public_api" + }, + { + "name": "init_public_api2" + }, + { + "name": "init_public_api3" + }, + { + "name": "init_public_api4" + }, + { + "name": "init_pure_function" + }, + { + "name": "init_query" + }, + { + "name": "init_query_list" + }, + { + "name": "init_r3_injector" + }, + { + "name": "init_r3_symbols" + }, + { + "name": "init_raf" + }, + { + "name": "init_reactive_lview_consumer" + }, + { + "name": "init_refCount" + }, + { + "name": "init_reflection_capabilities" + }, + { + "name": "init_render" + }, + { + "name": "init_render2" + }, + { + "name": "init_render3" + }, + { + "name": "init_resource_loading" + }, + { + "name": "init_rxSubscriber" + }, + { + "name": "init_sanitization" + }, + { + "name": "init_sanitizer" + }, + { + "name": "init_scheduleArray" + }, + { + "name": "init_scheduleIterable" + }, + { + "name": "init_scheduleObservable" + }, + { + "name": "init_schedulePromise" + }, + { + "name": "init_scheduled" + }, + { + "name": "init_schema" + }, + { + "name": "init_scope" + }, + { + "name": "init_security" + }, + { + "name": "init_share" + }, + { + "name": "init_shared" + }, + { + "name": "init_shared2" + }, + { + "name": "init_signal" + }, + { + "name": "init_signals" + }, + { + "name": "init_simple_change" + }, + { + "name": "init_skip_hydration" + }, + { + "name": "init_slice_pipe" + }, + { + "name": "init_special_cased_styles" + }, + { + "name": "init_standalone_feature" + }, + { + "name": "init_state" + }, + { + "name": "init_static_styling" + }, + { + "name": "init_storage" + }, + { + "name": "init_stringify" + }, + { + "name": "init_stringify_utils" + }, + { + "name": "init_style_binding_list" + }, + { + "name": "init_style_map_interpolation" + }, + { + "name": "init_style_prop_interpolation" + }, + { + "name": "init_styling" + }, + { + "name": "init_styling2" + }, + { + "name": "init_styling_parser" + }, + { + "name": "init_subscribeTo" + }, + { + "name": "init_subscribeToArray" + }, + { + "name": "init_subscribeToIterable" + }, + { + "name": "init_subscribeToObservable" + }, + { + "name": "init_subscribeToPromise" + }, + { + "name": "init_switchMap" + }, + { + "name": "init_template" + }, + { + "name": "init_template_ref" + }, + { + "name": "init_testability" + }, + { + "name": "init_text" + }, + { + "name": "init_text_interpolation" + }, + { + "name": "init_timeline_animation_engine" + }, + { + "name": "init_toSubscriber" + }, + { + "name": "init_tokens" + }, + { + "name": "init_tokens2" + }, + { + "name": "init_tokens3" + }, + { + "name": "init_tokens4" + }, + { + "name": "init_transfer_state" + }, + { + "name": "init_transition_animation_engine" + }, + { + "name": "init_trusted_types" + }, + { + "name": "init_trusted_types_bypass" + }, + { + "name": "init_type" + }, + { + "name": "init_type_checks" + }, + { + "name": "init_untracked" + }, + { + "name": "init_url" + }, + { + "name": "init_url_sanitizer" + }, + { + "name": "init_util" + }, + { + "name": "init_util2" + }, + { + "name": "init_util3" + }, + { + "name": "init_util4" + }, + { + "name": "init_utils" + }, + { + "name": "init_version" + }, + { + "name": "init_version2" + }, + { + "name": "init_view" + }, + { + "name": "init_view2" + }, + { + "name": "init_view_container_ref" + }, + { + "name": "init_view_engine_compatibility_prebound" + }, + { + "name": "init_view_ref" + }, + { + "name": "init_view_ref2" + }, + { + "name": "init_view_traversal_utils" + }, + { + "name": "init_view_utils" + }, + { + "name": "init_viewport_scroller" + }, + { + "name": "init_views" + }, + { + "name": "init_warning_helpers" + }, + { + "name": "init_watch" + }, + { + "name": "init_weak_ref" + }, + { + "name": "init_web_animations_player" + }, + { + "name": "init_xhr" + }, + { + "name": "init_zone" + }, + { + "name": "initializeDirectives" + }, + { + "name": "inject" + }, + { + "name": "injectArgs" + }, + { + "name": "injectElementRef" + }, + { + "name": "injectInjectorOnly" + }, + { + "name": "injectRootLimpMode" + }, + { + "name": "injectableDefOrInjectorDefFactory" + }, + { + "name": "innerSubscribe" + }, + { + "name": "insertBloom" + }, + { + "name": "instructionState" + }, + { + "name": "internalImportProvidersFrom" + }, + { + "name": "interpolateParams" + }, + { + "name": "invalidTimingValue" + }, + { + "name": "invertObject" + }, + { + "name": "invokeDirectivesHostBindings" + }, + { + "name": "invokeHostBindingsInCreationMode" + }, + { + "name": "invokeQuery" + }, + { + "name": "isArray" + }, + { + "name": "isArrayLike" + }, + { + "name": "isComponentDef" + }, + { + "name": "isComponentHost" + }, + { + "name": "isContentQueryHost" + }, + { + "name": "isCssClassMatching" + }, + { + "name": "isCurrentTNodeParent" + }, + { + "name": "isElementNode" + }, + { + "name": "isEnvironmentProviders" + }, + { + "name": "isForwardRef" + }, + { + "name": "isFunction" + }, + { + "name": "isInlineTemplate" + }, + { + "name": "isLContainer" + }, + { + "name": "isLView" + }, + { + "name": "isNodeMatchingSelector" + }, + { + "name": "isNodeMatchingSelectorList" + }, + { + "name": "isObject" + }, + { + "name": "isPlatformServer" + }, + { + "name": "isPositive" + }, + { + "name": "isPromise" + }, + { + "name": "isPromise2" + }, + { + "name": "isScheduler" + }, + { + "name": "isStableFactory" + }, + { + "name": "isTemplateNode" + }, + { + "name": "isTypeProvider" + }, + { + "name": "isValueProvider" + }, + { + "name": "issueAnimationCommand" + }, + { + "name": "iterator" + }, + { + "name": "iteratorToArray" + }, + { + "name": "leaveDI" + }, + { + "name": "leaveView" + }, + { + "name": "leaveViewLight" + }, + { + "name": "listenOnPlayer" + }, + { + "name": "lookupTokenUsingModuleInjector" + }, + { + "name": "lookupTokenUsingNodeInjector" + }, + { + "name": "makeAnimationEvent" + }, + { + "name": "makeLambdaFromStates" + }, + { + "name": "makeRecord" + }, + { + "name": "makeTimingAst" + }, + { + "name": "map" + }, + { + "name": "markAsComponentHost" + }, + { + "name": "markViewDirty" + }, + { + "name": "markViewForRefresh" + }, + { + "name": "maybeWrapInNotSelector" + }, + { + "name": "mergeAll" + }, + { + "name": "mergeHostAttribute" + }, + { + "name": "mergeHostAttrs" + }, + { + "name": "mergeMap" + }, + { + "name": "nativeAppendChild" + }, + { + "name": "nativeAppendOrInsertBefore" + }, + { + "name": "nativeInsertBefore" + }, + { + "name": "nextNgElementId" + }, + { + "name": "ngOnChangesSetInput" + }, + { + "name": "ngZoneApplicationErrorHandlerFactory" + }, + { + "name": "noSideEffects" + }, + { + "name": "nonNull" + }, + { + "name": "noop" + }, + { + "name": "normalizeAnimationEntry" + }, + { + "name": "normalizeAnimationOptions" + }, + { + "name": "normalizeKeyframes2" + }, + { + "name": "notFoundValueOrThrow" + }, + { + "name": "observable" + }, + { + "name": "onEnter" + }, + { + "name": "onLeave" + }, + { + "name": "optimizeGroupPlayer" + }, + { + "name": "optionsReducer" + }, + { + "name": "parseTimelineCommand" + }, + { + "name": "parseTransitionExpr" + }, + { + "name": "platformBrowser" + }, + { + "name": "platformCore" + }, + { + "name": "processInjectorTypesWithProviders" + }, + { + "name": "profiler" + }, + { + "name": "refCount" + }, + { + "name": "refreshContentQueries" + }, + { + "name": "refreshView" + }, + { + "name": "registerPostOrderHooks" + }, + { + "name": "rememberChangeHistoryAndInvokeOnChangesHook" + }, + { + "name": "remove" + }, + { + "name": "removeClass" + }, + { + "name": "removeFromArray" + }, + { + "name": "removeNodesAfterAnimationDone" + }, + { + "name": "renderComponent" + }, + { + "name": "renderView" + }, + { + "name": "replacePostStylesAsPre" + }, + { + "name": "resetPreOrderHookFlags" + }, + { + "name": "resolveForwardRef" + }, + { + "name": "resolveTiming" + }, + { + "name": "resolveTimingValue" + }, + { + "name": "retrieveHydrationInfo" + }, + { + "name": "roundOffset" + }, + { + "name": "rxSubscriber" + }, + { + "name": "saveNameToExportMap" + }, + { + "name": "scheduleArray" + }, + { + "name": "searchTokensOnInjector" + }, + { + "name": "sequence" + }, + { + "name": "setActiveConsumer" + }, + { + "name": "setBindingRootForHostBindings" + }, + { + "name": "setCurrentDirectiveIndex" + }, + { + "name": "setCurrentInjector" + }, + { + "name": "setCurrentQueryIndex" + }, + { + "name": "setCurrentTNode" + }, + { + "name": "setDirectiveInputsWhichShadowsStyling" + }, + { + "name": "setIncludeViewProviders" + }, + { + "name": "setInjectImplementation" + }, + { + "name": "setInputsForProperty" + }, + { + "name": "setInputsFromAttrs" + }, + { + "name": "setSelectedIndex" + }, + { + "name": "setStyles" + }, + { + "name": "setUpAttributes" + }, + { + "name": "setupStaticAttributes" + }, + { + "name": "share" + }, + { + "name": "shareSubjectFactory" + }, + { + "name": "shimStylesContent" + }, + { + "name": "shouldSearchParent" + }, + { + "name": "stringify" + }, + { + "name": "stringifyCSSSelector" + }, + { + "name": "style" + }, + { + "name": "subscribeTo" + }, + { + "name": "subscribeToArray" + }, + { + "name": "subscribeToIterable" + }, + { + "name": "subscribeToObservable" + }, + { + "name": "subscribeToPromise" + }, + { + "name": "switchMap" + }, + { + "name": "throwProviderNotFoundError" + }, + { + "name": "toRefArray" + }, + { + "name": "transition" + }, + { + "name": "uniqueIdCounter" + }, + { + "name": "unwrapRNode" + }, + { + "name": "updateMicroTaskStatus" + }, + { + "name": "updateViewsToRefresh" + }, + { + "name": "urlParsingNode" + }, + { + "name": "visitDslNode" + }, + { + "name": "walkProviderTree" + }, + { + "name": "writeDirectClass" + }, + { + "name": "writeToDirectiveInput" + }, + { + "name": "ɵPRE_STYLE" + }, + { + "name": "ɵɵNgOnChangesFeature" }, { "name": "ɵɵdefineComponent" diff --git a/packages/core/test/render3/imported_renderer2.ts b/packages/core/test/render3/imported_renderer2.ts index 4bb5312bede2d3..0e38557cc19223 100644 --- a/packages/core/test/render3/imported_renderer2.ts +++ b/packages/core/test/render3/imported_renderer2.ts @@ -6,13 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ -import {ɵAnimationEngine, ɵNoopAnimationStyleNormalizer} from '@angular/animations/browser'; -import {MockAnimationDriver} from '@angular/animations/browser/testing'; import {PLATFORM_BROWSER_ID, PLATFORM_SERVER_ID} from '@angular/common/src/platform_id'; import {NgZone, RendererFactory2, RendererType2} from '@angular/core'; import {NoopNgZone} from '@angular/core/src/zone/ng_zone'; import {EventManager, ɵDomRendererFactory2, ɵSharedStylesHost} from '@angular/platform-browser'; -import {ɵAnimationRendererFactory} from '@angular/platform-browser/animations'; import {EventManagerPlugin} from '@angular/platform-browser/src/dom/events/event_manager'; import {isTextNode} from '@angular/platform-browser/testing/src/browser_util'; @@ -52,15 +49,6 @@ export function getRendererFactory2(document: any): RendererFactory2 { return rendererFactory; } -export function getAnimationRendererFactory2(document: any): RendererFactory2 { - const fakeNgZone: NgZone = new NoopNgZone(); - return new ɵAnimationRendererFactory( - getRendererFactory2(document), - new ɵAnimationEngine( - document.body, new MockAnimationDriver(), new ɵNoopAnimationStyleNormalizer()), - fakeNgZone); -} - // TODO: code duplicated from ../linker/change_detection_integration_spec.ts, to be removed // START duplicated code export class RenderLog { diff --git a/packages/platform-browser/BUILD.bazel b/packages/platform-browser/BUILD.bazel index d9a5e0306f4583..4f5e98af630ef9 100644 --- a/packages/platform-browser/BUILD.bazel +++ b/packages/platform-browser/BUILD.bazel @@ -48,6 +48,7 @@ ng_package( deps = [ ":platform-browser", "//packages/platform-browser/animations", + "//packages/platform-browser/lazy-animations", "//packages/platform-browser/testing", ], ) diff --git a/packages/platform-browser/animations/BUILD.bazel b/packages/platform-browser/animations/BUILD.bazel index feac85e0856093..0901028785675e 100644 --- a/packages/platform-browser/animations/BUILD.bazel +++ b/packages/platform-browser/animations/BUILD.bazel @@ -18,6 +18,7 @@ ng_module( "//packages/common", "//packages/core", "//packages/platform-browser", + "//packages/platform-browser/lazy-animations", ], ) diff --git a/packages/platform-browser/animations/src/animation_builder.ts b/packages/platform-browser/animations/src/animation_builder.ts index a682d20f5cc857..f304265bd9b982 100644 --- a/packages/platform-browser/animations/src/animation_builder.ts +++ b/packages/platform-browser/animations/src/animation_builder.ts @@ -9,14 +9,17 @@ import {AnimationBuilder, AnimationFactory, AnimationMetadata, AnimationOptions, import {DOCUMENT} from '@angular/common'; import {Inject, Injectable, RendererFactory2, RendererType2, ViewEncapsulation} from '@angular/core'; -import {AnimationRenderer} from './animation_renderer'; +import {AnimationRenderer} from './lazy/abstract_animation_renderer'; -@Injectable() +/** + * @public + */ +@Injectable({providedIn: 'root'}) export class BrowserAnimationBuilder extends AnimationBuilder { private _nextAnimationId = 0; private _renderer: AnimationRenderer; - constructor(rootRenderer: RendererFactory2, @Inject(DOCUMENT) doc: any) { + constructor(rootRenderer: RendererFactory2, @Inject(DOCUMENT) doc: Document) { super(); const typeData = {id: '0', encapsulation: ViewEncapsulation.None, styles: [], data: {animation: []}} as @@ -33,7 +36,7 @@ export class BrowserAnimationBuilder extends AnimationBuilder { } } -export class BrowserAnimationFactory extends AnimationFactory { +class BrowserAnimationFactory extends AnimationFactory { constructor(private _id: string, private _renderer: AnimationRenderer) { super(); } diff --git a/packages/platform-browser/animations/src/animation_renderer.ts b/packages/platform-browser/animations/src/animation_renderer.ts index 12e1e5a3ac0531..6270e334d15541 100644 --- a/packages/platform-browser/animations/src/animation_renderer.ts +++ b/packages/platform-browser/animations/src/animation_renderer.ts @@ -5,39 +5,19 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import {AnimationTriggerMetadata} from '@angular/animations'; -import {ɵAnimationEngine as AnimationEngine} from '@angular/animations/browser'; -import {Injectable, NgZone, Renderer2, RendererFactory2, RendererStyleFlags2, RendererType2} from '@angular/core'; +import type {ɵAnimationEngine as AnimationEngine} from '@angular/animations/browser'; +import {Injectable, NgZone, Renderer2, RendererFactory2, RendererType2} from '@angular/core'; +import {AbstractAnimationRendererFactory, AnimationRenderer, BaseAnimationRenderer, NestedAnimationTriggerMetadata} from './lazy/abstract_animation_renderer'; -const ANIMATION_PREFIX = '@'; -const DISABLE_ANIMATIONS_FLAG = '@.disabled'; - -// Define a recursive type to allow for nested arrays of `AnimationTriggerMetadata`. Note that an -// interface declaration is used as TypeScript prior to 3.7 does not support recursive type -// references, see https://github.com/microsoft/TypeScript/pull/33050 for details. -type NestedAnimationTriggerMetadata = AnimationTriggerMetadata|RecursiveAnimationTriggerMetadata; -interface RecursiveAnimationTriggerMetadata extends Array {} @Injectable() -export class AnimationRendererFactory implements RendererFactory2 { - private _currentId: number = 0; - private _microtaskId: number = 1; - private _animationCallbacksBuffer: [(e: any) => any, any][] = []; - private _rendererCache = new Map(); - private _cdRecurDepth = 0; +export class AnimationRendererFactory extends AbstractAnimationRendererFactory { + override engine: AnimationEngine; - constructor( - private delegate: RendererFactory2, private engine: AnimationEngine, private _zone: NgZone) { - engine.onRemovalComplete = (element: any, delegate: Renderer2) => { - // Note: if a component element has a leave animation, and a host leave animation, - // the view engine will call `removeChild` for the parent - // component renderer as well as for the child component renderer. - // Therefore, we need to check if we already removed the element. - const parentNode = delegate?.parentNode(element); - if (parentNode) { - delegate.removeChild(parentNode, element); - } - }; + constructor(delegate: RendererFactory2, engine: AnimationEngine, _zone: NgZone) { + super(delegate, _zone); + this.engine = engine; + this.registerEngineRemovalComplete(engine); } createRenderer(hostElement: any, type: RendererType2): Renderer2 { @@ -47,22 +27,22 @@ export class AnimationRendererFactory implements RendererFactory2 { // be used by which cached renderer const delegate = this.delegate.createRenderer(hostElement, type); if (!hostElement || !type || !type.data || !type.data['animation']) { - let renderer: BaseAnimationRenderer|undefined = this._rendererCache.get(delegate); + let renderer: BaseAnimationRenderer|undefined = this.rendererCache.get(delegate); if (!renderer) { // Ensure that the renderer is removed from the cache on destroy // since it may contain references to detached DOM nodes. - const onRendererDestroy = () => this._rendererCache.delete(delegate); + const onRendererDestroy = () => this.rendererCache.delete(delegate); renderer = new BaseAnimationRenderer(EMPTY_NAMESPACE_ID, delegate, this.engine, onRendererDestroy); // only cache this result when the base renderer is used - this._rendererCache.set(delegate, renderer); + this.rendererCache.set(delegate, renderer); } return renderer; } const componentId = type.id; - const namespaceId = type.id + '-' + this._currentId; - this._currentId++; + const namespaceId = type.id + '-' + this.currentId; + this.currentId++; this.engine.register(namespaceId, hostElement); @@ -78,224 +58,4 @@ export class AnimationRendererFactory implements RendererFactory2 { return new AnimationRenderer(this, namespaceId, delegate, this.engine); } - - begin() { - this._cdRecurDepth++; - if (this.delegate.begin) { - this.delegate.begin(); - } - } - - private _scheduleCountTask() { - queueMicrotask(() => { - this._microtaskId++; - }); - } - - /** @internal */ - scheduleListenerCallback(count: number, fn: (e: any) => any, data: any) { - if (count >= 0 && count < this._microtaskId) { - this._zone.run(() => fn(data)); - return; - } - - if (this._animationCallbacksBuffer.length == 0) { - queueMicrotask(() => { - this._zone.run(() => { - this._animationCallbacksBuffer.forEach(tuple => { - const [fn, data] = tuple; - fn(data); - }); - this._animationCallbacksBuffer = []; - }); - }); - } - - this._animationCallbacksBuffer.push([fn, data]); - } - - end() { - this._cdRecurDepth--; - - // this is to prevent animations from running twice when an inner - // component does CD when a parent component instead has inserted it - if (this._cdRecurDepth == 0) { - this._zone.runOutsideAngular(() => { - this._scheduleCountTask(); - this.engine.flush(this._microtaskId); - }); - } - if (this.delegate.end) { - this.delegate.end(); - } - } - - whenRenderingDone(): Promise { - return this.engine.whenRenderingDone(); - } -} - -export class BaseAnimationRenderer implements Renderer2 { - constructor( - protected namespaceId: string, public delegate: Renderer2, public engine: AnimationEngine, - private _onDestroy?: () => void) { - this.destroyNode = this.delegate.destroyNode ? (n) => delegate.destroyNode!(n) : null; - } - - get data() { - return this.delegate.data; - } - - destroyNode: ((n: any) => void)|null; - - destroy(): void { - this.engine.destroy(this.namespaceId, this.delegate); - this.delegate.destroy(); - this._onDestroy?.(); - } - - createElement(name: string, namespace?: string|null|undefined) { - return this.delegate.createElement(name, namespace); - } - - createComment(value: string) { - return this.delegate.createComment(value); - } - - createText(value: string) { - return this.delegate.createText(value); - } - - appendChild(parent: any, newChild: any): void { - this.delegate.appendChild(parent, newChild); - this.engine.onInsert(this.namespaceId, newChild, parent, false); - } - - insertBefore(parent: any, newChild: any, refChild: any, isMove: boolean = true): void { - this.delegate.insertBefore(parent, newChild, refChild); - // If `isMove` true than we should animate this insert. - this.engine.onInsert(this.namespaceId, newChild, parent, isMove); - } - - removeChild(parent: any, oldChild: any, isHostElement: boolean): void { - this.engine.onRemove(this.namespaceId, oldChild, this.delegate); - } - - selectRootElement(selectorOrNode: any, preserveContent?: boolean) { - return this.delegate.selectRootElement(selectorOrNode, preserveContent); - } - - parentNode(node: any) { - return this.delegate.parentNode(node); - } - - nextSibling(node: any) { - return this.delegate.nextSibling(node); - } - - setAttribute(el: any, name: string, value: string, namespace?: string|null|undefined): void { - this.delegate.setAttribute(el, name, value, namespace); - } - - removeAttribute(el: any, name: string, namespace?: string|null|undefined): void { - this.delegate.removeAttribute(el, name, namespace); - } - - addClass(el: any, name: string): void { - this.delegate.addClass(el, name); - } - - removeClass(el: any, name: string): void { - this.delegate.removeClass(el, name); - } - - setStyle(el: any, style: string, value: any, flags?: RendererStyleFlags2|undefined): void { - this.delegate.setStyle(el, style, value, flags); - } - - removeStyle(el: any, style: string, flags?: RendererStyleFlags2|undefined): void { - this.delegate.removeStyle(el, style, flags); - } - - setProperty(el: any, name: string, value: any): void { - if (name.charAt(0) == ANIMATION_PREFIX && name == DISABLE_ANIMATIONS_FLAG) { - this.disableAnimations(el, !!value); - } else { - this.delegate.setProperty(el, name, value); - } - } - - setValue(node: any, value: string): void { - this.delegate.setValue(node, value); - } - - listen(target: any, eventName: string, callback: (event: any) => boolean | void): () => void { - return this.delegate.listen(target, eventName, callback); - } - - protected disableAnimations(element: any, value: boolean) { - this.engine.disableAnimations(element, value); - } -} - -export class AnimationRenderer extends BaseAnimationRenderer implements Renderer2 { - constructor( - public factory: AnimationRendererFactory, namespaceId: string, delegate: Renderer2, - engine: AnimationEngine, onDestroy?: () => void) { - super(namespaceId, delegate, engine, onDestroy); - this.namespaceId = namespaceId; - } - - override setProperty(el: any, name: string, value: any): void { - if (name.charAt(0) == ANIMATION_PREFIX) { - if (name.charAt(1) == '.' && name == DISABLE_ANIMATIONS_FLAG) { - value = value === undefined ? true : !!value; - this.disableAnimations(el, value as boolean); - } else { - this.engine.process(this.namespaceId, el, name.slice(1), value); - } - } else { - this.delegate.setProperty(el, name, value); - } - } - - override listen( - target: 'window'|'document'|'body'|any, eventName: string, - callback: (event: any) => any): () => void { - if (eventName.charAt(0) == ANIMATION_PREFIX) { - const element = resolveElementFromTarget(target); - let name = eventName.slice(1); - let phase = ''; - // @listener.phase is for trigger animation callbacks - // @@listener is for animation builder callbacks - if (name.charAt(0) != ANIMATION_PREFIX) { - [name, phase] = parseTriggerCallbackName(name); - } - return this.engine.listen(this.namespaceId, element, name, phase, event => { - const countId = (event as any)['_data'] || -1; - this.factory.scheduleListenerCallback(countId, callback, event); - }); - } - return this.delegate.listen(target, eventName, callback); - } -} - -function resolveElementFromTarget(target: 'window'|'document'|'body'|any): any { - switch (target) { - case 'body': - return document.body; - case 'document': - return document; - case 'window': - return window; - default: - return target; - } -} - -function parseTriggerCallbackName(triggerName: string) { - const dotIndex = triggerName.indexOf('.'); - const trigger = triggerName.substring(0, dotIndex); - const phase = triggerName.slice(dotIndex + 1); - return [trigger, phase]; } diff --git a/packages/platform-browser/animations/src/animations.ts b/packages/platform-browser/animations/src/animations.ts index 5def93c50dbadb..4edfa58639844c 100644 --- a/packages/platform-browser/animations/src/animations.ts +++ b/packages/platform-browser/animations/src/animations.ts @@ -12,6 +12,7 @@ * Entry point for all animation APIs of the animation browser package. */ export {ANIMATION_MODULE_TYPE} from '@angular/core'; +export {BrowserAnimationBuilder} from './animation_builder'; export {BrowserAnimationsModule, BrowserAnimationsModuleConfig, NoopAnimationsModule, provideAnimations, provideNoopAnimations} from './module'; export * from './private_export'; diff --git a/packages/platform-browser/animations/src/lazy/abstract_animation_renderer.ts b/packages/platform-browser/animations/src/lazy/abstract_animation_renderer.ts new file mode 100644 index 00000000000000..ff27a13b5e2ad9 --- /dev/null +++ b/packages/platform-browser/animations/src/lazy/abstract_animation_renderer.ts @@ -0,0 +1,277 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import type {AnimationTriggerMetadata} from '@angular/animations'; +import type {ɵAnimationEngine as AnimationEngine} from '@angular/animations/browser'; +import {Injectable, NgZone, Renderer2, RendererFactory2, RendererStyleFlags2, RendererType2} from '@angular/core'; + +const ANIMATION_PREFIX = '@'; +const DISABLE_ANIMATIONS_FLAG = '@.disabled'; + +// Define a recursive type to allow for nested arrays of `AnimationTriggerMetadata`. +export type NestedAnimationTriggerMetadata = + AnimationTriggerMetadata|RecursiveAnimationTriggerMetadata; +type RecursiveAnimationTriggerMetadata = Array; + +/** + * To preserve lazy-loading capabilities, the lazy-animation module cannot import from + * `platform-browser/animations` + * + * + * This base class is used to share the behavior between `AnimationRendererFactory` and + * `AsyncAnimationRendererFactory` + */ +@Injectable() +export abstract class AbstractAnimationRendererFactory implements RendererFactory2 { + private _microtaskId: number = 1; + private _animationCallbacksBuffer: [(e: any) => any, any][] = []; + private _cdRecurDepth = 0; + + protected currentId: number = 0; + protected rendererCache = new Map(); + protected promise: Promise = Promise.resolve(0); + protected engine: AnimationEngine|null = null; + + constructor(protected delegate: RendererFactory2, private _zone: NgZone) {} + + abstract createRenderer(hostElement: any, type: RendererType2|null): Renderer2; + + begin() { + this._cdRecurDepth++; + if (this.delegate.begin) { + this.delegate.begin(); + } + } + + private _scheduleCountTask() { + // always use promise to schedule microtask instead of use Zone + this.promise.then(() => { + this._microtaskId++; + }); + } + + /** @internal */ + scheduleListenerCallback(count: number, fn: (e: any) => any, data: any) { + if (count >= 0 && count < this._microtaskId) { + this._zone.run(() => fn(data)); + return; + } + + if (this._animationCallbacksBuffer.length == 0) { + Promise.resolve(null).then(() => { + this._zone.run(() => { + this._animationCallbacksBuffer.forEach((tuple) => { + const [fn, data] = tuple; + fn(data); + }); + this._animationCallbacksBuffer = []; + }); + }); + } + + this._animationCallbacksBuffer.push([fn, data]); + } + + end() { + this._cdRecurDepth--; + + // this is to prevent animations from running twice when an inner + // component does CD when a parent component instead has inserted it + if (this._cdRecurDepth == 0) { + this._zone.runOutsideAngular(() => { + this._scheduleCountTask(); + this.engine?.flush(this._microtaskId); + }); + } + if (this.delegate.end) { + this.delegate.end(); + } + } + + whenRenderingDone(): Promise { + return this.engine?.whenRenderingDone() ?? Promise.resolve(); + } + + protected registerEngineRemovalComplete(engine: AnimationEngine) { + engine.onRemovalComplete = (element: any, delegate: Renderer2) => { + // Note: if a component element has a leave animation, and a host leave animation, + // the view engine will call `removeChild` for the parent + // component renderer as well as for the child component renderer. + // Therefore, we need to check if we already removed the element. + const parentNode = delegate?.parentNode(element); + if (parentNode) { + delegate.removeChild(parentNode, element); + } + }; + } +} + +export class BaseAnimationRenderer implements Renderer2 { + constructor( + protected namespaceId: string, public delegate: Renderer2, public engine: AnimationEngine, + private _onDestroy?: () => void) { + this.destroyNode = this.delegate.destroyNode ? (n) => delegate.destroyNode!(n) : null; + } + + get data() { + return this.delegate.data; + } + + destroyNode: ((n: any) => void)|null; + + destroy(): void { + this.engine.destroy(this.namespaceId, this.delegate); + this.delegate.destroy(); + this._onDestroy?.(); + } + + createElement(name: string, namespace?: string|null|undefined) { + return this.delegate.createElement(name, namespace); + } + + createComment(value: string) { + return this.delegate.createComment(value); + } + + createText(value: string) { + return this.delegate.createText(value); + } + + appendChild(parent: any, newChild: any): void { + this.delegate.appendChild(parent, newChild); + this.engine.onInsert(this.namespaceId, newChild, parent, false); + } + + insertBefore(parent: any, newChild: any, refChild: any, isMove: boolean = true): void { + this.delegate.insertBefore(parent, newChild, refChild); + // If `isMove` true than we should animate this insert. + this.engine.onInsert(this.namespaceId, newChild, parent, isMove); + } + + removeChild(parent: any, oldChild: any, isHostElement: boolean): void { + this.engine.onRemove(this.namespaceId, oldChild, this.delegate); + } + + selectRootElement(selectorOrNode: any, preserveContent?: boolean) { + return this.delegate.selectRootElement(selectorOrNode, preserveContent); + } + + parentNode(node: any) { + return this.delegate.parentNode(node); + } + + nextSibling(node: any) { + return this.delegate.nextSibling(node); + } + + setAttribute(el: any, name: string, value: string, namespace?: string|null|undefined): void { + this.delegate.setAttribute(el, name, value, namespace); + } + + removeAttribute(el: any, name: string, namespace?: string|null|undefined): void { + this.delegate.removeAttribute(el, name, namespace); + } + + addClass(el: any, name: string): void { + this.delegate.addClass(el, name); + } + + removeClass(el: any, name: string): void { + this.delegate.removeClass(el, name); + } + + setStyle(el: any, style: string, value: any, flags?: RendererStyleFlags2|undefined): void { + this.delegate.setStyle(el, style, value, flags); + } + + removeStyle(el: any, style: string, flags?: RendererStyleFlags2|undefined): void { + this.delegate.removeStyle(el, style, flags); + } + + setProperty(el: any, name: string, value: any): void { + if (name.charAt(0) == ANIMATION_PREFIX && name == DISABLE_ANIMATIONS_FLAG) { + this.disableAnimations(el, !!value); + } else { + this.delegate.setProperty(el, name, value); + } + } + + setValue(node: any, value: string): void { + this.delegate.setValue(node, value); + } + + listen(target: any, eventName: string, callback: (event: any) => boolean | void): () => void { + return this.delegate.listen(target, eventName, callback); + } + + protected disableAnimations(element: any, value: boolean) { + this.engine.disableAnimations(element, value); + } +} + +export class AnimationRenderer extends BaseAnimationRenderer implements Renderer2 { + constructor( + public factory: AbstractAnimationRendererFactory, namespaceId: string, delegate: Renderer2, + engine: AnimationEngine, onDestroy?: () => void) { + super(namespaceId, delegate, engine, onDestroy); + this.namespaceId = namespaceId; + } + + override setProperty(el: any, name: string, value: any): void { + if (name.charAt(0) == ANIMATION_PREFIX) { + if (name.charAt(1) == '.' && name == DISABLE_ANIMATIONS_FLAG) { + value = value === undefined ? true : !!value; + this.disableAnimations(el, value as boolean); + } else { + this.engine.process(this.namespaceId, el, name.slice(1), value); + } + } else { + this.delegate.setProperty(el, name, value); + } + } + + override listen( + target: 'window'|'document'|'body'|any, eventName: string, + callback: (event: any) => any): () => void { + if (eventName.charAt(0) == ANIMATION_PREFIX) { + const element = resolveElementFromTarget(target); + let name = eventName.slice(1); + let phase = ''; + // @listener.phase is for trigger animation callbacks + // @@listener is for animation builder callbacks + if (name.charAt(0) != ANIMATION_PREFIX) { + [name, phase] = parseTriggerCallbackName(name); + } + return this.engine.listen(this.namespaceId, element, name, phase, event => { + const countId = (event as any)['_data'] || -1; + this.factory.scheduleListenerCallback(countId, callback, event); + }); + } + return this.delegate.listen(target, eventName, callback); + } +} + +function resolveElementFromTarget(target: 'window'|'document'|'body'|any): any { + switch (target) { + case 'body': + return document.body; + case 'document': + return document; + case 'window': + return window; + default: + return target; + } +} + +function parseTriggerCallbackName(triggerName: string) { + const dotIndex = triggerName.indexOf('.'); + const trigger = triggerName.substring(0, dotIndex); + const phase = triggerName.slice(dotIndex + 1); + return [trigger, phase]; +} diff --git a/packages/platform-browser/animations/src/lazy/async_animation_renderer.ts b/packages/platform-browser/animations/src/lazy/async_animation_renderer.ts new file mode 100644 index 00000000000000..ef47095e38b914 --- /dev/null +++ b/packages/platform-browser/animations/src/lazy/async_animation_renderer.ts @@ -0,0 +1,216 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import type {ɵAnimationEngine as AnimationEngine, ɵcreateEngine as createEngine} from '@angular/animations/browser'; +import {ɵDefaultDomRenderer2 as DefaultDomRenderer2} from '@angular/platform-browser'; +import {DOCUMENT} from '@angular/common'; +import {ApplicationRef, Inject, Injectable, NgZone, Renderer2, RendererFactory2, RendererStyleFlags2, RendererType2, ɵRuntimeError as RuntimeError} from '@angular/core'; + +import {AbstractAnimationRendererFactory, AnimationRenderer, BaseAnimationRenderer, NestedAnimationTriggerMetadata} from './abstract_animation_renderer'; +import {RuntimeErrorCode} from './errors'; + +@Injectable() +export class AsyncAnimationRendererFactory extends AbstractAnimationRendererFactory { + private _moduleLoaded: Promise|null = null; + private _renderer: DynamicDelegationRenderer|null = null; + + constructor( + @Inject(DOCUMENT) private doc: Document, private applicationRef: ApplicationRef, + delegate: RendererFactory2, zone: NgZone, type: 'animations'|'noop', + moduleImpl: Promise = + import('@angular/animations/browser').then(m => m.ɵcreateEngine)) { + super(delegate, zone); + + this._moduleLoaded = this.loadImpl(moduleImpl, type); + } + + /** + * @internal + */ + private loadImpl(moduleImpl: Promise, type: 'animations'|'noop'): + Promise { + return moduleImpl + .catch((e) => { + throw new RuntimeError( + RuntimeErrorCode.MODULE_LOADING_FAILED, + (typeof ngDevMode !== 'undefined' && ngDevMode) && + 'Failed to load the @angular/animations/browser module'); + }) + .then((createEngine) => { + // We can't create the renderer yet because we might need the hostElement and the type + // Both are provided in createRenderer(), so we first create the engine + + this.engine = createEngine(type, this.applicationRef, this.doc); + this.registerEngineRemovalComplete(this.engine); + return this.engine; + }); + } + + createRenderer(hostElement: any, type: RendererType2): Renderer2 { + const delegateRenderer = this.delegate.createRenderer(hostElement, type); + this._renderer = new DynamicDelegationRenderer(delegateRenderer); + + // We need to prevent the DomRenderer to throw an error because of synthetic properties + if (delegateRenderer instanceof DefaultDomRenderer2) { + delegateRenderer.disableCheck = true; + } + + if (this.engine) { + // The module is already loaded + if (!hostElement || !type?.data?.animation) { + this.useBaseAnimationRenderer(delegateRenderer, this.engine); + } else if (type?.data?.animation) { + this.useAnimationRenderer(hostElement, type, delegateRenderer, this.engine); + } + } else { + // We wait for the module to be loaded to use the animation renderer + this._moduleLoaded?.then((engine) => { + if (!hostElement || !type?.data?.animation) { + this.useBaseAnimationRenderer(delegateRenderer, engine); + } else if (type?.data?.animation) { + this.useAnimationRenderer(hostElement, type, delegateRenderer, engine); + } + }); + } + + return this._renderer; + } + + /** @internal */ + private useBaseAnimationRenderer(delegate: Renderer2, engine: AnimationEngine): void { + // cache the delegates to find out which cached delegate can + // be used by which cached renderer + const EMPTY_NAMESPACE_ID = ''; + + let cachedRenderer: BaseAnimationRenderer|undefined = this.rendererCache.get(delegate); + if (!cachedRenderer) { + // Ensure that the renderer is removed from the cache on destroy + // since it may contain references to detached DOM nodes. + const onRendererDestroy = () => this.rendererCache.delete(delegate); + cachedRenderer = + new BaseAnimationRenderer(EMPTY_NAMESPACE_ID, delegate, engine, onRendererDestroy); + // only cache this result when the base renderer is used + this.rendererCache.set(delegate, cachedRenderer); + } + this._renderer?.use(cachedRenderer); + } + + /** @internal */ + private useAnimationRenderer( + hostElement: any, type: RendererType2, delegate: Renderer2, engine: AnimationEngine): void { + const componentId = type.id; + const namespaceId = type.id + '-' + this.currentId; + this.currentId++; + + engine.register(namespaceId, hostElement); + + const registerTrigger = (trigger: NestedAnimationTriggerMetadata) => { + if (Array.isArray(trigger)) { + trigger.forEach(registerTrigger); + } else { + engine.registerTrigger(componentId, namespaceId, hostElement, trigger.name, trigger); + } + }; + const animationTriggers = type.data.animation as NestedAnimationTriggerMetadata[]; + animationTriggers.forEach(registerTrigger); + + this._renderer?.use(new AnimationRenderer(this, namespaceId, delegate, engine)); + } +} + +export class DynamicDelegationRenderer implements Renderer2 { + constructor(private delegate: Renderer2) {} + + use(impl: Renderer2) { + this.delegate = impl; + } + + get data(): {[key: string]: any;} { + return this.delegate.data; + } + + destroy(): void { + this.delegate.destroy(); + } + + createElement(name: string, namespace?: string|null) { + return this.delegate.createElement(name, namespace); + } + + createComment(value: string): void { + return this.delegate.createComment(value); + } + + createText(value: string): any { + return this.delegate.createText(value); + } + + get destroyNode(): ((node: any) => void)|null { + return this.delegate.destroyNode; + } + + appendChild(parent: any, newChild: any): void { + this.delegate.appendChild(parent, newChild); + } + + insertBefore(parent: any, newChild: any, refChild: any, isMove?: boolean|undefined): void { + this.delegate.insertBefore(parent, newChild, refChild, isMove); + } + + removeChild(parent: any, oldChild: any, isHostElement?: boolean|undefined): void { + this.delegate.removeChild(parent, oldChild, isHostElement); + } + + selectRootElement(selectorOrNode: any, preserveContent?: boolean|undefined): any { + return this.delegate.selectRootElement(selectorOrNode, preserveContent); + } + + parentNode(node: any): any { + return this.delegate.parentNode(node); + } + + nextSibling(node: any): any { + return this.delegate.nextSibling(node); + } + + setAttribute(el: any, name: string, value: string, namespace?: string|null|undefined): void { + this.delegate.setAttribute(el, name, value, namespace); + } + + removeAttribute(el: any, name: string, namespace?: string|null|undefined): void { + this.delegate.removeAttribute(el, name, namespace); + } + + addClass(el: any, name: string): void { + this.delegate.addClass(el, name); + } + + removeClass(el: any, name: string): void { + this.delegate.removeClass(el, name); + } + + setStyle(el: any, style: string, value: any, flags?: RendererStyleFlags2|undefined): void { + this.delegate.setStyle(el, style, value, flags); + } + + removeStyle(el: any, style: string, flags?: RendererStyleFlags2|undefined): void { + this.delegate.removeStyle(el, style, flags); + } + + setProperty(el: any, name: string, value: any): void { + this.delegate.setProperty(el, name, value); + } + + setValue(node: any, value: string): void { + this.delegate.setValue(node, value); + } + + listen(target: any, eventName: string, callback: (event: any) => boolean | void): () => void { + return this.delegate.listen(target, eventName, callback); + } +} diff --git a/packages/platform-browser/animations/src/lazy/errors.ts b/packages/platform-browser/animations/src/lazy/errors.ts new file mode 100644 index 00000000000000..68a5613a2ae77c --- /dev/null +++ b/packages/platform-browser/animations/src/lazy/errors.ts @@ -0,0 +1,14 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +/** + * The list of error codes used in runtime code of the `platform-browser/lazy-animation` package + * entry. Reserved error code range: 5950-5999. + */ +export const enum RuntimeErrorCode { + MODULE_LOADING_FAILED = 5950, +} diff --git a/packages/platform-browser/animations/src/lazy/lazy_providers.ts b/packages/platform-browser/animations/src/lazy/lazy_providers.ts new file mode 100644 index 00000000000000..e4541bbead7921 --- /dev/null +++ b/packages/platform-browser/animations/src/lazy/lazy_providers.ts @@ -0,0 +1,54 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {DOCUMENT} from '@angular/common'; +import {ANIMATION_MODULE_TYPE, ApplicationRef, NgZone, Provider, RendererFactory2} from '@angular/core'; +import {ɵDomRendererFactory2 as DomRendererFactory2} from '@angular/platform-browser'; + +import {AsyncAnimationRendererFactory} from './async_animation_renderer'; + +/** + * Returns the set of [dependency-injection providers](guide/glossary#provider) + * to enable animations in an application. See [animations guide](guide/animations) + * to learn more about animations in Angular. + * + * When you use this function instead of the eager `provideAnimations()`, animations won't be + * renderered until the renderer is loaded. + * + * @usageNotes + * + * The function is useful when you want to enable animations in an application + * bootstrapped using the `bootstrapApplication` function. In this scenario there + * is no need to import the `BrowserAnimationsModule` NgModule at all, just add + * providers returned by this function to the `providers` list as show below. + * + * ```typescript + * bootstrapApplication(RootComponent, { + * providers: [ + * provideLazyLoadedAnimations() + * ] + * }); + * ``` + * + * @param type pass `'noop'` as argument to disable animations. + * + * @publicApi + */ +export function provideLazyLoadedAnimations(type: 'animations'|'noop' = 'animations'): Provider[] { + return [ + { + provide: RendererFactory2, + useFactory: + (doc: Document, appRef: ApplicationRef, renderer: DomRendererFactory2, zone: NgZone) => { + return new AsyncAnimationRendererFactory(doc, appRef, renderer, zone, type); + }, + deps: [DOCUMENT, ApplicationRef, DomRendererFactory2, NgZone] + }, + {provide: ANIMATION_MODULE_TYPE, useValue: 'BrowserAnimations'} + ]; +} diff --git a/packages/platform-browser/animations/src/module.ts b/packages/platform-browser/animations/src/module.ts index c774a712514f2e..a117e9d461d22e 100644 --- a/packages/platform-browser/animations/src/module.ts +++ b/packages/platform-browser/animations/src/module.ts @@ -86,6 +86,14 @@ export function provideAnimations(): Provider[] { return [...BROWSER_ANIMATIONS_PROVIDERS]; } +/** + * + * @publicApi + */ +// export function provideLazyLoadedAnimations(): Provider[] { +// return [...LAZY_LOADED_ANIMATIONS_PROVIDERS]; +// } + /** * A null player that must be imported to allow disabling of animations. * @publicApi diff --git a/packages/platform-browser/animations/src/private_export.ts b/packages/platform-browser/animations/src/private_export.ts index bb1384e89db83b..daf82aa0e0ec35 100644 --- a/packages/platform-browser/animations/src/private_export.ts +++ b/packages/platform-browser/animations/src/private_export.ts @@ -5,6 +5,7 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -export {BrowserAnimationBuilder as ɵBrowserAnimationBuilder, BrowserAnimationFactory as ɵBrowserAnimationFactory} from './animation_builder'; -export {AnimationRenderer as ɵAnimationRenderer, AnimationRendererFactory as ɵAnimationRendererFactory} from './animation_renderer'; -export {InjectableAnimationEngine as ɵInjectableAnimationEngine} from './providers'; +export {BrowserAnimationBuilder as ɵBrowserAnimationBuilder} from './animation_builder'; +export {AnimationRendererFactory as ɵAnimationRendererFactory} from './animation_renderer'; +export {AbstractAnimationRendererFactory as ɵAbstractAnimationRendererFactory, AnimationRenderer as ɵAnimationRenderer, BaseAnimationRenderer as ɵBaseAnimationRenderer, NestedAnimationTriggerMetadata as ɵNestedAnimationTriggerMetadata} from './lazy/abstract_animation_renderer'; +export {AsyncAnimationRendererFactory as ɵAsyncAnimationRendererFactory, DynamicDelegationRenderer as ɵDynamicDelegationRenderer} from './lazy/async_animation_renderer'; diff --git a/packages/platform-browser/animations/src/providers.ts b/packages/platform-browser/animations/src/providers.ts index 954482067994c7..0d3b4b4697be2e 100644 --- a/packages/platform-browser/animations/src/providers.ts +++ b/packages/platform-browser/animations/src/providers.ts @@ -8,42 +8,22 @@ import {AnimationBuilder} from '@angular/animations'; import {AnimationDriver, ɵAnimationEngine as AnimationEngine, ɵAnimationStyleNormalizer as AnimationStyleNormalizer, ɵNoopAnimationDriver as NoopAnimationDriver, ɵWebAnimationsDriver as WebAnimationsDriver, ɵWebAnimationsStyleNormalizer as WebAnimationsStyleNormalizer} from '@angular/animations/browser'; -import {DOCUMENT} from '@angular/common'; -import {ANIMATION_MODULE_TYPE, ApplicationRef, Inject, Injectable, NgZone, OnDestroy, Provider, RendererFactory2} from '@angular/core'; +import {ANIMATION_MODULE_TYPE, NgZone, Provider, RendererFactory2} from '@angular/core'; import {ɵDomRendererFactory2 as DomRendererFactory2} from '@angular/platform-browser'; import {BrowserAnimationBuilder} from './animation_builder'; import {AnimationRendererFactory} from './animation_renderer'; -@Injectable() -export class InjectableAnimationEngine extends AnimationEngine implements OnDestroy { - // The `ApplicationRef` is injected here explicitly to force the dependency ordering. - // Since the `ApplicationRef` should be created earlier before the `AnimationEngine`, they - // both have `ngOnDestroy` hooks and `flush()` must be called after all views are destroyed. - constructor( - @Inject(DOCUMENT) doc: any, driver: AnimationDriver, normalizer: AnimationStyleNormalizer, - appRef: ApplicationRef) { - super(doc.body, driver, normalizer); - } - - ngOnDestroy(): void { - this.flush(); - } -} - -export function instantiateDefaultStyleNormalizer() { - return new WebAnimationsStyleNormalizer(); -} - -export function instantiateRendererFactory( - renderer: DomRendererFactory2, engine: AnimationEngine, zone: NgZone) { +function instantiateRendererFactory( + renderer: DomRendererFactory2, + engine: AnimationEngine, + zone: NgZone, +) { return new AnimationRendererFactory(renderer, engine, zone); } const SHARED_ANIMATION_PROVIDERS: Provider[] = [ - {provide: AnimationBuilder, useClass: BrowserAnimationBuilder}, - {provide: AnimationStyleNormalizer, useFactory: instantiateDefaultStyleNormalizer}, - {provide: AnimationEngine, useClass: InjectableAnimationEngine}, { + {provide: AnimationBuilder, useClass: BrowserAnimationBuilder}, { provide: RendererFactory2, useFactory: instantiateRendererFactory, deps: [DomRendererFactory2, AnimationEngine, NgZone] @@ -55,6 +35,7 @@ const SHARED_ANIMATION_PROVIDERS: Provider[] = [ * include them in the BrowserModule. */ export const BROWSER_ANIMATIONS_PROVIDERS: Provider[] = [ + {provide: AnimationStyleNormalizer, useClass: WebAnimationsStyleNormalizer}, {provide: AnimationDriver, useFactory: () => new WebAnimationsDriver()}, {provide: ANIMATION_MODULE_TYPE, useValue: 'BrowserAnimations'}, ...SHARED_ANIMATION_PROVIDERS ]; diff --git a/packages/platform-browser/animations/test/animation_renderer_spec.ts b/packages/platform-browser/animations/test/animation_renderer_spec.ts index ea26fe448bfbda..726dd0b5ab3eeb 100644 --- a/packages/platform-browser/animations/test/animation_renderer_spec.ts +++ b/packages/platform-browser/animations/test/animation_renderer_spec.ts @@ -5,18 +5,17 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import {animate, AnimationPlayer, AnimationTriggerMetadata, state, style, transition, trigger} from '@angular/animations'; +import {animate, AnimationPlayer, AnimationTriggerMetadata, style, transition, trigger} from '@angular/animations'; import {ɵAnimationEngine as AnimationEngine} from '@angular/animations/browser'; import {APP_INITIALIZER, Component, destroyPlatform, importProvidersFrom, Injectable, NgModule, NgZone, RendererFactory2, RendererType2, ViewChild} from '@angular/core'; import {TestBed} from '@angular/core/testing'; import {bootstrapApplication} from '@angular/platform-browser'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; -import {BrowserAnimationsModule, ɵAnimationRendererFactory as AnimationRendererFactory, ɵInjectableAnimationEngine as InjectableAnimationEngine} from '@angular/platform-browser/animations'; +import {BrowserAnimationsModule, ɵAnimationRendererFactory as AnimationRendererFactory} from '@angular/platform-browser/animations'; import {DomRendererFactory2} from '@angular/platform-browser/src/dom/dom_renderer'; +import {el} from '@angular/platform-browser/testing/src/browser_util'; import {withBody} from '@angular/private/testing'; -import {el} from '../../testing/src/browser_util'; - (function() { if (isNode) return; describe('AnimationRenderer', () => { @@ -133,6 +132,10 @@ describe('AnimationRenderer', () => { }); describe('flushing animations', () => { + beforeEach(() => { + TestBed.resetTestingModule(); + }); + // these tests are only meant to be run within the DOM if (isNode) return; @@ -153,10 +156,7 @@ describe('AnimationRenderer', () => { } } - TestBed.configureTestingModule({ - providers: [{provide: AnimationEngine, useClass: InjectableAnimationEngine}], - declarations: [Cmp] - }); + TestBed.configureTestingModule({declarations: [Cmp]}); const engine = TestBed.inject(AnimationEngine); const fixture = TestBed.createComponent(Cmp); @@ -189,10 +189,7 @@ describe('AnimationRenderer', () => { @ViewChild('elm') public element: any; } - TestBed.configureTestingModule({ - providers: [{provide: AnimationEngine, useClass: InjectableAnimationEngine}], - declarations: [Cmp] - }); + TestBed.configureTestingModule({declarations: [Cmp]}); const fixture = TestBed.createComponent(Cmp); const cmp = fixture.componentInstance; @@ -238,10 +235,7 @@ describe('AnimationRenderer', () => { @ViewChild('elm3') public elm3: any; } - TestBed.configureTestingModule({ - providers: [{provide: AnimationEngine, useClass: InjectableAnimationEngine}], - declarations: [Cmp] - }); + TestBed.configureTestingModule({declarations: [Cmp]}); const engine = TestBed.inject(AnimationEngine); const fixture = TestBed.createComponent(Cmp); @@ -308,10 +302,7 @@ describe('AnimationRendererFactory', () => { public exp: any; } - TestBed.configureTestingModule({ - providers: [{provide: AnimationEngine, useClass: InjectableAnimationEngine}], - declarations: [Cmp] - }); + TestBed.configureTestingModule({declarations: [Cmp]}); const renderer = TestBed.inject(RendererFactory2) as ExtendedAnimationRendererFactory; const fixture = TestBed.createComponent(Cmp); @@ -454,7 +445,7 @@ describe('destroy', () => { })(); @Injectable() -class MockAnimationEngine extends InjectableAnimationEngine { +class MockAnimationEngine extends AnimationEngine { captures: {[method: string]: any[]} = {}; triggers: AnimationTriggerMetadata[] = []; diff --git a/packages/platform-browser/animations/test/browser_animation_builder_spec.ts b/packages/platform-browser/animations/test/browser_animation_builder_spec.ts index 9f8e6f5112fee9..3332185485aff8 100644 --- a/packages/platform-browser/animations/test/browser_animation_builder_spec.ts +++ b/packages/platform-browser/animations/test/browser_animation_builder_spec.ts @@ -10,7 +10,7 @@ import {AnimationDriver} from '@angular/animations/browser'; import {MockAnimationDriver} from '@angular/animations/browser/testing'; import {Component, ViewChild} from '@angular/core'; import {fakeAsync, flushMicrotasks, TestBed} from '@angular/core/testing'; -import {NoopAnimationsModule, ɵBrowserAnimationBuilder as BrowserAnimationBuilder} from '@angular/platform-browser/animations'; +import {BrowserAnimationsModule, NoopAnimationsModule, ɵBrowserAnimationBuilder as BrowserAnimationBuilder} from '@angular/platform-browser/animations'; { describe('BrowserAnimationBuilder', () => { @@ -22,12 +22,17 @@ import {NoopAnimationsModule, ɵBrowserAnimationBuilder as BrowserAnimationBuild beforeEach(() => { TestBed.configureTestingModule({ - imports: [NoopAnimationsModule], + imports: [BrowserAnimationsModule], providers: [{provide: AnimationDriver, useClass: MockAnimationDriver}] }); }); it('should inject AnimationBuilder into a component', () => { + TestBed.resetTestingModule(); + TestBed.configureTestingModule({ + imports: [NoopAnimationsModule], + providers: [{provide: AnimationDriver, useClass: MockAnimationDriver}] + }); @Component({ selector: 'ani-cmp', template: '...', diff --git a/packages/platform-browser/animations/test/noop_animations_module_spec.ts b/packages/platform-browser/animations/test/noop_animations_module_spec.ts index 67e19d593359fc..22c21d86fc1dab 100644 --- a/packages/platform-browser/animations/test/noop_animations_module_spec.ts +++ b/packages/platform-browser/animations/test/noop_animations_module_spec.ts @@ -38,10 +38,10 @@ describe('provideNoopAnimations()', () => { function noopAnimationTests() { - it('should flush and fire callbacks when the zone becomes stable', (async) => { + it('should flush and fire callbacks when the zone becomes stable', (done) => { // This test is only meant to be run inside the browser. if (isNode) { - async(); + done(); return; } @@ -77,7 +77,7 @@ function noopAnimationTests() { expect(cmp.startEvent.phaseName).toEqual('start'); expect(cmp.doneEvent.triggerName).toEqual('myAnimation'); expect(cmp.doneEvent.phaseName).toEqual('done'); - async(); + done(); }); }); diff --git a/packages/platform-browser/lazy-animations/BUILD.bazel b/packages/platform-browser/lazy-animations/BUILD.bazel new file mode 100644 index 00000000000000..3929fb0c2ea9f6 --- /dev/null +++ b/packages/platform-browser/lazy-animations/BUILD.bazel @@ -0,0 +1,46 @@ +load("//tools:defaults.bzl", "api_golden_test", "ng_module", "tsec_test") + +package(default_visibility = ["//visibility:public"]) + +exports_files(["package.json"]) + +ng_module( + name = "lazy-animations", + srcs = glob( + [ + "*.ts", + "src/**/*.ts", + ], + ), + deps = [ + "//packages/animations", + "//packages/animations/browser", + "//packages/common", + "//packages/core", + "//packages/platform-browser", + ], +) + +tsec_test( + name = "tsec_test", + target = "lazy-animations", + tsconfig = "//packages:tsec_config", +) + +api_golden_test( + name = "platform-browser-lazy-animations_errors", + data = [ + "//goldens:public-api", + "//packages/platform-browser/lazy-animations", + ], + entry_point = "angular/packages/platform-browser/lazy-animations/src/errors.d.ts", + golden = "angular/goldens/public-api/platform-browser/lazy-animations/errors.md", +) + +filegroup( + name = "files_for_docgen", + srcs = glob([ + "*.ts", + "src/**/*.ts", + ]) + ["PACKAGE.md"], +) diff --git a/packages/platform-browser/lazy-animations/PACKAGE.md b/packages/platform-browser/lazy-animations/PACKAGE.md new file mode 100644 index 00000000000000..fdefd66dc46bcc --- /dev/null +++ b/packages/platform-browser/lazy-animations/PACKAGE.md @@ -0,0 +1 @@ +Provides a lazy loaded infrastructure for the rendering of animations in supported browsers. \ No newline at end of file diff --git a/packages/platform-browser/lazy-animations/index.ts b/packages/platform-browser/lazy-animations/index.ts new file mode 100644 index 00000000000000..7ef6571cd58831 --- /dev/null +++ b/packages/platform-browser/lazy-animations/index.ts @@ -0,0 +1,14 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +// This file is not used to build this module. It is only used during editing +// by the TypeScript language service and during build for verification. `ngc` +// replaces this file with production index.ts when it rewrites private symbol +// names. + +export * from './public_api'; diff --git a/packages/platform-browser/lazy-animations/public_api.ts b/packages/platform-browser/lazy-animations/public_api.ts new file mode 100644 index 00000000000000..e7a74aa6d4dde8 --- /dev/null +++ b/packages/platform-browser/lazy-animations/public_api.ts @@ -0,0 +1,14 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +/** + * @module + * @description + * Entry point for all public APIs of this package. + */ +export * from './src/lazy-animations'; diff --git a/packages/platform-browser/lazy-animations/src/abstract_animation_renderer.ts b/packages/platform-browser/lazy-animations/src/abstract_animation_renderer.ts new file mode 100644 index 00000000000000..ff27a13b5e2ad9 --- /dev/null +++ b/packages/platform-browser/lazy-animations/src/abstract_animation_renderer.ts @@ -0,0 +1,277 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import type {AnimationTriggerMetadata} from '@angular/animations'; +import type {ɵAnimationEngine as AnimationEngine} from '@angular/animations/browser'; +import {Injectable, NgZone, Renderer2, RendererFactory2, RendererStyleFlags2, RendererType2} from '@angular/core'; + +const ANIMATION_PREFIX = '@'; +const DISABLE_ANIMATIONS_FLAG = '@.disabled'; + +// Define a recursive type to allow for nested arrays of `AnimationTriggerMetadata`. +export type NestedAnimationTriggerMetadata = + AnimationTriggerMetadata|RecursiveAnimationTriggerMetadata; +type RecursiveAnimationTriggerMetadata = Array; + +/** + * To preserve lazy-loading capabilities, the lazy-animation module cannot import from + * `platform-browser/animations` + * + * + * This base class is used to share the behavior between `AnimationRendererFactory` and + * `AsyncAnimationRendererFactory` + */ +@Injectable() +export abstract class AbstractAnimationRendererFactory implements RendererFactory2 { + private _microtaskId: number = 1; + private _animationCallbacksBuffer: [(e: any) => any, any][] = []; + private _cdRecurDepth = 0; + + protected currentId: number = 0; + protected rendererCache = new Map(); + protected promise: Promise = Promise.resolve(0); + protected engine: AnimationEngine|null = null; + + constructor(protected delegate: RendererFactory2, private _zone: NgZone) {} + + abstract createRenderer(hostElement: any, type: RendererType2|null): Renderer2; + + begin() { + this._cdRecurDepth++; + if (this.delegate.begin) { + this.delegate.begin(); + } + } + + private _scheduleCountTask() { + // always use promise to schedule microtask instead of use Zone + this.promise.then(() => { + this._microtaskId++; + }); + } + + /** @internal */ + scheduleListenerCallback(count: number, fn: (e: any) => any, data: any) { + if (count >= 0 && count < this._microtaskId) { + this._zone.run(() => fn(data)); + return; + } + + if (this._animationCallbacksBuffer.length == 0) { + Promise.resolve(null).then(() => { + this._zone.run(() => { + this._animationCallbacksBuffer.forEach((tuple) => { + const [fn, data] = tuple; + fn(data); + }); + this._animationCallbacksBuffer = []; + }); + }); + } + + this._animationCallbacksBuffer.push([fn, data]); + } + + end() { + this._cdRecurDepth--; + + // this is to prevent animations from running twice when an inner + // component does CD when a parent component instead has inserted it + if (this._cdRecurDepth == 0) { + this._zone.runOutsideAngular(() => { + this._scheduleCountTask(); + this.engine?.flush(this._microtaskId); + }); + } + if (this.delegate.end) { + this.delegate.end(); + } + } + + whenRenderingDone(): Promise { + return this.engine?.whenRenderingDone() ?? Promise.resolve(); + } + + protected registerEngineRemovalComplete(engine: AnimationEngine) { + engine.onRemovalComplete = (element: any, delegate: Renderer2) => { + // Note: if a component element has a leave animation, and a host leave animation, + // the view engine will call `removeChild` for the parent + // component renderer as well as for the child component renderer. + // Therefore, we need to check if we already removed the element. + const parentNode = delegate?.parentNode(element); + if (parentNode) { + delegate.removeChild(parentNode, element); + } + }; + } +} + +export class BaseAnimationRenderer implements Renderer2 { + constructor( + protected namespaceId: string, public delegate: Renderer2, public engine: AnimationEngine, + private _onDestroy?: () => void) { + this.destroyNode = this.delegate.destroyNode ? (n) => delegate.destroyNode!(n) : null; + } + + get data() { + return this.delegate.data; + } + + destroyNode: ((n: any) => void)|null; + + destroy(): void { + this.engine.destroy(this.namespaceId, this.delegate); + this.delegate.destroy(); + this._onDestroy?.(); + } + + createElement(name: string, namespace?: string|null|undefined) { + return this.delegate.createElement(name, namespace); + } + + createComment(value: string) { + return this.delegate.createComment(value); + } + + createText(value: string) { + return this.delegate.createText(value); + } + + appendChild(parent: any, newChild: any): void { + this.delegate.appendChild(parent, newChild); + this.engine.onInsert(this.namespaceId, newChild, parent, false); + } + + insertBefore(parent: any, newChild: any, refChild: any, isMove: boolean = true): void { + this.delegate.insertBefore(parent, newChild, refChild); + // If `isMove` true than we should animate this insert. + this.engine.onInsert(this.namespaceId, newChild, parent, isMove); + } + + removeChild(parent: any, oldChild: any, isHostElement: boolean): void { + this.engine.onRemove(this.namespaceId, oldChild, this.delegate); + } + + selectRootElement(selectorOrNode: any, preserveContent?: boolean) { + return this.delegate.selectRootElement(selectorOrNode, preserveContent); + } + + parentNode(node: any) { + return this.delegate.parentNode(node); + } + + nextSibling(node: any) { + return this.delegate.nextSibling(node); + } + + setAttribute(el: any, name: string, value: string, namespace?: string|null|undefined): void { + this.delegate.setAttribute(el, name, value, namespace); + } + + removeAttribute(el: any, name: string, namespace?: string|null|undefined): void { + this.delegate.removeAttribute(el, name, namespace); + } + + addClass(el: any, name: string): void { + this.delegate.addClass(el, name); + } + + removeClass(el: any, name: string): void { + this.delegate.removeClass(el, name); + } + + setStyle(el: any, style: string, value: any, flags?: RendererStyleFlags2|undefined): void { + this.delegate.setStyle(el, style, value, flags); + } + + removeStyle(el: any, style: string, flags?: RendererStyleFlags2|undefined): void { + this.delegate.removeStyle(el, style, flags); + } + + setProperty(el: any, name: string, value: any): void { + if (name.charAt(0) == ANIMATION_PREFIX && name == DISABLE_ANIMATIONS_FLAG) { + this.disableAnimations(el, !!value); + } else { + this.delegate.setProperty(el, name, value); + } + } + + setValue(node: any, value: string): void { + this.delegate.setValue(node, value); + } + + listen(target: any, eventName: string, callback: (event: any) => boolean | void): () => void { + return this.delegate.listen(target, eventName, callback); + } + + protected disableAnimations(element: any, value: boolean) { + this.engine.disableAnimations(element, value); + } +} + +export class AnimationRenderer extends BaseAnimationRenderer implements Renderer2 { + constructor( + public factory: AbstractAnimationRendererFactory, namespaceId: string, delegate: Renderer2, + engine: AnimationEngine, onDestroy?: () => void) { + super(namespaceId, delegate, engine, onDestroy); + this.namespaceId = namespaceId; + } + + override setProperty(el: any, name: string, value: any): void { + if (name.charAt(0) == ANIMATION_PREFIX) { + if (name.charAt(1) == '.' && name == DISABLE_ANIMATIONS_FLAG) { + value = value === undefined ? true : !!value; + this.disableAnimations(el, value as boolean); + } else { + this.engine.process(this.namespaceId, el, name.slice(1), value); + } + } else { + this.delegate.setProperty(el, name, value); + } + } + + override listen( + target: 'window'|'document'|'body'|any, eventName: string, + callback: (event: any) => any): () => void { + if (eventName.charAt(0) == ANIMATION_PREFIX) { + const element = resolveElementFromTarget(target); + let name = eventName.slice(1); + let phase = ''; + // @listener.phase is for trigger animation callbacks + // @@listener is for animation builder callbacks + if (name.charAt(0) != ANIMATION_PREFIX) { + [name, phase] = parseTriggerCallbackName(name); + } + return this.engine.listen(this.namespaceId, element, name, phase, event => { + const countId = (event as any)['_data'] || -1; + this.factory.scheduleListenerCallback(countId, callback, event); + }); + } + return this.delegate.listen(target, eventName, callback); + } +} + +function resolveElementFromTarget(target: 'window'|'document'|'body'|any): any { + switch (target) { + case 'body': + return document.body; + case 'document': + return document; + case 'window': + return window; + default: + return target; + } +} + +function parseTriggerCallbackName(triggerName: string) { + const dotIndex = triggerName.indexOf('.'); + const trigger = triggerName.substring(0, dotIndex); + const phase = triggerName.slice(dotIndex + 1); + return [trigger, phase]; +} diff --git a/packages/platform-browser/lazy-animations/src/async_animation_renderer.ts b/packages/platform-browser/lazy-animations/src/async_animation_renderer.ts new file mode 100644 index 00000000000000..b0bea485629849 --- /dev/null +++ b/packages/platform-browser/lazy-animations/src/async_animation_renderer.ts @@ -0,0 +1,216 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {ɵAnimationEngine as AnimationEngine, type ɵcreateEngine as createEngine} from '@angular/animations/browser'; +import {ɵDefaultDomRenderer2 as DefaultDomRenderer2} from '@angular/platform-browser'; +import {DOCUMENT} from '@angular/common'; +import {ApplicationRef, Inject, Injectable, NgZone, Renderer2, RendererFactory2, RendererStyleFlags2, RendererType2, ɵRuntimeError as RuntimeError} from '@angular/core'; + +import {AbstractAnimationRendererFactory, AnimationRenderer, BaseAnimationRenderer, NestedAnimationTriggerMetadata} from './abstract_animation_renderer'; +import {RuntimeErrorCode} from './errors'; + +@Injectable() +export class AsyncAnimationRendererFactory extends AbstractAnimationRendererFactory { + private _moduleLoaded: Promise|null = null; + private _renderer: DynamicDelegationRenderer|null = null; + + constructor( + @Inject(DOCUMENT) private doc: Document, private applicationRef: ApplicationRef, + delegate: RendererFactory2, zone: NgZone, type: 'animations'|'noop', + moduleImpl: Promise = + import('@angular/animations/browser').then(m => m.ɵcreateEngine)) { + super(delegate, zone); + + this._moduleLoaded = this.loadImpl(moduleImpl, type); + } + + /** + * @internal + */ + private loadImpl(moduleImpl: Promise, type: 'animations'|'noop'): + Promise { + return moduleImpl + .catch((e) => { + throw new RuntimeError( + RuntimeErrorCode.MODULE_LOADING_FAILED, + (typeof ngDevMode !== 'undefined' && ngDevMode) && + 'Failed to load the @angular/animations/browser module'); + }) + .then((createEngine) => { + // We can't create the renderer yet because we might need the hostElement and the type + // Both are provided in createRenderer(), so we first create the engine + + this.engine = createEngine(type, this.applicationRef, this.doc); + this.registerEngineRemovalComplete(this.engine); + return this.engine; + }); + } + + createRenderer(hostElement: any, type: RendererType2): Renderer2 { + const delegateRenderer = this.delegate.createRenderer(hostElement, type); + this._renderer = new DynamicDelegationRenderer(delegateRenderer); + + // We need to prevent the DomRenderer to throw an error because of synthetic properties + if (delegateRenderer instanceof DefaultDomRenderer2) { + delegateRenderer.disableCheck = true; + } + + if (this.engine) { + // The module is already loaded + if (!hostElement || !type?.data?.animation) { + this.useBaseAnimationRenderer(delegateRenderer, this.engine); + } else if (type?.data?.animation) { + this.useAnimationRenderer(hostElement, type, delegateRenderer, this.engine); + } + } else { + // We wait for the module to be loaded to use the animation renderer + this._moduleLoaded?.then((engine) => { + if (!hostElement || !type?.data?.animation) { + this.useBaseAnimationRenderer(delegateRenderer, engine); + } else if (type?.data?.animation) { + this.useAnimationRenderer(hostElement, type, delegateRenderer, engine); + } + }); + } + + return this._renderer; + } + + /** @internal */ + private useBaseAnimationRenderer(delegate: Renderer2, engine: AnimationEngine): void { + // cache the delegates to find out which cached delegate can + // be used by which cached renderer + const EMPTY_NAMESPACE_ID = ''; + + let cachedRenderer: BaseAnimationRenderer|undefined = this.rendererCache.get(delegate); + if (!cachedRenderer) { + // Ensure that the renderer is removed from the cache on destroy + // since it may contain references to detached DOM nodes. + const onRendererDestroy = () => this.rendererCache.delete(delegate); + cachedRenderer = + new BaseAnimationRenderer(EMPTY_NAMESPACE_ID, delegate, engine, onRendererDestroy); + // only cache this result when the base renderer is used + this.rendererCache.set(delegate, cachedRenderer); + } + this._renderer?.use(cachedRenderer); + } + + /** @internal */ + private useAnimationRenderer( + hostElement: any, type: RendererType2, delegate: Renderer2, engine: AnimationEngine): void { + const componentId = type.id; + const namespaceId = type.id + '-' + this.currentId; + this.currentId++; + + engine.register(namespaceId, hostElement); + + const registerTrigger = (trigger: NestedAnimationTriggerMetadata) => { + if (Array.isArray(trigger)) { + trigger.forEach(registerTrigger); + } else { + engine.registerTrigger(componentId, namespaceId, hostElement, trigger.name, trigger); + } + }; + const animationTriggers = type.data.animation as NestedAnimationTriggerMetadata[]; + animationTriggers.forEach(registerTrigger); + + this._renderer?.use(new AnimationRenderer(this, namespaceId, delegate, engine)); + } +} + +export class DynamicDelegationRenderer implements Renderer2 { + constructor(private delegate: Renderer2) {} + + use(impl: Renderer2) { + this.delegate = impl; + } + + get data(): {[key: string]: any;} { + return this.delegate.data; + } + + destroy(): void { + this.delegate.destroy(); + } + + createElement(name: string, namespace?: string|null) { + return this.delegate.createElement(name, namespace); + } + + createComment(value: string): void { + return this.delegate.createComment(value); + } + + createText(value: string): any { + return this.delegate.createText(value); + } + + get destroyNode(): ((node: any) => void)|null { + return this.delegate.destroyNode; + } + + appendChild(parent: any, newChild: any): void { + this.delegate.appendChild(parent, newChild); + } + + insertBefore(parent: any, newChild: any, refChild: any, isMove?: boolean|undefined): void { + this.delegate.insertBefore(parent, newChild, refChild, isMove); + } + + removeChild(parent: any, oldChild: any, isHostElement?: boolean|undefined): void { + this.delegate.removeChild(parent, oldChild, isHostElement); + } + + selectRootElement(selectorOrNode: any, preserveContent?: boolean|undefined): any { + return this.delegate.selectRootElement(selectorOrNode, preserveContent); + } + + parentNode(node: any): any { + return this.delegate.parentNode(node); + } + + nextSibling(node: any): any { + return this.delegate.nextSibling(node); + } + + setAttribute(el: any, name: string, value: string, namespace?: string|null|undefined): void { + this.delegate.setAttribute(el, name, value, namespace); + } + + removeAttribute(el: any, name: string, namespace?: string|null|undefined): void { + this.delegate.removeAttribute(el, name, namespace); + } + + addClass(el: any, name: string): void { + this.delegate.addClass(el, name); + } + + removeClass(el: any, name: string): void { + this.delegate.removeClass(el, name); + } + + setStyle(el: any, style: string, value: any, flags?: RendererStyleFlags2|undefined): void { + this.delegate.setStyle(el, style, value, flags); + } + + removeStyle(el: any, style: string, flags?: RendererStyleFlags2|undefined): void { + this.delegate.removeStyle(el, style, flags); + } + + setProperty(el: any, name: string, value: any): void { + this.delegate.setProperty(el, name, value); + } + + setValue(node: any, value: string): void { + this.delegate.setValue(node, value); + } + + listen(target: any, eventName: string, callback: (event: any) => boolean | void): () => void { + return this.delegate.listen(target, eventName, callback); + } +} diff --git a/packages/platform-browser/lazy-animations/src/errors.ts b/packages/platform-browser/lazy-animations/src/errors.ts new file mode 100644 index 00000000000000..68a5613a2ae77c --- /dev/null +++ b/packages/platform-browser/lazy-animations/src/errors.ts @@ -0,0 +1,14 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +/** + * The list of error codes used in runtime code of the `platform-browser/lazy-animation` package + * entry. Reserved error code range: 5950-5999. + */ +export const enum RuntimeErrorCode { + MODULE_LOADING_FAILED = 5950, +} diff --git a/packages/platform-browser/lazy-animations/src/lazy-animations.ts b/packages/platform-browser/lazy-animations/src/lazy-animations.ts new file mode 100644 index 00000000000000..9e3381768d628a --- /dev/null +++ b/packages/platform-browser/lazy-animations/src/lazy-animations.ts @@ -0,0 +1,16 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +/** + * @module + * @description + * Entry point for all animation APIs of the animation browser package. + */ +export {provideLazyLoadedAnimations} from './lazy_providers'; + +export * from './private_export'; diff --git a/packages/platform-browser/lazy-animations/src/lazy_providers.ts b/packages/platform-browser/lazy-animations/src/lazy_providers.ts new file mode 100644 index 00000000000000..e4541bbead7921 --- /dev/null +++ b/packages/platform-browser/lazy-animations/src/lazy_providers.ts @@ -0,0 +1,54 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {DOCUMENT} from '@angular/common'; +import {ANIMATION_MODULE_TYPE, ApplicationRef, NgZone, Provider, RendererFactory2} from '@angular/core'; +import {ɵDomRendererFactory2 as DomRendererFactory2} from '@angular/platform-browser'; + +import {AsyncAnimationRendererFactory} from './async_animation_renderer'; + +/** + * Returns the set of [dependency-injection providers](guide/glossary#provider) + * to enable animations in an application. See [animations guide](guide/animations) + * to learn more about animations in Angular. + * + * When you use this function instead of the eager `provideAnimations()`, animations won't be + * renderered until the renderer is loaded. + * + * @usageNotes + * + * The function is useful when you want to enable animations in an application + * bootstrapped using the `bootstrapApplication` function. In this scenario there + * is no need to import the `BrowserAnimationsModule` NgModule at all, just add + * providers returned by this function to the `providers` list as show below. + * + * ```typescript + * bootstrapApplication(RootComponent, { + * providers: [ + * provideLazyLoadedAnimations() + * ] + * }); + * ``` + * + * @param type pass `'noop'` as argument to disable animations. + * + * @publicApi + */ +export function provideLazyLoadedAnimations(type: 'animations'|'noop' = 'animations'): Provider[] { + return [ + { + provide: RendererFactory2, + useFactory: + (doc: Document, appRef: ApplicationRef, renderer: DomRendererFactory2, zone: NgZone) => { + return new AsyncAnimationRendererFactory(doc, appRef, renderer, zone, type); + }, + deps: [DOCUMENT, ApplicationRef, DomRendererFactory2, NgZone] + }, + {provide: ANIMATION_MODULE_TYPE, useValue: 'BrowserAnimations'} + ]; +} diff --git a/packages/platform-browser/lazy-animations/src/private_export.ts b/packages/platform-browser/lazy-animations/src/private_export.ts new file mode 100644 index 00000000000000..40ae27e6e93f55 --- /dev/null +++ b/packages/platform-browser/lazy-animations/src/private_export.ts @@ -0,0 +1,9 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +export {AbstractAnimationRendererFactory as ɵAbstractAnimationRendererFactory, AnimationRenderer as ɵAnimationRenderer, BaseAnimationRenderer as ɵBaseAnimationRenderer, NestedAnimationTriggerMetadata as ɵNestedAnimationTriggerMetadata} from './abstract_animation_renderer'; +export {AsyncAnimationRendererFactory as ɵAsyncAnimationRendererFactory, DynamicDelegationRenderer as ɵDynamicDelegationRenderer} from './async_animation_renderer'; diff --git a/packages/platform-browser/lazy-animations/test/BUILD.bazel b/packages/platform-browser/lazy-animations/test/BUILD.bazel new file mode 100644 index 00000000000000..5044cd30fd9ec6 --- /dev/null +++ b/packages/platform-browser/lazy-animations/test/BUILD.bazel @@ -0,0 +1,46 @@ +load("//tools:defaults.bzl", "jasmine_node_test", "karma_web_test_suite", "ts_library") +load("//tools/circular_dependency_test:index.bzl", "circular_dependency_test") + +circular_dependency_test( + name = "circular_deps_test", + entry_point = "angular/packages/platform-browser/animations/index.mjs", + deps = ["//packages/platform-browser/animations"], +) + +ts_library( + name = "test_lib", + testonly = True, + srcs = glob(["**/*.ts"]), + deps = [ + "//packages:types", + "//packages/animations", + "//packages/animations/browser", + "//packages/animations/browser/testing", + "//packages/common", + "//packages/compiler", + "//packages/core", + "//packages/core/testing", + "//packages/platform-browser", + "//packages/platform-browser-dynamic", + "//packages/platform-browser/animations", + "//packages/platform-browser/lazy-animations", + "//packages/platform-browser/testing", + "//packages/private/testing", + "@npm//rxjs", + ], +) + +jasmine_node_test( + name = "test", + bootstrap = ["//tools/testing:node"], + deps = [ + ":test_lib", + ], +) + +karma_web_test_suite( + name = "test_web", + deps = [ + ":test_lib", + ], +) diff --git a/packages/platform-browser/lazy-animations/test/animation_renderer_spec.ts b/packages/platform-browser/lazy-animations/test/animation_renderer_spec.ts new file mode 100644 index 00000000000000..21a10639c8a24e --- /dev/null +++ b/packages/platform-browser/lazy-animations/test/animation_renderer_spec.ts @@ -0,0 +1,379 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +import {animate, AnimationPlayer, AnimationTriggerMetadata, style, transition, trigger} from '@angular/animations'; +import {ɵAnimationEngine as AnimationEngine} from '@angular/animations/browser'; +import {DOCUMENT} from '@angular/common'; +import {ANIMATION_MODULE_TYPE, ApplicationRef, Component, Injectable, NgZone, Renderer2, RendererFactory2, RendererType2, ViewChild} from '@angular/core'; +import {TestBed} from '@angular/core/testing'; +import {ɵDomRendererFactory2 as DomRendererFactory2} from '@angular/platform-browser'; +import {ɵAbstractAnimationRendererFactory as AbstractAnimationRendererFactory, ɵAsyncAnimationRendererFactory as AsyncAnimationRendererFactory, ɵDynamicDelegationRenderer as DynamicDelegationRenderer} from '@angular/platform-browser/lazy-animations'; +import {el} from '@angular/platform-browser/testing/src/browser_util'; + +type AnimationBrowserModule = typeof import('@angular/animations/browser'); + +(function() { +if (isNode) { + it('empty test so jasmine doesnt complain', () => {}); + return; +} + +describe('AnimationRenderer', () => { + let element: any; + beforeEach(() => { + element = el('
'); + + TestBed.configureTestingModule({ + providers: [ + { + provide: RendererFactory2, + useFactory: + (doc: Document, appRef: ApplicationRef, renderer: DomRendererFactory2, zone: NgZone, + engine: MockAnimationEngine) => { + const animationModule = { + ɵcreateEngine: (_: 'animations'|'noop', _1: ApplicationRef, _2: Document): + AnimationEngine => engine, + ɵAnimationEngine: MockAnimationEngine as any, + } satisfies Partialas AnimationBrowserModule; + + return new AsyncAnimationRendererFactory( + doc, appRef, renderer, zone, 'animations', + Promise.resolve(animationModule.ɵcreateEngine)); + }, + deps: [DOCUMENT, ApplicationRef, DomRendererFactory2, NgZone, AnimationEngine] + }, + {provide: ANIMATION_MODULE_TYPE, useValue: 'BrowserAnimations'}, + {provide: AnimationEngine, useClass: MockAnimationEngine} + ], + }); + }); + + function makeRenderer(animationTriggers: any[] = []): Promise { + const type = { + id: 'id', + encapsulation: null!, + styles: [], + data: {'animation': animationTriggers} + }; + const factory = TestBed.inject(RendererFactory2) as AsyncAnimationRendererFactory; + const renderer = factory.createRenderer(element, type); + return (factory as any)._moduleLoaded.then(() => renderer); + } + + it('should hook into the engine\'s insert operations when appending children', async () => { + const renderer = await makeRenderer(); + const engine = (renderer as any).delegate.engine as MockAnimationEngine; + const container = el('
'); + + renderer.appendChild(container, element); + expect(engine.captures['onInsert'].pop()).toEqual([element]); + }); + + it('should hook into the engine\'s insert operations when inserting a child before another', + async () => { + const renderer = await makeRenderer(); + const engine = (renderer as any).delegate.engine as MockAnimationEngine; + const container = el('
'); + const element2 = el('
'); + container.appendChild(element2); + + renderer.insertBefore(container, element, element2); + expect(engine.captures['onInsert'].pop()).toEqual([element]); + }); + + it('should hook into the engine\'s insert operations when removing children', async () => { + const renderer = await makeRenderer(); + const engine = (renderer as any).delegate.engine as MockAnimationEngine; + const container = el('
'); + + renderer.removeChild(container, element, false); + expect(engine.captures['onRemove'].pop()).toEqual([element]); + }); + + it('should hook into the engine\'s setProperty call if the property begins with `@`', + async () => { + const renderer = await makeRenderer(); + const engine = (renderer as any).delegate.engine as MockAnimationEngine; + + renderer.setProperty(element, 'prop', 'value'); + expect(engine.captures['setProperty']).toBeFalsy(); + + renderer.setProperty(element, '@prop', 'value'); + expect(engine.captures['setProperty'].pop()).toEqual([element, 'prop', 'value']); + }); + + // https://github.com/angular/angular/issues/32794 + it('should support nested animation triggers', async () => { + const renderer = await makeRenderer([[trigger('myAnimation', [])]]); + + const {triggers} = (renderer as any).delegate.engine as MockAnimationEngine; + + expect(triggers.length).toEqual(1); + expect(triggers[0].name).toEqual('myAnimation'); + }); + + describe('listen', () => { + it('should hook into the engine\'s listen call if the property begins with `@`', async () => { + const renderer = await makeRenderer(); + const engine = (renderer as any).delegate.engine as MockAnimationEngine; + + const cb = (event: any): boolean => { + return true; + }; + + renderer.listen(element, 'event', cb); + expect(engine.captures['listen']).toBeFalsy(); + + renderer.listen(element, '@event.phase', cb); + expect(engine.captures['listen'].pop()).toEqual([element, 'event', 'phase']); + }); + + it('should resolve the body|document|window nodes given their values as strings as input', + async () => { + const renderer = await makeRenderer(); + const engine = (renderer as any).delegate.engine as MockAnimationEngine; + + const cb = (event: any): boolean => { + return true; + }; + + renderer.listen('body', '@event', cb); + expect(engine.captures['listen'].pop()[0]).toBe(document.body); + + renderer.listen('document', '@event', cb); + expect(engine.captures['listen'].pop()[0]).toBe(document); + + renderer.listen('window', '@event', cb); + expect(engine.captures['listen'].pop()[0]).toBe(window); + }); + }); + + describe('registering animations', () => { + it('should only create a trigger definition once even if the registered multiple times'); + }); + + describe('flushing animations', () => { + beforeEach(() => { + TestBed.resetTestingModule(); + }); + + // these tests are only meant to be run within the DOM + if (isNode) return; + + it('should flush and fire callbacks when the zone becomes stable', (async) => { + @Component({ + selector: 'my-cmp', + template: '
', + animations: [trigger( + 'myAnimation', + [transition( + '* => state', [style({'opacity': '0'}), animate(500, style({'opacity': '1'}))])])], + }) + class Cmp { + exp: any; + event: any; + onStart(event: any) { + this.event = event; + } + } + + TestBed.configureTestingModule({declarations: [Cmp]}); + + const engine = TestBed.inject(AnimationEngine); + const fixture = TestBed.createComponent(Cmp); + const cmp = fixture.componentInstance; + cmp.exp = 'state'; + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(cmp.event.triggerName).toEqual('myAnimation'); + expect(cmp.event.phaseName).toEqual('start'); + cmp.event = null; + + engine.flush(); + expect(cmp.event).toBeFalsy(); + async(); + }); + }); + + it('should properly insert/remove nodes through the animation renderer that do not contain animations', + (async) => { + @Component({ + selector: 'my-cmp', + template: '
', + animations: [trigger( + 'someAnimation', + [transition( + '* => *', [style({'opacity': '0'}), animate(500, style({'opacity': '1'}))])])], + }) + class Cmp { + exp: any; + @ViewChild('elm') public element: any; + } + + TestBed.configureTestingModule({declarations: [Cmp]}); + + const fixture = TestBed.createComponent(Cmp); + const cmp = fixture.componentInstance; + cmp.exp = true; + fixture.detectChanges(); + + fixture.whenStable().then(() => { + cmp.exp = false; + const element = cmp.element; + expect(element.nativeElement.parentNode).toBeTruthy(); + + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(element.nativeElement.parentNode).toBeFalsy(); + async(); + }); + }); + }); + + it('should only queue up dom removals if the element itself contains a valid leave animation', + () => { + @Component({ + selector: 'my-cmp', + template: ` +
+
+
+ `, + animations: [ + trigger('animation1', [transition('a => b', [])]), + trigger('animation2', [transition(':leave', [])]), + ] + }) + class Cmp { + exp1: any = true; + exp2: any = true; + exp3: any = true; + + @ViewChild('elm1') public elm1: any; + + @ViewChild('elm2') public elm2: any; + + @ViewChild('elm3') public elm3: any; + } + + TestBed.configureTestingModule({declarations: [Cmp]}); + + const engine = TestBed.inject(AnimationEngine); + const fixture = TestBed.createComponent(Cmp); + const cmp = fixture.componentInstance; + + fixture.detectChanges(); + const elm1 = cmp.elm1; + const elm2 = cmp.elm2; + const elm3 = cmp.elm3; + assertHasParent(elm1); + assertHasParent(elm2); + assertHasParent(elm3); + engine.flush(); + finishPlayers(engine.players); + + cmp.exp1 = false; + fixture.detectChanges(); + assertHasParent(elm1, false); + assertHasParent(elm2); + assertHasParent(elm3); + engine.flush(); + expect(engine.players.length).toEqual(0); + + cmp.exp2 = false; + fixture.detectChanges(); + assertHasParent(elm1, false); + assertHasParent(elm2, false); + assertHasParent(elm3); + engine.flush(); + expect(engine.players.length).toEqual(0); + + cmp.exp3 = false; + fixture.detectChanges(); + assertHasParent(elm1, false); + assertHasParent(elm2, false); + assertHasParent(elm3); + engine.flush(); + expect(engine.players.length).toEqual(1); + }); + }); +}); +})(); + +@Injectable() +class MockAnimationEngine extends AnimationEngine { + captures: {[method: string]: any[]} = {}; + triggers: AnimationTriggerMetadata[] = []; + + private _capture(name: string, args: any[]) { + const data = this.captures[name] = this.captures[name] || []; + data.push(args); + } + + override registerTrigger( + componentId: string, namespaceId: string, hostElement: any, name: string, + metadata: AnimationTriggerMetadata): void { + this.triggers.push(metadata); + } + + override onInsert(namespaceId: string, element: any): void { + this._capture('onInsert', [element]); + } + + override onRemove(namespaceId: string, element: any, domFn: () => any): void { + this._capture('onRemove', [element]); + } + + override process(namespaceId: string, element: any, property: string, value: any): boolean { + this._capture('setProperty', [element, property, value]); + return true; + } + + override listen( + namespaceId: string, element: any, eventName: string, eventPhase: string, + callback: (event: any) => any): () => void { + // we don't capture the callback here since the renderer wraps it in a zone + this._capture('listen', [element, eventName, eventPhase]); + return () => {}; + } + + override flush() {} + + override destroy(namespaceId: string) {} +} + +@Injectable() +class ExtendedAnimationRendererFactory extends AbstractAnimationRendererFactory { + public log: string[] = []; + + override begin() { + super.begin(); + this.log.push('begin'); + } + + override end() { + super.end(); + this.log.push('end'); + } + + override createRenderer(hostElement: any, type: RendererType2|null): Renderer2 { + return {} as any; + } +} + +function assertHasParent(element: any, yes: boolean = true) { + const parent = element.nativeElement.parentNode; + if (yes) { + expect(parent).toBeTruthy(); + } else { + expect(parent).toBeFalsy(); + } +} + +function finishPlayers(players: AnimationPlayer[]) { + players.forEach(player => player.finish()); +} diff --git a/packages/platform-browser/src/dom/dom_renderer.ts b/packages/platform-browser/src/dom/dom_renderer.ts index 4d1d53b7a33120..d0ea64416521a4 100644 --- a/packages/platform-browser/src/dom/dom_renderer.ts +++ b/packages/platform-browser/src/dom/dom_renderer.ts @@ -144,8 +144,9 @@ export class DomRendererFactory2 implements RendererFactory2, OnDestroy { } } -class DefaultDomRenderer2 implements Renderer2 { +export class DefaultDomRenderer2 implements Renderer2 { data: {[key: string]: any} = Object.create(null); + disableCheck = false; constructor( private readonly eventManager: EventManager, private readonly doc: Document, @@ -191,7 +192,6 @@ class DefaultDomRenderer2 implements Renderer2 { targetParent.insertBefore(newChild, refChild); } } - removeChild(parent: any, oldChild: any): void { if (parent) { parent.removeChild(oldChild); @@ -274,7 +274,8 @@ class DefaultDomRenderer2 implements Renderer2 { } setProperty(el: any, name: string, value: any): void { - (typeof ngDevMode === 'undefined' || ngDevMode) && checkNoSyntheticProp(name, 'property'); + (typeof ngDevMode === 'undefined' || ngDevMode) && !this.disableCheck && + checkNoSyntheticProp(name, 'property'); el[name] = value; } @@ -284,7 +285,8 @@ class DefaultDomRenderer2 implements Renderer2 { listen(target: 'window'|'document'|'body'|any, event: string, callback: (event: any) => boolean): () => void { - (typeof ngDevMode === 'undefined' || ngDevMode) && checkNoSyntheticProp(event, 'listener'); + (typeof ngDevMode === 'undefined' || ngDevMode) && !this.disableCheck && + checkNoSyntheticProp(event, 'listener'); if (typeof target === 'string') { target = getDOM().getGlobalEventTarget(this.doc, target); if (!target) { diff --git a/packages/platform-browser/src/errors.ts b/packages/platform-browser/src/errors.ts index c0a7e128509392..296e11bd74a902 100644 --- a/packages/platform-browser/src/errors.ts +++ b/packages/platform-browser/src/errors.ts @@ -25,4 +25,8 @@ export const enum RuntimeErrorCode { SANITIZATION_UNSAFE_SCRIPT = 5200, SANITIZATION_UNSAFE_RESOURCE_URL = 5201, SANITIZATION_UNEXPECTED_CTX = 5202, + + + // 5900-5949 range is reserved for animations + // 5950-5999 range is reserved for lazy-animations } diff --git a/packages/platform-browser/src/private_export.ts b/packages/platform-browser/src/private_export.ts index ca172948f2b0b4..e5637744ee2522 100644 --- a/packages/platform-browser/src/private_export.ts +++ b/packages/platform-browser/src/private_export.ts @@ -10,7 +10,7 @@ export {ɵgetDOM} from '@angular/common'; export {initDomAdapter as ɵinitDomAdapter, INTERNAL_BROWSER_PLATFORM_PROVIDERS as ɵINTERNAL_BROWSER_PLATFORM_PROVIDERS} from './browser'; export {BrowserDomAdapter as ɵBrowserDomAdapter} from './browser/browser_adapter'; export {BrowserGetTestability as ɵBrowserGetTestability} from './browser/testability'; -export {DomRendererFactory2 as ɵDomRendererFactory2} from './dom/dom_renderer'; +export {DefaultDomRenderer2 as ɵDefaultDomRenderer2, DomRendererFactory2 as ɵDomRendererFactory2} from './dom/dom_renderer'; export {DomEventsPlugin as ɵDomEventsPlugin} from './dom/events/dom_events'; export {HammerGesturesPlugin as ɵHammerGesturesPlugin} from './dom/events/hammer_gestures'; export {KeyEventsPlugin as ɵKeyEventsPlugin} from './dom/events/key_events';