Compare commits
24 Commits
Author | SHA1 | Date | |
---|---|---|---|
cbd93fe0d0 | |||
e1dfd9b17a | |||
e95b5d77bc | |||
9e69d97b77 | |||
b450c83091 | |||
38be44df9f | |||
e4ce695b66 | |||
4da184c29b | |||
647ca64216 | |||
53aff1fb83 | |||
71a6f1768e | |||
108b6e77dd | |||
ead759670b | |||
bdaee508cf | |||
e099911dd0 | |||
66fd1f8ce6 | |||
0e8f0288cb | |||
c7b211ca3c | |||
22bbd6e045 | |||
2a5b6194bb | |||
264260f997 | |||
c70cd258d5 | |||
2b0c896d78 | |||
c0bf1b0545 |
17
CHANGELOG.md
17
CHANGELOG.md
@ -1,3 +1,20 @@
|
||||
<a name="5.0.4"></a>
|
||||
## [5.0.4](https://github.com/angular/angular/compare/5.0.3...5.0.4) (2017-12-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** ensure multi-level enter animations work ([#19455](https://github.com/angular/angular/issues/19455)) ([22bbd6e](https://github.com/angular/angular/commit/22bbd6e))
|
||||
* **animations:** ensure multi-level leave animations work ([#19455](https://github.com/angular/angular/issues/19455)) ([c7b211c](https://github.com/angular/angular/commit/c7b211c))
|
||||
* **common:** accept falsy values as HTTP bodies ([#19958](https://github.com/angular/angular/issues/19958)) ([66fd1f8](https://github.com/angular/angular/commit/66fd1f8)), closes [#19825](https://github.com/angular/angular/issues/19825) [#19195](https://github.com/angular/angular/issues/19195)
|
||||
* **common:** don't strip XSSI prefix for if error isn't JSON ([#19958](https://github.com/angular/angular/issues/19958)) ([ead7596](https://github.com/angular/angular/commit/ead7596))
|
||||
* **common:** remove useless guard in HttpClient ([#19958](https://github.com/angular/angular/issues/19958)) ([e099911](https://github.com/angular/angular/commit/e099911)), closes [#19223](https://github.com/angular/angular/issues/19223)
|
||||
* **common:** treat an empty body as null when parsing JSON in HttpClient ([#19958](https://github.com/angular/angular/issues/19958)) ([bdaee50](https://github.com/angular/angular/commit/bdaee50)), closes [#18680](https://github.com/angular/angular/issues/18680) [#19413](https://github.com/angular/angular/issues/19413) [#19502](https://github.com/angular/angular/issues/19502) [#19555](https://github.com/angular/angular/issues/19555)
|
||||
* **compiler-cli:** fix memory leak in program creation ([#20692](https://github.com/angular/angular/issues/20692)) ([38be44d](https://github.com/angular/angular/commit/38be44d)), closes [#20691](https://github.com/angular/angular/issues/20691)
|
||||
* **compiler-cli:** normalize sourcepaths for i18n extracted files ([#20417](https://github.com/angular/angular/issues/20417)) ([2b0c896](https://github.com/angular/angular/commit/2b0c896)), closes [#20416](https://github.com/angular/angular/issues/20416)
|
||||
|
||||
|
||||
|
||||
<a name="5.0.3"></a>
|
||||
## [5.0.3](https://github.com/angular/angular/compare/5.0.2...5.0.3) (2017-11-22)
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
<blockquote>
|
||||
<!-- #docregion built-in, asterisk, ngif -->
|
||||
<div *ngIf="hero" >{{hero.name}}</div>
|
||||
<div *ngIf="hero" class="name">{{hero.name}}</div>
|
||||
<!-- #enddocregion built-in, asterisk, ngif -->
|
||||
</blockquote>
|
||||
|
||||
@ -51,7 +51,7 @@
|
||||
<p><ng-template> element</p>
|
||||
<!-- #docregion ngif-template -->
|
||||
<ng-template [ngIf]="hero">
|
||||
<div>{{hero.name}}</div>
|
||||
<div class="name">{{hero.name}}</div>
|
||||
</ng-template>
|
||||
<!-- #enddocregion ngif-template -->
|
||||
|
||||
|
@ -74,8 +74,8 @@ Generate a new project and skeleton application by running the following command
|
||||
|
||||
|
||||
|
||||
Patience please.
|
||||
It takes time to set up a new project, most of it spent installing npm packages.
|
||||
Patience, please.
|
||||
It takes time to set up a new project; most of it is spent installing npm packages.
|
||||
|
||||
|
||||
</div>
|
||||
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 79 KiB |
@ -241,6 +241,12 @@
|
||||
"UI Components": {
|
||||
"order": 4,
|
||||
"resources": {
|
||||
"IgniteUIforAngular": {
|
||||
"desc": "Ignite UI for Angular is a dependency-free Angular toolkit for building modern web apps.",
|
||||
"rev": true,
|
||||
"title": "Ignite UI for Angular",
|
||||
"url": "https://www.infragistics.com/products/ignite-ui-angular"
|
||||
},
|
||||
"DevExtreme": {
|
||||
"desc": "50+ UI components including data grid, pivot grid, scheduler, charts, editors, maps and other multi-purpose controls for creating highly responsive web applications for touch devices and traditional desktops.",
|
||||
"rev": true,
|
||||
|
@ -3,7 +3,7 @@
|
||||
The Tour of Heroes `HeroesComponent` is currently getting and displaying fake data.
|
||||
|
||||
After the refactoring in this tutorial, `HeroesComponent` will be lean and focused on supporting the view.
|
||||
It will also be easier to unit-test with a mock services.
|
||||
It will also be easier to unit-test with a mock service.
|
||||
|
||||
## Why services
|
||||
|
||||
|
@ -28,7 +28,7 @@ describe('Api pages', function() {
|
||||
|
||||
it('should show readonly properties as getters', () => {
|
||||
const page = new ApiPage('api/common/http/HttpRequest');
|
||||
expect(page.getOverview('class').getText()).toContain('get body: T|null');
|
||||
expect(page.getOverview('class').getText()).toContain('get body: T | null');
|
||||
});
|
||||
|
||||
it('should not show parenthesis for getters', () => {
|
||||
|
@ -80,8 +80,6 @@ describe('site App', function() {
|
||||
});
|
||||
});
|
||||
|
||||
// TODO(https://github.com/angular/angular/issues/19785): Activate this again
|
||||
// once it is no more flaky.
|
||||
describe('google analytics', () => {
|
||||
|
||||
it('should call ga with initial URL', done => {
|
||||
|
@ -29,10 +29,7 @@ export class SitePage {
|
||||
locationPath() { return browser.executeScript('return document.location.pathname') as promise.Promise<string>; }
|
||||
|
||||
navigateTo(pageUrl = '') {
|
||||
return browser.get('/' + pageUrl)
|
||||
// We need to tell the index.html not to load the real analytics library
|
||||
// See the GA snippet in index.html
|
||||
.then(() => browser.executeScript('sessionStorage.setItem("__e2e__", true);'));
|
||||
return browser.get('/' + pageUrl);
|
||||
}
|
||||
|
||||
getDocViewerText() {
|
||||
|
@ -100,7 +100,7 @@
|
||||
"cross-spawn": "^5.1.0",
|
||||
"css-selector-parser": "^1.3.0",
|
||||
"dgeni": "^0.4.7",
|
||||
"dgeni-packages": "0.22.0",
|
||||
"dgeni-packages": "0.22.1",
|
||||
"entities": "^1.1.1",
|
||||
"eslint": "^3.19.0",
|
||||
"eslint-plugin-jasmine": "^2.2.0",
|
||||
|
@ -34,11 +34,13 @@
|
||||
|
||||
<!-- Google Analytics -->
|
||||
<script>
|
||||
// Note this is a customised version of the GA tracking snippet to aid e2e testing
|
||||
// See the bit between /**/.../**/
|
||||
// Note this is a customised version of the GA tracking snippet
|
||||
// See the comments below for more info
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;/**/i.sessionStorage.__e2e__||/**/m.parentNode.insertBefore(a,m)
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;
|
||||
~i.name.indexOf('NG_DEFER_BOOTSTRAP')|| // only load library if not running e2e tests
|
||||
m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||
</script>
|
||||
<!-- End Google Analytics -->
|
||||
|
@ -62,7 +62,7 @@ class ExampleZipper {
|
||||
let exampleZipName;
|
||||
const exampleType = this._getExampleType(path.join(sourceDirName, relativeDirName));
|
||||
if (relativeDirName.indexOf('/') !== -1) { // Special example
|
||||
exampleZipName = relativeDirName.split('/')[0];
|
||||
exampleZipName = relativeDirName.split('/').join('-');
|
||||
} else {
|
||||
exampleZipName = jsonFileName.replace(/(plnkr|zipper).json/, relativeDirName);
|
||||
}
|
||||
|
@ -26,13 +26,13 @@
|
||||
"zone.js": "^0.8.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular/cli": "1.5.0",
|
||||
"@angular/cli": "1.5.4",
|
||||
"@angular/compiler-cli": "^5.0.0",
|
||||
"@angular/language-service": "^5.0.0",
|
||||
"@types/jasmine": "~2.5.53",
|
||||
"@types/jasminewd2": "~2.0.2",
|
||||
"@types/node": "~6.0.60",
|
||||
"codelyzer": "~3.2.0",
|
||||
"codelyzer": "^4.0.1",
|
||||
"jasmine-core": "~2.6.2",
|
||||
"jasmine-spec-reporter": "~4.1.0",
|
||||
"karma": "~1.7.0",
|
||||
|
@ -2287,9 +2287,9 @@ devtools-timeline-model@1.1.6:
|
||||
chrome-devtools-frontend "1.0.401423"
|
||||
resolve "1.1.7"
|
||||
|
||||
dgeni-packages@0.22.0:
|
||||
version "0.22.0"
|
||||
resolved "https://registry.yarnpkg.com/dgeni-packages/-/dgeni-packages-0.22.0.tgz#7ed07af9074f6547847256c1a65b488a5a17ad03"
|
||||
dgeni-packages@0.22.1:
|
||||
version "0.22.1"
|
||||
resolved "https://registry.yarnpkg.com/dgeni-packages/-/dgeni-packages-0.22.1.tgz#c4587a765689c4c9d48ed661517ed2249403bfb2"
|
||||
dependencies:
|
||||
canonical-path "0.0.2"
|
||||
catharsis "^0.8.1"
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "angular-srcs",
|
||||
"version": "5.0.3",
|
||||
"version": "5.0.4",
|
||||
"private": true,
|
||||
"branchPattern": "2.0.*",
|
||||
"description": "Angular - a web framework for modern web apps",
|
||||
|
@ -8,7 +8,7 @@
|
||||
import {AnimationMetadata, AnimationMetadataType, AnimationOptions, ɵStyleData} from '@angular/animations';
|
||||
|
||||
import {AnimationDriver} from '../render/animation_driver';
|
||||
import {normalizeStyles} from '../util';
|
||||
import {ENTER_CLASSNAME, LEAVE_CLASSNAME, normalizeStyles} from '../util';
|
||||
|
||||
import {Ast} from './animation_ast';
|
||||
import {buildAnimationAst} from './animation_ast_builder';
|
||||
@ -39,7 +39,8 @@ export class Animation {
|
||||
const errors: any = [];
|
||||
subInstructions = subInstructions || new ElementInstructionMap();
|
||||
const result = buildAnimationTimelines(
|
||||
this._driver, element, this._animationAst, start, dest, options, subInstructions, errors);
|
||||
this._driver, element, this._animationAst, ENTER_CLASSNAME, LEAVE_CLASSNAME, start, dest,
|
||||
options, subInstructions, errors);
|
||||
if (errors.length) {
|
||||
const errorMessage = `animation building failed:\n${errors.join("\n")}`;
|
||||
throw new Error(errorMessage);
|
||||
|
@ -60,10 +60,6 @@ export function buildAnimationAst(
|
||||
return new AnimationAstBuilderVisitor(driver).build(metadata, errors);
|
||||
}
|
||||
|
||||
const LEAVE_TOKEN = ':leave';
|
||||
const LEAVE_TOKEN_REGEX = new RegExp(LEAVE_TOKEN, 'g');
|
||||
const ENTER_TOKEN = ':enter';
|
||||
const ENTER_TOKEN_REGEX = new RegExp(ENTER_TOKEN, 'g');
|
||||
const ROOT_SELECTOR = '';
|
||||
|
||||
export class AnimationAstBuilderVisitor implements AnimationDslVisitor {
|
||||
@ -478,9 +474,8 @@ function normalizeSelector(selector: string): [string, boolean] {
|
||||
selector = selector.replace(SELF_TOKEN_REGEX, '');
|
||||
}
|
||||
|
||||
selector = selector.replace(ENTER_TOKEN_REGEX, ENTER_SELECTOR)
|
||||
.replace(LEAVE_TOKEN_REGEX, LEAVE_SELECTOR)
|
||||
.replace(/@\*/g, NG_TRIGGER_SELECTOR)
|
||||
// the :enter and :leave selectors are filled in at runtime during timeline building
|
||||
selector = selector.replace(/@\*/g, NG_TRIGGER_SELECTOR)
|
||||
.replace(/@\w+/g, match => NG_TRIGGER_SELECTOR + '-' + match.substr(1))
|
||||
.replace(/:animating/g, NG_ANIMATING_SELECTOR);
|
||||
|
||||
|
@ -15,6 +15,10 @@ import {AnimationTimelineInstruction, createTimelineInstruction} from './animati
|
||||
import {ElementInstructionMap} from './element_instruction_map';
|
||||
|
||||
const ONE_FRAME_IN_MILLISECONDS = 1;
|
||||
const ENTER_TOKEN = ':enter';
|
||||
const ENTER_TOKEN_REGEX = new RegExp(ENTER_TOKEN, 'g');
|
||||
const LEAVE_TOKEN = ':leave';
|
||||
const LEAVE_TOKEN_REGEX = new RegExp(LEAVE_TOKEN, 'g');
|
||||
|
||||
/*
|
||||
* The code within this file aims to generate web-animations-compatible keyframes from Angular's
|
||||
@ -102,19 +106,23 @@ const ONE_FRAME_IN_MILLISECONDS = 1;
|
||||
*/
|
||||
export function buildAnimationTimelines(
|
||||
driver: AnimationDriver, rootElement: any, ast: Ast<AnimationMetadataType>,
|
||||
startingStyles: ɵStyleData = {}, finalStyles: ɵStyleData = {}, options: AnimationOptions,
|
||||
enterClassName: string, leaveClassName: string, startingStyles: ɵStyleData = {},
|
||||
finalStyles: ɵStyleData = {}, options: AnimationOptions,
|
||||
subInstructions?: ElementInstructionMap, errors: any[] = []): AnimationTimelineInstruction[] {
|
||||
return new AnimationTimelineBuilderVisitor().buildKeyframes(
|
||||
driver, rootElement, ast, startingStyles, finalStyles, options, subInstructions, errors);
|
||||
driver, rootElement, ast, enterClassName, leaveClassName, startingStyles, finalStyles,
|
||||
options, subInstructions, errors);
|
||||
}
|
||||
|
||||
export class AnimationTimelineBuilderVisitor implements AstVisitor {
|
||||
buildKeyframes(
|
||||
driver: AnimationDriver, rootElement: any, ast: Ast<AnimationMetadataType>,
|
||||
startingStyles: ɵStyleData, finalStyles: ɵStyleData, options: AnimationOptions,
|
||||
subInstructions?: ElementInstructionMap, errors: any[] = []): AnimationTimelineInstruction[] {
|
||||
enterClassName: string, leaveClassName: string, startingStyles: ɵStyleData,
|
||||
finalStyles: ɵStyleData, options: AnimationOptions, subInstructions?: ElementInstructionMap,
|
||||
errors: any[] = []): AnimationTimelineInstruction[] {
|
||||
subInstructions = subInstructions || new ElementInstructionMap();
|
||||
const context = new AnimationTimelineContext(driver, rootElement, subInstructions, errors, []);
|
||||
const context = new AnimationTimelineContext(
|
||||
driver, rootElement, subInstructions, enterClassName, leaveClassName, errors, []);
|
||||
context.options = options;
|
||||
context.currentTimeline.setStyles([startingStyles], null, context.errors, options);
|
||||
|
||||
@ -445,8 +453,9 @@ export class AnimationTimelineContext {
|
||||
|
||||
constructor(
|
||||
private _driver: AnimationDriver, public element: any,
|
||||
public subInstructions: ElementInstructionMap, public errors: any[],
|
||||
public timelines: TimelineBuilder[], initialTimeline?: TimelineBuilder) {
|
||||
public subInstructions: ElementInstructionMap, private _enterClassName: string,
|
||||
private _leaveClassName: string, public errors: any[], public timelines: TimelineBuilder[],
|
||||
initialTimeline?: TimelineBuilder) {
|
||||
this.currentTimeline = initialTimeline || new TimelineBuilder(this._driver, element, 0);
|
||||
timelines.push(this.currentTimeline);
|
||||
}
|
||||
@ -499,8 +508,8 @@ export class AnimationTimelineContext {
|
||||
AnimationTimelineContext {
|
||||
const target = element || this.element;
|
||||
const context = new AnimationTimelineContext(
|
||||
this._driver, target, this.subInstructions, this.errors, this.timelines,
|
||||
this.currentTimeline.fork(target, newTime || 0));
|
||||
this._driver, target, this.subInstructions, this._enterClassName, this._leaveClassName,
|
||||
this.errors, this.timelines, this.currentTimeline.fork(target, newTime || 0));
|
||||
context.previousNode = this.previousNode;
|
||||
context.currentAnimateTimings = this.currentAnimateTimings;
|
||||
|
||||
@ -555,6 +564,8 @@ export class AnimationTimelineContext {
|
||||
results.push(this.element);
|
||||
}
|
||||
if (selector.length > 0) { // if :self is only used then the selector is empty
|
||||
selector = selector.replace(ENTER_TOKEN_REGEX, '.' + this._enterClassName);
|
||||
selector = selector.replace(LEAVE_TOKEN_REGEX, '.' + this._leaveClassName);
|
||||
const multi = limit != 1;
|
||||
let elements = this._driver.query(this.element, selector, multi);
|
||||
if (limit !== 0) {
|
||||
|
@ -37,7 +37,8 @@ export class AnimationTransitionFactory {
|
||||
|
||||
build(
|
||||
driver: AnimationDriver, element: any, currentState: any, nextState: any,
|
||||
currentOptions?: AnimationOptions, nextOptions?: AnimationOptions,
|
||||
enterClassName: string, leaveClassName: string, currentOptions?: AnimationOptions,
|
||||
nextOptions?: AnimationOptions,
|
||||
subInstructions?: ElementInstructionMap): AnimationTransitionInstruction {
|
||||
const errors: any[] = [];
|
||||
|
||||
@ -55,8 +56,8 @@ export class AnimationTransitionFactory {
|
||||
const animationOptions = {params: {...transitionAnimationParams, ...nextAnimationParams}};
|
||||
|
||||
const timelines = buildAnimationTimelines(
|
||||
driver, element, this.ast.animation, currentStateStyles, nextStateStyles, animationOptions,
|
||||
subInstructions, errors);
|
||||
driver, element, this.ast.animation, enterClassName, leaveClassName, currentStateStyles,
|
||||
nextStateStyles, animationOptions, subInstructions, errors);
|
||||
|
||||
if (errors.length) {
|
||||
return createTransitionInstruction(
|
||||
|
@ -13,6 +13,7 @@ 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 {ENTER_CLASSNAME, LEAVE_CLASSNAME} from '../util';
|
||||
|
||||
import {AnimationDriver} from './animation_driver';
|
||||
import {getOrSetAsInMap, listenOnPlayer, makeAnimationEvent, normalizeKeyframes, optimizeGroupPlayer} from './shared';
|
||||
@ -55,7 +56,8 @@ export class TimelineAnimationEngine {
|
||||
|
||||
if (ast) {
|
||||
instructions = buildAnimationTimelines(
|
||||
this._driver, element, ast, {}, {}, options, EMPTY_INSTRUCTION_MAP, errors);
|
||||
this._driver, element, ast, ENTER_CLASSNAME, LEAVE_CLASSNAME, {}, {}, options,
|
||||
EMPTY_INSTRUCTION_MAP, errors);
|
||||
instructions.forEach(inst => {
|
||||
const styles = getOrSetAsInMap(autoStylesMap, inst.element, {});
|
||||
inst.postStyleProps.forEach(prop => styles[prop] = null);
|
||||
|
@ -13,7 +13,7 @@ import {AnimationTransitionInstruction} from '../dsl/animation_transition_instru
|
||||
import {AnimationTrigger} from '../dsl/animation_trigger';
|
||||
import {ElementInstructionMap} from '../dsl/element_instruction_map';
|
||||
import {AnimationStyleNormalizer} from '../dsl/style_normalization/animation_style_normalizer';
|
||||
import {ENTER_CLASSNAME, LEAVE_CLASSNAME, NG_ANIMATING_CLASSNAME, NG_ANIMATING_SELECTOR, NG_TRIGGER_CLASSNAME, NG_TRIGGER_SELECTOR, copyObj, eraseStyles, setStyles} from '../util';
|
||||
import {ENTER_CLASSNAME, LEAVE_CLASSNAME, NG_ANIMATING_CLASSNAME, NG_ANIMATING_SELECTOR, NG_TRIGGER_CLASSNAME, NG_TRIGGER_SELECTOR, copyObj, eraseStyles, iteratorToArray, setStyles} from '../util';
|
||||
|
||||
import {AnimationDriver} from './animation_driver';
|
||||
import {getBodyNode, getOrSetAsInMap, listenOnPlayer, makeAnimationEvent, normalizeKeyframes, optimizeGroupPlayer} from './shared';
|
||||
@ -22,6 +22,8 @@ const QUEUED_CLASSNAME = 'ng-animate-queued';
|
||||
const QUEUED_SELECTOR = '.ng-animate-queued';
|
||||
const DISABLED_CLASSNAME = 'ng-animate-disabled';
|
||||
const DISABLED_SELECTOR = '.ng-animate-disabled';
|
||||
const STAR_CLASSNAME = 'ng-star-inserted';
|
||||
const STAR_SELECTOR = '.ng-star-inserted';
|
||||
|
||||
const EMPTY_PLAYER_ARRAY: TransitionAnimationPlayer[] = [];
|
||||
const NULL_REMOVAL_STATE: ElementAnimationState = {
|
||||
@ -714,10 +716,12 @@ export class TransitionAnimationEngine {
|
||||
return () => {};
|
||||
}
|
||||
|
||||
private _buildInstruction(entry: QueueInstruction, subTimelines: ElementInstructionMap) {
|
||||
private _buildInstruction(
|
||||
entry: QueueInstruction, subTimelines: ElementInstructionMap, enterClassName: string,
|
||||
leaveClassName: string) {
|
||||
return entry.transition.build(
|
||||
this.driver, entry.element, entry.fromState.value, entry.toState.value,
|
||||
entry.fromState.options, entry.toState.options, subTimelines);
|
||||
this.driver, entry.element, entry.fromState.value, entry.toState.value, enterClassName,
|
||||
leaveClassName, entry.fromState.options, entry.toState.options, subTimelines);
|
||||
}
|
||||
|
||||
destroyInnerAnimations(containerElement: any) {
|
||||
@ -798,6 +802,13 @@ export class TransitionAnimationEngine {
|
||||
this.newHostElements.clear();
|
||||
}
|
||||
|
||||
if (this.totalAnimations && this.collectedEnterElements.length) {
|
||||
for (let i = 0; i < this.collectedEnterElements.length; i++) {
|
||||
const elm = this.collectedEnterElements[i];
|
||||
addClass(elm, STAR_CLASSNAME);
|
||||
}
|
||||
}
|
||||
|
||||
if (this._namespaceList.length &&
|
||||
(this.totalQueuedPlayers || this.collectedLeaveElements.length)) {
|
||||
const cleanupFns: Function[] = [];
|
||||
@ -862,37 +873,57 @@ export class TransitionAnimationEngine {
|
||||
});
|
||||
|
||||
const bodyNode = getBodyNode();
|
||||
const allEnterNodes: any[] = this.collectedEnterElements.length ?
|
||||
this.collectedEnterElements.filter(createIsRootFilterFn(this.collectedEnterElements)) :
|
||||
[];
|
||||
const allTriggerElements = Array.from(this.statesByElement.keys());
|
||||
const enterNodeMap = buildRootMap(allTriggerElements, this.collectedEnterElements);
|
||||
|
||||
// this must occur before the instructions are built below such that
|
||||
// the :enter queries match the elements (since the timeline queries
|
||||
// are fired during instruction building).
|
||||
for (let i = 0; i < allEnterNodes.length; i++) {
|
||||
addClass(allEnterNodes[i], ENTER_CLASSNAME);
|
||||
}
|
||||
const enterNodeMapIds = new Map<any, string>();
|
||||
let i = 0;
|
||||
enterNodeMap.forEach((nodes, root) => {
|
||||
const className = ENTER_CLASSNAME + i++;
|
||||
enterNodeMapIds.set(root, className);
|
||||
nodes.forEach(node => addClass(node, className));
|
||||
});
|
||||
|
||||
const allLeaveNodes: any[] = [];
|
||||
const mergedLeaveNodes = new Set<any>();
|
||||
const leaveNodesWithoutAnimations = new Set<any>();
|
||||
for (let i = 0; i < this.collectedLeaveElements.length; i++) {
|
||||
const element = this.collectedLeaveElements[i];
|
||||
const details = element[REMOVAL_FLAG] as ElementAnimationState;
|
||||
if (details && details.setForRemoval) {
|
||||
addClass(element, LEAVE_CLASSNAME);
|
||||
allLeaveNodes.push(element);
|
||||
if (!details.hasAnimation) {
|
||||
mergedLeaveNodes.add(element);
|
||||
if (details.hasAnimation) {
|
||||
this.driver.query(element, STAR_SELECTOR, true).forEach(elm => mergedLeaveNodes.add(elm));
|
||||
} else {
|
||||
leaveNodesWithoutAnimations.add(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const leaveNodeMapIds = new Map<any, string>();
|
||||
const leaveNodeMap = buildRootMap(allTriggerElements, Array.from(mergedLeaveNodes));
|
||||
leaveNodeMap.forEach((nodes, root) => {
|
||||
const className = LEAVE_CLASSNAME + i++;
|
||||
leaveNodeMapIds.set(root, className);
|
||||
nodes.forEach(node => addClass(node, className));
|
||||
});
|
||||
|
||||
cleanupFns.push(() => {
|
||||
allEnterNodes.forEach(element => removeClass(element, ENTER_CLASSNAME));
|
||||
allLeaveNodes.forEach(element => {
|
||||
removeClass(element, LEAVE_CLASSNAME);
|
||||
this.processLeaveNode(element);
|
||||
enterNodeMap.forEach((nodes, root) => {
|
||||
const className = enterNodeMapIds.get(root) !;
|
||||
nodes.forEach(node => removeClass(node, className));
|
||||
});
|
||||
|
||||
leaveNodeMap.forEach((nodes, root) => {
|
||||
const className = leaveNodeMapIds.get(root) !;
|
||||
nodes.forEach(node => removeClass(node, className));
|
||||
});
|
||||
|
||||
allLeaveNodes.forEach(element => { this.processLeaveNode(element); });
|
||||
});
|
||||
|
||||
const allPlayers: TransitionAnimationPlayer[] = [];
|
||||
@ -909,7 +940,10 @@ export class TransitionAnimationEngine {
|
||||
return;
|
||||
}
|
||||
|
||||
const instruction = this._buildInstruction(entry, subTimelines) !;
|
||||
const leaveClassName = leaveNodeMapIds.get(element) !;
|
||||
const enterClassName = enterNodeMapIds.get(element) !;
|
||||
const instruction =
|
||||
this._buildInstruction(entry, subTimelines, enterClassName, leaveClassName) !;
|
||||
if (instruction.errors && instruction.errors.length) {
|
||||
erroneousTransitions.push(instruction);
|
||||
return;
|
||||
@ -973,18 +1007,6 @@ export class TransitionAnimationEngine {
|
||||
this.reportError(errors);
|
||||
}
|
||||
|
||||
// these can only be detected here since we have a map of all the elements
|
||||
// that have animations attached to them... We use a set here in the event
|
||||
// multiple enter captures on the same element were caught in different
|
||||
// renderer namespaces (e.g. when a @trigger was on a host binding that had *ngIf)
|
||||
const enterNodesWithoutAnimations = new Set<any>();
|
||||
for (let i = 0; i < allEnterNodes.length; i++) {
|
||||
const element = allEnterNodes[i];
|
||||
if (!subTimelines.has(element)) {
|
||||
enterNodesWithoutAnimations.add(element);
|
||||
}
|
||||
}
|
||||
|
||||
const allPreviousPlayersMap = new Map<any, TransitionAnimationPlayer[]>();
|
||||
// this map works to tell which element in the DOM tree is contained by
|
||||
// which animation. Further down below this map will get populated once
|
||||
@ -1022,8 +1044,9 @@ export class TransitionAnimationEngine {
|
||||
});
|
||||
|
||||
// POST STAGE: fill the * styles
|
||||
const [postStylesMap, allLeaveQueriedNodes] = cloakAndComputeStyles(
|
||||
this.driver, leaveNodesWithoutAnimations, allPostStyleElements, AUTO_STYLE);
|
||||
const postStylesMap = new Map<any, ɵStyleData>();
|
||||
const allLeaveQueriedNodes = cloakAndComputeStyles(
|
||||
postStylesMap, this.driver, leaveNodesWithoutAnimations, allPostStyleElements, AUTO_STYLE);
|
||||
|
||||
allLeaveQueriedNodes.forEach(node => {
|
||||
if (replacePostStylesAsPre(node, allPreStyleElements, allPostStyleElements)) {
|
||||
@ -1032,10 +1055,11 @@ export class TransitionAnimationEngine {
|
||||
});
|
||||
|
||||
// PRE STAGE: fill the ! styles
|
||||
const [preStylesMap] = allPreStyleElements.size ?
|
||||
cloakAndComputeStyles(
|
||||
this.driver, enterNodesWithoutAnimations, allPreStyleElements, PRE_STYLE) :
|
||||
[new Map<any, ɵStyleData>()];
|
||||
const preStylesMap = new Map<any, ɵStyleData>();
|
||||
enterNodeMap.forEach((nodes, root) => {
|
||||
cloakAndComputeStyles(
|
||||
preStylesMap, this.driver, new Set(nodes), allPreStyleElements, PRE_STYLE);
|
||||
});
|
||||
|
||||
replaceNodes.forEach(node => {
|
||||
const post = postStylesMap.get(node);
|
||||
@ -1505,12 +1529,11 @@ function cloakElement(element: any, value?: string) {
|
||||
}
|
||||
|
||||
function cloakAndComputeStyles(
|
||||
driver: AnimationDriver, elements: Set<any>, elementPropsMap: Map<any, Set<string>>,
|
||||
defaultStyle: string): [Map<any, ɵStyleData>, any[]] {
|
||||
valuesMap: Map<any, ɵStyleData>, driver: AnimationDriver, elements: Set<any>,
|
||||
elementPropsMap: Map<any, Set<string>>, defaultStyle: string): any[] {
|
||||
const cloakVals: string[] = [];
|
||||
elements.forEach(element => cloakVals.push(cloakElement(element)));
|
||||
|
||||
const valuesMap = new Map<any, ɵStyleData>();
|
||||
const failedElements: any[] = [];
|
||||
|
||||
elementPropsMap.forEach((props: Set<string>, element: any) => {
|
||||
@ -1532,39 +1555,57 @@ function cloakAndComputeStyles(
|
||||
// an index value for the closure (but instead just the value)
|
||||
let i = 0;
|
||||
elements.forEach(element => cloakElement(element, cloakVals[i++]));
|
||||
return [valuesMap, failedElements];
|
||||
|
||||
return failedElements;
|
||||
}
|
||||
|
||||
/*
|
||||
Since the Angular renderer code will return a collection of inserted
|
||||
nodes in all areas of a DOM tree, it's up to this algorithm to figure
|
||||
out which nodes are roots.
|
||||
out which nodes are roots for each animation @trigger.
|
||||
|
||||
By placing all nodes into a set and traversing upwards to the edge,
|
||||
the recursive code can figure out if a clean path from the DOM node
|
||||
to the edge container is clear. If no other node is detected in the
|
||||
set then it is a root element.
|
||||
|
||||
This algorithm also keeps track of all nodes along the path so that
|
||||
if other sibling nodes are also tracked then the lookup process can
|
||||
skip a lot of steps in between and avoid traversing the entire tree
|
||||
multiple times to the edge.
|
||||
By placing each inserted node into a Set and traversing upwards, it
|
||||
is possible to find the @trigger elements and well any direct *star
|
||||
insertion nodes, if a @trigger root is found then the enter element
|
||||
is placed into the Map[@trigger] spot.
|
||||
*/
|
||||
function createIsRootFilterFn(nodes: any): (node: any) => boolean {
|
||||
function buildRootMap(roots: any[], nodes: any[]): Map<any, any[]> {
|
||||
const rootMap = new Map<any, any[]>();
|
||||
roots.forEach(root => rootMap.set(root, []));
|
||||
|
||||
if (nodes.length == 0) return rootMap;
|
||||
|
||||
const NULL_NODE = 1;
|
||||
const nodeSet = new Set(nodes);
|
||||
const knownRootContainer = new Set();
|
||||
let isRoot: (node: any) => boolean;
|
||||
isRoot = node => {
|
||||
if (!node) return true;
|
||||
if (nodeSet.has(node.parentNode)) return false;
|
||||
if (knownRootContainer.has(node.parentNode)) return true;
|
||||
if (isRoot(node.parentNode)) {
|
||||
knownRootContainer.add(node);
|
||||
return true;
|
||||
const localRootMap = new Map<any, any>();
|
||||
|
||||
function getRoot(node: any): any {
|
||||
if (!node) return NULL_NODE;
|
||||
|
||||
let root = localRootMap.get(node);
|
||||
if (root) return root;
|
||||
|
||||
const parent = node.parentNode;
|
||||
if (rootMap.has(parent)) { // ngIf inside @trigger
|
||||
root = parent;
|
||||
} else if (nodeSet.has(parent)) { // ngIf inside ngIf
|
||||
root = NULL_NODE;
|
||||
} else { // recurse upwards
|
||||
root = getRoot(parent);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
return isRoot;
|
||||
|
||||
localRootMap.set(node, root);
|
||||
return root;
|
||||
}
|
||||
|
||||
nodes.forEach(node => {
|
||||
const root = getRoot(node);
|
||||
if (root !== NULL_NODE) {
|
||||
rootMap.get(root) !.push(node);
|
||||
}
|
||||
});
|
||||
|
||||
return rootMap;
|
||||
}
|
||||
|
||||
const CLASSES_CACHE_KEY = '$$classes';
|
||||
|
@ -10,6 +10,7 @@ import {AnimationOptions, animate, state, style, transition} from '@angular/anim
|
||||
import {AnimationTransitionInstruction} from '@angular/animations/browser/src/dsl/animation_transition_instruction';
|
||||
import {AnimationTrigger} from '@angular/animations/browser/src/dsl/animation_trigger';
|
||||
|
||||
import {ENTER_CLASSNAME, LEAVE_CLASSNAME} from '../../src/util';
|
||||
import {MockAnimationDriver} from '../../testing';
|
||||
import {makeTrigger} from '../shared';
|
||||
|
||||
@ -228,7 +229,9 @@ function buildTransition(
|
||||
const trans = trigger.matchTransition(fromState, toState) !;
|
||||
if (trans) {
|
||||
const driver = new MockAnimationDriver();
|
||||
return trans.build(driver, element, fromState, toState, fromOptions, toOptions) !;
|
||||
return trans.build(
|
||||
driver, element, fromState, toState, ENTER_CLASSNAME, LEAVE_CLASSNAME, fromOptions,
|
||||
toOptions) !;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -352,12 +352,10 @@ export class HttpClient {
|
||||
|
||||
// Figure out the headers.
|
||||
let headers: HttpHeaders|undefined = undefined;
|
||||
if (!!options.headers !== undefined) {
|
||||
if (options.headers instanceof HttpHeaders) {
|
||||
headers = options.headers;
|
||||
} else {
|
||||
headers = new HttpHeaders(options.headers);
|
||||
}
|
||||
if (options.headers instanceof HttpHeaders) {
|
||||
headers = options.headers;
|
||||
} else {
|
||||
headers = new HttpHeaders(options.headers);
|
||||
}
|
||||
|
||||
// Sort out parameters.
|
||||
@ -371,7 +369,7 @@ export class HttpClient {
|
||||
}
|
||||
|
||||
// Construct the request.
|
||||
req = new HttpRequest(first, url !, options.body || null, {
|
||||
req = new HttpRequest(first, url !, (options.body !== undefined ? options.body : null), {
|
||||
headers,
|
||||
params,
|
||||
reportProgress: options.reportProgress,
|
||||
|
@ -171,7 +171,7 @@ export class HttpRequest<T> {
|
||||
// the body argument is to use a known no-body method like GET.
|
||||
if (mightHaveBody(this.method) || !!fourth) {
|
||||
// Body is the third argument, options are the fourth.
|
||||
this.body = third as T || null;
|
||||
this.body = (third !== undefined) ? third as T : null;
|
||||
options = fourth;
|
||||
} else {
|
||||
// No body required, options are the third argument. The body stays null.
|
||||
|
@ -262,7 +262,7 @@ export class HttpResponse<T> extends HttpResponseBase {
|
||||
body?: T | null, headers?: HttpHeaders; status?: number; statusText?: string; url?: string;
|
||||
} = {}) {
|
||||
super(init);
|
||||
this.body = init.body || null;
|
||||
this.body = init.body !== undefined ? init.body : null;
|
||||
}
|
||||
|
||||
readonly type: HttpEventType.Response = HttpEventType.Response;
|
||||
|
@ -180,24 +180,27 @@ export class HttpXhrBackend implements HttpBackend {
|
||||
|
||||
// Check whether the body needs to be parsed as JSON (in many cases the browser
|
||||
// will have done that already).
|
||||
if (ok && req.responseType === 'json' && typeof body === 'string') {
|
||||
// Attempt the parse. If it fails, a parse error should be delivered to the user.
|
||||
if (req.responseType === 'json' && typeof body === 'string') {
|
||||
// Save the original body, before attempting XSSI prefix stripping.
|
||||
const originalBody = body;
|
||||
body = body.replace(XSSI_PREFIX, '');
|
||||
try {
|
||||
body = JSON.parse(body);
|
||||
// Attempt the parse. If it fails, a parse error should be delivered to the user.
|
||||
body = body !== '' ? JSON.parse(body) : null;
|
||||
} catch (error) {
|
||||
// Even though the response status was 2xx, this is still an error.
|
||||
ok = false;
|
||||
// The parse error contains the text of the body that failed to parse.
|
||||
body = { error, text: body } as HttpJsonParseError;
|
||||
}
|
||||
} else if (!ok && req.responseType === 'json' && typeof body === 'string') {
|
||||
try {
|
||||
// Attempt to parse the body as JSON.
|
||||
body = JSON.parse(body);
|
||||
} catch (error) {
|
||||
// Cannot be certain that the body was meant to be parsed as JSON.
|
||||
// Leave the body as a string.
|
||||
// Since the JSON.parse failed, it's reasonable to assume this might not have been a
|
||||
// JSON response. Restore the original body (including any XSSI prefix) to deliver
|
||||
// a better error response.
|
||||
body = originalBody;
|
||||
|
||||
// If this was an error request to begin with, leave it as a string, it probably
|
||||
// just isn't JSON. Otherwise, deliver the parsing error to the user.
|
||||
if (ok) {
|
||||
// Even though the response status was 2xx, this is still an error.
|
||||
ok = false;
|
||||
// The parse error contains the text of the body that failed to parse.
|
||||
body = { error, text: body } as HttpJsonParseError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -115,6 +115,26 @@ export function main() {
|
||||
expect(testReq.request.body).toBe(body);
|
||||
testReq.flush('hello world');
|
||||
});
|
||||
it('with a json body of false', (done: DoneFn) => {
|
||||
client.post('/test', false, {observe: 'response', responseType: 'text'}).subscribe(res => {
|
||||
expect(res.ok).toBeTruthy();
|
||||
expect(res.status).toBe(200);
|
||||
done();
|
||||
});
|
||||
const testReq = backend.expectOne('/test');
|
||||
expect(testReq.request.body).toBe(false);
|
||||
testReq.flush('hello world');
|
||||
});
|
||||
it('with a json body of 0', (done: DoneFn) => {
|
||||
client.post('/test', 0, {observe: 'response', responseType: 'text'}).subscribe(res => {
|
||||
expect(res.ok).toBeTruthy();
|
||||
expect(res.status).toBe(200);
|
||||
done();
|
||||
});
|
||||
const testReq = backend.expectOne('/test');
|
||||
expect(testReq.request.body).toBe(0);
|
||||
testReq.flush('hello world');
|
||||
});
|
||||
it('with an arraybuffer', (done: DoneFn) => {
|
||||
const body = new ArrayBuffer(4);
|
||||
client.post('/test', body, {observe: 'response', responseType: 'text'}).subscribe(res => {
|
||||
|
@ -40,6 +40,10 @@ export function main() {
|
||||
expect(resp.ok).toBeTruthy();
|
||||
expect(resp.url).toBeNull();
|
||||
});
|
||||
it('accepts a falsy body', () => {
|
||||
expect(new HttpResponse({body: false}).body).toEqual(false);
|
||||
expect(new HttpResponse({body: 0}).body).toEqual(0);
|
||||
});
|
||||
});
|
||||
it('.ok is determined by status', () => {
|
||||
const good = new HttpResponse({status: 200});
|
||||
|
@ -25,6 +25,8 @@ const TEST_POST = new HttpRequest('POST', '/test', 'some body', {
|
||||
responseType: 'text',
|
||||
});
|
||||
|
||||
const XSSI_PREFIX = ')]}\'\n';
|
||||
|
||||
export function main() {
|
||||
describe('XhrBackend', () => {
|
||||
let factory: MockXhrFactory = null !;
|
||||
@ -92,6 +94,13 @@ export function main() {
|
||||
const res = events[1] as HttpResponse<{data: string}>;
|
||||
expect(res.body !.data).toBe('some data');
|
||||
});
|
||||
it('handles a blank json response', () => {
|
||||
const events = trackEvents(backend.handle(TEST_POST.clone({responseType: 'json'})));
|
||||
factory.mock.mockFlush(200, 'OK', '');
|
||||
expect(events.length).toBe(2);
|
||||
const res = events[1] as HttpResponse<{data: string}>;
|
||||
expect(res.body).toBeNull();
|
||||
});
|
||||
it('handles a json error response', () => {
|
||||
const events = trackEvents(backend.handle(TEST_POST.clone({responseType: 'json'})));
|
||||
factory.mock.mockFlush(500, 'Error', JSON.stringify({data: 'some data'}));
|
||||
@ -99,6 +108,13 @@ export function main() {
|
||||
const res = events[1] as any as HttpErrorResponse;
|
||||
expect(res.error !.data).toBe('some data');
|
||||
});
|
||||
it('handles a json error response with XSSI prefix', () => {
|
||||
const events = trackEvents(backend.handle(TEST_POST.clone({responseType: 'json'})));
|
||||
factory.mock.mockFlush(500, 'Error', XSSI_PREFIX + JSON.stringify({data: 'some data'}));
|
||||
expect(events.length).toBe(2);
|
||||
const res = events[1] as any as HttpErrorResponse;
|
||||
expect(res.error !.data).toBe('some data');
|
||||
});
|
||||
it('handles a json string response', () => {
|
||||
const events = trackEvents(backend.handle(TEST_POST.clone({responseType: 'json'})));
|
||||
expect(factory.mock.responseType).toEqual('text');
|
||||
@ -109,7 +125,7 @@ export function main() {
|
||||
});
|
||||
it('handles a json response with an XSSI prefix', () => {
|
||||
const events = trackEvents(backend.handle(TEST_POST.clone({responseType: 'json'})));
|
||||
factory.mock.mockFlush(200, 'OK', ')]}\'\n' + JSON.stringify({data: 'some data'}));
|
||||
factory.mock.mockFlush(200, 'OK', XSSI_PREFIX + JSON.stringify({data: 'some data'}));
|
||||
expect(events.length).toBe(2);
|
||||
const res = events[1] as HttpResponse<{data: string}>;
|
||||
expect(res.body !.data).toBe('some data');
|
||||
|
@ -62,7 +62,7 @@ class AngularCompilerProgram implements Program {
|
||||
|
||||
constructor(
|
||||
private rootNames: string[], private options: CompilerOptions, private host: CompilerHost,
|
||||
private oldProgram?: Program) {
|
||||
oldProgram?: Program) {
|
||||
const [major, minor] = ts.version.split('.');
|
||||
if (Number(major) < 2 || (Number(major) === 2 && Number(minor) < 4)) {
|
||||
throw new Error('The Angular Compiler requires TypeScript >= 2.4.');
|
||||
@ -801,9 +801,16 @@ export function i18nSerialize(
|
||||
default:
|
||||
serializer = new Xliff();
|
||||
}
|
||||
return bundle.write(
|
||||
serializer, (sourcePath: string) =>
|
||||
options.basePath ? path.relative(options.basePath, sourcePath) : sourcePath);
|
||||
|
||||
return bundle.write(serializer, getPathNormalizer(options.basePath));
|
||||
}
|
||||
|
||||
function getPathNormalizer(basePath?: string) {
|
||||
// normalize sourcepaths by removing the base path and always using "/" as a separator
|
||||
return (sourcePath: string) => {
|
||||
sourcePath = basePath ? path.relative(basePath, sourcePath) : sourcePath;
|
||||
return sourcePath.split(path.sep).join('/');
|
||||
};
|
||||
}
|
||||
|
||||
export function i18nGetExtension(formatName: string): string {
|
||||
|
@ -31,7 +31,7 @@ import {EventEmitter} from '../event_emitter';
|
||||
* import {NgIf} from '@angular/common';
|
||||
*
|
||||
* @Component({
|
||||
* selector: 'ng-zone-demo'.
|
||||
* selector: 'ng-zone-demo',
|
||||
* template: `
|
||||
* <h2>Demo: NgZone</h2>
|
||||
*
|
||||
@ -63,9 +63,10 @@ import {EventEmitter} from '../event_emitter';
|
||||
* this.progress = 0;
|
||||
* this._ngZone.runOutsideAngular(() => {
|
||||
* this._increaseProgress(() => {
|
||||
* // reenter the Angular zone and display done
|
||||
* this._ngZone.run(() => {console.log('Outside Done!') });
|
||||
* }}));
|
||||
* // reenter the Angular zone and display done
|
||||
* this._ngZone.run(() => { console.log('Outside Done!'); });
|
||||
* });
|
||||
* });
|
||||
* }
|
||||
*
|
||||
* _increaseProgress(doneCallback: () => void) {
|
||||
@ -73,7 +74,7 @@ import {EventEmitter} from '../event_emitter';
|
||||
* console.log(`Current progress: ${this.progress}%`);
|
||||
*
|
||||
* if (this.progress < 100) {
|
||||
* window.setTimeout(() => this._increaseProgress(doneCallback)), 10)
|
||||
* window.setTimeout(() => this._increaseProgress(doneCallback), 10);
|
||||
* } else {
|
||||
* doneCallback();
|
||||
* }
|
||||
|
@ -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 {AUTO_STYLE, AnimationPlayer, animate, animateChild, query, stagger, state, style, transition, trigger, ɵAnimationGroupPlayer as AnimationGroupPlayer} from '@angular/animations';
|
||||
import {AUTO_STYLE, AnimationPlayer, animate, animateChild, group, query, sequence, stagger, state, style, transition, trigger, ɵAnimationGroupPlayer as AnimationGroupPlayer} from '@angular/animations';
|
||||
import {AnimationDriver, ɵAnimationEngine} from '@angular/animations/browser';
|
||||
import {matchesElement} from '@angular/animations/browser/src/render/shared';
|
||||
import {ENTER_CLASSNAME, LEAVE_CLASSNAME} from '@angular/animations/browser/src/util';
|
||||
@ -2968,6 +2968,137 @@ export function main() {
|
||||
{offset: 0, width: '0px'}, {offset: .67, width: '0px'}, {offset: 1, width: '200px'}
|
||||
]);
|
||||
});
|
||||
|
||||
it('should scope :enter queries between sub animations', () => {
|
||||
@Component({
|
||||
selector: 'cmp',
|
||||
animations: [
|
||||
trigger(
|
||||
'parent',
|
||||
[
|
||||
transition(':enter', group([
|
||||
sequence([
|
||||
style({opacity: 0}),
|
||||
animate(1000, style({opacity: 1})),
|
||||
]),
|
||||
query(':enter @child', animateChild()),
|
||||
])),
|
||||
]),
|
||||
trigger(
|
||||
'child',
|
||||
[
|
||||
transition(
|
||||
':enter',
|
||||
[
|
||||
query(
|
||||
':enter .item',
|
||||
[style({opacity: 0}), animate(1000, style({opacity: 1}))]),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
template: `
|
||||
<div @parent *ngIf="exp1" class="container">
|
||||
<div *ngIf="exp2">
|
||||
<div @child>
|
||||
<div *ngIf="exp3">
|
||||
<div class="item"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
class Cmp {
|
||||
public exp1: any;
|
||||
public exp2: any;
|
||||
public exp3: any;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [Cmp]});
|
||||
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
fixture.detectChanges();
|
||||
resetLog();
|
||||
|
||||
const cmp = fixture.componentInstance;
|
||||
cmp.exp1 = true;
|
||||
cmp.exp2 = true;
|
||||
cmp.exp3 = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
const players = getLog();
|
||||
expect(players.length).toEqual(2);
|
||||
|
||||
const [p1, p2] = players;
|
||||
expect(p1.element.classList.contains('container')).toBeTruthy();
|
||||
expect(p2.element.classList.contains('item')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should scope :leave queries between sub animations', () => {
|
||||
@Component({
|
||||
selector: 'cmp',
|
||||
animations: [
|
||||
trigger(
|
||||
'parent',
|
||||
[
|
||||
transition(':leave', group([
|
||||
sequence([
|
||||
style({opacity: 0}),
|
||||
animate(1000, style({opacity: 1})),
|
||||
]),
|
||||
query(':leave @child', animateChild()),
|
||||
])),
|
||||
]),
|
||||
trigger(
|
||||
'child',
|
||||
[
|
||||
transition(
|
||||
':leave',
|
||||
[
|
||||
query(
|
||||
':leave .item',
|
||||
[style({opacity: 0}), animate(1000, style({opacity: 1}))]),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
template: `
|
||||
<div @parent *ngIf="exp1" class="container">
|
||||
<div *ngIf="exp2">
|
||||
<div @child>
|
||||
<div *ngIf="exp3">
|
||||
<div class="item"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
class Cmp {
|
||||
public exp1: any;
|
||||
public exp2: any;
|
||||
public exp3: any;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [Cmp]});
|
||||
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
cmp.exp1 = true;
|
||||
cmp.exp2 = true;
|
||||
cmp.exp3 = true;
|
||||
fixture.detectChanges();
|
||||
resetLog();
|
||||
|
||||
cmp.exp1 = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
const players = getLog();
|
||||
expect(players.length).toEqual(2);
|
||||
|
||||
const [p1, p2] = players;
|
||||
expect(p1.element.classList.contains('container')).toBeTruthy();
|
||||
expect(p2.element.classList.contains('item')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('animation control flags', () => {
|
||||
|
@ -1,4 +1,4 @@
|
||||
{
|
||||
g{
|
||||
"maxLength": 120,
|
||||
"types": [
|
||||
"build",
|
||||
|
Reference in New Issue
Block a user