Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
28a92b2bcd | |||
48be539824 | |||
d788c679b6 | |||
a38f14b39c | |||
6a5e46cedd | |||
6316e5df71 | |||
90fca7c879 | |||
d871ae2dc6 | |||
44e84d87f9 | |||
b9e979e0a5 | |||
cb2aa41782 | |||
189a7e3750 |
13
CHANGELOG.md
13
CHANGELOG.md
@ -1,3 +1,16 @@
|
|||||||
|
<a name="2.4.1"></a>
|
||||||
|
## [2.4.1](https://github.com/angular/angular/compare/2.4.0...2.4.1) (2016-12-21)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **animations:** always quote string map key values in AOT code ([#13602](https://github.com/angular/angular/issues/13602)) ([6a5e46c](https://github.com/angular/angular/commit/6a5e46c))
|
||||||
|
* **animations:** always recover from a failed animation step ([#13604](https://github.com/angular/angular/issues/13604)) ([d788c67](https://github.com/angular/angular/commit/d788c67))
|
||||||
|
* **compiler:** ignore `@import` in comments ([#13368](https://github.com/angular/angular/issues/13368)) ([6316e5d](https://github.com/angular/angular/commit/6316e5d)), closes [#12196](https://github.com/angular/angular/issues/12196)
|
||||||
|
* **core:** improve error message when component factory cannot be found ([#13541](https://github.com/angular/angular/issues/13541)) ([b9e979e](https://github.com/angular/angular/commit/b9e979e)), closes [#12678](https://github.com/angular/angular/issues/12678)
|
||||||
|
* **router:** should reset location if a navigation by location is successful ([#13545](https://github.com/angular/angular/issues/13545)) ([a38f14b](https://github.com/angular/angular/commit/a38f14b)), closes [#13491](https://github.com/angular/angular/issues/13491)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="2.4.0"></a>
|
<a name="2.4.0"></a>
|
||||||
# [2.4.0 stability-interjection](https://github.com/angular/angular/compare/2.3.1...2.4.0) (2016-12-20)
|
# [2.4.0 stability-interjection](https://github.com/angular/angular/compare/2.3.1...2.4.0) (2016-12-20)
|
||||||
|
|
||||||
|
12
DEVELOPER.md
12
DEVELOPER.md
@ -74,6 +74,15 @@ use in these instructions.
|
|||||||
*Option 2*: defining a bash alias like `alias nbin='PATH=$(npm bin):$PATH'` as detailed in this
|
*Option 2*: defining a bash alias like `alias nbin='PATH=$(npm bin):$PATH'` as detailed in this
|
||||||
[Stackoverflow answer](http://stackoverflow.com/questions/9679932/how-to-use-package-installed-locally-in-node-modules/15157360#15157360) and used like this: e.g., `nbin gulp build`.
|
[Stackoverflow answer](http://stackoverflow.com/questions/9679932/how-to-use-package-installed-locally-in-node-modules/15157360#15157360) and used like this: e.g., `nbin gulp build`.
|
||||||
|
|
||||||
|
## Installing Bower Modules
|
||||||
|
|
||||||
|
Now run `bower` to install additional dependencies:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# Install other Angular project dependencies (bower.json)
|
||||||
|
bower install
|
||||||
|
```
|
||||||
|
|
||||||
## Windows only
|
## Windows only
|
||||||
|
|
||||||
In order to create the right symlinks, run **as administrator**:
|
In order to create the right symlinks, run **as administrator**:
|
||||||
@ -155,3 +164,6 @@ For subsequent snapshots, just run
|
|||||||
``` shell
|
``` shell
|
||||||
$ ./scripts/publish/publish-build-artifacts.sh [github username]
|
$ ./scripts/publish/publish-build-artifacts.sh [github username]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The script will publish the build snapshot to a branch with the same name as your current branch,
|
||||||
|
and create it if it doesn't exist.
|
||||||
|
@ -89,9 +89,18 @@ export class NgForRow {
|
|||||||
@Directive({selector: '[ngFor][ngForOf]'})
|
@Directive({selector: '[ngFor][ngForOf]'})
|
||||||
export class NgFor implements DoCheck, OnChanges {
|
export class NgFor implements DoCheck, OnChanges {
|
||||||
@Input() ngForOf: any;
|
@Input() ngForOf: any;
|
||||||
@Input() ngForTrackBy: TrackByFn;
|
@Input()
|
||||||
|
set ngForTrackBy(fn: TrackByFn) {
|
||||||
|
if (typeof fn !== 'function') {
|
||||||
|
throw new Error(`trackBy must be a function, but received ${JSON.stringify(fn)}`);
|
||||||
|
}
|
||||||
|
this._trackByFn = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
get ngForTrackBy(): TrackByFn { return this._trackByFn; }
|
||||||
|
|
||||||
private _differ: IterableDiffer = null;
|
private _differ: IterableDiffer = null;
|
||||||
|
private _trackByFn: TrackByFn;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private _viewContainer: ViewContainerRef, private _template: TemplateRef<NgForRow>,
|
private _viewContainer: ViewContainerRef, private _template: TemplateRef<NgForRow>,
|
||||||
@ -119,7 +128,7 @@ export class NgFor implements DoCheck, OnChanges {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngDoCheck() {
|
ngDoCheck(): void {
|
||||||
if (this._differ) {
|
if (this._differ) {
|
||||||
const changes = this._differ.diff(this.ngForOf);
|
const changes = this._differ.diff(this.ngForOf);
|
||||||
if (changes) this._applyChanges(changes);
|
if (changes) this._applyChanges(changes);
|
||||||
|
@ -294,6 +294,16 @@ export function main() {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
describe('track by', () => {
|
describe('track by', () => {
|
||||||
|
it('should throw if trackBy is not a function', async(() => {
|
||||||
|
const template =
|
||||||
|
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="item?.id"></template>`;
|
||||||
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
|
getComponent().items = [{id: 1}, {id: 2}];
|
||||||
|
expect(() => fixture.detectChanges())
|
||||||
|
.toThrowError(/trackBy must be a function, but received null/);
|
||||||
|
}));
|
||||||
|
|
||||||
it('should set the context to the component instance', async(() => {
|
it('should set the context to the component instance', async(() => {
|
||||||
const template =
|
const template =
|
||||||
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackByContext.bind(this)"></template>`;
|
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackByContext.bind(this)"></template>`;
|
||||||
|
@ -66,7 +66,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
|||||||
ast.styles.forEach(entry => {
|
ast.styles.forEach(entry => {
|
||||||
const entries =
|
const entries =
|
||||||
Object.keys(entry).map((key): [string, o.Expression] => [key, o.literal(entry[key])]);
|
Object.keys(entry).map((key): [string, o.Expression] => [key, o.literal(entry[key])]);
|
||||||
stylesArr.push(o.literalMap(entries));
|
stylesArr.push(o.literalMap(entries, null, true));
|
||||||
});
|
});
|
||||||
|
|
||||||
return o.importExpr(createIdentifier(Identifiers.AnimationStyles)).instantiate([
|
return o.importExpr(createIdentifier(Identifiers.AnimationStyles)).instantiate([
|
||||||
@ -322,12 +322,13 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
|||||||
if (isPresent(value)) {
|
if (isPresent(value)) {
|
||||||
const styleMap: any[] = [];
|
const styleMap: any[] = [];
|
||||||
Object.keys(value).forEach(key => { styleMap.push([key, o.literal(value[key])]); });
|
Object.keys(value).forEach(key => { styleMap.push([key, o.literal(value[key])]); });
|
||||||
variableValue = o.literalMap(styleMap);
|
variableValue = o.literalMap(styleMap, null, true);
|
||||||
}
|
}
|
||||||
lookupMap.push([stateName, variableValue]);
|
lookupMap.push([stateName, variableValue]);
|
||||||
});
|
});
|
||||||
|
|
||||||
const compiledStatesMapStmt = this._statesMapVar.set(o.literalMap(lookupMap)).toDeclStmt();
|
const compiledStatesMapStmt =
|
||||||
|
this._statesMapVar.set(o.literalMap(lookupMap, null, true)).toDeclStmt();
|
||||||
const statements: o.Statement[] = [compiledStatesMapStmt, fnStatement];
|
const statements: o.Statement[] = [compiledStatesMapStmt, fnStatement];
|
||||||
|
|
||||||
return new AnimationEntryCompileResult(this.animationName, statements, fnVariable);
|
return new AnimationEntryCompileResult(this.animationName, statements, fnVariable);
|
||||||
|
@ -894,8 +894,10 @@ export function literalArr(values: Expression[], type: Type = null): LiteralArra
|
|||||||
return new LiteralArrayExpr(values, type);
|
return new LiteralArrayExpr(values, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function literalMap(values: [string, Expression][], type: MapType = null): LiteralMapExpr {
|
export function literalMap(
|
||||||
return new LiteralMapExpr(values.map(entry => new LiteralMapEntry(entry[0], entry[1])), type);
|
values: [string, Expression][], type: MapType = null, quoted: boolean = false): LiteralMapExpr {
|
||||||
|
return new LiteralMapExpr(
|
||||||
|
values.map(entry => new LiteralMapEntry(entry[0], entry[1], quoted)), type);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function not(expr: Expression): NotExpr {
|
export function not(expr: Expression): NotExpr {
|
||||||
|
@ -9,8 +9,6 @@
|
|||||||
// Some of the code comes from WebComponents.JS
|
// Some of the code comes from WebComponents.JS
|
||||||
// https://github.com/webcomponents/webcomponentsjs/blob/master/src/HTMLImports/path.js
|
// https://github.com/webcomponents/webcomponentsjs/blob/master/src/HTMLImports/path.js
|
||||||
|
|
||||||
import {isBlank, isPresent} from './facade/lang';
|
|
||||||
|
|
||||||
import {UrlResolver} from './url_resolver';
|
import {UrlResolver} from './url_resolver';
|
||||||
|
|
||||||
export class StyleWithImports {
|
export class StyleWithImports {
|
||||||
@ -18,8 +16,8 @@ export class StyleWithImports {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function isStyleUrlResolvable(url: string): boolean {
|
export function isStyleUrlResolvable(url: string): boolean {
|
||||||
if (isBlank(url) || url.length === 0 || url[0] == '/') return false;
|
if (url == null || url.length === 0 || url[0] == '/') return false;
|
||||||
const schemeMatch = url.match(_urlWithSchemaRe);
|
const schemeMatch = url.match(URL_WITH_SCHEMA_REGEXP);
|
||||||
return schemeMatch === null || schemeMatch[1] == 'package' || schemeMatch[1] == 'asset';
|
return schemeMatch === null || schemeMatch[1] == 'package' || schemeMatch[1] == 'asset';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,17 +28,20 @@ export function isStyleUrlResolvable(url: string): boolean {
|
|||||||
export function extractStyleUrls(
|
export function extractStyleUrls(
|
||||||
resolver: UrlResolver, baseUrl: string, cssText: string): StyleWithImports {
|
resolver: UrlResolver, baseUrl: string, cssText: string): StyleWithImports {
|
||||||
const foundUrls: string[] = [];
|
const foundUrls: string[] = [];
|
||||||
const modifiedCssText = cssText.replace(_cssImportRe, function(...m: string[]) {
|
|
||||||
const url = m[1] || m[2];
|
const modifiedCssText =
|
||||||
if (!isStyleUrlResolvable(url)) {
|
cssText.replace(CSS_COMMENT_REGEXP, '').replace(CSS_IMPORT_REGEXP, (...m: string[]) => {
|
||||||
// Do not attempt to resolve non-package absolute URLs with URI scheme
|
const url = m[1] || m[2];
|
||||||
return m[0];
|
if (!isStyleUrlResolvable(url)) {
|
||||||
}
|
// Do not attempt to resolve non-package absolute URLs with URI scheme
|
||||||
foundUrls.push(resolver.resolve(baseUrl, url));
|
return m[0];
|
||||||
return '';
|
}
|
||||||
});
|
foundUrls.push(resolver.resolve(baseUrl, url));
|
||||||
|
return '';
|
||||||
|
});
|
||||||
return new StyleWithImports(modifiedCssText, foundUrls);
|
return new StyleWithImports(modifiedCssText, foundUrls);
|
||||||
}
|
}
|
||||||
|
|
||||||
const _cssImportRe = /@import\s+(?:url\()?\s*(?:(?:['"]([^'"]*))|([^;\)\s]*))[^;]*;?/g;
|
const CSS_IMPORT_REGEXP = /@import\s+(?:url\()?\s*(?:(?:['"]([^'"]*))|([^;\)\s]*))[^;]*;?/g;
|
||||||
const _urlWithSchemaRe = /^([^:/?#]+):/;
|
const CSS_COMMENT_REGEXP = /\/\*.+?\*\//g;
|
||||||
|
const URL_WITH_SCHEMA_REGEXP = /^([^:/?#]+):/;
|
||||||
|
@ -113,6 +113,14 @@ export function main() {
|
|||||||
expect(emitStmt(o.literalMap([['someKey', o.literal(1)]]).toStmt())).toEqual(`{someKey: 1};`);
|
expect(emitStmt(o.literalMap([['someKey', o.literal(1)]]).toStmt())).toEqual(`{someKey: 1};`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should apply quotes to each entry within a map produced with literalMap when true', () => {
|
||||||
|
expect(
|
||||||
|
emitStmt(
|
||||||
|
o.literalMap([['a', o.literal('a')], ['*', o.literal('star')]], null, true).toStmt())
|
||||||
|
.replace(/\s+/gm, ''))
|
||||||
|
.toEqual(`{'a':'a','*':'star'};`);
|
||||||
|
});
|
||||||
|
|
||||||
it('should support blank literals', () => {
|
it('should support blank literals', () => {
|
||||||
expect(emitStmt(o.literal(null).toStmt())).toEqual('(null as any);');
|
expect(emitStmt(o.literal(null).toStmt())).toEqual('(null as any);');
|
||||||
expect(emitStmt(o.literal(undefined).toStmt())).toEqual('(undefined as any);');
|
expect(emitStmt(o.literal(undefined).toStmt())).toEqual('(undefined as any);');
|
||||||
|
@ -36,6 +36,17 @@ export function main() {
|
|||||||
expect(styleWithImports.styleUrls).toEqual(['http://ng.io/1.css', 'http://ng.io/2.css']);
|
expect(styleWithImports.styleUrls).toEqual(['http://ng.io/1.css', 'http://ng.io/2.css']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should ignore "@import" in comments', () => {
|
||||||
|
const css = `
|
||||||
|
@import '1.css';
|
||||||
|
/*@import '2.css';*/
|
||||||
|
`;
|
||||||
|
const styleWithImports = extractStyleUrls(urlResolver, 'http://ng.io', css);
|
||||||
|
expect(styleWithImports.style.trim()).toEqual('');
|
||||||
|
expect(styleWithImports.styleUrls).toContain('http://ng.io/1.css');
|
||||||
|
expect(styleWithImports.styleUrls).not.toContain('http://ng.io/2.css');
|
||||||
|
});
|
||||||
|
|
||||||
it('should extract "@import url()" urls', () => {
|
it('should extract "@import url()" urls', () => {
|
||||||
const css = `
|
const css = `
|
||||||
@import url('3.css');
|
@import url('3.css');
|
||||||
|
@ -19,7 +19,8 @@ import {ComponentFactory} from './component_factory';
|
|||||||
*/
|
*/
|
||||||
export class NoComponentFactoryError extends BaseError {
|
export class NoComponentFactoryError extends BaseError {
|
||||||
constructor(public component: Function) {
|
constructor(public component: Function) {
|
||||||
super(`No component factory found for ${stringify(component)}`);
|
super(
|
||||||
|
`No component factory found for ${stringify(component)}. Did you add it to @NgModule.entryComponents?`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,13 +17,11 @@ import {WebAnimationsDriver} from '@angular/platform-browser/src/dom/web_animati
|
|||||||
import {WebAnimationsPlayer} from '@angular/platform-browser/src/dom/web_animations_player';
|
import {WebAnimationsPlayer} from '@angular/platform-browser/src/dom/web_animations_player';
|
||||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||||
import {MockAnimationDriver} from '@angular/platform-browser/testing/mock_animation_driver';
|
import {MockAnimationDriver} from '@angular/platform-browser/testing/mock_animation_driver';
|
||||||
|
|
||||||
import {DomAnimatePlayer} from '../../../platform-browser/src/dom/dom_animate_player';
|
|
||||||
import {ApplicationRef, Component, HostBinding, HostListener, NgModule, NgZone, destroyPlatform} from '../../index';
|
import {ApplicationRef, Component, HostBinding, HostListener, NgModule, NgZone, destroyPlatform} from '../../index';
|
||||||
import {DEFAULT_STATE} from '../../src/animation/animation_constants';
|
import {DEFAULT_STATE} from '../../src/animation/animation_constants';
|
||||||
import {AnimationGroupPlayer} from '../../src/animation/animation_group_player';
|
import {AnimationGroupPlayer} from '../../src/animation/animation_group_player';
|
||||||
import {AnimationKeyframe} from '../../src/animation/animation_keyframe';
|
import {AnimationKeyframe} from '../../src/animation/animation_keyframe';
|
||||||
import {AnimationPlayer} from '../../src/animation/animation_player';
|
import {AnimationPlayer, NoOpAnimationPlayer} from '../../src/animation/animation_player';
|
||||||
import {AnimationStyles} from '../../src/animation/animation_styles';
|
import {AnimationStyles} from '../../src/animation/animation_styles';
|
||||||
import {AnimationTransitionEvent} from '../../src/animation/animation_transition_event';
|
import {AnimationTransitionEvent} from '../../src/animation/animation_transition_event';
|
||||||
import {AUTO_STYLE, animate, group, keyframes, sequence, state, style, transition, trigger} from '../../src/animation/metadata';
|
import {AUTO_STYLE, animate, group, keyframes, sequence, state, style, transition, trigger} from '../../src/animation/metadata';
|
||||||
@ -2244,6 +2242,42 @@ function declareTests({useJit}: {useJit: boolean}) {
|
|||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('error handling', () => {
|
||||||
|
it('should recover if an animation driver or player throws an error during an animation',
|
||||||
|
fakeAsync(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [DummyIfCmp],
|
||||||
|
providers: [{provide: AnimationDriver, useClass: ErroneousAnimationDriver}],
|
||||||
|
imports: [CommonModule]
|
||||||
|
});
|
||||||
|
TestBed.overrideComponent(DummyIfCmp, {
|
||||||
|
set: {
|
||||||
|
template: `
|
||||||
|
<div [@myAnimation]="exp" (@myAnimation.start)="callback1($event)" (@myAnimation.done)="callback2($event)"></div>
|
||||||
|
`,
|
||||||
|
animations: [trigger('myAnimation', [transition(
|
||||||
|
'* => *',
|
||||||
|
[
|
||||||
|
animate(1000, style({transform: 'noooooo'})),
|
||||||
|
])])]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const fixture = TestBed.createComponent(DummyIfCmp);
|
||||||
|
const cmp = fixture.componentInstance;
|
||||||
|
let started = false;
|
||||||
|
let done = false;
|
||||||
|
cmp.callback1 = (event: AnimationTransitionEvent) => started = true;
|
||||||
|
cmp.callback2 = (event: AnimationTransitionEvent) => done = true;
|
||||||
|
cmp.exp = true;
|
||||||
|
fixture.detectChanges();
|
||||||
|
flushMicrotasks();
|
||||||
|
|
||||||
|
expect(started).toBe(true);
|
||||||
|
expect(done).toBe(true);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
describe('full animation integration tests', () => {
|
describe('full animation integration tests', () => {
|
||||||
if (!getDOM().supportsWebAnimation()) return;
|
if (!getDOM().supportsWebAnimation()) return;
|
||||||
|
|
||||||
@ -2537,3 +2571,11 @@ class ExtendedWebAnimationsDriver extends WebAnimationsDriver {
|
|||||||
return player;
|
return player;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ErroneousAnimationDriver extends MockAnimationDriver {
|
||||||
|
animate(
|
||||||
|
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
|
||||||
|
duration: number, delay: number, easing: string): WebAnimationsPlayer {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -67,7 +67,7 @@ const _chromeNumKeyPadMap = {
|
|||||||
* @security Tread carefully! Interacting with the DOM directly is dangerous and
|
* @security Tread carefully! Interacting with the DOM directly is dangerous and
|
||||||
* can introduce XSS risks.
|
* can introduce XSS risks.
|
||||||
*/
|
*/
|
||||||
/* tslint:disable:requireParameterType */
|
/* tslint:disable:requireParameterType no-console */
|
||||||
export class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
export class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
||||||
parse(templateHtml: string) { throw new Error('parse not implemented'); }
|
parse(templateHtml: string) { throw new Error('parse not implemented'); }
|
||||||
static makeCurrent() { setRootDomAdapter(new BrowserDomAdapter()); }
|
static makeCurrent() { setRootDomAdapter(new BrowserDomAdapter()); }
|
||||||
@ -82,7 +82,6 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
|||||||
if (console.error) {
|
if (console.error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
} else {
|
} else {
|
||||||
// tslint:disable-next-line:no-console
|
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -90,7 +89,6 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
|||||||
|
|
||||||
log(error: string): void {
|
log(error: string): void {
|
||||||
if (window.console) {
|
if (window.console) {
|
||||||
// tslint:disable-next-line:no-console
|
|
||||||
window.console.log && window.console.log(error);
|
window.console.log && window.console.log(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
import {APP_ID, Inject, Injectable, RenderComponentType, Renderer, RootRenderer, ViewEncapsulation} from '@angular/core';
|
import {APP_ID, Inject, Injectable, RenderComponentType, Renderer, RootRenderer, ViewEncapsulation} from '@angular/core';
|
||||||
|
|
||||||
import {isBlank, isPresent, stringify} from '../facade/lang';
|
import {isBlank, isPresent, stringify} from '../facade/lang';
|
||||||
import {AnimationKeyframe, AnimationPlayer, AnimationStyles, DirectRenderer, RenderDebugInfo} from '../private_import_core';
|
import {AnimationKeyframe, AnimationPlayer, AnimationStyles, DirectRenderer, NoOpAnimationPlayer, RenderDebugInfo} from '../private_import_core';
|
||||||
|
|
||||||
import {AnimationDriver} from './animation_driver';
|
import {AnimationDriver} from './animation_driver';
|
||||||
import {DOCUMENT} from './dom_tokens';
|
import {DOCUMENT} from './dom_tokens';
|
||||||
@ -262,8 +262,12 @@ export class DomRenderer implements Renderer {
|
|||||||
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
|
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
|
||||||
duration: number, delay: number, easing: string,
|
duration: number, delay: number, easing: string,
|
||||||
previousPlayers: AnimationPlayer[] = []): AnimationPlayer {
|
previousPlayers: AnimationPlayer[] = []): AnimationPlayer {
|
||||||
return this._animationDriver.animate(
|
try {
|
||||||
element, startingStyles, keyframes, duration, delay, easing, previousPlayers);
|
return this._animationDriver.animate(
|
||||||
|
element, startingStyles, keyframes, duration, delay, easing, previousPlayers);
|
||||||
|
} catch (e) {
|
||||||
|
return new NoOpAnimationPlayer();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,3 +21,5 @@ export type RenderDebugInfo = typeof r._RenderDebugInfo;
|
|||||||
export var RenderDebugInfo: typeof r.RenderDebugInfo = r.RenderDebugInfo;
|
export var RenderDebugInfo: typeof r.RenderDebugInfo = r.RenderDebugInfo;
|
||||||
export type DebugDomRootRenderer = typeof r._DebugDomRootRenderer;
|
export type DebugDomRootRenderer = typeof r._DebugDomRootRenderer;
|
||||||
export var DebugDomRootRenderer: typeof r.DebugDomRootRenderer = r.DebugDomRootRenderer;
|
export var DebugDomRootRenderer: typeof r.DebugDomRootRenderer = r.DebugDomRootRenderer;
|
||||||
|
export type NoOpAnimationPlayer = typeof r._NoOpAnimationPlayer;
|
||||||
|
export var NoOpAnimationPlayer: typeof r.NoOpAnimationPlayer = r.NoOpAnimationPlayer;
|
||||||
|
@ -10,7 +10,7 @@ import {APP_ID, Inject, Injectable, NgZone, RenderComponentType, Renderer, RootR
|
|||||||
import {AnimationDriver, DOCUMENT} from '@angular/platform-browser';
|
import {AnimationDriver, DOCUMENT} from '@angular/platform-browser';
|
||||||
|
|
||||||
import {isBlank, isPresent, stringify} from './facade/lang';
|
import {isBlank, isPresent, stringify} from './facade/lang';
|
||||||
import {AnimationKeyframe, AnimationPlayer, AnimationStyles, RenderDebugInfo} from './private_import_core';
|
import {AnimationKeyframe, AnimationPlayer, AnimationStyles, NoOpAnimationPlayer, RenderDebugInfo} from './private_import_core';
|
||||||
import {NAMESPACE_URIS, SharedStylesHost, flattenStyles, getDOM, isNamespaced, shimContentAttribute, shimHostAttribute, splitNamespace} from './private_import_platform-browser';
|
import {NAMESPACE_URIS, SharedStylesHost, flattenStyles, getDOM, isNamespaced, shimContentAttribute, shimHostAttribute, splitNamespace} from './private_import_platform-browser';
|
||||||
|
|
||||||
const TEMPLATE_COMMENT_TEXT = 'template bindings={}';
|
const TEMPLATE_COMMENT_TEXT = 'template bindings={}';
|
||||||
@ -208,8 +208,12 @@ export class ServerRenderer implements Renderer {
|
|||||||
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
|
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
|
||||||
duration: number, delay: number, easing: string,
|
duration: number, delay: number, easing: string,
|
||||||
previousPlayers: AnimationPlayer[] = []): AnimationPlayer {
|
previousPlayers: AnimationPlayer[] = []): AnimationPlayer {
|
||||||
return this._animationDriver.animate(
|
try {
|
||||||
element, startingStyles, keyframes, duration, delay, easing, previousPlayers);
|
return this._animationDriver.animate(
|
||||||
|
element, startingStyles, keyframes, duration, delay, easing, previousPlayers);
|
||||||
|
} catch (e) {
|
||||||
|
return new NoOpAnimationPlayer();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -266,6 +266,8 @@ function defaultErrorHandler(error: any): any {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NavigationSource = 'imperative' | 'popstate' | 'hashchange';
|
||||||
|
|
||||||
type NavigationParams = {
|
type NavigationParams = {
|
||||||
id: number,
|
id: number,
|
||||||
rawUrl: UrlTree,
|
rawUrl: UrlTree,
|
||||||
@ -273,7 +275,7 @@ type NavigationParams = {
|
|||||||
resolve: any,
|
resolve: any,
|
||||||
reject: any,
|
reject: any,
|
||||||
promise: Promise<boolean>,
|
promise: Promise<boolean>,
|
||||||
imperative: boolean,
|
source: NavigationSource,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -374,20 +376,8 @@ export class Router {
|
|||||||
if (!this.locationSubscription) {
|
if (!this.locationSubscription) {
|
||||||
this.locationSubscription = <any>this.location.subscribe(Zone.current.wrap((change: any) => {
|
this.locationSubscription = <any>this.location.subscribe(Zone.current.wrap((change: any) => {
|
||||||
const rawUrlTree = this.urlSerializer.parse(change['url']);
|
const rawUrlTree = this.urlSerializer.parse(change['url']);
|
||||||
const lastNavigation = this.navigations.value;
|
const source: NavigationSource = change['type'] === 'popstate' ? 'popstate' : 'hashchange';
|
||||||
|
setTimeout(() => { this.scheduleNavigation(rawUrlTree, source, {replaceUrl: true}); }, 0);
|
||||||
// If the user triggers a navigation imperatively (e.g., by using navigateByUrl),
|
|
||||||
// and that navigation results in 'replaceState' that leads to the same URL,
|
|
||||||
// we should skip those.
|
|
||||||
if (lastNavigation && lastNavigation.imperative &&
|
|
||||||
lastNavigation.rawUrl.toString() === rawUrlTree.toString()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
this.scheduleNavigation(
|
|
||||||
rawUrlTree, false, {skipLocationChange: change['pop'], replaceUrl: true});
|
|
||||||
}, 0);
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -505,12 +495,12 @@ export class Router {
|
|||||||
Promise<boolean> {
|
Promise<boolean> {
|
||||||
if (url instanceof UrlTree) {
|
if (url instanceof UrlTree) {
|
||||||
return this.scheduleNavigation(
|
return this.scheduleNavigation(
|
||||||
this.urlHandlingStrategy.merge(url, this.rawUrlTree), true, extras);
|
this.urlHandlingStrategy.merge(url, this.rawUrlTree), 'imperative', extras);
|
||||||
}
|
}
|
||||||
|
|
||||||
const urlTree = this.urlSerializer.parse(url);
|
const urlTree = this.urlSerializer.parse(url);
|
||||||
return this.scheduleNavigation(
|
return this.scheduleNavigation(
|
||||||
this.urlHandlingStrategy.merge(urlTree, this.rawUrlTree), true, extras);
|
this.urlHandlingStrategy.merge(urlTree, this.rawUrlTree), 'imperative', extras);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -585,8 +575,26 @@ export class Router {
|
|||||||
.subscribe(() => {});
|
.subscribe(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
private scheduleNavigation(rawUrl: UrlTree, imperative: boolean, extras: NavigationExtras):
|
private scheduleNavigation(rawUrl: UrlTree, source: NavigationSource, extras: NavigationExtras):
|
||||||
Promise<boolean> {
|
Promise<boolean> {
|
||||||
|
const lastNavigation = this.navigations.value;
|
||||||
|
|
||||||
|
// If the user triggers a navigation imperatively (e.g., by using navigateByUrl),
|
||||||
|
// and that navigation results in 'replaceState' that leads to the same URL,
|
||||||
|
// we should skip those.
|
||||||
|
if (lastNavigation && source !== 'imperative' && lastNavigation.source === 'imperative' &&
|
||||||
|
lastNavigation.rawUrl.toString() === rawUrl.toString()) {
|
||||||
|
return null; // return value is not used
|
||||||
|
}
|
||||||
|
|
||||||
|
// Because of a bug in IE and Edge, the location class fires two events (popstate and
|
||||||
|
// hashchange)
|
||||||
|
// every single time. The second one should be ignored. Otherwise, the URL will flicker.
|
||||||
|
if (lastNavigation && source == 'hashchange' && lastNavigation.source === 'popstate' &&
|
||||||
|
lastNavigation.rawUrl.toString() === rawUrl.toString()) {
|
||||||
|
return null; // return value is not used
|
||||||
|
}
|
||||||
|
|
||||||
let resolve: any = null;
|
let resolve: any = null;
|
||||||
let reject: any = null;
|
let reject: any = null;
|
||||||
|
|
||||||
@ -596,7 +604,7 @@ export class Router {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const id = ++this.navigationId;
|
const id = ++this.navigationId;
|
||||||
this.navigations.next({id, imperative, rawUrl, extras, resolve, reject, promise});
|
this.navigations.next({id, source, rawUrl, extras, resolve, reject, promise});
|
||||||
|
|
||||||
// Make sure that the error is propagated even though `processNavigations` catch
|
// Make sure that the error is propagated even though `processNavigations` catch
|
||||||
// handler does not rethrow
|
// handler does not rethrow
|
||||||
|
@ -389,6 +389,43 @@ describe('Integration', () => {
|
|||||||
expect(cmp.recordedUrls()).toEqual(['one/two', 'three/four']);
|
expect(cmp.recordedUrls()).toEqual(['one/two', 'three/four']);
|
||||||
})));
|
})));
|
||||||
|
|
||||||
|
describe('should reset location if a navigation by location is successful', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [{
|
||||||
|
provide: 'in1Second',
|
||||||
|
useValue: (c: any, a: ActivatedRouteSnapshot, b: RouterStateSnapshot) => {
|
||||||
|
let res: any = null;
|
||||||
|
const p = new Promise(_ => res = _);
|
||||||
|
setTimeout(() => res(true), 1000);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('work', fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
||||||
|
const fixture = createRoot(router, RootCmp);
|
||||||
|
|
||||||
|
router.resetConfig([{path: 'simple', component: SimpleCmp, canActivate: ['in1Second']}]);
|
||||||
|
|
||||||
|
// Trigger two location changes to the same URL.
|
||||||
|
// Because of the guard the order will look as follows:
|
||||||
|
// - location change 'simple'
|
||||||
|
// - start processing the change, start a guard
|
||||||
|
// - location change 'simple'
|
||||||
|
// - the first location change gets canceled, the URL gets reset to '/'
|
||||||
|
// - the second location change gets finished, the URL should be reset to '/simple'
|
||||||
|
(<any>location).simulateUrlPop('/simple');
|
||||||
|
(<any>location).simulateUrlPop('/simple');
|
||||||
|
|
||||||
|
tick(2000);
|
||||||
|
advance(fixture);
|
||||||
|
|
||||||
|
expect(location.path()).toEqual('/simple');
|
||||||
|
})));
|
||||||
|
});
|
||||||
|
|
||||||
it('should support secondary routes', fakeAsync(inject([Router], (router: Router) => {
|
it('should support secondary routes', fakeAsync(inject([Router], (router: Router) => {
|
||||||
const fixture = createRoot(router, RootCmp);
|
const fixture = createRoot(router, RootCmp);
|
||||||
|
|
||||||
@ -1415,7 +1452,7 @@ describe('Integration', () => {
|
|||||||
log.push('called');
|
log.push('called');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "angular-srcs",
|
"name": "angular-srcs",
|
||||||
"version": "2.4.0",
|
"version": "2.4.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"branchPattern": "2.0.*",
|
"branchPattern": "2.0.*",
|
||||||
"description": "Angular 2 - a web framework for modern web apps",
|
"description": "Angular 2 - a web framework for modern web apps",
|
||||||
|
@ -23,17 +23,20 @@ function publishRepo {
|
|||||||
cd $REPO_DIR && \
|
cd $REPO_DIR && \
|
||||||
git init && \
|
git init && \
|
||||||
git remote add origin $REPO_URL && \
|
git remote add origin $REPO_URL && \
|
||||||
git fetch origin master --depth=1 && \
|
# use the remote branch if it exists
|
||||||
git checkout origin/master && \
|
if git ls-remote --exit-code origin ${BRANCH}; then
|
||||||
git checkout -b master
|
git fetch origin ${BRANCH} --depth=1 && \
|
||||||
|
git checkout origin/${BRANCH}
|
||||||
|
fi
|
||||||
|
git checkout -b "${BRANCH}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# copy over build artifacts into the repo directory
|
# copy over build artifacts into the repo directory
|
||||||
rm -rf $REPO_DIR/*
|
rm -rf $REPO_DIR/*
|
||||||
cp -R $ARTIFACTS_DIR/* $REPO_DIR/
|
cp -R $ARTIFACTS_DIR/* $REPO_DIR/
|
||||||
|
|
||||||
# Replace $$ANGULAR_VESION$$ with the build version.
|
# Replace $$ANGULAR_VERSION$$ with the build version.
|
||||||
BUILD_VER="2.0.0-${SHORT_SHA}"
|
BUILD_VER="${LATEST_TAG}+${SHORT_SHA}"
|
||||||
if [[ ${TRAVIS} ]]; then
|
if [[ ${TRAVIS} ]]; then
|
||||||
find $REPO_DIR/ -type f -name package.json -print0 | xargs -0 sed -i "s/\\\$\\\$ANGULAR_VERSION\\\$\\\$/${BUILD_VER}/g"
|
find $REPO_DIR/ -type f -name package.json -print0 | xargs -0 sed -i "s/\\\$\\\$ANGULAR_VERSION\\\$\\\$/${BUILD_VER}/g"
|
||||||
|
|
||||||
@ -59,7 +62,7 @@ function publishRepo {
|
|||||||
git add --all && \
|
git add --all && \
|
||||||
git commit -m "${COMMIT_MSG}" && \
|
git commit -m "${COMMIT_MSG}" && \
|
||||||
git tag "${BUILD_VER}" && \
|
git tag "${BUILD_VER}" && \
|
||||||
git push origin master --tags --force
|
git push origin "${BRANCH}" --tags --force
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,6 +88,7 @@ function publishPackages {
|
|||||||
COMMIT_MSG=`git log --oneline | head -n1`
|
COMMIT_MSG=`git log --oneline | head -n1`
|
||||||
COMMITTER_USER_NAME=`git --no-pager show -s --format='%cN' HEAD`
|
COMMITTER_USER_NAME=`git --no-pager show -s --format='%cN' HEAD`
|
||||||
COMMITTER_USER_EMAIL=`git --no-pager show -s --format='%cE' HEAD`
|
COMMITTER_USER_EMAIL=`git --no-pager show -s --format='%cE' HEAD`
|
||||||
|
LATEST_TAG=`git describe --tags --abbrev=0`
|
||||||
|
|
||||||
publishRepo "${COMPONENT}" "${JS_BUILD_ARTIFACTS_DIR}"
|
publishRepo "${COMPONENT}" "${JS_BUILD_ARTIFACTS_DIR}"
|
||||||
done
|
done
|
||||||
@ -93,6 +97,7 @@ function publishPackages {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# See DEVELOPER.md for help
|
# See DEVELOPER.md for help
|
||||||
|
BRANCH=${TRAVIS_BRANCH:-$(git symbolic-ref --short HEAD)}
|
||||||
if [ $# -gt 0 ]; then
|
if [ $# -gt 0 ]; then
|
||||||
ORG=$1
|
ORG=$1
|
||||||
publishPackages "ssh"
|
publishPackages "ssh"
|
||||||
@ -103,5 +108,5 @@ elif [[ \
|
|||||||
ORG="angular"
|
ORG="angular"
|
||||||
publishPackages "http"
|
publishPackages "http"
|
||||||
else
|
else
|
||||||
echo "Not building the upstream/master branch, build artifacts won't be published."
|
echo "Not building the upstream/${BRANCH} branch, build artifacts won't be published."
|
||||||
fi
|
fi
|
||||||
|
Reference in New Issue
Block a user