From 6c61d20476bf214176994dff4e46d6452bd821cf Mon Sep 17 00:00:00 2001 From: dario-piotrowicz Date: Sat, 12 Feb 2022 22:32:03 +0000 Subject: [PATCH 01/24] fix(animations): allow animations with unsupported CSS properties (#45185) currently animations with unsupported CSS properties cause a hard error and the crash of the animation itself, instead of this behaviour just ignore such properties and provide a warning for the developer in the console (only in dev mode) this change also introduces a general way to present warnings in the animations code resolves #23195 PR Close #45185 --- .../animations/browser/src/dsl/animation.ts | 7 ++- .../browser/src/dsl/animation_ast_builder.ts | 25 ++++++++--- .../src/render/animation_engine_next.ts | 9 +++- .../src/render/timeline_animation_engine.ts | 7 ++- .../animations/browser/src/warning_helpers.ts | 43 +++++++++++++++++++ .../browser/test/dsl/animation_spec.ts | 34 ++++++++++++--- .../transition_animation_engine_spec.ts | 4 +- packages/animations/browser/test/shared.ts | 7 ++- 8 files changed, 116 insertions(+), 20 deletions(-) create mode 100644 packages/animations/browser/src/warning_helpers.ts diff --git a/packages/animations/browser/src/dsl/animation.ts b/packages/animations/browser/src/dsl/animation.ts index f861efe91203..ba51897ac6da 100644 --- a/packages/animations/browser/src/dsl/animation.ts +++ b/packages/animations/browser/src/dsl/animation.ts @@ -10,6 +10,7 @@ import {AnimationMetadata, AnimationMetadataType, AnimationOptions, ɵStyleData} import {buildingFailed, validationFailed} from '../error_helpers'; import {AnimationDriver} from '../render/animation_driver'; import {ENTER_CLASSNAME, LEAVE_CLASSNAME, normalizeStyles} from '../util'; +import {warnValidation} from '../warning_helpers'; import {Ast} from './animation_ast'; import {buildAnimationAst} from './animation_ast_builder'; @@ -21,10 +22,14 @@ export class Animation { private _animationAst: Ast; constructor(private _driver: AnimationDriver, input: AnimationMetadata|AnimationMetadata[]) { const errors: Error[] = []; - const ast = buildAnimationAst(_driver, input, errors); + const warnings: string[] = []; + const ast = buildAnimationAst(_driver, input, errors, warnings); if (errors.length) { throw validationFailed(errors); } + if (warnings.length) { + warnValidation(warnings); + } this._animationAst = ast; } diff --git a/packages/animations/browser/src/dsl/animation_ast_builder.ts b/packages/animations/browser/src/dsl/animation_ast_builder.ts index fb49c29ea325..7b708533fbe2 100644 --- a/packages/animations/browser/src/dsl/animation_ast_builder.ts +++ b/packages/animations/browser/src/dsl/animation_ast_builder.ts @@ -11,6 +11,7 @@ import {invalidDefinition, invalidKeyframes, invalidOffset, invalidParallelAnima import {AnimationDriver} from '../render/animation_driver'; import {getOrSetAsInMap} from '../render/shared'; import {copyObj, extractStyleParams, iteratorToArray, NG_ANIMATING_SELECTOR, NG_TRIGGER_SELECTOR, normalizeAnimationEntry, resolveTiming, SUBSTITUTION_EXPR_START, validateStyleParams, visitDslNode} from '../util'; +import {pushUnrecognizedPropertiesWarning} from '../warning_helpers'; import {AnimateAst, AnimateChildAst, AnimateRefAst, Ast, DynamicTimingAst, GroupAst, KeyframesAst, QueryAst, ReferenceAst, SequenceAst, StaggerAst, StateAst, StyleAst, TimingAst, TransitionAst, TriggerAst} from './animation_ast'; import {AnimationDslVisitor} from './animation_dsl_visitor'; @@ -56,9 +57,9 @@ const SELF_TOKEN_REGEX = new RegExp(`\s*${SELF_TOKEN}\s*,?`, 'g'); * Otherwise an error will be thrown. */ export function buildAnimationAst( - driver: AnimationDriver, metadata: AnimationMetadata|AnimationMetadata[], - errors: Error[]): Ast { - return new AnimationAstBuilderVisitor(driver).build(metadata, errors); + driver: AnimationDriver, metadata: AnimationMetadata|AnimationMetadata[], errors: Error[], + warnings: string[]): Ast { + return new AnimationAstBuilderVisitor(driver).build(metadata, errors, warnings); } const ROOT_SELECTOR = ''; @@ -66,12 +67,20 @@ const ROOT_SELECTOR = ''; export class AnimationAstBuilderVisitor implements AnimationDslVisitor { constructor(private _driver: AnimationDriver) {} - build(metadata: AnimationMetadata|AnimationMetadata[], errors: Error[]): + build(metadata: AnimationMetadata|AnimationMetadata[], errors: Error[], warnings: string[]): Ast { const context = new AnimationAstBuilderContext(errors); this._resetContextStyleTimingState(context); - return >visitDslNode( - this, normalizeAnimationEntry(metadata), context); + const ast = + >visitDslNode(this, normalizeAnimationEntry(metadata), context); + + if (context.unsupportedCSSPropertiesFound.size) { + pushUnrecognizedPropertiesWarning( + warnings, + [...context.unsupportedCSSPropertiesFound.keys()], + ); + } + return ast; } private _resetContextStyleTimingState(context: AnimationAstBuilderContext) { @@ -303,7 +312,8 @@ export class AnimationAstBuilderVisitor implements AnimationDslVisitor { Object.keys(tuple).forEach(prop => { if (!this._driver.validateStyleProperty(prop)) { - context.errors.push(invalidProperty(prop)); + delete tuple[prop]; + context.unsupportedCSSPropertiesFound.add(prop); return; } @@ -507,6 +517,7 @@ export class AnimationAstBuilderContext { public currentTime: number = 0; public collectedStyles: {[selectorName: string]: {[propName: string]: StyleTimeTuple}} = {}; public options: AnimationOptions|null = null; + public unsupportedCSSPropertiesFound: Set = new Set(); constructor(public errors: Error[]) {} } diff --git a/packages/animations/browser/src/render/animation_engine_next.ts b/packages/animations/browser/src/render/animation_engine_next.ts index 2202743e87fa..f34f17273d4a 100644 --- a/packages/animations/browser/src/render/animation_engine_next.ts +++ b/packages/animations/browser/src/render/animation_engine_next.ts @@ -12,6 +12,7 @@ import {buildAnimationAst} from '../dsl/animation_ast_builder'; 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'; import {AnimationDriver} from './animation_driver'; import {parseTimelineCommand} from './shared'; @@ -44,11 +45,15 @@ export class AnimationEngine { let trigger = this._triggerCache[cacheKey]; if (!trigger) { const errors: Error[] = []; - const ast = - buildAnimationAst(this._driver, metadata as AnimationMetadata, errors) as TriggerAst; + const warnings: string[] = []; + const ast = buildAnimationAst( + this._driver, metadata as AnimationMetadata, errors, warnings) as TriggerAst; if (errors.length) { throw triggerBuildFailed(name, errors); } + if (warnings.length) { + warnTriggerBuild(name, warnings); + } trigger = buildTrigger(name, ast, this._normalizer); this._triggerCache[cacheKey] = trigger; } diff --git a/packages/animations/browser/src/render/timeline_animation_engine.ts b/packages/animations/browser/src/render/timeline_animation_engine.ts index 77469cfebdb0..2aa999c68d7a 100644 --- a/packages/animations/browser/src/render/timeline_animation_engine.ts +++ b/packages/animations/browser/src/render/timeline_animation_engine.ts @@ -15,6 +15,7 @@ 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'; import {AnimationDriver} from './animation_driver'; import {getOrSetAsInMap, listenOnPlayer, makeAnimationEvent, normalizeKeyframes, optimizeGroupPlayer} from './shared'; @@ -32,10 +33,14 @@ export class TimelineAnimationEngine { register(id: string, metadata: AnimationMetadata|AnimationMetadata[]) { const errors: Error[] = []; - const ast = buildAnimationAst(this._driver, metadata, errors); + const warnings: string[] = []; + const ast = buildAnimationAst(this._driver, metadata, errors, warnings); if (errors.length) { throw registerFailed(errors); } else { + if (warnings.length) { + warnRegister(warnings); + } this._animations[id] = ast; } } diff --git a/packages/animations/browser/src/warning_helpers.ts b/packages/animations/browser/src/warning_helpers.ts new file mode 100644 index 000000000000..d93ca15dd7ef --- /dev/null +++ b/packages/animations/browser/src/warning_helpers.ts @@ -0,0 +1,43 @@ +/** + * @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 + */ + +const NG_DEV_MODE = typeof ngDevMode === 'undefined' || !!ngDevMode; + +function createListOfWarnings(warnings: string[]): string { + const LINE_START = '\n - '; + return `${LINE_START}${warnings.filter(Boolean).map(warning => warning).join(LINE_START)}`; +} + +export function warnValidation(warnings: string[]): void { + NG_DEV_MODE && console.warn(`animation validation warnings:${createListOfWarnings(warnings)}`); +} + +export function warnTriggerBuild(name: string, warnings: string[]): void { + NG_DEV_MODE && + console.warn(`The animation trigger "${name}" has built with the following warnings:${ + createListOfWarnings(warnings)}`); +} + +export function warnRegister(warnings: string[]): void { + NG_DEV_MODE && + console.warn(`Animation built with the following warnings:${createListOfWarnings(warnings)}`); +} + +export function triggerParsingWarnings(name: string, warnings: string[]): void { + NG_DEV_MODE && + console.warn(`Animation parsing for the ${name} trigger presents the following warnings:${ + createListOfWarnings(warnings)}`); +} + +export function pushUnrecognizedPropertiesWarning(warnings: string[], props: string[]): void { + if (ngDevMode && props.length) { + warnings.push( + `The provided CSS properties are not recognized properties supported for animations: ${ + props.join(', ')}`); + } +} diff --git a/packages/animations/browser/test/dsl/animation_spec.ts b/packages/animations/browser/test/dsl/animation_spec.ts index 330d03ecbcba..c76b4afdaa65 100644 --- a/packages/animations/browser/test/dsl/animation_spec.ts +++ b/packages/animations/browser/test/dsl/animation_spec.ts @@ -218,16 +218,28 @@ function createDiv() { /state\("panfinal", ...\) must define default values for all the following style substitutions: greyColor/); }); - it('should throw an error if an invalid CSS property is used in the animation', () => { + it('should provide a warning if an invalid CSS property is used in the animation', () => { const steps = [animate(1000, style({abc: '500px'}))]; - expect(() => { - validateAndThrowAnimationSequence(steps); - }) - .toThrowError( - /The provided animation property "abc" is not a supported CSS property for animations/); + expect(getValidationWarningsForAnimationSequence(steps)).toEqual([ + 'The provided CSS properties are not recognized properties supported for animations: abc' + ]); }); + it('should provide a warning if multiple invalid CSS properties are used in the animation', + () => { + const steps = [ + state('state', style({ + '123': '100px', + })), + style({abc: '200px'}), animate(1000, style({xyz: '300px'})) + ]; + + expect(getValidationWarningsForAnimationSequence(steps)).toEqual([ + 'The provided CSS properties are not recognized properties supported for animations: 123, abc, xyz' + ]); + }); + it('should allow a vendor-prefixed property to be used in an animation sequence without throwing an error', () => { const steps = [ @@ -1070,12 +1082,20 @@ function invokeAnimationSequence( function validateAndThrowAnimationSequence(steps: AnimationMetadata|AnimationMetadata[]) { const driver = new MockAnimationDriver(); const errors: Error[] = []; - const ast = buildAnimationAst(driver, steps, errors); + const ast = buildAnimationAst(driver, steps, errors, []); if (errors.length) { throw new Error(errors.join('\n')); } } +function getValidationWarningsForAnimationSequence(steps: AnimationMetadata| + AnimationMetadata[]): string[] { + const driver = new MockAnimationDriver(); + const warnings: string[] = []; + buildAnimationAst(driver, steps, [], warnings); + return warnings; +} + function buildParams(params: {[name: string]: any}): AnimationOptions { return {params}; } 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 1a041e1fd3d4..5134bd39eacb 100644 --- a/packages/animations/browser/test/render/transition_animation_engine_spec.ts +++ b/packages/animations/browser/test/render/transition_animation_engine_spec.ts @@ -732,9 +732,11 @@ function registerTrigger( element: any, engine: TransitionAnimationEngine, metadata: AnimationTriggerMetadata, id: string = DEFAULT_NAMESPACE_ID) { const errors: Error[] = []; + const warnings: string[] = []; const driver = new MockAnimationDriver(); const name = metadata.name; - const ast = buildAnimationAst(driver, metadata as AnimationMetadata, errors) as TriggerAst; + const ast = + buildAnimationAst(driver, metadata as AnimationMetadata, errors, warnings) as TriggerAst; if (errors.length) { } const trigger = buildTrigger(name, ast, new NoopAnimationStyleNormalizer()); diff --git a/packages/animations/browser/test/shared.ts b/packages/animations/browser/test/shared.ts index bcbd55a66486..e699c243dbcc 100644 --- a/packages/animations/browser/test/shared.ts +++ b/packages/animations/browser/test/shared.ts @@ -13,16 +13,21 @@ import {buildAnimationAst} from '../src/dsl/animation_ast_builder'; 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'; export function makeTrigger( name: string, steps: any, skipErrors: boolean = false): AnimationTrigger { const driver = new MockAnimationDriver(); const errors: Error[] = []; + const warnings: string[] = []; const triggerData = trigger(name, steps); - const triggerAst = buildAnimationAst(driver, triggerData, errors) as TriggerAst; + const triggerAst = buildAnimationAst(driver, triggerData, errors, warnings) as TriggerAst; if (!skipErrors && errors.length) { throw triggerParsingFailed(name, errors); } + if (warnings.length) { + triggerParsingWarnings(name, warnings); + } return buildTrigger(name, triggerAst, new NoopAnimationStyleNormalizer()); } From 6c906a5bb9f7d7c86122bfc36275d6e6b81a0631 Mon Sep 17 00:00:00 2001 From: ivanwonder Date: Wed, 23 Feb 2022 08:36:06 +0800 Subject: [PATCH 02/24] fix(compiler-cli): Support resolve animation name from the DTS (#45169) Before this, the compiler resolves the value in the DTS as dynamic. If the `trigger` is imported from `@angular/animations`, this PR will use FFR to simulate the actual implementation in JS and extracts the animation name. PR Close #45169 --- .../src/ngtsc/annotations/src/component.ts | 5 ++-- .../src/ngtsc/annotations/src/util.ts | 22 +++++++++++++++ .../ngtsc/annotations/test/component_spec.ts | 27 ++++++++++++------- 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/component.ts b/packages/compiler-cli/src/ngtsc/annotations/src/component.ts index d455250c02a2..fea0b89d788e 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/component.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/component.ts @@ -33,7 +33,7 @@ import {DirectiveSymbol, extractDirectiveMetadata, parseFieldArrayValue} from '. import {compileDeclareFactory, compileNgFactoryDefField} from './factory'; import {extractClassMetadata} from './metadata'; import {NgModuleSymbol} from './ng_module'; -import {compileResults, findAngularDecorator, isAngularCoreReference, isExpressionForwardReference, readBaseClass, resolveProvidersRequiringFactory, toFactoryMetadata, unwrapExpression, wrapFunctionExpressionsInParens} from './util'; +import {animationTriggerResolver, compileResults, findAngularDecorator, isAngularCoreReference, isExpressionForwardReference, readBaseClass, resolveProvidersRequiringFactory, toFactoryMetadata, unwrapExpression, wrapFunctionExpressionsInParens} from './util'; const EMPTY_MAP = new Map(); const EMPTY_ARRAY: any[] = []; @@ -352,7 +352,8 @@ export class ComponentDecoratorHandler implements let animationTriggerNames: AnimationTriggerNames|null = null; if (component.has('animations')) { animations = new WrappedNodeExpr(component.get('animations')!); - const animationsValue = this.evaluator.evaluate(component.get('animations')!); + const animationsValue = + this.evaluator.evaluate(component.get('animations')!, animationTriggerResolver); animationTriggerNames = {includesDynamicAnimations: false, staticTriggerNames: []}; collectAnimationNames(animationsValue, animationTriggerNames); } diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/util.ts b/packages/compiler-cli/src/ngtsc/annotations/src/util.ts index 56d38ccace29..990fcfd24803 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/util.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/util.ts @@ -596,3 +596,25 @@ export function toFactoryMetadata( target }; } + +export function isAngularAnimationsReference(reference: Reference, symbolName: string): boolean { + return reference.ownedByModuleGuess === '@angular/animations' && + reference.debugName === symbolName; +} + +export const animationTriggerResolver: ForeignFunctionResolver = (ref, args) => { + const animationTriggerMethodName = 'trigger'; + if (!isAngularAnimationsReference(ref, animationTriggerMethodName)) { + return null; + } + const triggerNameExpression = args[0]; + if (!triggerNameExpression) { + return null; + } + const factory = ts.factory; + return factory.createObjectLiteralExpression( + [ + factory.createPropertyAssignment(factory.createIdentifier('name'), triggerNameExpression), + ], + true); +}; diff --git a/packages/compiler-cli/src/ngtsc/annotations/test/component_spec.ts b/packages/compiler-cli/src/ngtsc/annotations/test/component_spec.ts index c6367af53e40..6b15b845be32 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/test/component_spec.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/test/component_spec.ts @@ -407,13 +407,16 @@ runInEachFileSystem(() => { name: _('/node_modules/@angular/core/index.d.ts'), contents: 'export const Component: any;', }, + { + name: _('/node_modules/@angular/animations/index.d.ts'), + contents: 'export declare function trigger(name: any): any', + }, { name: _('/entry.ts'), contents: ` import {Component} from '@angular/core'; - function trigger(name) { - return {name}; - } + import {trigger} from '@angular/animations'; + @Component({ template: '', animations: [ @@ -446,13 +449,16 @@ runInEachFileSystem(() => { name: _('/node_modules/@angular/core/index.d.ts'), contents: 'export const Component: any;', }, + { + name: _('/node_modules/@angular/animations/index.d.ts'), + contents: 'export declare function trigger(name: any): any', + }, { name: _('/entry.ts'), contents: ` import {Component} from '@angular/core'; - function trigger(name) { - return {name}; - } + import {trigger} from '@angular/animations'; + function buildComplexAnimations() { const name = 'complex'; return [trigger(name)]; @@ -487,13 +493,16 @@ runInEachFileSystem(() => { name: _('/node_modules/@angular/core/index.d.ts'), contents: 'export const Component: any;', }, + { + name: _('/node_modules/@angular/animations/index.d.ts'), + contents: 'export declare function trigger(name: any): any', + }, { name: _('/entry.ts'), contents: ` import {Component} from '@angular/core'; - function trigger(name) { - return {name}; - } + import {trigger} from '@angular/animations'; + function buildComplexAnimations() { const name = 'complex'; return [trigger(name)]; From 4f1870362ef77de7c62ef6dd40d7337e9fa87cd0 Mon Sep 17 00:00:00 2001 From: dario-piotrowicz Date: Thu, 17 Feb 2022 21:03:05 +0000 Subject: [PATCH 03/24] docs: remove activatedRouteData backward accesses in aio guides (#45140) developers should not access the router-outlet directive in their template before defining a template variable for it, such implementation is present in a couple of aio guides, fix such guides so that they show the more correct way of accessing the outlet's data resolves #36173 PR Close #45140 --- .../examples/animations/src/app/app.component.html | 4 ++-- .../examples/animations/src/app/app.component.ts | 13 +++++++------ .../examples/router/src/app/app.component.2.html | 4 ++-- .../examples/router/src/app/app.component.2.ts | 8 +++++--- .../examples/router/src/app/app.component.4.html | 4 ++-- .../examples/router/src/app/app.component.5.html | 4 ++-- .../examples/router/src/app/app.component.6.html | 4 ++-- .../examples/router/src/app/app.component.html | 4 ++-- .../examples/router/src/app/app.component.ts | 8 +++++--- aio/content/guide/route-animations.md | 6 +++--- aio/content/guide/router-tutorial-toh.md | 4 ++-- 11 files changed, 34 insertions(+), 29 deletions(-) diff --git a/aio/content/examples/animations/src/app/app.component.html b/aio/content/examples/animations/src/app/app.component.html index 50195d3f5639..3082ece03ed8 100644 --- a/aio/content/examples/animations/src/app/app.component.html +++ b/aio/content/examples/animations/src/app/app.component.html @@ -21,7 +21,7 @@

Animations

-
- +
+
diff --git a/aio/content/examples/animations/src/app/app.component.ts b/aio/content/examples/animations/src/app/app.component.ts index 2c79714b5d74..39b2c72e6b24 100644 --- a/aio/content/examples/animations/src/app/app.component.ts +++ b/aio/content/examples/animations/src/app/app.component.ts @@ -11,7 +11,7 @@ import { } from '@angular/animations'; // #enddocregion imports -import { RouterOutlet } from '@angular/router'; +import { ChildrenOutletContexts, RouterOutlet } from '@angular/router'; import { slideInAnimation } from './animations'; // #docregion decorator, toggle-app-animations, define @@ -34,12 +34,13 @@ export class AppComponent { public animationsDisabled = false; // #enddocregion toggle-app-animations -// #docregion prepare-router-outlet - prepareRoute(outlet: RouterOutlet) { - return outlet?.activatedRouteData?.['animation']; - } +// #docregion get-route-animations-data + constructor(private contexts: ChildrenOutletContexts) {} -// #enddocregion prepare-router-outlet + getRouteAnimationData() { + return this.contexts.getContext('primary')?.route?.snapshot?.data?.['animation']; + } +// #enddocregion get-route-animations-data toggleAnimations() { this.animationsDisabled = !this.animationsDisabled; diff --git a/aio/content/examples/router/src/app/app.component.2.html b/aio/content/examples/router/src/app/app.component.2.html index 51e62b5bd178..a361c1d7e14f 100644 --- a/aio/content/examples/router/src/app/app.component.2.html +++ b/aio/content/examples/router/src/app/app.component.2.html @@ -4,6 +4,6 @@

Angular Router

Crisis Center Heroes -
- +
+
\ No newline at end of file diff --git a/aio/content/examples/router/src/app/app.component.2.ts b/aio/content/examples/router/src/app/app.component.2.ts index 533022096e94..8e0a63362d99 100644 --- a/aio/content/examples/router/src/app/app.component.2.ts +++ b/aio/content/examples/router/src/app/app.component.2.ts @@ -2,7 +2,7 @@ // #docregion import { Component } from '@angular/core'; // #docregion animation-imports -import { RouterOutlet } from '@angular/router'; +import { ChildrenOutletContexts } from '@angular/router'; import { slideInAnimation } from './animations'; @Component({ @@ -14,8 +14,10 @@ import { slideInAnimation } from './animations'; // #enddocregion animation-imports // #docregion function-binding export class AppComponent { - getAnimationData(outlet: RouterOutlet) { - return outlet?.activatedRouteData?.['animation']; + constructor(private contexts: ChildrenOutletContexts) {} + + getAnimationData() { + return this.contexts.getContext('primary')?.route?.snapshot?.data?.['animation']; } } // #enddocregion function-binding diff --git a/aio/content/examples/router/src/app/app.component.4.html b/aio/content/examples/router/src/app/app.component.4.html index 4a32aedb34c2..1db98d81a9e9 100644 --- a/aio/content/examples/router/src/app/app.component.4.html +++ b/aio/content/examples/router/src/app/app.component.4.html @@ -8,8 +8,8 @@

Angular Router

-
- +
+
\ No newline at end of file diff --git a/aio/content/examples/router/src/app/app.component.5.html b/aio/content/examples/router/src/app/app.component.5.html index 4cc8c91ffc7c..a5c392a60257 100644 --- a/aio/content/examples/router/src/app/app.component.5.html +++ b/aio/content/examples/router/src/app/app.component.5.html @@ -6,7 +6,7 @@

Angular Router

Admin Contact -
- +
+
\ No newline at end of file diff --git a/aio/content/examples/router/src/app/app.component.6.html b/aio/content/examples/router/src/app/app.component.6.html index b082558ecb34..48cc9ba0bbc3 100644 --- a/aio/content/examples/router/src/app/app.component.6.html +++ b/aio/content/examples/router/src/app/app.component.6.html @@ -7,7 +7,7 @@

Angular Router

Login Contact -
- +
+
\ No newline at end of file diff --git a/aio/content/examples/router/src/app/app.component.html b/aio/content/examples/router/src/app/app.component.html index eff3c6d5d259..2cbe855b524b 100644 --- a/aio/content/examples/router/src/app/app.component.html +++ b/aio/content/examples/router/src/app/app.component.html @@ -8,8 +8,8 @@

Angular Router

Login Contact -
- +
+
diff --git a/aio/content/examples/router/src/app/app.component.ts b/aio/content/examples/router/src/app/app.component.ts index b3dd99b684ea..58f6c90c6642 100644 --- a/aio/content/examples/router/src/app/app.component.ts +++ b/aio/content/examples/router/src/app/app.component.ts @@ -1,7 +1,7 @@ // #docplaster // #docregion import { Component } from '@angular/core'; -import { RouterOutlet } from '@angular/router'; +import { ChildrenOutletContexts } from '@angular/router'; import { slideInAnimation } from './animations'; @Component({ @@ -11,7 +11,9 @@ import { slideInAnimation } from './animations'; animations: [ slideInAnimation ] }) export class AppComponent { - getAnimationData(outlet: RouterOutlet) { - return outlet?.activatedRouteData?.['animation']; + constructor(private contexts: ChildrenOutletContexts) {} + + getRouteAnimationData() { + return this.contexts.getContext('primary')?.route?.snapshot?.data?.['animation']; } } diff --git a/aio/content/guide/route-animations.md b/aio/content/guide/route-animations.md index b41cbab7fe5d..2c99bc73325c 100644 --- a/aio/content/guide/route-animations.md +++ b/aio/content/guide/route-animations.md @@ -59,15 +59,15 @@ In addition to `path` and `component`, the `data` property of each route defines After configuring the routes, add a `` inside the root `AppComponent` template. The `` directive tells the Angular router where to render the views when matched with a route. -The `` directive holds the custom data set for the currently active route which can be accessed via the directive's `activatedRouteData` property, we can use such data to animate our routing transitions. +The `ChildrenOutletContexts` holds information about outlets and activated routes. We can use the `data` property of each `Route` to animate our routing transitions. `AppComponent` defines a method that can detect when a view changes. The method assigns an animation state value to the animation trigger (`@routeAnimation`) based on the route configuration `data` property value. Here's an example of an `AppComponent` method that detects when a route change happens. - + -Here, the `prepareRoute()` method takes the value of the outlet directive (established through `#outlet="outlet"`) and returns a string value representing the state of the animation based on the custom data of the current active route. Use this data to control which transition to execute for each route. +Here, the `getRouteAnimationData()` method takes the value of the outlet and returns a string which represents the state of the animation based on the custom data of the current active route. Use this data to control which transition to execute for each route. ## Animation definition diff --git a/aio/content/guide/router-tutorial-toh.md b/aio/content/guide/router-tutorial-toh.md index a2cacc9d1500..332feddee411 100644 --- a/aio/content/guide/router-tutorial-toh.md +++ b/aio/content/guide/router-tutorial-toh.md @@ -1225,8 +1225,8 @@ This example uses a variable of `routerOutlet`. -The `@routeAnimation` property is bound to the `getAnimationData()` with the provided `routerOutlet` reference, so the next step is to define that function in the `AppComponent`. -The `getAnimationData()` function returns the animation property from the `data` provided through the `ActivatedRoute`. The `animation` property matches the `transition` names you used in the `slideInAnimation` defined in `animations.ts`. +The `@routeAnimation` property is bound to the `getAnimationData()` which returns the animation property from the `data` provided by the primary route. The `animation` property matches the `transition` names you used in the `slideInAnimation` defined in `animations.ts`. + From d388522745835b8e30b66597560254f0e821c040 Mon Sep 17 00:00:00 2001 From: JoostK Date: Wed, 23 Feb 2022 20:42:15 +0100 Subject: [PATCH 04/24] fix(localize): avoid imports into `compiler-cli` package (#45180) The compiler-cli's declaration files are not necessarily compatible with web environments that use `@angular/localize`, and would inadvertently include `typescript` declaration files in any compilation unit that uses `@angular/localize` (which increases parsing time and memory usage during builds) using a default import that only type-checks when `allowSyntheticDefaultImports` is enabled. Fixes #45179 PR Close #45180 --- integration/typings_test_ts44/include-all.ts | 2 ++ integration/typings_test_ts44/package.json | 1 + integration/typings_test_ts45/include-all.ts | 2 ++ integration/typings_test_ts45/package.json | 1 + packages/localize/src/utils/BUILD.bazel | 1 - packages/localize/src/utils/src/messages.ts | 15 +++++++++++++-- 6 files changed, 19 insertions(+), 3 deletions(-) diff --git a/integration/typings_test_ts44/include-all.ts b/integration/typings_test_ts44/include-all.ts index 86b57f2789d8..925e0ef07853 100644 --- a/integration/typings_test_ts44/include-all.ts +++ b/integration/typings_test_ts44/include-all.ts @@ -21,6 +21,7 @@ import * as core from '@angular/core'; import * as coreTesting from '@angular/core/testing'; import * as elements from '@angular/elements'; import * as forms from '@angular/forms'; +import * as localize from '@angular/localize'; import * as platformBrowser from '@angular/platform-browser'; import * as platformBrowserDynamic from '@angular/platform-browser-dynamic'; import * as platformBrowserDynamicTesting from '@angular/platform-browser-dynamic/testing'; @@ -51,6 +52,7 @@ export default { coreTesting, elements, forms, + localize, platformBrowser, platformBrowserTesting, platformBrowserDynamic, diff --git a/integration/typings_test_ts44/package.json b/integration/typings_test_ts44/package.json index 11b24fd1a827..9a4bcb938a03 100644 --- a/integration/typings_test_ts44/package.json +++ b/integration/typings_test_ts44/package.json @@ -11,6 +11,7 @@ "@angular/core": "file:../../dist/packages-dist/core", "@angular/elements": "file:../../dist/packages-dist/elements", "@angular/forms": "file:../../dist/packages-dist/forms", + "@angular/localize": "file:../../dist/packages-dist/localize", "@angular/platform-browser": "file:../../dist/packages-dist/platform-browser", "@angular/platform-browser-dynamic": "file:../../dist/packages-dist/platform-browser-dynamic", "@angular/platform-server": "file:../../dist/packages-dist/platform-server", diff --git a/integration/typings_test_ts45/include-all.ts b/integration/typings_test_ts45/include-all.ts index 86b57f2789d8..925e0ef07853 100644 --- a/integration/typings_test_ts45/include-all.ts +++ b/integration/typings_test_ts45/include-all.ts @@ -21,6 +21,7 @@ import * as core from '@angular/core'; import * as coreTesting from '@angular/core/testing'; import * as elements from '@angular/elements'; import * as forms from '@angular/forms'; +import * as localize from '@angular/localize'; import * as platformBrowser from '@angular/platform-browser'; import * as platformBrowserDynamic from '@angular/platform-browser-dynamic'; import * as platformBrowserDynamicTesting from '@angular/platform-browser-dynamic/testing'; @@ -51,6 +52,7 @@ export default { coreTesting, elements, forms, + localize, platformBrowser, platformBrowserTesting, platformBrowserDynamic, diff --git a/integration/typings_test_ts45/package.json b/integration/typings_test_ts45/package.json index ee8b39d77c3d..4f8eed6b8573 100644 --- a/integration/typings_test_ts45/package.json +++ b/integration/typings_test_ts45/package.json @@ -11,6 +11,7 @@ "@angular/core": "file:../../dist/packages-dist/core", "@angular/elements": "file:../../dist/packages-dist/elements", "@angular/forms": "file:../../dist/packages-dist/forms", + "@angular/localize": "file:../../dist/packages-dist/localize", "@angular/platform-browser": "file:../../dist/packages-dist/platform-browser", "@angular/platform-browser-dynamic": "file:../../dist/packages-dist/platform-browser-dynamic", "@angular/platform-server": "file:../../dist/packages-dist/platform-server", diff --git a/packages/localize/src/utils/BUILD.bazel b/packages/localize/src/utils/BUILD.bazel index 2d3df3496826..ad07f16b7fe9 100644 --- a/packages/localize/src/utils/BUILD.bazel +++ b/packages/localize/src/utils/BUILD.bazel @@ -13,6 +13,5 @@ ts_library( module_name = "@angular/localize/src/utils", deps = [ "//packages/compiler", - "//packages/compiler-cli/private", ], ) diff --git a/packages/localize/src/utils/src/messages.ts b/packages/localize/src/utils/src/messages.ts index 568dba3d31cc..d6305a24e143 100644 --- a/packages/localize/src/utils/src/messages.ts +++ b/packages/localize/src/utils/src/messages.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ import {computeMsgId} from '@angular/compiler'; -import {AbsoluteFsPath} from '@angular/compiler-cli/private/localize'; import {BLOCK_MARKER, ID_SEPARATOR, LEGACY_ID_INDICATOR, MEANING_SEPARATOR} from './constants'; @@ -39,6 +38,18 @@ export type TargetMessage = string; */ export type MessageId = string; +/** + * Declares a copy of the `AbsoluteFsPath` branded type in `@angular/compiler-cli` to avoid an + * import into `@angular/compiler-cli`. The compiler-cli's declaration files are not necessarily + * compatible with web environments that use `@angular/localize`, and would inadvertently include + * `typescript` declaration files in any compilation unit that uses `@angular/localize` (which + * increases parsing time and memory usage during builds) using a default import that only + * type-checks when `allowSyntheticDefaultImports` is enabled. + * + * @see https://github.com/angular/angular/issues/45179 + */ +type AbsoluteFsPathLocalizeCopy = string&{_brand: 'AbsoluteFsPath'}; + /** * The location of the message in the source file. * @@ -47,7 +58,7 @@ export type MessageId = string; export interface SourceLocation { start: {line: number, column: number}; end: {line: number, column: number}; - file: AbsoluteFsPath; + file: AbsoluteFsPathLocalizeCopy; text?: string; } From 64da1daa7827ca99c6d0b3992ac10b1ea0ec5b4a Mon Sep 17 00:00:00 2001 From: Martin Sikora Date: Fri, 24 Apr 2020 20:49:34 +0200 Subject: [PATCH 05/24] fix(common): canceled JSONP requests won't throw console error with missing callback function (#36807) This commit fixes a use-case where unsubscribing from a JSONP request will result in "Uncaught ReferenceError: ng_jsonp_callback_xy is not defined" thrown into console. Unsubscribing won't remove its associated callback function because the requested script will finish loading anyway and will try to call the handler. PR Close #34818 PR Close #36807 --- packages/common/http/src/jsonp.ts | 14 +++++++++----- packages/common/http/test/jsonp_spec.ts | 6 +++++- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/common/http/src/jsonp.ts b/packages/common/http/src/jsonp.ts index e5be9634973f..e718f7bb2373 100644 --- a/packages/common/http/src/jsonp.ts +++ b/packages/common/http/src/jsonp.ts @@ -125,15 +125,17 @@ export class JsonpClientBackend implements HttpBackend { // cleanup() is a utility closure that removes the