Compare commits

...

32 Commits

Author SHA1 Message Date
Chuck Jazdzewski 6b57928d35 docs: add changelog for 2.4.10 2017-03-16 19:26:58 -07:00
Chuck Jazdzewski 47471ee49e release: cut the 2.4.10 release 2017-03-16 19:22:09 -07:00
Chuck Jazdzewski b0ae464695 ci: changing deployment token 2017-03-16 16:11:17 -07:00
Victor Savkin 34403cda60 fix(router): do not finish bootstrap until all the routes are resolved (#15121)
Cherry-pick of 5df998d086 onto 2.4.x branch.

DEPRECATION:

Use `RouterModule.forRoot(routes, {initialNavigation: 'enabled'})` instead of
`RouterModule.forRoot(routes, {initialNavigtaion: true})`.

Before doing this, move the initialization logic affecting the router
from the bootstrapped component to the boostrapped module.

Similarly, use `RouterModule.forRoot(routes, {initialNavigation: 'disabled'})`
instead of `RouterModule.forRoot(routes, {initialNavigation: false})`.

Deprecated options: 'legacy_enabled', `true` (same as 'legacy_enabled'),
'legacy_disabled', `false` (same as 'legacy_disabled').

The "Router Initial Navigation" design document covers this change.
Read more here:
https://docs.google.com/document/d/1Hlw1fPaVs-PCj5KPeJRKhrQGAvFOxdvTlwAcnZosu5A/edit?usp=sharing
2017-03-16 15:39:30 -07:00
Victor Berchet e5c9bbcbdd fix(compiler): fix decoding surrogate pairs (#15154) 2017-03-16 15:37:25 -07:00
Igor Minar 80fe41a88e release: cut v2.4.9 2017-03-01 23:11:26 -08:00
Igor Minar af652a7c8b docs: add release notes for 2.4.9 2017-03-01 23:11:08 -08:00
Victor Berchet de36f8a3b9 revert: fix(router): do not finish bootstrap until all the routes are resolved (#14327)
This reverts commit 541de26f7e because it introduced a regression.

Closes #14681, #14588
2017-02-27 14:37:33 -08:00
Jacob Windsor b658fa9ea0 fix(http): Make ResponseOptionsArgs an interface
closes #13708
2017-02-20 17:34:30 -08:00
Victor Berchet 2a123463ac fix(router): improve robustness (#14602)
sync a 4.x change from https://github.com/angular/angular/pull/14155
2017-02-20 16:59:28 -08:00
Igor Minar 4f93ac8762 release: cut v2.4.8 2017-02-18 13:55:23 -08:00
Igor Minar 37ec5b9c1a docs: add changelog for 2.4.8 2017-02-18 13:54:34 -08:00
Igor Minar 612950bdb2 test: pin down @types/* dependencies in typings test (#14569)
This is needed because the latest versions are no longer compatible with typescript 1.8 which results in build errors:
https://travis-ci.org/angular/angular/jobs/202752040#L3863
2017-02-17 14:35:56 -08:00
Igor Minar 3804ad1d23 revert: build: first pass of de-duplicating tsconfig.json content (#14369)
This reverts commit 1c112ae66e.

First failed build (one commit after the change being reverted): https://travis-ci.org/angular/angular/jobs/202414385#L2511
2017-02-17 12:15:49 -08:00
Igor Minar 1b1f228525 revert: build: update jasmine to 2.4 (#14362)
This reverts commit d6a8b0b686.

Jasmine 2.4 requires new typings, which require typescript 2.

See CI failure https://travis-ci.org/angular/angular/jobs/202412220#L3859
2017-02-17 11:57:17 -08:00
Dzmitry Shylovich 19e9094275 docs(router): fix broken link (#14431)
Closes #14430
2017-02-16 15:13:43 -08:00
Victor Berchet 8ff3ab0e6d docs(router): fix guards API docs (#14528) 2017-02-16 15:13:30 -08:00
George Kalpakas 9e8d740a96 ci: fix getLatestLabel (#14535) 2017-02-16 15:12:32 -08:00
Tobias Bosch 3a3a100b27 build: update .pullapprove (#14506)
* build: update .pullapprove

Add tbosch/vicb/chuckjaz to more projects.

* Update .pullapprove.yml
2017-02-16 15:02:42 -08:00
Miško Hevery f0575e014c fix(compiler): REVERT allow absolute style urls (#14365)
This reverts commit 6b9aa2ca3d.
2017-02-16 14:51:01 -08:00
Dzmitry Shylovich ea7737ee11 fix(forms): getRawValue should correctly work with nested FormGroups/Arrays (#12964)
Closed #12963

PR Close #12964
2017-02-16 14:50:49 -08:00
Dzmitry Shylovich fadaf1e01a fix(platform-browser): should only add styles with native encapsulation in shadow DOM (#14313)
Closes #7887

PR Close #14313
2017-02-16 14:50:11 -08:00
Mathou54 c716532ff2 test(forms): test undefined as argument to forms (#13720)
PR Close #13720
2017-02-16 13:49:33 -08:00
Alex Eagle 1c112ae66e build: first pass of de-duplicating tsconfig.json content (#14369)
PR Close #14369
2017-02-16 13:47:10 -08:00
Dzmitry Shylovich 193a0ae4a0 fix(compiler): allow absolute style urls (#14365)
Closes #4974

PR Close #14365
2017-02-16 13:46:42 -08:00
Miško Hevery 9ceb5d1afe fix(http): REVERT: remove dots from jsonp callback name (#13219)
This reverts commit 9e5617e41e.
2017-02-16 13:45:48 -08:00
Victor Berchet d6a8b0b686 build: update jasmine to 2.4 (#14362)
PR Close #14362
2017-02-16 13:44:47 -08:00
Georgios Kalpakas 3e216dd4ad ci: find latest tag when deeper than the git clone depth (#14231)
Since we have a shallow clone of the repository, it might be the case that the
latest tag (which we need for publishing the build artifacts) might not be in
the current history.

This commit incrementally deepens the clone until it finds a tag (or reaches a
max depth).

PR Close #14231
2017-02-16 13:43:15 -08:00
Ward Bell 7b0aba4655 ci: bump node version to 6.9.5 and npm to 3.10.7
this is required by @angular/cli
2017-02-16 13:42:25 -08:00
George Kalpakas 7c87c52c38 fix(upgrade): pass correct values to ngOnChanges for interpolation bindings (#14400)
Previously, the `previousValue` and `currentValue` arguments passed to the
`SimpleChange` constructor were swapped for interpolation bindings.

This commit also refactors the code, so that interpolation bindings and property
bindings share the same implementation, and fixes some broken tests (that hide
failures by allowing the `$exceptionHandler` to swallow thrown exceptions).

This is the same as #14301, but for the 2.4.x branch.
2017-02-10 12:53:26 -08:00
Victor Savkin 541de26f7e fix(router): do not finish bootstrap until all the routes are resolved (#14327)
Fixes #12162
2017-02-09 11:59:08 -08:00
George Kalpakas 74cb575219 fix(upgrade): correctly project content on downgraded components with structural directives (#14274)
Previously, downgraded component adapters were compiling and projecting the
contents of the template element instead of the link-element. This didn't make
any difference is most cases (as the elements are the same), but broke with
structural directives (e.g. `ngRepeat`), which compile the orginal (template)
element once and then create and link clones of it.

This commit fixes it by always compiling and projecting the contents of the
correct (link) element.

Fixes #14260
2017-02-09 10:07:11 -08:00
46 changed files with 1001 additions and 366 deletions
+1 -1
View File
@@ -1 +1 @@
6.6.0
6.9.5
+6 -2
View File
@@ -116,6 +116,8 @@ groups:
- "modules/@angular/compiler/*"
users:
- tbosch #primary
- vicb
- chuckjaz
- mhevery
- IgorMinar #fallback
@@ -197,7 +199,7 @@ groups:
- "modules/@angular/platform-browser/*"
users:
- tbosch #primary
- vicb
- vicb #secondary
- IgorMinar #fallback
- mhevery #fallback
@@ -208,6 +210,8 @@ groups:
users:
- vikerman #primary
- alxhub
- vicb
- tbosch
- IgorMinar #fallback
- mhevery #fallback
@@ -217,7 +221,7 @@ groups:
- "modules/@angular/platform-webworker/*"
users:
- vicb #primary
# needs secondary
- tbosch #secondary
- IgorMinar #fallback
- mhevery #fallback
+3 -6
View File
@@ -1,10 +1,7 @@
language: node_js
sudo: false
node_js:
- '6.6.0'
git:
# Increased from default (50) to ensure last release tag is in this range
depth: 150
- '6.9.5'
addons:
# firefox: "38.0"
@@ -26,9 +23,9 @@ cache:
env:
global:
# GITHUB_TOKEN_ANGULAR
# GITHUB_TOKEN_ANGULAR=<github token, a personal access token of the angular-builds account, account access in valentine>
# This is needed for the e2e Travis matrix task to publish packages to github for continuous packages delivery.
- secure: "fq/U7VDMWO8O8SnAQkdbkoSe2X92PVqg4d044HmRYVmcf6YbO48+xeGJ8yOk0pCBwl3ISO4Q2ot0x546kxfiYBuHkZetlngZxZCtQiFT9kyId8ZKcYdXaIW9OVdw3Gh3tQyUwDucfkVhqcs52D6NZjyE2aWZ4/d1V4kWRO/LMgo="
- secure: "rNqXoy2gqjbF5tBXlRBy+oiYntO3BtzcxZuEtlLMzNaTNzC4dyMOFub0GkzIPWwOzkARoEU9Kv+bC97fDVbCBUKeyzzEqxqddUKhzRxeaYjsefJ6XeTvBvDxwo7wDwyxZSuWdBeGAe4eARVHm7ypsd+AlvqxtzjyS27TK2BzdL4="
matrix:
# Order: a slower build first, so that we don't occupy an idle travis worker waiting for others to complete.
- CI_MODE=js
+43 -2
View File
@@ -1,3 +1,44 @@
<a name="2.4.10"></a>
## [2.4.10](https://github.com/angular/angular/compare/2.4.9...2.4.10) (2017-03-17)
### Bug Fixes
* **compiler:** fix decoding surrogate pairs ([#15154](https://github.com/angular/angular/issues/15154)) ([e5c9bbc](https://github.com/angular/angular/commit/e5c9bbc))
* **router:** do not finish bootstrap until all the routes are resolved ([#15121](https://github.com/angular/angular/issues/15121)) ([34403cd](https://github.com/angular/angular/commit/34403cd))
<a name="2.4.9"></a>
## [2.4.9](https://github.com/angular/angular/compare/2.4.8...2.4.9) (2017-03-02)
### Bug Fixes
* **http:** Make ResponseOptionsArgs an interface ([b658fa9](https://github.com/angular/angular/commit/b658fa9)), closes [#13708](https://github.com/angular/angular/issues/13708)
* **router:** improve robustness ([#14602](https://github.com/angular/angular/issues/14602)) ([2a12346](https://github.com/angular/angular/commit/2a12346))
### Reverts
* fix(router): do not finish bootstrap until all the routes are resolved ([#14327](https://github.com/angular/angular/issues/14327)) ([de36f8a](https://github.com/angular/angular/commit/de36f8a)), closes [#14681](https://github.com/angular/angular/issues/14681) [#14588](https://github.com/angular/angular/issues/14588)
<a name="2.4.8"></a>
## [2.4.8](https://github.com/angular/angular/compare/2.4.7...2.4.8) (2017-02-18)
### Bug Fixes
* **forms:** getRawValue should correctly work with nested FormGroups/Arrays ([#12964](https://github.com/angular/angular/issues/12964)) ([ea7737e](https://github.com/angular/angular/commit/ea7737e)), closes [#12963](https://github.com/angular/angular/issues/12963)
* **http:** REVERT: remove dots from jsonp callback name ([#13219](https://github.com/angular/angular/issues/13219)) ([9ceb5d1](https://github.com/angular/angular/commit/9ceb5d1))
* **platform-browser:** should only add styles with native encapsulation in shadow DOM ([#14313](https://github.com/angular/angular/issues/14313)) ([fadaf1e](https://github.com/angular/angular/commit/fadaf1e)), closes [#7887](https://github.com/angular/angular/issues/7887)
* **router:** do not finish bootstrap until all the routes are resolved ([#14327](https://github.com/angular/angular/issues/14327)) ([541de26](https://github.com/angular/angular/commit/541de26)), closes [#12162](https://github.com/angular/angular/issues/12162)
* **upgrade:** correctly project content on downgraded components with structural directives ([#14274](https://github.com/angular/angular/issues/14274)) ([74cb575](https://github.com/angular/angular/commit/74cb575)), closes [#14260](https://github.com/angular/angular/issues/14260)
* **upgrade:** pass correct values to `ngOnChanges` for interpolation bindings ([#14400](https://github.com/angular/angular/issues/14400)) ([7c87c52](https://github.com/angular/angular/commit/7c87c52))
<a name="2.4.7"></a>
## [2.4.7](https://github.com/angular/angular/compare/2.4.6...2.4.7) (2017-02-09)
@@ -185,10 +226,10 @@
### Note ###
Due to regression in the 2.3.0 release that was fixed by [#13464](https://github.com/angular/angular/pull/13464),
Due to regression in the 2.3.0 release that was fixed by [#13464](https://github.com/angular/angular/pull/13464),
components that have been compiled using 2.3.0 and published to npm will need to be recompiled and republished.
The >=2.3.1 compiler will issue is the following error if it encounters components compiled with 2.3.0:
The >=2.3.1 compiler will issue is the following error if it encounters components compiled with 2.3.0:
`Unsupported metadata version 2 for module ${module}. This module should be compiled with a newer version of ngc`.
We are adding more tests to our test suite to catch these kinds of problems before we cut a release.
+2 -2
View File
@@ -1,10 +1,10 @@
machine:
node:
version: 6.6.0
version: 6.9.5
dependencies:
pre:
- npm install -g npm@3.5.3
- npm install -g npm@3.10.7
test:
override:
+2 -2
View File
@@ -11,8 +11,8 @@
// THIS CHECK SHOULD BE THE FIRST THING IN THIS FILE
// This is to ensure that we catch env issues before we error while requiring other dependencies.
require('./tools/check-environment')({
requiredNpmVersion: '>=3.5.3 <4.0.0',
requiredNodeVersion: '>=5.4.1 <7.0.0',
requiredNpmVersion: '>=3.10.7 <4.0.0',
requiredNodeVersion: '>=6.9.5 <7.0.0',
});
const gulp = require('gulp');
@@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {OpaqueToken} from '@angular/core';
/**
* This class should not be used directly by an application developer. Instead, use
* {@link Location}.
@@ -50,6 +51,12 @@ export abstract class PlatformLocation {
abstract back(): void;
}
/**
* @whatItDoes indicates when a location is initialized
* @experimental
*/
export const LOCATION_INITIALIZED = new OpaqueToken('Location Initialized');
/**
* A serializable version of the event from onPopState or onHashChange
*
+17 -26
View File
@@ -206,47 +206,38 @@ enum Endian {
Big,
}
function utf8Encode(str: string): string {
let encoded: string = '';
export function utf8Encode(str: string): string {
let encoded = '';
for (let index = 0; index < str.length; index++) {
const codePoint = decodeSurrogatePairs(str, index);
let codePoint = str.charCodeAt(index);
// decode surrogate
// see https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
if (codePoint >= 0xd800 && codePoint <= 0xdbff && str.length > (index + 1)) {
const low = str.charCodeAt(index + 1);
if (low >= 0xdc00 && low <= 0xdfff) {
index++;
codePoint = ((codePoint - 0xd800) << 10) + low - 0xdc00 + 0x10000;
}
}
if (codePoint <= 0x7f) {
encoded += String.fromCharCode(codePoint);
} else if (codePoint <= 0x7ff) {
encoded += String.fromCharCode(0xc0 | codePoint >>> 6, 0x80 | codePoint & 0x3f);
encoded += String.fromCharCode(((codePoint >> 6) & 0x1F) | 0xc0, (codePoint & 0x3f) | 0x80);
} else if (codePoint <= 0xffff) {
encoded += String.fromCharCode(
0xe0 | codePoint >>> 12, 0x80 | codePoint >>> 6 & 0x3f, 0x80 | codePoint & 0x3f);
(codePoint >> 12) | 0xe0, ((codePoint >> 6) & 0x3f) | 0x80, (codePoint & 0x3f) | 0x80);
} else if (codePoint <= 0x1fffff) {
encoded += String.fromCharCode(
0xf0 | codePoint >>> 18, 0x80 | codePoint >>> 12 & 0x3f, 0x80 | codePoint >>> 6 & 0x3f,
0x80 | codePoint & 0x3f);
((codePoint >> 18) & 0x07) | 0xf0, ((codePoint >> 12) & 0x3f) | 0x80,
((codePoint >> 6) & 0x3f) | 0x80, (codePoint & 0x3f) | 0x80);
}
}
return encoded;
}
// see https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
function decodeSurrogatePairs(str: string, index: number): number {
if (index < 0 || index >= str.length) {
throw new Error(`index=${index} is out of range in "${str}"`);
}
const high = str.charCodeAt(index);
if (high >= 0xd800 && high <= 0xdfff && str.length > index + 1) {
const low = byteAt(str, index + 1);
if (low >= 0xdc00 && low <= 0xdfff) {
return (high - 0xd800) * 0x400 + low - 0xdc00 + 0x10000;
}
}
return high;
}
function add32(a: number, b: number): number {
return add32to64(a, b)[1];
}
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {computeMsgId, sha1} from '../../src/i18n/digest';
import {computeMsgId, sha1, utf8Encode} from '../../src/i18n/digest';
export function main(): void {
describe('digest', () => {
@@ -100,7 +100,46 @@ export function main(): void {
}
expect(computeMsgId(result, '')).toEqual('2122606631351252558');
});
});
describe('utf8encode', () => {
// tests from https://github.com/mathiasbynens/wtf-8
it('should encode to utf8', () => {
const tests = [
['abc', 'abc'],
// // 1-byte
['\0', '\0'],
// // 2-byte
['\u0080', '\xc2\x80'],
['\u05ca', '\xd7\x8a'],
['\u07ff', '\xdf\xbf'],
// // 3-byte
['\u0800', '\xe0\xa0\x80'],
['\u2c3c', '\xe2\xb0\xbc'],
['\uffff', '\xef\xbf\xbf'],
// //4-byte
['\uD800\uDC00', '\xF0\x90\x80\x80'],
['\uD834\uDF06', '\xF0\x9D\x8C\x86'],
['\uDBFF\uDFFF', '\xF4\x8F\xBF\xBF'],
// unmatched surrogate halves
// high surrogates: 0xD800 to 0xDBFF
['\uD800', '\xED\xA0\x80'],
['\uD800\uD800', '\xED\xA0\x80\xED\xA0\x80'],
['\uD800A', '\xED\xA0\x80A'],
['\uD800\uD834\uDF06\uD800', '\xED\xA0\x80\xF0\x9D\x8C\x86\xED\xA0\x80'],
['\uD9AF', '\xED\xA6\xAF'],
['\uDBFF', '\xED\xAF\xBF'],
// low surrogates: 0xDC00 to 0xDFFF
['\uDC00', '\xED\xB0\x80'],
['\uDC00\uDC00', '\xED\xB0\x80\xED\xB0\x80'],
['\uDC00A', '\xED\xB0\x80A'],
['\uDC00\uD834\uDF06\uDC00', '\xED\xB0\x80\xF0\x9D\x8C\x86\xED\xB0\x80'],
['\uDEEE', '\xED\xBB\xAE'],
['\uDFFF', '\xED\xBF\xBF'],
];
tests.forEach(
([input, output]: [string, string]) => { expect(utf8Encode(input)).toEqual(output); });
});
});
});
}
+6 -2
View File
@@ -1020,7 +1020,7 @@ export class FormGroup extends AbstractControl {
getRawValue(): any {
return this._reduceChildren(
{}, (acc: {[k: string]: AbstractControl}, control: AbstractControl, name: string) => {
acc[name] = control.value;
acc[name] = control instanceof FormControl ? control.value : (<any>control).getRawValue();
return acc;
});
}
@@ -1321,7 +1321,11 @@ export class FormArray extends AbstractControl {
* If you'd like to include all values regardless of disabled status, use this method.
* Otherwise, the `value` property is the best way to get the value of the array.
*/
getRawValue(): any[] { return this.controls.map((control) => control.value); }
getRawValue(): any[] {
return this.controls.map((control: AbstractControl) => {
return control instanceof FormControl ? control.value : (<any>control).getRawValue();
});
}
/** @internal */
_throwIfControlMissing(index: number): void {
@@ -32,6 +32,7 @@ export function main() {
}
describe('FormArray', () => {
describe('adding/removing', () => {
let a: FormArray;
let c1: FormControl, c2: FormControl, c3: FormControl;
@@ -81,6 +82,21 @@ export function main() {
});
});
describe('getRawValue()', () => {
let a: FormArray;
it('should work with nested form groups/arrays', () => {
a = new FormArray([
new FormGroup({'c2': new FormControl('v2'), 'c3': new FormControl('v3')}),
new FormArray([new FormControl('v4'), new FormControl('v5')])
]);
a.at(0).get('c3').disable();
(a.at(1) as FormArray).at(1).disable();
expect(a.getRawValue()).toEqual([{'c2': 'v2', 'c3': 'v3'}, ['v4', 'v5']]);
});
});
describe('setValue', () => {
let c: FormControl, c2: FormControl, a: FormArray;
+19 -1
View File
@@ -8,7 +8,7 @@
import {async, fakeAsync, tick} from '@angular/core/testing';
import {AsyncTestCompleter, beforeEach, describe, inject, it} from '@angular/core/testing/testing_internal';
import {AbstractControl, FormControl, FormGroup, Validators} from '@angular/forms';
import {AbstractControl, FormArray, FormControl, FormGroup, Validators} from '@angular/forms';
import {EventEmitter} from '../src/facade/async';
import {isPresent} from '../src/facade/lang';
@@ -62,6 +62,24 @@ export function main() {
});
});
describe('getRawValue', () => {
let fg: FormGroup;
it('should work with nested form groups/arrays', () => {
fg = new FormGroup({
'c1': new FormControl('v1'),
'group': new FormGroup({'c2': new FormControl('v2'), 'c3': new FormControl('v3')}),
'array': new FormArray([new FormControl('v4'), new FormControl('v5')])
});
fg.get('group').get('c3').disable();
(fg.get('array') as FormArray).at(1).disable();
expect(fg.getRawValue())
.toEqual({'c1': 'v1', 'group': {'c2': 'v2', 'c3': 'v3'}, 'array': ['v4', 'v5']});
});
});
describe('adding and removing controls', () => {
it('should update value and validity when control is added', () => {
const g = new FormGroup({'one': new FormControl('1')});
+11 -3
View File
@@ -43,6 +43,10 @@ export function main() {
it('should error on null',
() => { expect(Validators.required(new FormControl(null))).toEqual({'required': true}); });
it('should not error on undefined', () => {
expect(Validators.required(new FormControl(undefined))).toEqual({'required': true});
});
it('should not error on a non-empty string',
() => { expect(Validators.required(new FormControl('not empty'))).toBeNull(); });
@@ -72,7 +76,7 @@ export function main() {
() => { expect(Validators.minLength(2)(new FormControl(null))).toBeNull(); });
it('should not error on undefined',
() => { expect(Validators.minLength(2)(new FormControl(null))).toBeNull(); });
() => { expect(Validators.minLength(2)(new FormControl(undefined))).toBeNull(); });
it('should not error on valid strings',
() => { expect(Validators.minLength(2)(new FormControl('aa'))).toBeNull(); });
@@ -103,6 +107,9 @@ export function main() {
it('should not error on null',
() => { expect(Validators.maxLength(2)(new FormControl(null))).toBeNull(); });
it('should not error on undefined',
() => { expect(Validators.maxLength(2)(new FormControl(undefined))).toBeNull(); });
it('should not error on valid strings',
() => { expect(Validators.maxLength(2)(new FormControl('aa'))).toBeNull(); });
@@ -132,8 +139,9 @@ export function main() {
it('should not error on null',
() => { expect(Validators.pattern('[a-zA-Z ]+')(new FormControl(null))).toBeNull(); });
it('should not error on undefined',
() => { expect(Validators.pattern('[a-zA-Z ]+')(new FormControl(null))).toBeNull(); });
it('should not error on undefined', () => {
expect(Validators.pattern('[a-zA-Z ]+')(new FormControl(undefined))).toBeNull();
});
it('should not error on null value and "null" pattern',
() => { expect(Validators.pattern('null')(new FormControl(null))).toBeNull(); });
@@ -32,23 +32,23 @@ export class BrowserJsonp {
nextRequestID(): string { return `__req${_nextRequestId++}`; }
requestCallback(id: string): string { return `${JSONP_HOME}${id}_finished`; }
requestCallback(id: string): string { return `${JSONP_HOME}.${id}.finished`; }
exposeConnection(id: string, connection: any): void {
exposeConnection(id: string, connection: any) {
const connections = _getJsonpConnections();
connections[id] = connection;
}
removeConnection(id: string): void {
removeConnection(id: string) {
const connections = _getJsonpConnections();
connections[id] = null;
}
// Attach the <script> element to the DOM
send(node: any): void { document.body.appendChild(<Node>(node)); }
send(node: any) { document.body.appendChild(<Node>(node)); }
// Remove <script> element from the DOM
cleanup(node: any): void {
cleanup(node: any) {
if (node.parentNode) {
node.parentNode.removeChild(<Node>(node));
}
+5 -3
View File
@@ -66,9 +66,11 @@ export interface RequestArgs extends RequestOptionsArgs { url: string; }
*
* @experimental
*/
export type ResponseOptionsArgs = {
body?: string | Object | FormData | ArrayBuffer | Blob; status?: number; statusText?: string;
export interface ResponseOptionsArgs {
body?: string|Object|FormData|ArrayBuffer|Blob;
status?: number;
statusText?: string;
headers?: Headers;
type?: ResponseType;
url?: string;
};
}
@@ -70,11 +70,6 @@ export function main() {
expect(instance).toBeAnInstanceOf(JSONPConnection);
});
it('callback name should not contain dots', () => {
const domJsonp = new MockBrowserJsonp();
const callback: string = domJsonp.requestCallback(domJsonp.nextRequestID());
expect(callback.indexOf('.') === -1).toBeTruthy();
});
describe('JSONPConnection', () => {
it('should use the injected BaseResponseOptions to create the response',
@@ -127,7 +127,6 @@ export class DomRenderer implements Renderer {
let nodesParent: Element|DocumentFragment;
if (this.componentProto.encapsulation === ViewEncapsulation.Native) {
nodesParent = (hostElement as any).createShadowRoot();
this._rootRenderer.sharedStylesHost.addHost(nodesParent);
for (let i = 0; i < this._styles.length; i++) {
const styleEl = document.createElement('style');
styleEl.textContent = this._styles[i];
@@ -0,0 +1,85 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {CommonModule} from '@angular/common';
import {Component, NgModule, ViewEncapsulation} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {describe, it} from '@angular/core/testing/testing_internal';
import {BrowserModule} from '@angular/platform-browser';
import {By} from '@angular/platform-browser/src/dom/debug/by';
import {browserDetection} from '@angular/platform-browser/testing/browser_util';
import {expect} from '@angular/platform-browser/testing/matchers';
export function main() {
describe('DomRenderer', () => {
beforeEach(() => TestBed.configureTestingModule({imports: [BrowserModule, TestModule]}));
// other browsers don't support shadow dom
if (browserDetection.isChromeDesktop) {
it('should add only styles with native encapsulation to the shadow DOM', () => {
const fixture = TestBed.createComponent(SomeApp);
fixture.detectChanges();
const cmp = fixture.debugElement.query(By.css('cmp-native')).nativeElement;
const styles = cmp.shadowRoot.querySelectorAll('style');
expect(styles.length).toBe(1);
expect(styles[0]).toHaveText('.cmp-native { color: red; }');
});
}
});
}
@Component({
selector: 'cmp-native',
template: ``,
styles: [`.cmp-native { color: red; }`],
encapsulation: ViewEncapsulation.Native
})
class CmpEncapsulationNative {
}
@Component({
selector: 'cmp-emulated',
template: ``,
styles: [`.cmp-emulated { color: blue; }`],
encapsulation: ViewEncapsulation.Emulated
})
class CmpEncapsulationEmulated {
}
@Component({
selector: 'cmp-none',
template: ``,
styles: [`.cmp-none { color: yellow; }`],
encapsulation: ViewEncapsulation.None
})
class CmpEncapsulationNone {
}
@Component({
selector: 'some-app',
template: `
<cmp-native></cmp-native>
<cmp-emulated></cmp-emulated>
<cmp-none></cmp-none>
`,
})
export class SomeApp {
}
@NgModule({
declarations: [
SomeApp,
CmpEncapsulationNative,
CmpEncapsulationEmulated,
CmpEncapsulationNone,
],
imports: [CommonModule]
})
class TestModule {
}
@@ -6,8 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import {PlatformLocation} from '@angular/common';
import {APP_INITIALIZER, NgZone} from '@angular/core';
import {LOCATION_INITIALIZED, PlatformLocation} from '@angular/common';
import {APP_INITIALIZER, NgZone, OpaqueToken} from '@angular/core';
import {WebWorkerPlatformLocation} from './platform_location';
@@ -23,10 +23,15 @@ export const WORKER_APP_LOCATION_PROVIDERS = [
useFactory: appInitFnFactory,
multi: true,
deps: [PlatformLocation, NgZone]
}
},
{provide: LOCATION_INITIALIZED, useFactory: locationInitialized, deps: [PlatformLocation]}
];
function appInitFnFactory(platformLocation: WebWorkerPlatformLocation, zone: NgZone): () =>
export function locationInitialized(platformLocation: WebWorkerPlatformLocation) {
return platformLocation.initialized;
}
export function appInitFnFactory(platformLocation: WebWorkerPlatformLocation, zone: NgZone): () =>
Promise<boolean> {
return () => zone.runGuarded(() => platformLocation.init());
}
@@ -25,6 +25,8 @@ export class WebWorkerPlatformLocation extends PlatformLocation {
private _hashChangeListeners: Array<Function> = [];
private _location: LocationType = null;
private _channelSource: EventEmitter<Object>;
public initialized: Promise<any>;
private initializedResolve: () => void;
constructor(
brokerFactory: ClientMessageBrokerFactory, bus: MessageBus, private _serializer: Serializer) {
@@ -52,6 +54,7 @@ export class WebWorkerPlatformLocation extends PlatformLocation {
}
}
});
this.initialized = new Promise(res => this.initializedResolve = res);
}
/** @internal **/
@@ -63,6 +66,7 @@ export class WebWorkerPlatformLocation extends PlatformLocation {
(val: LocationType):
boolean => {
this._location = val;
this.initializedResolve();
return true;
},
(err): boolean => { throw new Error(err); });
+1 -1
View File
@@ -6,7 +6,7 @@ Managing state transitions is one of the hardest parts of building applications.
The Angular router is designed to solve these problems. Using the router, you can declaratively specify application state, manage state transitions while taking care of the URL, and load components on demand.
## Overview
Read the overview of the Router [here](http://victorsavkin.com/post/145672529346/angular-router).
Read the overview of the Router [here](https://vsavkin.com/angular-2-router-d9e30599f9ea).
## Guide
Read the dev guide [here](https://angular.io/docs/ts/latest/guide/router.html).
+10 -11
View File
@@ -13,7 +13,7 @@ import {ActivatedRouteSnapshot, RouterStateSnapshot} from './router_state';
/**
* @whatItDoes Indicates that a class can implement to be a guard deciding if a route can be
* @whatItDoes Interface that a class can implement to be a guard deciding if a route can be
* activated.
*
* @howToUse
@@ -53,7 +53,7 @@ import {ActivatedRouteSnapshot, RouterStateSnapshot} from './router_state';
* class AppModule {}
* ```
*
* You can also provide a function with the same signature instead of the class:
* You can alternatively provide a function with the `canActivate` signature:
*
* ```
* @NgModule({
@@ -84,7 +84,7 @@ export interface CanActivate {
}
/**
* @whatItDoes Indicates that a class can implement to be a guard deciding if a child route can be
* @whatItDoes Interface that a class can implement to be a guard deciding if a child route can be
* activated.
*
* @howToUse
@@ -129,7 +129,7 @@ export interface CanActivate {
* class AppModule {}
* ```
*
* You can also provide a function with the same signature instead of the class:
* You can alternatively provide a function with the `canActivateChild` signature:
*
* ```
* @NgModule({
@@ -159,14 +159,13 @@ export interface CanActivate {
*
* @stable
*/
export interface CanActivateChild {
canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot):
Observable<boolean>|Promise<boolean>|boolean;
}
/**
* @whatItDoes Indicates that a class can implement to be a guard deciding if a route can be
* @whatItDoes Interface that a class can implement to be a guard deciding if a route can be
* deactivated.
*
* @howToUse
@@ -207,7 +206,7 @@ export interface CanActivateChild {
* class AppModule {}
* ```
*
* You can also provide a function with the same signature instead of the class:
* You can alternatively provide a function with the `canDeactivate` signature:
*
* ```
* @NgModule({
@@ -238,7 +237,7 @@ export interface CanDeactivate<T> {
}
/**
* @whatItDoes Indicates that class can implement to be a data provider.
* @whatItDoes Interface that class can implement to be a data provider.
*
* @howToUse
*
@@ -278,7 +277,7 @@ export interface CanDeactivate<T> {
* class AppModule {}
* ```
*
* You can also provide a function with the same signature instead of the class.
* You can alternatively provide a function with the `resolve` signature:
*
* ```
* @NgModule({
@@ -310,7 +309,7 @@ export interface Resolve<T> {
/**
* @whatItDoes Indicates that a class can implement to be a guard deciding if a children can be
* @whatItDoes Interface that a class can implement to be a guard deciding if a children can be
* loaded.
*
* @howToUse
@@ -350,7 +349,7 @@ export interface Resolve<T> {
* class AppModule {}
* ```
*
* You can also provide a function with the same signature instead of the class:
* You can alternatively provide a function with the `canLoad` signature:
*
* ```
* @NgModule({
+46 -10
View File
@@ -279,6 +279,18 @@ type NavigationParams = {
};
/**
* @internal
*/
export type RouterHook = (snapshot: RouterStateSnapshot) => Observable<void>;
/**
* @internal
*/
function defaultRouterHook(snapshot: RouterStateSnapshot): Observable<void> {
return of (null);
}
/**
* Does not detach any subtrees. Reuses routes as long as their route config is the same.
*/
@@ -320,6 +332,8 @@ export class Router {
*/
errorHandler: ErrorHandler = defaultErrorHandler;
/**
* Indicates if at least one navigation happened.
*/
@@ -327,6 +341,17 @@ export class Router {
/**
* Extracts and merges URLs. Used for Angular 1 to Angular 2 migrations.
* Used by RouterModule. This allows us to
* pause the navigation either before preactivation or after it.
* @internal
*/
hooks: {beforePreactivation: RouterHook, afterPreactivation: RouterHook} = {
beforePreactivation: defaultRouterHook,
afterPreactivation: defaultRouterHook
};
/**
* Extracts and merges URLs. Used for AngularJS to Angular migrations.
*/
urlHandlingStrategy: UrlHandlingStrategy = new DefaultUrlHandlingStrategy();
@@ -681,18 +706,25 @@ export class Router {
urlAndSnapshot$ = of ({appliedUrl: url, snapshot: precreatedState});
}
const beforePreactivationDone$ = mergeMap.call(
urlAndSnapshot$, (p: {appliedUrl: string, snapshot: RouterStateSnapshot}) => {
return map.call(this.hooks.beforePreactivation(p.snapshot), () => p);
});
// run preactivation: guards and data resolvers
let preActivation: PreActivation;
const preactivationTraverse$ = map.call(urlAndSnapshot$, ({appliedUrl, snapshot}: any) => {
preActivation =
new PreActivation(snapshot, this.currentRouterState.snapshot, this.injector);
preActivation.traverse(this.outletMap);
return {appliedUrl, snapshot};
});
const preactivationTraverse$ = map.call(
beforePreactivationDone$,
({appliedUrl, snapshot}: {appliedUrl: string, snapshot: RouterStateSnapshot}) => {
preActivation =
new PreActivation(snapshot, this.currentRouterState.snapshot, this.injector);
preActivation.traverse(this.outletMap);
return {appliedUrl, snapshot};
});
const preactivationCheckGuards =
mergeMap.call(preactivationTraverse$, ({appliedUrl, snapshot}: any) => {
const preactivationCheckGuards$ = mergeMap.call(
preactivationTraverse$,
({appliedUrl, snapshot}: {appliedUrl: string, snapshot: RouterStateSnapshot}) => {
if (this.navigationId !== id) return of (false);
return map.call(preActivation.checkGuards(), (shouldActivate: boolean) => {
@@ -700,7 +732,7 @@ export class Router {
});
});
const preactivationResolveData$ = mergeMap.call(preactivationCheckGuards, (p: any) => {
const preactivationResolveData$ = mergeMap.call(preactivationCheckGuards$, (p: any) => {
if (this.navigationId !== id) return of (false);
if (p.shouldActivate) {
@@ -710,11 +742,15 @@ export class Router {
}
});
const preactivationDone$ = mergeMap.call(preactivationResolveData$, (p: any) => {
return map.call(this.hooks.afterPreactivation(p.snapshot), () => p);
});
// create router state
// this operation has side effects => route state is being affected
const routerState$ =
map.call(preactivationResolveData$, ({appliedUrl, snapshot, shouldActivate}: any) => {
map.call(preactivationDone$, ({appliedUrl, snapshot, shouldActivate}: any) => {
if (shouldActivate) {
const state =
createRouterState(this.routeReuseStrategy, snapshot, this.currentRouterState);
@@ -44,10 +44,13 @@ export class RouterConfigLoader {
if (typeof loadChildren === 'string') {
return fromPromise(this.loader.load(loadChildren));
} else {
const offlineMode = this.compiler instanceof Compiler;
return mergeMap.call(
wrapIntoObservable(loadChildren()),
(t: any) => offlineMode ? of (<any>t) : fromPromise(this.compiler.compileModuleAsync(t)));
return mergeMap.call(wrapIntoObservable(loadChildren()), (t: NgModuleFactory<any>| any) => {
if (t instanceof NgModuleFactory) {
return of (t);
} else {
return fromPromise(this.compiler.compileModuleAsync(t));
}
});
}
}
}
+125 -16
View File
@@ -6,8 +6,10 @@
* found in the LICENSE file at https://angular.io/license
*/
import {APP_BASE_HREF, HashLocationStrategy, Location, LocationStrategy, PathLocationStrategy, PlatformLocation} from '@angular/common';
import {ANALYZE_FOR_ENTRY_COMPONENTS, APP_BOOTSTRAP_LISTENER, ApplicationRef, Compiler, ComponentRef, Inject, Injector, ModuleWithProviders, NgModule, NgModuleFactoryLoader, NgProbeToken, OpaqueToken, Optional, Provider, SkipSelf, SystemJsNgModuleLoader} from '@angular/core';
import {APP_BASE_HREF, HashLocationStrategy, LOCATION_INITIALIZED, Location, LocationStrategy, PathLocationStrategy, PlatformLocation} from '@angular/common';
import {ANALYZE_FOR_ENTRY_COMPONENTS, APP_BOOTSTRAP_LISTENER, APP_INITIALIZER, ApplicationRef, Compiler, ComponentRef, Inject, Injectable, Injector, ModuleWithProviders, NgModule, NgModuleFactoryLoader, NgProbeToken, OpaqueToken, Optional, Provider, SkipSelf, SystemJsNgModuleLoader} from '@angular/core';
import {Subject} from 'rxjs/Subject';
import {of } from 'rxjs/observable/of';
import {Route, Routes} from './config';
import {RouterLink, RouterLinkWithHref} from './directives/router_link';
@@ -19,7 +21,7 @@ import {ErrorHandler, Router} from './router';
import {ROUTES} from './router_config_loader';
import {RouterOutletMap} from './router_outlet_map';
import {NoPreloading, PreloadAllModules, PreloadingStrategy, RouterPreloader} from './router_preloader';
import {ActivatedRoute} from './router_state';
import {ActivatedRoute, RouterStateSnapshot} from './router_state';
import {UrlHandlingStrategy} from './url_handling_strategy';
import {DefaultUrlSerializer, UrlSerializer} from './url_tree';
import {flatten} from './utils/collection';
@@ -208,6 +210,32 @@ export function provideRoutes(routes: Routes): any {
];
}
/**
* @whatItDoes Represents an option to configure when the initial navigation is performed.
*
* @description
* * 'enabled' - the initial navigation starts before the root component is created.
* The bootstrap is blocked until the initial navigation is complete.
* * 'disabled' - the initial navigation is not performed. The location listener is set up before
* the root component gets created.
* * 'legacy_enabled'- the initial navigation starts after the root component has been created.
* The bootstrap is not blocked until the initial navigation is complete. @deprecated
* * 'legacy_disabled'- the initial navigation is not performed. The location listener is set up
* after @deprecated
* the root component gets created.
* * `true` - same as 'legacy_enabled'. @deprecated
* * `false` - same as 'legacy_disabled'. @deprecated
*
* The 'enabled' option should be used for applications unless there is a reason to have
* more control over when the router starts its initial navigation due to some complex
* initialization logic. In this case, 'disabled' should be used.
*
* The 'legacy_enabled' and 'legacy_disabled' should not be used for new applications.
*
* @experimental
*/
export type InitialNavigation =
boolean | 'enabled' | 'disabled' | 'legacy_enabled' | 'legacy_disabled';
/**
* @whatItDoes Represents options to configure the router.
@@ -228,7 +256,7 @@ export interface ExtraOptions {
/**
* Disables the initial navigation.
*/
initialNavigation?: boolean;
initialNavigation?: InitialNavigation;
/**
* A custom error handler.
@@ -278,22 +306,100 @@ export function rootRoute(router: Router): ActivatedRoute {
return router.routerState.root;
}
export function initialRouterNavigation(
router: Router, ref: ApplicationRef, preloader: RouterPreloader, opts: ExtraOptions) {
return (bootstrappedComponentRef: ComponentRef<any>) => {
/**
* To initialize the router properly we need to do in two steps:
*
* We need to start the navigation in a APP_INITIALIZER to block the bootstrap if
* a resolver or a guards executes asynchronously. Second, we need to actually run
* activation in a BOOTSTRAP_LISTENER. We utilize the afterPreactivation
* hook provided by the router to do that.
*
* The router navigation starts, reaches the point when preactivation is done, and then
* pauses. It waits for the hook to be resolved. We then resolve it only in a bootstrap listener.
*/
@Injectable()
export class RouterInitializer {
private initNavigation: boolean = false;
private resultOfPreactivationDone = new Subject<void>();
constructor(private injector: Injector) {}
appInitializer(): Promise<any> {
const p: Promise<any> = this.injector.get(LOCATION_INITIALIZED, Promise.resolve(null));
return p.then(() => {
let resolve: Function = null;
const res = new Promise(r => resolve = r);
const router = this.injector.get(Router);
const opts = this.injector.get(ROUTER_CONFIGURATION);
if (this.isLegacyDisabled(opts) || this.isLegacyEnabled(opts)) {
resolve(true);
} else if (opts.initialNavigation === 'disabled') {
router.setUpLocationChangeListener();
resolve(true);
} else if (opts.initialNavigation === 'enabled') {
router.hooks.afterPreactivation = () => {
// only the initial navigation should be delayed
if (!this.initNavigation) {
this.initNavigation = true;
resolve(true);
return this.resultOfPreactivationDone;
// subsequent navigations should not be delayed
} else {
return of (null);
}
};
router.initialNavigation();
} else {
throw new Error(`Invalid initialNavigation options: '${opts.initialNavigation}'`);
}
return res;
});
}
bootstrapListener(bootstrappedComponentRef: ComponentRef<any>): void {
const opts = this.injector.get(ROUTER_CONFIGURATION);
const preloader = this.injector.get(RouterPreloader);
const router = this.injector.get(Router);
const ref = this.injector.get(ApplicationRef);
if (bootstrappedComponentRef !== ref.components[0]) {
return;
}
router.resetRootComponentType(ref.componentTypes[0]);
preloader.setUpPreloading();
if (opts.initialNavigation === false) {
router.setUpLocationChangeListener();
} else {
if (this.isLegacyEnabled(opts)) {
router.initialNavigation();
} else if (this.isLegacyDisabled(opts)) {
router.setUpLocationChangeListener();
}
};
preloader.setUpPreloading();
router.resetRootComponentType(ref.componentTypes[0]);
this.resultOfPreactivationDone.next(null);
this.resultOfPreactivationDone.complete();
}
private isLegacyEnabled(opts: ExtraOptions): boolean {
return opts.initialNavigation === 'legacy_enabled' || opts.initialNavigation === true ||
opts.initialNavigation === undefined;
}
private isLegacyDisabled(opts: ExtraOptions): boolean {
return opts.initialNavigation === 'legacy_disabled' || opts.initialNavigation === false;
}
}
export function getAppInitializer(r: RouterInitializer) {
return r.appInitializer.bind(r);
}
export function getBootstrapListener(r: RouterInitializer) {
return r.bootstrapListener.bind(r);
}
/**
@@ -305,11 +411,14 @@ export const ROUTER_INITIALIZER = new OpaqueToken('Router Initializer');
export function provideRouterInitializer() {
return [
RouterInitializer,
{
provide: ROUTER_INITIALIZER,
useFactory: initialRouterNavigation,
deps: [Router, ApplicationRef, RouterPreloader, ROUTER_CONFIGURATION]
provide: APP_INITIALIZER,
multi: true,
useFactory: getAppInitializer,
deps: [RouterInitializer]
},
{provide: ROUTER_INITIALIZER, useFactory: getBootstrapListener, deps: [RouterInitializer]},
{provide: APP_BOOTSTRAP_LISTENER, multi: true, useExisting: ROUTER_INITIALIZER},
];
}
@@ -0,0 +1,245 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {APP_BASE_HREF} from '@angular/common';
import {ApplicationRef, CUSTOM_ELEMENTS_SCHEMA, Component, NgModule, destroyPlatform} from '@angular/core';
import {inject} from '@angular/core/testing';
import {BrowserModule, DOCUMENT} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {NavigationEnd, Resolve, Router, RouterModule} from '@angular/router';
import {getDOM} from '../src/private_import_platform-browser';
describe('bootstrap', () => {
let log: any[] = [];
let testProviders: any[] = null;
@Component({selector: 'test-app', template: 'root <router-outlet></router-outlet>'})
class RootCmp {
constructor() { log.push('RootCmp'); }
}
@Component({selector: 'test-app2', template: 'root <router-outlet></router-outlet>'})
class SecondRootCmp {
}
class TestResolver implements Resolve<any> {
resolve() {
let resolve: any = null;
const res = new Promise(r => resolve = r);
setTimeout(() => resolve('test-data'), 0);
return res;
}
}
beforeEach(inject([DOCUMENT], (doc: any) => {
destroyPlatform();
const el1 = getDOM().createElement('test-app', doc);
const el2 = getDOM().createElement('test-app2', doc);
getDOM().appendChild(doc.body, el1);
getDOM().appendChild(doc.body, el2);
log = [];
testProviders = [{provide: APP_BASE_HREF, useValue: ''}];
}));
afterEach(inject([DOCUMENT], (doc: any) => {
const oldRoots = getDOM().querySelectorAll(doc, 'test-app,test-app2');
for (let i = 0; i < oldRoots.length; i++) {
getDOM().remove(oldRoots[i]);
}
}));
it('should wait for resolvers to complete when initialNavigation = enabled', (done) => {
@Component({selector: 'test', template: 'test'})
class TestCmpEnabled {
}
@NgModule({
imports: [
BrowserModule, RouterModule.forRoot(
[{path: '**', component: TestCmpEnabled, resolve: {test: TestResolver}}],
{useHash: true, initialNavigation: 'enabled'})
],
declarations: [RootCmp, TestCmpEnabled],
bootstrap: [RootCmp],
providers: [...testProviders, TestResolver],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
class TestModule {
constructor(router: Router) {
log.push('TestModule');
router.events.subscribe(e => log.push(e.constructor.name));
}
}
platformBrowserDynamic([]).bootstrapModule(TestModule).then(res => {
const router = res.injector.get(Router);
const data = router.routerState.snapshot.root.firstChild.data;
expect(data['test']).toEqual('test-data');
expect(log).toEqual(
['TestModule', 'NavigationStart', 'RoutesRecognized', 'RootCmp', 'NavigationEnd']);
done();
});
});
it('should NOT wait for resolvers to complete when initialNavigation = legacy_enabled',
(done) => {
@Component({selector: 'test', template: 'test'})
class TestCmpLegacyEnabled {
}
@NgModule({
imports: [
BrowserModule,
RouterModule.forRoot(
[{path: '**', component: TestCmpLegacyEnabled, resolve: {test: TestResolver}}],
{useHash: true, initialNavigation: 'legacy_enabled'})
],
declarations: [RootCmp, TestCmpLegacyEnabled],
bootstrap: [RootCmp],
providers: [...testProviders, TestResolver],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
class TestModule {
constructor(router: Router) {
log.push('TestModule');
router.events.subscribe(e => log.push(e.constructor.name));
}
}
platformBrowserDynamic([]).bootstrapModule(TestModule).then(res => {
const router = res.injector.get(Router);
expect(router.routerState.snapshot.root.firstChild).toBeNull();
// NavigationEnd has not been emitted yet because bootstrap returned too early
expect(log).toEqual(['TestModule', 'RootCmp', 'NavigationStart', 'RoutesRecognized']);
router.events.subscribe((e: any) => {
if (e instanceof NavigationEnd) {
done();
}
});
});
});
it('should not run navigation when initialNavigation = disabled', (done) => {
@Component({selector: 'test', template: 'test'})
class TestCmpDiabled {
}
@NgModule({
imports: [
BrowserModule, RouterModule.forRoot(
[{path: '**', component: TestCmpDiabled, resolve: {test: TestResolver}}],
{useHash: true, initialNavigation: 'disabled'})
],
declarations: [RootCmp, TestCmpDiabled],
bootstrap: [RootCmp],
providers: [...testProviders, TestResolver],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
class TestModule {
constructor(router: Router) {
log.push('TestModule');
router.events.subscribe(e => log.push(e.constructor.name));
}
}
platformBrowserDynamic([]).bootstrapModule(TestModule).then(res => {
const router = res.injector.get(Router);
expect(log).toEqual(['TestModule', 'RootCmp']);
done();
});
});
it('should not run navigation when initialNavigation = legacy_disabled', (done) => {
@Component({selector: 'test', template: 'test'})
class TestCmpLegacyDisabled {
}
@NgModule({
imports: [
BrowserModule,
RouterModule.forRoot(
[{path: '**', component: TestCmpLegacyDisabled, resolve: {test: TestResolver}}],
{useHash: true, initialNavigation: 'legacy_disabled'})
],
declarations: [RootCmp, TestCmpLegacyDisabled],
bootstrap: [RootCmp],
providers: [...testProviders, TestResolver],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
class TestModule {
constructor(router: Router) {
log.push('TestModule');
router.events.subscribe(e => log.push(e.constructor.name));
}
}
platformBrowserDynamic([]).bootstrapModule(TestModule).then(res => {
const router = res.injector.get(Router);
expect(log).toEqual(['TestModule', 'RootCmp']);
done();
});
});
it('should not init router navigation listeners if a non root component is bootstrapped',
(done) => {
@NgModule({
imports: [BrowserModule, RouterModule.forRoot([], {useHash: true})],
declarations: [SecondRootCmp, RootCmp],
entryComponents: [SecondRootCmp],
bootstrap: [RootCmp],
providers: testProviders,
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
class TestModule {
}
platformBrowserDynamic([]).bootstrapModule(TestModule).then(res => {
const router = res.injector.get(Router);
spyOn(router, 'resetRootComponentType').and.callThrough();
const appRef: ApplicationRef = res.injector.get(ApplicationRef);
appRef.bootstrap(SecondRootCmp);
expect(router.resetRootComponentType).not.toHaveBeenCalled();
done();
});
});
it('should reinit router navigation listeners if a previously bootstrapped root component is destroyed',
(done) => {
@NgModule({
imports: [BrowserModule, RouterModule.forRoot([], {useHash: true})],
declarations: [SecondRootCmp, RootCmp],
entryComponents: [SecondRootCmp],
bootstrap: [RootCmp],
providers: testProviders,
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
class TestModule {
}
platformBrowserDynamic([]).bootstrapModule(TestModule).then(res => {
const router = res.injector.get(Router);
spyOn(router, 'resetRootComponentType').and.callThrough();
const appRef: ApplicationRef = res.injector.get(ApplicationRef);
appRef.components[0].onDestroy(() => {
appRef.bootstrap(SecondRootCmp);
expect(router.resetRootComponentType).toHaveBeenCalled();
done();
});
appRef.components[0].destroy();
});
});
});
@@ -1,91 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {APP_BASE_HREF} from '@angular/common';
import {ApplicationRef, Component, NgModule} from '@angular/core';
import {TestBed, inject} from '@angular/core/testing';
import {DOCUMENT} from '@angular/platform-browser';
import {Router, RouterModule, Routes} from '@angular/router';
@Component({selector: 'app-root', template: ''})
export class AppRootComponent {
}
@Component({selector: 'bootstrappable-component', template: ''})
export class BootstrappableComponent {
}
export const appRoutes: Routes = [{path: '**', redirectTo: ''}];
@NgModule({
imports: [RouterModule.forRoot(appRoutes)],
declarations: [AppRootComponent, BootstrappableComponent],
entryComponents: [AppRootComponent, BootstrappableComponent],
providers: [{provide: APP_BASE_HREF, useValue: '/'}]
})
export class RouterInitTestModule {
}
describe('RouterModule', () => {
describe('RouterInitializer', () => {
beforeEach(() => { TestBed.configureTestingModule({imports: [RouterInitTestModule]}); });
beforeEach(inject([DOCUMENT], function(doc: HTMLDocument) {
const elRootApp = doc.createElement('app-root');
doc.body.appendChild(elRootApp);
const elBootComp = doc.createElement('bootstrappable-component');
doc.body.appendChild(elBootComp);
}));
it('should not init router navigation listeners if a non root component is bootstrapped',
() => {
const appRef: ApplicationRef = TestBed.get(ApplicationRef);
const r: Router = TestBed.get(Router);
const spy = spyOn(r, 'resetRootComponentType').and.callThrough();
appRef.bootstrap(AppRootComponent);
expect(r.resetRootComponentType).toHaveBeenCalled();
spy.calls.reset();
appRef.bootstrap(BootstrappableComponent);
expect(r.resetRootComponentType).not.toHaveBeenCalled();
});
it('should reinit router navigation listeners if a previously bootstrapped root component is destroyed',
(done) => {
const appRef: ApplicationRef = TestBed.get(ApplicationRef);
const r: Router = TestBed.get(Router);
const spy = spyOn(r, 'resetRootComponentType').and.callThrough();
const compRef = appRef.bootstrap(AppRootComponent);
expect(r.resetRootComponentType).toHaveBeenCalled();
spy.calls.reset();
compRef.onDestroy(() => {
appRef.bootstrap(BootstrappableComponent);
expect(r.resetRootComponentType).toHaveBeenCalled();
done();
});
compRef.destroy();
});
});
});
+16 -30
View File
@@ -6,8 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import {APP_BOOTSTRAP_LISTENER, ApplicationRef, OpaqueToken} from '@angular/core';
import {ExtraOptions, ROUTER_CONFIGURATION, ROUTER_INITIALIZER, Router, RouterPreloader} from '@angular/router';
import {APP_BOOTSTRAP_LISTENER, ComponentRef, OpaqueToken} from '@angular/core';
import {Router} from '@angular/router';
import {UpgradeModule} from '@angular/upgrade/static';
@@ -35,38 +35,17 @@ import {UpgradeModule} from '@angular/upgrade/static';
* @experimental
*/
export const RouterUpgradeInitializer = {
provide: ROUTER_INITIALIZER,
useFactory: initialRouterNavigation,
deps: [UpgradeModule, ApplicationRef, RouterPreloader, ROUTER_CONFIGURATION]
provide: APP_BOOTSTRAP_LISTENER,
multi: true,
useFactory: locationSyncBootstrapListener,
deps: [UpgradeModule]
};
/**
* @internal
*/
export function initialRouterNavigation(
ngUpgrade: UpgradeModule, ref: ApplicationRef, preloader: RouterPreloader,
opts: ExtraOptions): Function {
return () => {
if (!ngUpgrade.$injector) {
throw new Error(`
RouterUpgradeInitializer can be used only after UpgradeModule.bootstrap has been called.
Remove RouterUpgradeInitializer and call setUpLocationSync after UpgradeModule.bootstrap.
`);
}
const router = ngUpgrade.injector.get(Router);
const ref = ngUpgrade.injector.get(ApplicationRef);
router.resetRootComponentType(ref.componentTypes[0]);
preloader.setUpPreloading();
if (opts.initialNavigation === false) {
router.setUpLocationChangeListener();
} else {
router.initialNavigation();
}
setUpLocationSync(ngUpgrade);
};
export function locationSyncBootstrapListener(ngUpgrade: UpgradeModule) {
return () => { setUpLocationSync(ngUpgrade); };
}
/**
@@ -77,7 +56,14 @@ export function initialRouterNavigation(
*
* @experimental
*/
export function setUpLocationSync(ngUpgrade: UpgradeModule): void {
export function setUpLocationSync(ngUpgrade: UpgradeModule) {
if (!ngUpgrade.$injector) {
throw new Error(`
RouterUpgradeInitializer can be used only after UpgradeModule.bootstrap has been called.
Remove RouterUpgradeInitializer and call setUpLocationSync after UpgradeModule.bootstrap.
`);
}
const router: Router = ngUpgrade.injector.get(Router);
const url = document.createElement('a');
+3 -3
View File
@@ -8,6 +8,8 @@
export type Ng1Token = string;
export type Ng1Expression = string | Function;
export interface IAnnotatedFunction extends Function { $inject?: Ng1Token[]; }
export type IInjectable = (Ng1Token | Function)[] | IAnnotatedFunction;
@@ -45,9 +47,7 @@ export interface IRootScopeService {
$watch(expr: any, fn?: (a1?: any, a2?: any) => void): Function;
$on(event: string, fn?: (event?: any, ...args: any[]) => void): Function;
$destroy(): any;
$apply(): any;
$apply(exp: string): any;
$apply(exp: Function): any;
$apply(exp?: Ng1Expression): any;
$digest(): any;
$evalAsync(): any;
$on(event: string, fn?: (event?: any, ...args: any[]) => void): Function;
@@ -57,16 +57,15 @@ export class DowngradeComponentAdapter {
let expr: any /** TODO #9100 */ = null;
if (attrs.hasOwnProperty(input.attr)) {
const observeFn = ((prop: any /** TODO #9100 */) => {
const observeFn = (prop => {
let prevValue = INITIAL_VALUE;
return (value: any /** TODO #9100 */) => {
if (this.inputChanges !== null) {
this.inputChangeCount++;
this.inputChanges[prop] =
new Ng1Change(value, prevValue === INITIAL_VALUE ? value : prevValue);
prevValue = value;
return (currValue: any) => {
if (prevValue === INITIAL_VALUE) {
prevValue = currValue;
}
this.component[prop] = value;
this.updateInput(prop, prevValue, currValue);
prevValue = currValue;
};
})(input.prop);
attrs.$observe(input.attr, observeFn);
@@ -82,14 +81,8 @@ export class DowngradeComponentAdapter {
}
if (expr != null) {
const watchFn =
((prop: any /** TODO #9100 */) =>
(value: any /** TODO #9100 */, prevValue: any /** TODO #9100 */) => {
if (this.inputChanges != null) {
this.inputChangeCount++;
this.inputChanges[prop] = new Ng1Change(prevValue, value);
}
this.component[prop] = value;
})(input.prop);
(prop => (currValue: any, prevValue: any) =>
this.updateInput(prop, prevValue, currValue))(input.prop);
this.componentScope.$watch(expr, watchFn);
}
}
@@ -171,6 +164,15 @@ export class DowngradeComponentAdapter {
this.componentRef.destroy();
});
}
private updateInput(prop: string, prevValue: any, currValue: any) {
if (this.inputChanges) {
this.inputChangeCount++;
this.inputChanges[prop] = new Ng1Change(prevValue, currValue);
}
this.component[prop] = currValue;
}
}
class Ng1Change implements SimpleChange {
@@ -49,16 +49,15 @@ export class DowngradeNg2ComponentAdapter {
const input = inputs[i];
let expr: any /** TODO #9100 */ = null;
if (attrs.hasOwnProperty(input.attr)) {
const observeFn = ((prop: any /** TODO #9100 */) => {
const observeFn = (prop => {
let prevValue = INITIAL_VALUE;
return (value: any /** TODO #9100 */) => {
if (this.inputChanges !== null) {
this.inputChangeCount++;
this.inputChanges[prop] =
new Ng1Change(value, prevValue === INITIAL_VALUE ? value : prevValue);
prevValue = value;
return (currValue: any) => {
if (prevValue === INITIAL_VALUE) {
prevValue = currValue;
}
this.component[prop] = value;
this.updateInput(prop, prevValue, currValue);
prevValue = currValue;
};
})(input.prop);
attrs.$observe(input.attr, observeFn);
@@ -73,14 +72,8 @@ export class DowngradeNg2ComponentAdapter {
}
if (expr != null) {
const watchFn =
((prop: any /** TODO #9100 */) =>
(value: any /** TODO #9100 */, prevValue: any /** TODO #9100 */) => {
if (this.inputChanges != null) {
this.inputChangeCount++;
this.inputChanges[prop] = new Ng1Change(prevValue, value);
}
this.component[prop] = value;
})(input.prop);
(prop => (currValue: any, prevValue: any) =>
this.updateInput(prop, prevValue, currValue))(input.prop);
this.componentScope.$watch(expr, watchFn);
}
}
@@ -151,6 +144,15 @@ export class DowngradeNg2ComponentAdapter {
this.componentRef.destroy();
});
}
private updateInput(prop: string, prevValue: any, currValue: any) {
if (this.inputChanges) {
this.inputChangeCount++;
this.inputChanges[prop] = new Ng1Change(prevValue, currValue);
}
this.component[prop] = currValue;
}
}
class Ng1Change implements SimpleChange {
+37 -45
View File
@@ -656,67 +656,59 @@ function ng1ComponentDirective(info: ComponentInfo, idPrefix: string): Function
restrict: 'E',
terminal: true,
require: REQUIRE_INJECTOR,
compile: (templateElement: angular.IAugmentedJQuery, templateAttributes: angular.IAttributes,
transclude: angular.ITranscludeFunction) => {
link: (scope: angular.IScope, element: angular.IAugmentedJQuery, attrs: angular.IAttributes,
parentInjector: Injector | ParentInjectorPromise): void => {
// We might have compile the contents lazily, because this might have been triggered by the
// UpgradeNg1ComponentAdapterBuilder, when the ng2 templates have not been compiled yet
return {
post: (scope: angular.IScope, element: angular.IAugmentedJQuery,
attrs: angular.IAttributes, parentInjector: Injector | ParentInjectorPromise,
transclude: angular.ITranscludeFunction): void => {
let id = idPrefix + (idCount++);
(<any>element[0]).id = id;
let injectorPromise = new ParentInjectorPromise(element);
let id = idPrefix + (idCount++);
(<any>element[0]).id = id;
const ng2Compiler = ng1Injector.get(NG2_COMPILER) as Compiler;
const ngContentSelectors = ng2Compiler.getNgContentSelectors(info.type);
const linkFns = compileProjectedNodes(templateElement, ngContentSelectors);
let injectorPromise = new ParentInjectorPromise(element);
const componentFactory: ComponentFactory<any> = componentFactoryRefMap[info.selector];
if (!componentFactory)
throw new Error('Expecting ComponentFactory for: ' + info.selector);
const ng2Compiler = ng1Injector.get(NG2_COMPILER) as Compiler;
const ngContentSelectors = ng2Compiler.getNgContentSelectors(info.type);
const linkFns = compileProjectedNodes(element, ngContentSelectors);
element.empty();
let projectableNodes = linkFns.map(link => {
let projectedClone: Node[];
link(scope, (clone: Node[]) => {
projectedClone = clone;
element.append(clone);
});
return projectedClone;
});
const componentFactory: ComponentFactory<any> = componentFactoryRefMap[info.selector];
if (!componentFactory) throw new Error('Expecting ComponentFactory for: ' + info.selector);
parentInjector = parentInjector || ng1Injector.get(NG2_INJECTOR);
element.empty();
let projectableNodes = linkFns.map(link => {
let projectedClone: Node[];
link(scope, (clone: Node[]) => {
projectedClone = clone;
element.append(clone);
});
return projectedClone;
});
if (parentInjector instanceof ParentInjectorPromise) {
parentInjector.then((resolvedInjector: Injector) => downgrade(resolvedInjector));
} else {
downgrade(parentInjector);
}
parentInjector = parentInjector || ng1Injector.get(NG2_INJECTOR);
function downgrade(injector: Injector) {
const facade = new DowngradeNg2ComponentAdapter(
info, element, attrs, scope, injector, parse, componentFactory);
facade.setupInputs();
facade.bootstrapNg2(projectableNodes);
facade.setupOutputs();
facade.registerCleanup();
injectorPromise.resolve(facade.componentRef.injector);
}
}
};
if (parentInjector instanceof ParentInjectorPromise) {
parentInjector.then((resolvedInjector: Injector) => downgrade(resolvedInjector));
} else {
downgrade(parentInjector);
}
function downgrade(injector: Injector) {
const facade = new DowngradeNg2ComponentAdapter(
info, element, attrs, scope, injector, parse, componentFactory);
facade.setupInputs();
facade.bootstrapNg2(projectableNodes);
facade.setupOutputs();
facade.registerCleanup();
injectorPromise.resolve(facade.componentRef.injector);
}
}
};
function compileProjectedNodes(
templateElement: angular.IAugmentedJQuery,
ngContentSelectors: string[]): angular.ILinkFn[] {
element: angular.IAugmentedJQuery, ngContentSelectors: string[]): angular.ILinkFn[] {
if (!ngContentSelectors)
throw new Error('Expecting ngContentSelectors for: ' + info.selector);
// We have to sort the projected content before we compile it, hence the terminal: true
let projectableTemplateNodes =
sortProjectableNodes(ngContentSelectors, templateElement.contents());
let projectableTemplateNodes = sortProjectableNodes(ngContentSelectors, element.contents());
return projectableTemplateNodes.map(nodes => ng1Compile(nodes));
}
}
@@ -6,14 +6,14 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Component, Directive, ElementRef, Injector, NgModule, destroyPlatform} from '@angular/core';
import {Component, Directive, ElementRef, Injector, Input, NgModule, destroyPlatform} from '@angular/core';
import {async} from '@angular/core/testing';
import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import * as angular from '@angular/upgrade/src/angular_js';
import {UpgradeComponent, UpgradeModule, downgradeComponent} from '@angular/upgrade/static';
import {bootstrap, html} from '../test_helpers';
import {bootstrap, html, multiTrim} from '../test_helpers';
export function main() {
describe('content projection', () => {
@@ -52,6 +52,44 @@ export function main() {
});
}));
it('should correctly project structural directives', async(() => {
@Component({selector: 'ng2', template: 'ng2-{{ itemId }}(<ng-content></ng-content>)'})
class Ng2Component {
@Input() itemId: string;
}
@NgModule({
imports: [BrowserModule, UpgradeModule],
declarations: [Ng2Component],
entryComponents: [Ng2Component]
})
class Ng2Module {
ngDoBootstrap() {}
}
const ng1Module =
angular.module('ng1', [])
.directive(
'ng2', downgradeComponent({component: Ng2Component, inputs: ['itemId']}))
.run(($rootScope: angular.IRootScopeService) => {
$rootScope['items'] = [
{id: 'a', subitems: [1, 2, 3]}, {id: 'b', subitems: [4, 5, 6]},
{id: 'c', subitems: [7, 8, 9]}
];
});
const element = html(`
<ng2 ng-repeat="item in items" [item-id]="item.id">
<div ng-repeat="subitem in item.subitems">{{ subitem }}</div>
</ng2>
`);
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => {
expect(multiTrim(document.body.textContent))
.toBe('ng2-a( 123 )ng2-b( 456 )ng2-c( 789 )');
});
}));
it('should instantiate ng1 in ng2 template and project content', async(() => {
@Component({
@@ -13,7 +13,7 @@ import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import * as angular from '@angular/upgrade/src/angular_js';
import {UpgradeModule, downgradeComponent} from '@angular/upgrade/static';
import {bootstrap, html, multiTrim} from '../test_helpers';
import {$apply, bootstrap, html, multiTrim} from '../test_helpers';
export function main() {
describe('downgrade ng2 component', () => {
@@ -22,15 +22,18 @@ export function main() {
afterEach(() => destroyPlatform());
it('should bind properties, events', async(() => {
const ng1Module = angular.module('ng1', []).run(($rootScope: angular.IScope) => {
$rootScope['dataA'] = 'A';
$rootScope['dataB'] = 'B';
$rootScope['modelA'] = 'initModelA';
$rootScope['modelB'] = 'initModelB';
$rootScope['eventA'] = '?';
$rootScope['eventB'] = '?';
});
const ng1Module =
angular.module('ng1', []).value('$exceptionHandler', (err: any) => {
throw err;
}).run(($rootScope: angular.IScope) => {
$rootScope['name'] = 'world';
$rootScope['dataA'] = 'A';
$rootScope['dataB'] = 'B';
$rootScope['modelA'] = 'initModelA';
$rootScope['modelB'] = 'initModelB';
$rootScope['eventA'] = '?';
$rootScope['eventB'] = '?';
});
@Component({
selector: 'ng2',
@@ -94,9 +97,10 @@ export function main() {
break;
case 1:
assertChange('twoWayA', 'newA');
assertChange('twoWayB', 'newB');
break;
case 2:
assertChange('twoWayB', 'newB');
assertChange('interpolate', 'Hello everyone');
break;
default:
throw new Error('Called too many times! ' + JSON.stringify(changes));
@@ -125,7 +129,7 @@ export function main() {
const element = html(`
<div>
<ng2 literal="Text" interpolate="Hello {{'world'}}"
<ng2 literal="Text" interpolate="Hello {{name}}"
bind-one-way-a="dataA" [one-way-b]="dataB"
bindon-two-way-a="modelA" [(two-way-b)]="modelB"
on-event-a='eventA=$event' (event-b)="eventB=$event"></ng2>
@@ -139,6 +143,14 @@ export function main() {
'literal: Text; interpolate: Hello world; ' +
'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (2) | ' +
'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;');
$apply(upgrade, 'name = "everyone"');
expect(multiTrim(document.body.textContent))
.toEqual(
'ignore: -; ' +
'literal: Text; interpolate: Hello everyone; ' +
'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (3) | ' +
'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;');
});
}));
@@ -13,7 +13,7 @@ import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import * as angular from '@angular/upgrade/src/angular_js';
import {UpgradeComponent, UpgradeModule, downgradeComponent} from '@angular/upgrade/static';
import {bootstrap, digest, html, multiTrim} from '../test_helpers';
import {$digest, bootstrap, html, multiTrim} from '../test_helpers';
export function main() {
describe('upgrade ng1 component', () => {
@@ -530,7 +530,7 @@ export function main() {
ng2ComponentInstance.dataA = 'foo2';
ng2ComponentInstance.dataB = 'bar2';
digest(adapter);
$digest(adapter);
tick();
expect(multiTrim(element.textContent))
@@ -605,7 +605,7 @@ export function main() {
ng2ComponentInstance.dataA = {value: 'foo2'};
ng2ComponentInstance.dataB = {value: 'bar2'};
digest(adapter);
$digest(adapter);
tick();
expect(multiTrim(element.textContent))
@@ -682,7 +682,7 @@ export function main() {
ng2ComponentInstance.dataA = {value: 'foo2'};
ng2ComponentInstance.dataB = {value: 'bar2'};
digest(adapter);
$digest(adapter);
tick();
expect(multiTrim(element.textContent))
@@ -929,7 +929,7 @@ export function main() {
ng1Controller1.outputA({value: 'foo again'});
ng1Controller2.outputB('bar again');
digest(adapter);
$digest(adapter);
tick();
expect(ng1Controller0.inputA).toEqual({value: 'foo again'});
@@ -1010,7 +1010,7 @@ export function main() {
.toBe('ng1 - Data: [4,5] - Length: 2 | ng2 - Data: 4,5 - Length: 2');
ng1Controller.$scope.outputA(6);
digest(adapter);
$digest(adapter);
tick();
expect(ng1Controller.$scope.inputA).toEqual([4, 5, 6]);
@@ -1858,7 +1858,7 @@ export function main() {
// Change: Re-assign `data`
ng2ComponentInstance.data = {foo: 'baz'};
digest(adapter);
$digest(adapter);
tick();
expect(scopeOnChanges.calls.count()).toBe(2);
@@ -1879,7 +1879,7 @@ export function main() {
// No change: Update internal property
ng2ComponentInstance.data.foo = 'qux';
digest(adapter);
$digest(adapter);
tick();
expect(scopeOnChanges.calls.count()).toBe(2);
@@ -1888,7 +1888,7 @@ export function main() {
// Change: Re-assign `data` (even if it looks the same)
ng2ComponentInstance.data = {foo: 'qux'};
digest(adapter);
$digest(adapter);
tick();
expect(scopeOnChanges.calls.count()).toBe(3);
@@ -2008,7 +2008,7 @@ export function main() {
// Change: Re-assign `data`
ng2ComponentInstance.data = {foo: 'baz'};
digest(adapter);
$digest(adapter);
tick();
expect(scopeOnChangesA.calls.count()).toBe(2);
@@ -2029,7 +2029,7 @@ export function main() {
// No change: Update internal property
ng2ComponentInstance.data.foo = 'qux';
digest(adapter);
$digest(adapter);
tick();
expect(scopeOnChangesA.calls.count()).toBe(2);
@@ -2039,7 +2039,7 @@ export function main() {
// Change: Re-assign `data` (even if it looks the same)
ng2ComponentInstance.data = {foo: 'qux'};
digest(adapter);
$digest(adapter);
tick();
expect(scopeOnChangesA.calls.count()).toBe(3);
@@ -2631,7 +2631,7 @@ export function main() {
expect(scopeDestroyListener).not.toHaveBeenCalled();
ng2ComponentAInstance.destroyIt = true;
digest(adapter);
$digest(adapter);
expect(scopeDestroyListener).toHaveBeenCalled();
});
@@ -2795,7 +2795,7 @@ export function main() {
// (Should not propagate upwards.)
ng2ComponentBInstance.ng2BInputA = 'foo2';
ng2ComponentBInstance.ng2BInputC = 'baz2';
digest(adapter);
$digest(adapter);
tick();
expect(multiTrim(document.body.textContent))
@@ -2805,7 +2805,7 @@ export function main() {
// (Should propagate all the way up to `ng1ADataC` and back all the way down to
// `ng2BInputC`.)
ng2ComponentBInstance.ng2BOutputC.emit('baz3');
digest(adapter);
$digest(adapter);
tick();
expect(multiTrim(document.body.textContent))
@@ -2815,7 +2815,7 @@ export function main() {
// (Should not propagate upwards, only downwards.)
ng1ControllerXInstance.ng1XInputA = 'foo4';
ng1ControllerXInstance.ng1XInputB = {value: 'bar4'};
digest(adapter);
$digest(adapter);
tick();
expect(multiTrim(document.body.textContent))
@@ -2824,7 +2824,7 @@ export function main() {
// Update `ng1XInputC`.
// (Should propagate upwards and downwards.)
ng1ControllerXInstance.ng1XInputC = {value: 'baz5'};
digest(adapter);
$digest(adapter);
tick();
expect(multiTrim(document.body.textContent))
@@ -2833,7 +2833,7 @@ export function main() {
// Update a property on `ng1XInputC`.
// (Should propagate upwards and downwards.)
ng1ControllerXInstance.ng1XInputC.value = 'baz6';
digest(adapter);
$digest(adapter);
tick();
expect(multiTrim(document.body.textContent))
@@ -2842,7 +2842,7 @@ export function main() {
// Emit from `ng1XOutputA`.
// (Should propagate upwards to `ng1ADataA` and back all the way down to `ng2BInputA`.)
(ng1ControllerXInstance as any).ng1XOutputA({value: 'foo7'});
digest(adapter);
$digest(adapter);
tick();
expect(multiTrim(document.body.textContent))
@@ -2852,7 +2852,7 @@ export function main() {
// (Should propagate upwards to `ng1ADataB`, but not downwards,
// since `ng1XInputB` has been re-assigned (i.e. `ng2ADataB !== ng1XInputB`).)
(ng1ControllerXInstance as any).ng1XOutputB('bar8');
digest(adapter);
$digest(adapter);
tick();
expect(multiTrim(document.body.textContent))
@@ -2863,7 +2863,7 @@ export function main() {
ng2ComponentAInstance.ng2ADataA = {value: 'foo9'};
ng2ComponentAInstance.ng2ADataB = {value: 'bar9'};
ng2ComponentAInstance.ng2ADataC = {value: 'baz9'};
digest(adapter);
$digest(adapter);
tick();
expect(multiTrim(document.body.textContent))
@@ -21,11 +21,6 @@ export function bootstrap(
});
}
export function digest(adapter: UpgradeModule) {
const $rootScope = adapter.$injector.get($ROOT_SCOPE) as angular.IRootScopeService;
$rootScope.$digest();
}
export function html(html: string): Element {
// Don't return `body` itself, because using it as a `$rootElement` for ng1
// will attach `$injector` to it and that will affect subsequent tests.
@@ -43,3 +38,13 @@ export function html(html: string): Element {
export function multiTrim(text: string): string {
return text.replace(/\n/g, '').replace(/\s\s+/g, ' ').trim();
}
export function $apply(adapter: UpgradeModule, exp: angular.Ng1Expression) {
const $rootScope = adapter.$injector.get($ROOT_SCOPE) as angular.IRootScopeService;
$rootScope.$apply(exp);
}
export function $digest(adapter: UpgradeModule) {
const $rootScope = adapter.$injector.get($ROOT_SCOPE) as angular.IRootScopeService;
$rootScope.$digest();
}
+55 -7
View File
@@ -291,9 +291,11 @@ export function main() {
it('should bind properties, events', async(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const ng1Module = angular.module('ng1', []);
const ng1Module =
angular.module('ng1', []).value('$exceptionHandler', (err: any) => { throw err; });
ng1Module.run(($rootScope: any) => {
$rootScope.name = 'world';
$rootScope.dataA = 'A';
$rootScope.dataB = 'B';
$rootScope.modelA = 'initModelA';
@@ -364,9 +366,10 @@ export function main() {
break;
case 1:
assertChange('twoWayA', 'newA');
assertChange('twoWayB', 'newB');
break;
case 2:
assertChange('twoWayB', 'newB');
assertChange('interpolate', 'Hello everyone');
break;
default:
throw new Error('Called too many times! ' + JSON.stringify(changes));
@@ -381,7 +384,7 @@ export function main() {
}).Class({constructor: function() {}});
const element = html(`<div>
<ng2 literal="Text" interpolate="Hello {{'world'}}"
<ng2 literal="Text" interpolate="Hello {{name}}"
bind-one-way-a="dataA" [one-way-b]="dataB"
bindon-two-way-a="modelA" [(two-way-b)]="modelB"
on-event-a='eventA=$event' (event-b)="eventB=$event"></ng2>
@@ -394,6 +397,15 @@ export function main() {
'literal: Text; interpolate: Hello world; ' +
'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (2) | ' +
'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;');
ref.ng1RootScope.$apply('name = "everyone"');
expect(multiTrim(document.body.textContent))
.toEqual(
'ignore: -; ' +
'literal: Text; interpolate: Hello everyone; ' +
'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (3) | ' +
'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;');
ref.dispose();
});
@@ -491,6 +503,39 @@ export function main() {
});
}));
it('should correctly project structural directives', async(() => {
@Component({selector: 'ng2', template: 'ng2-{{ itemId }}(<ng-content></ng-content>)'})
class Ng2Component {
@Input() itemId: string;
}
@NgModule({imports: [BrowserModule], declarations: [Ng2Component]})
class Ng2Module {
}
const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2Module);
const ng1Module = angular.module('ng1', [])
.directive('ng2', adapter.downgradeNg2Component(Ng2Component))
.run(($rootScope: angular.IRootScopeService) => {
$rootScope['items'] = [
{id: 'a', subitems: [1, 2, 3]}, {id: 'b', subitems: [4, 5, 6]},
{id: 'c', subitems: [7, 8, 9]}
];
});
const element = html(`
<ng2 ng-repeat="item in items" [item-id]="item.id">
<div ng-repeat="subitem in item.subitems">{{ subitem }}</div>
</ng2>
`);
adapter.bootstrap(element, [ng1Module.name]).ready(ref => {
expect(multiTrim(document.body.textContent))
.toBe('ng2-a( 123 )ng2-b( 456 )ng2-c( 789 )');
ref.dispose();
});
}));
it('should allow attribute selectors for components in ng2', async(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => MyNg2Module));
const ng1Module = angular.module('myExample', []);
@@ -1901,14 +1946,17 @@ function multiTrim(text: string): string {
}
function html(html: string): Element {
// Don't return `body` itself, because using it as a `$rootElement` for ng1
// will attach `$injector` to it and that will affect subsequent tests.
const body = document.body;
body.innerHTML = html;
body.innerHTML = `<div>${html.trim()}</div>`;
const div = document.body.firstChild as Element;
if (body.childNodes.length == 1 && body.firstChild instanceof HTMLElement) {
return <Element>body.firstChild;
if (div.childNodes.length === 1 && div.firstChild instanceof HTMLElement) {
return div.firstChild;
}
return body;
return div;
}
function nodes(html: string) {
@@ -25,7 +25,9 @@ export const ROUTES = [
@NgModule({
imports: [WorkerAppModule, RouterModule.forRoot(ROUTES, {useHash: true})],
providers: [WORKER_APP_LOCATION_PROVIDERS],
providers: [
WORKER_APP_LOCATION_PROVIDERS,
],
bootstrap: [App],
declarations: [App, Start, Contact, About]
})
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "angular-srcs",
"version": "2.4.7",
"version": "2.4.10",
"private": true,
"branchPattern": "2.0.*",
"description": "Angular 2 - a web framework for modern web apps",
+4 -2
View File
@@ -2,9 +2,10 @@
set -e -o pipefail
echo 'travis_fold:start:ENV'
NODE_VERSION=5.4.1
NPM_VERSION=3.5.3
NODE_VERSION=6.9.5
NPM_VERSION=3.10.7 # do not upgrade to >3.10.8 unless https://github.com/npm/npm/issues/14042 is resolved
CHROMIUM_VERSION=403382 # Chrome 53 linux stable, see https://www.chromium.org/developers/calendar
SAUCE_CONNECT_VERSION=4.3.11
@@ -74,3 +75,4 @@ if [[ ${TRAVIS} ]]; then
export CHROME_BIN=${HOME}/.chrome/chromium/chrome-linux/chrome
fi
echo 'travis_fold:end:ENV'
+24 -1
View File
@@ -2,6 +2,29 @@
set -e -x
# Find the most recent tag that is reachable from the current commit.
# This is shallow clone of the repo, so we might need to fetch more commits to
# find the tag.
function getLatestTag {
local depth=`git log --oneline | wc -l`
local latestTag=`git describe --tags --abbrev=0 || echo NOT_FOUND`
while [ "$latestTag" == "NOT_FOUND" ]; do
# Avoid infinite loop.
if [ "$depth" -gt "1000" ]; then
echo "Error: Unable to find the latest tag." 1>&2
exit 1;
fi
# Increase the clone depth and look for a tag.
depth=$((depth + 50))
git fetch --depth=$depth
latestTag=`git describe --tags --abbrev=0 || echo NOT_FOUND`
done
echo $latestTag;
}
function publishRepo {
COMPONENT=$1
ARTIFACTS_DIR=$2
@@ -88,7 +111,7 @@ function publishPackages {
COMMIT_MSG=`git log --oneline | head -n1`
COMMITTER_USER_NAME=`git --no-pager show -s --format='%cN' HEAD`
COMMITTER_USER_EMAIL=`git --no-pager show -s --format='%cE' HEAD`
LATEST_TAG=`git describe --tags --abbrev=0`
LATEST_TAG=`getLatestTag`
publishRepo "${COMPONENT}" "${JS_BUILD_ARTIFACTS_DIR}"
done
+3
View File
@@ -80,6 +80,9 @@ export declare class Location {
static stripTrailingSlash(url: string): string;
}
/** @experimental */
export declare const LOCATION_INITIALIZED: OpaqueToken;
/** @experimental */
export interface LocationChangeEvent {
type: string;
+3 -3
View File
@@ -191,14 +191,14 @@ export declare class ResponseOptions {
}
/** @experimental */
export declare type ResponseOptionsArgs = {
export interface ResponseOptionsArgs {
body?: string | Object | FormData | ArrayBuffer | Blob;
headers?: Headers;
status?: number;
statusText?: string;
headers?: Headers;
type?: ResponseType;
url?: string;
};
}
/** @experimental */
export declare enum ResponseType {
+4
View File
@@ -89,6 +89,10 @@ export declare const WORKER_APP_LOCATION_PROVIDERS: ({
useFactory: (platformLocation: WebWorkerPlatformLocation, zone: NgZone) => () => Promise<boolean>;
multi: boolean;
deps: (typeof NgZone | typeof PlatformLocation)[];
} | {
provide: OpaqueToken;
useFactory: (platformLocation: WebWorkerPlatformLocation) => Promise<any>;
deps: typeof PlatformLocation[];
})[];
/** @experimental */
+1 -1
View File
@@ -76,7 +76,7 @@ export declare type Event = NavigationStart | NavigationEnd | NavigationCancel |
export interface ExtraOptions {
enableTracing?: boolean;
errorHandler?: ErrorHandler;
initialNavigation?: boolean;
initialNavigation?: InitialNavigation;
preloadingStrategy?: any;
useHash?: boolean;
}
+1 -1
View File
@@ -18,7 +18,7 @@ cp -R -v tools/typings-test/* $TMP
# create package.json so that npm install doesn't pollute any parent node_modules's directory
npm init --yes
npm install ${LINKABLE_PKGS[*]}
npm install @types/es6-promise @types/es6-collections @types/jasmine rxjs@5.0.0-beta.11
npm install @types/es6-promise@0.0.32 @types/es6-collections@0.5.29 @types/jasmine@2.5.41 rxjs@5.0.0-beta.11
npm install typescript@1.8.10
$(npm bin)/tsc --version
$(npm bin)/tsc -p tsconfig.json