Compare commits
22 Commits
Author | SHA1 | Date | |
---|---|---|---|
4f93ac8762 | |||
37ec5b9c1a | |||
612950bdb2 | |||
3804ad1d23 | |||
1b1f228525 | |||
19e9094275 | |||
8ff3ab0e6d | |||
9e8d740a96 | |||
3a3a100b27 | |||
f0575e014c | |||
ea7737ee11 | |||
fadaf1e01a | |||
c716532ff2 | |||
1c112ae66e | |||
193a0ae4a0 | |||
9ceb5d1afe | |||
d6a8b0b686 | |||
3e216dd4ad | |||
7b0aba4655 | |||
7c87c52c38 | |||
541de26f7e | |||
74cb575219 |
@ -116,6 +116,8 @@ groups:
|
|||||||
- "modules/@angular/compiler/*"
|
- "modules/@angular/compiler/*"
|
||||||
users:
|
users:
|
||||||
- tbosch #primary
|
- tbosch #primary
|
||||||
|
- vicb
|
||||||
|
- chuckjaz
|
||||||
- mhevery
|
- mhevery
|
||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
|
|
||||||
@ -197,7 +199,7 @@ groups:
|
|||||||
- "modules/@angular/platform-browser/*"
|
- "modules/@angular/platform-browser/*"
|
||||||
users:
|
users:
|
||||||
- tbosch #primary
|
- tbosch #primary
|
||||||
- vicb
|
- vicb #secondary
|
||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
- mhevery #fallback
|
- mhevery #fallback
|
||||||
|
|
||||||
@ -208,6 +210,8 @@ groups:
|
|||||||
users:
|
users:
|
||||||
- vikerman #primary
|
- vikerman #primary
|
||||||
- alxhub
|
- alxhub
|
||||||
|
- vicb
|
||||||
|
- tbosch
|
||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
- mhevery #fallback
|
- mhevery #fallback
|
||||||
|
|
||||||
@ -217,7 +221,7 @@ groups:
|
|||||||
- "modules/@angular/platform-webworker/*"
|
- "modules/@angular/platform-webworker/*"
|
||||||
users:
|
users:
|
||||||
- vicb #primary
|
- vicb #primary
|
||||||
# needs secondary
|
- tbosch #secondary
|
||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
- mhevery #fallback
|
- mhevery #fallback
|
||||||
|
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
language: node_js
|
language: node_js
|
||||||
sudo: false
|
sudo: false
|
||||||
node_js:
|
node_js:
|
||||||
- '6.6.0'
|
- '6.9.5'
|
||||||
git:
|
|
||||||
# Increased from default (50) to ensure last release tag is in this range
|
|
||||||
depth: 150
|
|
||||||
|
|
||||||
addons:
|
addons:
|
||||||
# firefox: "38.0"
|
# firefox: "38.0"
|
||||||
|
15
CHANGELOG.md
15
CHANGELOG.md
@ -1,3 +1,18 @@
|
|||||||
|
<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>
|
<a name="2.4.7"></a>
|
||||||
## [2.4.7](https://github.com/angular/angular/compare/2.4.6...2.4.7) (2017-02-09)
|
## [2.4.7](https://github.com/angular/angular/compare/2.4.6...2.4.7) (2017-02-09)
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
machine:
|
machine:
|
||||||
node:
|
node:
|
||||||
version: 6.6.0
|
version: 6.9.5
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
pre:
|
pre:
|
||||||
- npm install -g npm@3.5.3
|
- npm install -g npm@3.10.7
|
||||||
|
|
||||||
test:
|
test:
|
||||||
override:
|
override:
|
||||||
|
@ -11,8 +11,8 @@
|
|||||||
// THIS CHECK SHOULD BE THE FIRST THING IN THIS FILE
|
// 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.
|
// This is to ensure that we catch env issues before we error while requiring other dependencies.
|
||||||
require('./tools/check-environment')({
|
require('./tools/check-environment')({
|
||||||
requiredNpmVersion: '>=3.5.3 <4.0.0',
|
requiredNpmVersion: '>=3.10.7 <4.0.0',
|
||||||
requiredNodeVersion: '>=5.4.1 <7.0.0',
|
requiredNodeVersion: '>=6.9.5 <7.0.0',
|
||||||
});
|
});
|
||||||
|
|
||||||
const gulp = require('gulp');
|
const gulp = require('gulp');
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* 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
|
* This class should not be used directly by an application developer. Instead, use
|
||||||
* {@link Location}.
|
* {@link Location}.
|
||||||
@ -50,6 +51,12 @@ export abstract class PlatformLocation {
|
|||||||
abstract back(): void;
|
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
|
* A serializable version of the event from onPopState or onHashChange
|
||||||
*
|
*
|
||||||
|
@ -1020,7 +1020,7 @@ export class FormGroup extends AbstractControl {
|
|||||||
getRawValue(): any {
|
getRawValue(): any {
|
||||||
return this._reduceChildren(
|
return this._reduceChildren(
|
||||||
{}, (acc: {[k: string]: AbstractControl}, control: AbstractControl, name: string) => {
|
{}, (acc: {[k: string]: AbstractControl}, control: AbstractControl, name: string) => {
|
||||||
acc[name] = control.value;
|
acc[name] = control instanceof FormControl ? control.value : (<any>control).getRawValue();
|
||||||
return acc;
|
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.
|
* 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.
|
* 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 */
|
/** @internal */
|
||||||
_throwIfControlMissing(index: number): void {
|
_throwIfControlMissing(index: number): void {
|
||||||
|
@ -32,6 +32,7 @@ export function main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe('FormArray', () => {
|
describe('FormArray', () => {
|
||||||
|
|
||||||
describe('adding/removing', () => {
|
describe('adding/removing', () => {
|
||||||
let a: FormArray;
|
let a: FormArray;
|
||||||
let c1: FormControl, c2: FormControl, c3: FormControl;
|
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', () => {
|
describe('setValue', () => {
|
||||||
let c: FormControl, c2: FormControl, a: FormArray;
|
let c: FormControl, c2: FormControl, a: FormArray;
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import {async, fakeAsync, tick} from '@angular/core/testing';
|
import {async, fakeAsync, tick} from '@angular/core/testing';
|
||||||
import {AsyncTestCompleter, beforeEach, describe, inject, it} from '@angular/core/testing/testing_internal';
|
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 {EventEmitter} from '../src/facade/async';
|
||||||
import {isPresent} from '../src/facade/lang';
|
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', () => {
|
describe('adding and removing controls', () => {
|
||||||
it('should update value and validity when control is added', () => {
|
it('should update value and validity when control is added', () => {
|
||||||
const g = new FormGroup({'one': new FormControl('1')});
|
const g = new FormGroup({'one': new FormControl('1')});
|
||||||
|
@ -43,6 +43,10 @@ export function main() {
|
|||||||
it('should error on null',
|
it('should error on null',
|
||||||
() => { expect(Validators.required(new FormControl(null))).toEqual({'required': true}); });
|
() => { 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',
|
it('should not error on a non-empty string',
|
||||||
() => { expect(Validators.required(new FormControl('not empty'))).toBeNull(); });
|
() => { expect(Validators.required(new FormControl('not empty'))).toBeNull(); });
|
||||||
|
|
||||||
@ -72,7 +76,7 @@ export function main() {
|
|||||||
() => { expect(Validators.minLength(2)(new FormControl(null))).toBeNull(); });
|
() => { expect(Validators.minLength(2)(new FormControl(null))).toBeNull(); });
|
||||||
|
|
||||||
it('should not error on undefined',
|
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',
|
it('should not error on valid strings',
|
||||||
() => { expect(Validators.minLength(2)(new FormControl('aa'))).toBeNull(); });
|
() => { expect(Validators.minLength(2)(new FormControl('aa'))).toBeNull(); });
|
||||||
@ -103,6 +107,9 @@ export function main() {
|
|||||||
it('should not error on null',
|
it('should not error on null',
|
||||||
() => { expect(Validators.maxLength(2)(new FormControl(null))).toBeNull(); });
|
() => { 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',
|
it('should not error on valid strings',
|
||||||
() => { expect(Validators.maxLength(2)(new FormControl('aa'))).toBeNull(); });
|
() => { expect(Validators.maxLength(2)(new FormControl('aa'))).toBeNull(); });
|
||||||
|
|
||||||
@ -132,8 +139,9 @@ export function main() {
|
|||||||
it('should not error on null',
|
it('should not error on null',
|
||||||
() => { expect(Validators.pattern('[a-zA-Z ]+')(new FormControl(null))).toBeNull(); });
|
() => { expect(Validators.pattern('[a-zA-Z ]+')(new FormControl(null))).toBeNull(); });
|
||||||
|
|
||||||
it('should not error on undefined',
|
it('should not error on undefined', () => {
|
||||||
() => { expect(Validators.pattern('[a-zA-Z ]+')(new FormControl(null))).toBeNull(); });
|
expect(Validators.pattern('[a-zA-Z ]+')(new FormControl(undefined))).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
it('should not error on null value and "null" pattern',
|
it('should not error on null value and "null" pattern',
|
||||||
() => { expect(Validators.pattern('null')(new FormControl(null))).toBeNull(); });
|
() => { expect(Validators.pattern('null')(new FormControl(null))).toBeNull(); });
|
||||||
|
@ -32,23 +32,23 @@ export class BrowserJsonp {
|
|||||||
|
|
||||||
nextRequestID(): string { return `__req${_nextRequestId++}`; }
|
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();
|
const connections = _getJsonpConnections();
|
||||||
connections[id] = connection;
|
connections[id] = connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
removeConnection(id: string): void {
|
removeConnection(id: string) {
|
||||||
const connections = _getJsonpConnections();
|
const connections = _getJsonpConnections();
|
||||||
connections[id] = null;
|
connections[id] = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attach the <script> element to the DOM
|
// 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
|
// Remove <script> element from the DOM
|
||||||
cleanup(node: any): void {
|
cleanup(node: any) {
|
||||||
if (node.parentNode) {
|
if (node.parentNode) {
|
||||||
node.parentNode.removeChild(<Node>(node));
|
node.parentNode.removeChild(<Node>(node));
|
||||||
}
|
}
|
||||||
|
@ -70,11 +70,6 @@ export function main() {
|
|||||||
expect(instance).toBeAnInstanceOf(JSONPConnection);
|
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', () => {
|
describe('JSONPConnection', () => {
|
||||||
it('should use the injected BaseResponseOptions to create the response',
|
it('should use the injected BaseResponseOptions to create the response',
|
||||||
|
@ -127,7 +127,6 @@ export class DomRenderer implements Renderer {
|
|||||||
let nodesParent: Element|DocumentFragment;
|
let nodesParent: Element|DocumentFragment;
|
||||||
if (this.componentProto.encapsulation === ViewEncapsulation.Native) {
|
if (this.componentProto.encapsulation === ViewEncapsulation.Native) {
|
||||||
nodesParent = (hostElement as any).createShadowRoot();
|
nodesParent = (hostElement as any).createShadowRoot();
|
||||||
this._rootRenderer.sharedStylesHost.addHost(nodesParent);
|
|
||||||
for (let i = 0; i < this._styles.length; i++) {
|
for (let i = 0; i < this._styles.length; i++) {
|
||||||
const styleEl = document.createElement('style');
|
const styleEl = document.createElement('style');
|
||||||
styleEl.textContent = this._styles[i];
|
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,12 +6,13 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {PlatformLocation} from '@angular/common';
|
import {LOCATION_INITIALIZED, PlatformLocation} from '@angular/common';
|
||||||
import {APP_INITIALIZER, NgZone} from '@angular/core';
|
import {APP_INITIALIZER, NgZone, OpaqueToken} from '@angular/core';
|
||||||
|
|
||||||
import {WebWorkerPlatformLocation} from './platform_location';
|
import {WebWorkerPlatformLocation} from './platform_location';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Those providers should be added when the router is used in a worker context in addition to the
|
* Those providers should be added when the router is used in a worker context in addition to the
|
||||||
* {@link ROUTER_PROVIDERS} and after them.
|
* {@link ROUTER_PROVIDERS} and after them.
|
||||||
@ -23,10 +24,15 @@ export const WORKER_APP_LOCATION_PROVIDERS = [
|
|||||||
useFactory: appInitFnFactory,
|
useFactory: appInitFnFactory,
|
||||||
multi: true,
|
multi: true,
|
||||||
deps: [PlatformLocation, NgZone]
|
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> {
|
Promise<boolean> {
|
||||||
return () => zone.runGuarded(() => platformLocation.init());
|
return () => zone.runGuarded(() => platformLocation.init());
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,8 @@ export class WebWorkerPlatformLocation extends PlatformLocation {
|
|||||||
private _hashChangeListeners: Array<Function> = [];
|
private _hashChangeListeners: Array<Function> = [];
|
||||||
private _location: LocationType = null;
|
private _location: LocationType = null;
|
||||||
private _channelSource: EventEmitter<Object>;
|
private _channelSource: EventEmitter<Object>;
|
||||||
|
public initialized: Promise<any>;
|
||||||
|
private initializedResolve: () => void;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
brokerFactory: ClientMessageBrokerFactory, bus: MessageBus, private _serializer: Serializer) {
|
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 **/
|
/** @internal **/
|
||||||
@ -63,6 +66,7 @@ export class WebWorkerPlatformLocation extends PlatformLocation {
|
|||||||
(val: LocationType):
|
(val: LocationType):
|
||||||
boolean => {
|
boolean => {
|
||||||
this._location = val;
|
this._location = val;
|
||||||
|
this.initializedResolve();
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
(err): boolean => { throw new Error(err); });
|
(err): boolean => { throw new Error(err); });
|
||||||
|
@ -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.
|
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
|
## 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
|
## Guide
|
||||||
Read the dev guide [here](https://angular.io/docs/ts/latest/guide/router.html).
|
Read the dev guide [here](https://angular.io/docs/ts/latest/guide/router.html).
|
||||||
|
@ -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.
|
* activated.
|
||||||
*
|
*
|
||||||
* @howToUse
|
* @howToUse
|
||||||
@ -53,7 +53,7 @@ import {ActivatedRouteSnapshot, RouterStateSnapshot} from './router_state';
|
|||||||
* class AppModule {}
|
* 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({
|
* @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.
|
* activated.
|
||||||
*
|
*
|
||||||
* @howToUse
|
* @howToUse
|
||||||
@ -129,7 +129,7 @@ export interface CanActivate {
|
|||||||
* class AppModule {}
|
* 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({
|
* @NgModule({
|
||||||
@ -159,14 +159,13 @@ export interface CanActivate {
|
|||||||
*
|
*
|
||||||
* @stable
|
* @stable
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export interface CanActivateChild {
|
export interface CanActivateChild {
|
||||||
canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot):
|
canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot):
|
||||||
Observable<boolean>|Promise<boolean>|boolean;
|
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.
|
* deactivated.
|
||||||
*
|
*
|
||||||
* @howToUse
|
* @howToUse
|
||||||
@ -207,7 +206,7 @@ export interface CanActivateChild {
|
|||||||
* class AppModule {}
|
* 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({
|
* @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
|
* @howToUse
|
||||||
*
|
*
|
||||||
@ -278,7 +277,7 @@ export interface CanDeactivate<T> {
|
|||||||
* class AppModule {}
|
* 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({
|
* @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.
|
* loaded.
|
||||||
*
|
*
|
||||||
* @howToUse
|
* @howToUse
|
||||||
@ -350,7 +349,7 @@ export interface Resolve<T> {
|
|||||||
* class AppModule {}
|
* 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({
|
* @NgModule({
|
||||||
|
@ -278,6 +278,18 @@ type NavigationParams = {
|
|||||||
source: NavigationSource,
|
source: NavigationSource,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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.
|
* Does not detach any subtrees. Reuses routes as long as their route config is the same.
|
||||||
@ -320,11 +332,23 @@ export class Router {
|
|||||||
*/
|
*/
|
||||||
errorHandler: ErrorHandler = defaultErrorHandler;
|
errorHandler: ErrorHandler = defaultErrorHandler;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if at least one navigation happened.
|
* Indicates if at least one navigation happened.
|
||||||
*/
|
*/
|
||||||
navigated: boolean = false;
|
navigated: boolean = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 Angular 1 to Angular 2 migrations.
|
* Extracts and merges URLs. Used for Angular 1 to Angular 2 migrations.
|
||||||
*/
|
*/
|
||||||
@ -681,18 +705,25 @@ export class Router {
|
|||||||
urlAndSnapshot$ = of ({appliedUrl: url, snapshot: precreatedState});
|
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
|
// run preactivation: guards and data resolvers
|
||||||
let preActivation: PreActivation;
|
let preActivation: PreActivation;
|
||||||
const preactivationTraverse$ = map.call(urlAndSnapshot$, ({appliedUrl, snapshot}: any) => {
|
const preactivationTraverse$ = map.call(
|
||||||
|
beforePreactivationDone$,
|
||||||
|
({appliedUrl, snapshot}: {appliedUrl: string, snapshot: RouterStateSnapshot}) => {
|
||||||
preActivation =
|
preActivation =
|
||||||
new PreActivation(snapshot, this.currentRouterState.snapshot, this.injector);
|
new PreActivation(snapshot, this.currentRouterState.snapshot, this.injector);
|
||||||
preActivation.traverse(this.outletMap);
|
preActivation.traverse(this.outletMap);
|
||||||
return {appliedUrl, snapshot};
|
return {appliedUrl, snapshot};
|
||||||
});
|
});
|
||||||
|
|
||||||
const preactivationCheckGuards =
|
const preactivationCheckGuards$ = mergeMap.call(
|
||||||
mergeMap.call(preactivationTraverse$, ({appliedUrl, snapshot}: any) => {
|
preactivationTraverse$,
|
||||||
|
({appliedUrl, snapshot}: {appliedUrl: string, snapshot: RouterStateSnapshot}) => {
|
||||||
if (this.navigationId !== id) return of (false);
|
if (this.navigationId !== id) return of (false);
|
||||||
|
|
||||||
return map.call(preActivation.checkGuards(), (shouldActivate: boolean) => {
|
return map.call(preActivation.checkGuards(), (shouldActivate: boolean) => {
|
||||||
@ -700,7 +731,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 (this.navigationId !== id) return of (false);
|
||||||
|
|
||||||
if (p.shouldActivate) {
|
if (p.shouldActivate) {
|
||||||
@ -710,11 +741,15 @@ export class Router {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const preactivationDone$ = mergeMap.call(preactivationResolveData$, (p: any) => {
|
||||||
|
return map.call(this.hooks.afterPreactivation(p.snapshot), () => p);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
// create router state
|
// create router state
|
||||||
// this operation has side effects => route state is being affected
|
// this operation has side effects => route state is being affected
|
||||||
const routerState$ =
|
const routerState$ =
|
||||||
map.call(preactivationResolveData$, ({appliedUrl, snapshot, shouldActivate}: any) => {
|
map.call(preactivationDone$, ({appliedUrl, snapshot, shouldActivate}: any) => {
|
||||||
if (shouldActivate) {
|
if (shouldActivate) {
|
||||||
const state =
|
const state =
|
||||||
createRouterState(this.routeReuseStrategy, snapshot, this.currentRouterState);
|
createRouterState(this.routeReuseStrategy, snapshot, this.currentRouterState);
|
||||||
|
@ -6,8 +6,10 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {APP_BASE_HREF, HashLocationStrategy, Location, LocationStrategy, PathLocationStrategy, PlatformLocation} from '@angular/common';
|
import {APP_BASE_HREF, HashLocationStrategy, LOCATION_INITIALIZED, 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 {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 {Route, Routes} from './config';
|
||||||
import {RouterLink, RouterLinkWithHref} from './directives/router_link';
|
import {RouterLink, RouterLinkWithHref} from './directives/router_link';
|
||||||
@ -19,7 +21,7 @@ import {ErrorHandler, Router} from './router';
|
|||||||
import {ROUTES} from './router_config_loader';
|
import {ROUTES} from './router_config_loader';
|
||||||
import {RouterOutletMap} from './router_outlet_map';
|
import {RouterOutletMap} from './router_outlet_map';
|
||||||
import {NoPreloading, PreloadAllModules, PreloadingStrategy, RouterPreloader} from './router_preloader';
|
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 {UrlHandlingStrategy} from './url_handling_strategy';
|
||||||
import {DefaultUrlSerializer, UrlSerializer} from './url_tree';
|
import {DefaultUrlSerializer, UrlSerializer} from './url_tree';
|
||||||
import {flatten} from './utils/collection';
|
import {flatten} from './utils/collection';
|
||||||
@ -110,7 +112,7 @@ export function routerNgProbeToken() {
|
|||||||
* In addition, we often want to split applications into multiple bundles and load them on demand.
|
* In addition, we often want to split applications into multiple bundles and load them on demand.
|
||||||
* Doing this transparently is not trivial.
|
* Doing this transparently is not trivial.
|
||||||
*
|
*
|
||||||
* The Angular 2 router solves these problems. Using the router, you can declaratively specify
|
* The Angular router solves these problems. Using the router, you can declaratively specify
|
||||||
* application states, manage state transitions while taking care of the URL, and load bundles on
|
* application states, manage state transitions while taking care of the URL, and load bundles on
|
||||||
* demand.
|
* demand.
|
||||||
*
|
*
|
||||||
@ -278,22 +280,77 @@ export function rootRoute(router: Router): ActivatedRoute {
|
|||||||
return router.routerState.root;
|
return router.routerState.root;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initialRouterNavigation(
|
/**
|
||||||
router: Router, ref: ApplicationRef, preloader: RouterPreloader, opts: ExtraOptions) {
|
* To initialize the router properly we need to do in two steps:
|
||||||
return (bootstrappedComponentRef: ComponentRef<any>) => {
|
*
|
||||||
|
* 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;
|
||||||
|
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 (opts.initialNavigation === false) {
|
||||||
|
router.setUpLocationChangeListener();
|
||||||
|
} else {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bootstrapListener(bootstrappedComponentRef: ComponentRef<any>): void {
|
||||||
|
const ref = this.injector.get(ApplicationRef);
|
||||||
if (bootstrappedComponentRef !== ref.components[0]) {
|
if (bootstrappedComponentRef !== ref.components[0]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
router.resetRootComponentType(ref.componentTypes[0]);
|
const preloader = this.injector.get(RouterPreloader);
|
||||||
preloader.setUpPreloading();
|
preloader.setUpPreloading();
|
||||||
if (opts.initialNavigation === false) {
|
|
||||||
router.setUpLocationChangeListener();
|
const router = this.injector.get(Router);
|
||||||
} else {
|
router.resetRootComponentType(ref.componentTypes[0]);
|
||||||
router.initialNavigation();
|
|
||||||
|
this.resultOfPreactivationDone.next(null);
|
||||||
|
this.resultOfPreactivationDone.complete();
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
|
export function getAppInitializer(r: RouterInitializer) {
|
||||||
|
return r.appInitializer.bind(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBootstrapListener(r: RouterInitializer) {
|
||||||
|
return r.bootstrapListener.bind(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -305,11 +362,14 @@ export const ROUTER_INITIALIZER = new OpaqueToken('Router Initializer');
|
|||||||
|
|
||||||
export function provideRouterInitializer() {
|
export function provideRouterInitializer() {
|
||||||
return [
|
return [
|
||||||
|
RouterInitializer,
|
||||||
{
|
{
|
||||||
provide: ROUTER_INITIALIZER,
|
provide: APP_INITIALIZER,
|
||||||
useFactory: initialRouterNavigation,
|
multi: true,
|
||||||
deps: [Router, ApplicationRef, RouterPreloader, ROUTER_CONFIGURATION]
|
useFactory: getAppInitializer,
|
||||||
|
deps: [RouterInitializer]
|
||||||
},
|
},
|
||||||
|
{provide: ROUTER_INITIALIZER, useFactory: getBootstrapListener, deps: [RouterInitializer]},
|
||||||
{provide: APP_BOOTSTRAP_LISTENER, multi: true, useExisting: ROUTER_INITIALIZER},
|
{provide: APP_BOOTSTRAP_LISTENER, multi: true, useExisting: ROUTER_INITIALIZER},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
128
modules/@angular/router/test/bootstrap.spec.ts
Normal file
128
modules/@angular/router/test/bootstrap.spec.ts
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
/**
|
||||||
|
* @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 {BrowserModule, DOCUMENT} from '@angular/platform-browser';
|
||||||
|
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||||
|
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||||
|
import {Resolve, Router, RouterModule} from '@angular/router';
|
||||||
|
|
||||||
|
describe('bootstrap', () => {
|
||||||
|
|
||||||
|
@Component({selector: 'test-app', template: 'root <router-outlet></router-outlet>'})
|
||||||
|
class RootCmp {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'test-app2', template: 'root <router-outlet></router-outlet>'})
|
||||||
|
class SecondRootCmp {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'test', template: 'test'})
|
||||||
|
class TestCmp {
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestResolver implements Resolve<any> {
|
||||||
|
resolve() {
|
||||||
|
let resolve: any = null;
|
||||||
|
const res = new Promise(r => resolve = r);
|
||||||
|
setTimeout(() => resolve('test-data'), 0);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let testProviders: any[] = null;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
destroyPlatform();
|
||||||
|
const fakeDoc = getDOM().createHtmlDocument();
|
||||||
|
const el1 = getDOM().createElement('test-app', fakeDoc);
|
||||||
|
const el2 = getDOM().createElement('test-app2', fakeDoc);
|
||||||
|
getDOM().appendChild(fakeDoc.body, el1);
|
||||||
|
getDOM().appendChild(fakeDoc.body, el2);
|
||||||
|
testProviders =
|
||||||
|
[{provide: DOCUMENT, useValue: fakeDoc}, {provide: APP_BASE_HREF, useValue: ''}];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should wait for resolvers to complete', (done) => {
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
BrowserModule,
|
||||||
|
RouterModule.forRoot(
|
||||||
|
[{path: '**', component: TestCmp, resolve: {test: TestResolver}}], {useHash: true})
|
||||||
|
],
|
||||||
|
declarations: [SecondRootCmp, RootCmp, TestCmp],
|
||||||
|
bootstrap: [RootCmp],
|
||||||
|
providers: [...testProviders, TestResolver],
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
|
})
|
||||||
|
class TestModule {
|
||||||
|
}
|
||||||
|
|
||||||
|
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');
|
||||||
|
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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
@ -5,14 +5,14 @@
|
|||||||
* Use of this source code is governed by an MIT-style license that can be
|
* 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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
import {APP_BOOTSTRAP_LISTENER, ComponentRef, OpaqueToken} from '@angular/core';
|
||||||
import {APP_BOOTSTRAP_LISTENER, ApplicationRef, OpaqueToken} from '@angular/core';
|
import {Router} from '@angular/router';
|
||||||
import {ExtraOptions, ROUTER_CONFIGURATION, ROUTER_INITIALIZER, Router, RouterPreloader} from '@angular/router';
|
|
||||||
import {UpgradeModule} from '@angular/upgrade/static';
|
import {UpgradeModule} from '@angular/upgrade/static';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @whatItDoes Creates an initializer that in addition to setting up the Angular 2
|
* @whatItDoes Creates an initializer that in addition to setting up the Angular
|
||||||
* router sets up the ngRoute integration.
|
* router sets up the ngRoute integration.
|
||||||
*
|
*
|
||||||
* @howToUse
|
* @howToUse
|
||||||
@ -35,38 +35,17 @@ import {UpgradeModule} from '@angular/upgrade/static';
|
|||||||
* @experimental
|
* @experimental
|
||||||
*/
|
*/
|
||||||
export const RouterUpgradeInitializer = {
|
export const RouterUpgradeInitializer = {
|
||||||
provide: ROUTER_INITIALIZER,
|
provide: APP_BOOTSTRAP_LISTENER,
|
||||||
useFactory: initialRouterNavigation,
|
multi: true,
|
||||||
deps: [UpgradeModule, ApplicationRef, RouterPreloader, ROUTER_CONFIGURATION]
|
useFactory: locationSyncBootstrapListener,
|
||||||
|
deps: [UpgradeModule]
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export function initialRouterNavigation(
|
export function locationSyncBootstrapListener(ngUpgrade: UpgradeModule) {
|
||||||
ngUpgrade: UpgradeModule, ref: ApplicationRef, preloader: RouterPreloader,
|
return () => { setUpLocationSync(ngUpgrade); };
|
||||||
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);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -77,7 +56,14 @@ export function initialRouterNavigation(
|
|||||||
*
|
*
|
||||||
* @experimental
|
* @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 router: Router = ngUpgrade.injector.get(Router);
|
||||||
const url = document.createElement('a');
|
const url = document.createElement('a');
|
||||||
|
|
||||||
|
@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
export type Ng1Token = string;
|
export type Ng1Token = string;
|
||||||
|
|
||||||
|
export type Ng1Expression = string | Function;
|
||||||
|
|
||||||
export interface IAnnotatedFunction extends Function { $inject?: Ng1Token[]; }
|
export interface IAnnotatedFunction extends Function { $inject?: Ng1Token[]; }
|
||||||
|
|
||||||
export type IInjectable = (Ng1Token | Function)[] | IAnnotatedFunction;
|
export type IInjectable = (Ng1Token | Function)[] | IAnnotatedFunction;
|
||||||
@ -45,9 +47,7 @@ export interface IRootScopeService {
|
|||||||
$watch(expr: any, fn?: (a1?: any, a2?: any) => void): Function;
|
$watch(expr: any, fn?: (a1?: any, a2?: any) => void): Function;
|
||||||
$on(event: string, fn?: (event?: any, ...args: any[]) => void): Function;
|
$on(event: string, fn?: (event?: any, ...args: any[]) => void): Function;
|
||||||
$destroy(): any;
|
$destroy(): any;
|
||||||
$apply(): any;
|
$apply(exp?: Ng1Expression): any;
|
||||||
$apply(exp: string): any;
|
|
||||||
$apply(exp: Function): any;
|
|
||||||
$digest(): any;
|
$digest(): any;
|
||||||
$evalAsync(): any;
|
$evalAsync(): any;
|
||||||
$on(event: string, fn?: (event?: any, ...args: any[]) => void): Function;
|
$on(event: string, fn?: (event?: any, ...args: any[]) => void): Function;
|
||||||
|
@ -57,16 +57,15 @@ export class DowngradeComponentAdapter {
|
|||||||
let expr: any /** TODO #9100 */ = null;
|
let expr: any /** TODO #9100 */ = null;
|
||||||
|
|
||||||
if (attrs.hasOwnProperty(input.attr)) {
|
if (attrs.hasOwnProperty(input.attr)) {
|
||||||
const observeFn = ((prop: any /** TODO #9100 */) => {
|
const observeFn = (prop => {
|
||||||
let prevValue = INITIAL_VALUE;
|
let prevValue = INITIAL_VALUE;
|
||||||
return (value: any /** TODO #9100 */) => {
|
return (currValue: any) => {
|
||||||
if (this.inputChanges !== null) {
|
if (prevValue === INITIAL_VALUE) {
|
||||||
this.inputChangeCount++;
|
prevValue = currValue;
|
||||||
this.inputChanges[prop] =
|
|
||||||
new Ng1Change(value, prevValue === INITIAL_VALUE ? value : prevValue);
|
|
||||||
prevValue = value;
|
|
||||||
}
|
}
|
||||||
this.component[prop] = value;
|
|
||||||
|
this.updateInput(prop, prevValue, currValue);
|
||||||
|
prevValue = currValue;
|
||||||
};
|
};
|
||||||
})(input.prop);
|
})(input.prop);
|
||||||
attrs.$observe(input.attr, observeFn);
|
attrs.$observe(input.attr, observeFn);
|
||||||
@ -82,14 +81,8 @@ export class DowngradeComponentAdapter {
|
|||||||
}
|
}
|
||||||
if (expr != null) {
|
if (expr != null) {
|
||||||
const watchFn =
|
const watchFn =
|
||||||
((prop: any /** TODO #9100 */) =>
|
(prop => (currValue: any, prevValue: any) =>
|
||||||
(value: any /** TODO #9100 */, prevValue: any /** TODO #9100 */) => {
|
this.updateInput(prop, prevValue, currValue))(input.prop);
|
||||||
if (this.inputChanges != null) {
|
|
||||||
this.inputChangeCount++;
|
|
||||||
this.inputChanges[prop] = new Ng1Change(prevValue, value);
|
|
||||||
}
|
|
||||||
this.component[prop] = value;
|
|
||||||
})(input.prop);
|
|
||||||
this.componentScope.$watch(expr, watchFn);
|
this.componentScope.$watch(expr, watchFn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -171,6 +164,15 @@ export class DowngradeComponentAdapter {
|
|||||||
this.componentRef.destroy();
|
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 {
|
class Ng1Change implements SimpleChange {
|
||||||
|
@ -49,16 +49,15 @@ export class DowngradeNg2ComponentAdapter {
|
|||||||
const input = inputs[i];
|
const input = inputs[i];
|
||||||
let expr: any /** TODO #9100 */ = null;
|
let expr: any /** TODO #9100 */ = null;
|
||||||
if (attrs.hasOwnProperty(input.attr)) {
|
if (attrs.hasOwnProperty(input.attr)) {
|
||||||
const observeFn = ((prop: any /** TODO #9100 */) => {
|
const observeFn = (prop => {
|
||||||
let prevValue = INITIAL_VALUE;
|
let prevValue = INITIAL_VALUE;
|
||||||
return (value: any /** TODO #9100 */) => {
|
return (currValue: any) => {
|
||||||
if (this.inputChanges !== null) {
|
if (prevValue === INITIAL_VALUE) {
|
||||||
this.inputChangeCount++;
|
prevValue = currValue;
|
||||||
this.inputChanges[prop] =
|
|
||||||
new Ng1Change(value, prevValue === INITIAL_VALUE ? value : prevValue);
|
|
||||||
prevValue = value;
|
|
||||||
}
|
}
|
||||||
this.component[prop] = value;
|
|
||||||
|
this.updateInput(prop, prevValue, currValue);
|
||||||
|
prevValue = currValue;
|
||||||
};
|
};
|
||||||
})(input.prop);
|
})(input.prop);
|
||||||
attrs.$observe(input.attr, observeFn);
|
attrs.$observe(input.attr, observeFn);
|
||||||
@ -73,14 +72,8 @@ export class DowngradeNg2ComponentAdapter {
|
|||||||
}
|
}
|
||||||
if (expr != null) {
|
if (expr != null) {
|
||||||
const watchFn =
|
const watchFn =
|
||||||
((prop: any /** TODO #9100 */) =>
|
(prop => (currValue: any, prevValue: any) =>
|
||||||
(value: any /** TODO #9100 */, prevValue: any /** TODO #9100 */) => {
|
this.updateInput(prop, prevValue, currValue))(input.prop);
|
||||||
if (this.inputChanges != null) {
|
|
||||||
this.inputChangeCount++;
|
|
||||||
this.inputChanges[prop] = new Ng1Change(prevValue, value);
|
|
||||||
}
|
|
||||||
this.component[prop] = value;
|
|
||||||
})(input.prop);
|
|
||||||
this.componentScope.$watch(expr, watchFn);
|
this.componentScope.$watch(expr, watchFn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -151,6 +144,15 @@ export class DowngradeNg2ComponentAdapter {
|
|||||||
this.componentRef.destroy();
|
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 {
|
class Ng1Change implements SimpleChange {
|
||||||
|
@ -656,14 +656,11 @@ function ng1ComponentDirective(info: ComponentInfo, idPrefix: string): Function
|
|||||||
restrict: 'E',
|
restrict: 'E',
|
||||||
terminal: true,
|
terminal: true,
|
||||||
require: REQUIRE_INJECTOR,
|
require: REQUIRE_INJECTOR,
|
||||||
compile: (templateElement: angular.IAugmentedJQuery, templateAttributes: angular.IAttributes,
|
link: (scope: angular.IScope, element: angular.IAugmentedJQuery, attrs: angular.IAttributes,
|
||||||
transclude: angular.ITranscludeFunction) => {
|
parentInjector: Injector | ParentInjectorPromise): void => {
|
||||||
// We might have compile the contents lazily, because this might have been triggered by the
|
// 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
|
// 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++);
|
let id = idPrefix + (idCount++);
|
||||||
(<any>element[0]).id = id;
|
(<any>element[0]).id = id;
|
||||||
|
|
||||||
@ -671,11 +668,10 @@ function ng1ComponentDirective(info: ComponentInfo, idPrefix: string): Function
|
|||||||
|
|
||||||
const ng2Compiler = ng1Injector.get(NG2_COMPILER) as Compiler;
|
const ng2Compiler = ng1Injector.get(NG2_COMPILER) as Compiler;
|
||||||
const ngContentSelectors = ng2Compiler.getNgContentSelectors(info.type);
|
const ngContentSelectors = ng2Compiler.getNgContentSelectors(info.type);
|
||||||
const linkFns = compileProjectedNodes(templateElement, ngContentSelectors);
|
const linkFns = compileProjectedNodes(element, ngContentSelectors);
|
||||||
|
|
||||||
const componentFactory: ComponentFactory<any> = componentFactoryRefMap[info.selector];
|
const componentFactory: ComponentFactory<any> = componentFactoryRefMap[info.selector];
|
||||||
if (!componentFactory)
|
if (!componentFactory) throw new Error('Expecting ComponentFactory for: ' + info.selector);
|
||||||
throw new Error('Expecting ComponentFactory for: ' + info.selector);
|
|
||||||
|
|
||||||
element.empty();
|
element.empty();
|
||||||
let projectableNodes = linkFns.map(link => {
|
let projectableNodes = linkFns.map(link => {
|
||||||
@ -706,17 +702,13 @@ function ng1ComponentDirective(info: ComponentInfo, idPrefix: string): Function
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function compileProjectedNodes(
|
function compileProjectedNodes(
|
||||||
templateElement: angular.IAugmentedJQuery,
|
element: angular.IAugmentedJQuery, ngContentSelectors: string[]): angular.ILinkFn[] {
|
||||||
ngContentSelectors: string[]): angular.ILinkFn[] {
|
|
||||||
if (!ngContentSelectors)
|
if (!ngContentSelectors)
|
||||||
throw new Error('Expecting ngContentSelectors for: ' + info.selector);
|
throw new Error('Expecting ngContentSelectors for: ' + info.selector);
|
||||||
// We have to sort the projected content before we compile it, hence the terminal: true
|
// We have to sort the projected content before we compile it, hence the terminal: true
|
||||||
let projectableTemplateNodes =
|
let projectableTemplateNodes = sortProjectableNodes(ngContentSelectors, element.contents());
|
||||||
sortProjectableNodes(ngContentSelectors, templateElement.contents());
|
|
||||||
return projectableTemplateNodes.map(nodes => ng1Compile(nodes));
|
return projectableTemplateNodes.map(nodes => ng1Compile(nodes));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,14 +6,14 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* 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 {async} from '@angular/core/testing';
|
||||||
import {BrowserModule} from '@angular/platform-browser';
|
import {BrowserModule} from '@angular/platform-browser';
|
||||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||||
import * as angular from '@angular/upgrade/src/angular_js';
|
import * as angular from '@angular/upgrade/src/angular_js';
|
||||||
import {UpgradeComponent, UpgradeModule, downgradeComponent} from '@angular/upgrade/static';
|
import {UpgradeComponent, UpgradeModule, downgradeComponent} from '@angular/upgrade/static';
|
||||||
|
|
||||||
import {bootstrap, html} from '../test_helpers';
|
import {bootstrap, html, multiTrim} from '../test_helpers';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('content projection', () => {
|
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(() => {
|
it('should instantiate ng1 in ng2 template and project content', async(() => {
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -13,7 +13,7 @@ import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
|||||||
import * as angular from '@angular/upgrade/src/angular_js';
|
import * as angular from '@angular/upgrade/src/angular_js';
|
||||||
import {UpgradeModule, downgradeComponent} from '@angular/upgrade/static';
|
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() {
|
export function main() {
|
||||||
describe('downgrade ng2 component', () => {
|
describe('downgrade ng2 component', () => {
|
||||||
@ -22,8 +22,11 @@ export function main() {
|
|||||||
afterEach(() => destroyPlatform());
|
afterEach(() => destroyPlatform());
|
||||||
|
|
||||||
it('should bind properties, events', async(() => {
|
it('should bind properties, events', async(() => {
|
||||||
|
const ng1Module =
|
||||||
const ng1Module = angular.module('ng1', []).run(($rootScope: angular.IScope) => {
|
angular.module('ng1', []).value('$exceptionHandler', (err: any) => {
|
||||||
|
throw err;
|
||||||
|
}).run(($rootScope: angular.IScope) => {
|
||||||
|
$rootScope['name'] = 'world';
|
||||||
$rootScope['dataA'] = 'A';
|
$rootScope['dataA'] = 'A';
|
||||||
$rootScope['dataB'] = 'B';
|
$rootScope['dataB'] = 'B';
|
||||||
$rootScope['modelA'] = 'initModelA';
|
$rootScope['modelA'] = 'initModelA';
|
||||||
@ -94,9 +97,10 @@ export function main() {
|
|||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
assertChange('twoWayA', 'newA');
|
assertChange('twoWayA', 'newA');
|
||||||
|
assertChange('twoWayB', 'newB');
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
assertChange('twoWayB', 'newB');
|
assertChange('interpolate', 'Hello everyone');
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error('Called too many times! ' + JSON.stringify(changes));
|
throw new Error('Called too many times! ' + JSON.stringify(changes));
|
||||||
@ -125,7 +129,7 @@ export function main() {
|
|||||||
|
|
||||||
const element = html(`
|
const element = html(`
|
||||||
<div>
|
<div>
|
||||||
<ng2 literal="Text" interpolate="Hello {{'world'}}"
|
<ng2 literal="Text" interpolate="Hello {{name}}"
|
||||||
bind-one-way-a="dataA" [one-way-b]="dataB"
|
bind-one-way-a="dataA" [one-way-b]="dataB"
|
||||||
bindon-two-way-a="modelA" [(two-way-b)]="modelB"
|
bindon-two-way-a="modelA" [(two-way-b)]="modelB"
|
||||||
on-event-a='eventA=$event' (event-b)="eventB=$event"></ng2>
|
on-event-a='eventA=$event' (event-b)="eventB=$event"></ng2>
|
||||||
@ -139,6 +143,14 @@ export function main() {
|
|||||||
'literal: Text; interpolate: Hello world; ' +
|
'literal: Text; interpolate: Hello world; ' +
|
||||||
'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (2) | ' +
|
'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (2) | ' +
|
||||||
'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;');
|
'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 * as angular from '@angular/upgrade/src/angular_js';
|
||||||
import {UpgradeComponent, UpgradeModule, downgradeComponent} from '@angular/upgrade/static';
|
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() {
|
export function main() {
|
||||||
describe('upgrade ng1 component', () => {
|
describe('upgrade ng1 component', () => {
|
||||||
@ -530,7 +530,7 @@ export function main() {
|
|||||||
|
|
||||||
ng2ComponentInstance.dataA = 'foo2';
|
ng2ComponentInstance.dataA = 'foo2';
|
||||||
ng2ComponentInstance.dataB = 'bar2';
|
ng2ComponentInstance.dataB = 'bar2';
|
||||||
digest(adapter);
|
$digest(adapter);
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
expect(multiTrim(element.textContent))
|
expect(multiTrim(element.textContent))
|
||||||
@ -605,7 +605,7 @@ export function main() {
|
|||||||
|
|
||||||
ng2ComponentInstance.dataA = {value: 'foo2'};
|
ng2ComponentInstance.dataA = {value: 'foo2'};
|
||||||
ng2ComponentInstance.dataB = {value: 'bar2'};
|
ng2ComponentInstance.dataB = {value: 'bar2'};
|
||||||
digest(adapter);
|
$digest(adapter);
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
expect(multiTrim(element.textContent))
|
expect(multiTrim(element.textContent))
|
||||||
@ -682,7 +682,7 @@ export function main() {
|
|||||||
|
|
||||||
ng2ComponentInstance.dataA = {value: 'foo2'};
|
ng2ComponentInstance.dataA = {value: 'foo2'};
|
||||||
ng2ComponentInstance.dataB = {value: 'bar2'};
|
ng2ComponentInstance.dataB = {value: 'bar2'};
|
||||||
digest(adapter);
|
$digest(adapter);
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
expect(multiTrim(element.textContent))
|
expect(multiTrim(element.textContent))
|
||||||
@ -929,7 +929,7 @@ export function main() {
|
|||||||
|
|
||||||
ng1Controller1.outputA({value: 'foo again'});
|
ng1Controller1.outputA({value: 'foo again'});
|
||||||
ng1Controller2.outputB('bar again');
|
ng1Controller2.outputB('bar again');
|
||||||
digest(adapter);
|
$digest(adapter);
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
expect(ng1Controller0.inputA).toEqual({value: 'foo again'});
|
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');
|
.toBe('ng1 - Data: [4,5] - Length: 2 | ng2 - Data: 4,5 - Length: 2');
|
||||||
|
|
||||||
ng1Controller.$scope.outputA(6);
|
ng1Controller.$scope.outputA(6);
|
||||||
digest(adapter);
|
$digest(adapter);
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
expect(ng1Controller.$scope.inputA).toEqual([4, 5, 6]);
|
expect(ng1Controller.$scope.inputA).toEqual([4, 5, 6]);
|
||||||
@ -1858,7 +1858,7 @@ export function main() {
|
|||||||
|
|
||||||
// Change: Re-assign `data`
|
// Change: Re-assign `data`
|
||||||
ng2ComponentInstance.data = {foo: 'baz'};
|
ng2ComponentInstance.data = {foo: 'baz'};
|
||||||
digest(adapter);
|
$digest(adapter);
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
expect(scopeOnChanges.calls.count()).toBe(2);
|
expect(scopeOnChanges.calls.count()).toBe(2);
|
||||||
@ -1879,7 +1879,7 @@ export function main() {
|
|||||||
|
|
||||||
// No change: Update internal property
|
// No change: Update internal property
|
||||||
ng2ComponentInstance.data.foo = 'qux';
|
ng2ComponentInstance.data.foo = 'qux';
|
||||||
digest(adapter);
|
$digest(adapter);
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
expect(scopeOnChanges.calls.count()).toBe(2);
|
expect(scopeOnChanges.calls.count()).toBe(2);
|
||||||
@ -1888,7 +1888,7 @@ export function main() {
|
|||||||
|
|
||||||
// Change: Re-assign `data` (even if it looks the same)
|
// Change: Re-assign `data` (even if it looks the same)
|
||||||
ng2ComponentInstance.data = {foo: 'qux'};
|
ng2ComponentInstance.data = {foo: 'qux'};
|
||||||
digest(adapter);
|
$digest(adapter);
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
expect(scopeOnChanges.calls.count()).toBe(3);
|
expect(scopeOnChanges.calls.count()).toBe(3);
|
||||||
@ -2008,7 +2008,7 @@ export function main() {
|
|||||||
|
|
||||||
// Change: Re-assign `data`
|
// Change: Re-assign `data`
|
||||||
ng2ComponentInstance.data = {foo: 'baz'};
|
ng2ComponentInstance.data = {foo: 'baz'};
|
||||||
digest(adapter);
|
$digest(adapter);
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
expect(scopeOnChangesA.calls.count()).toBe(2);
|
expect(scopeOnChangesA.calls.count()).toBe(2);
|
||||||
@ -2029,7 +2029,7 @@ export function main() {
|
|||||||
|
|
||||||
// No change: Update internal property
|
// No change: Update internal property
|
||||||
ng2ComponentInstance.data.foo = 'qux';
|
ng2ComponentInstance.data.foo = 'qux';
|
||||||
digest(adapter);
|
$digest(adapter);
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
expect(scopeOnChangesA.calls.count()).toBe(2);
|
expect(scopeOnChangesA.calls.count()).toBe(2);
|
||||||
@ -2039,7 +2039,7 @@ export function main() {
|
|||||||
|
|
||||||
// Change: Re-assign `data` (even if it looks the same)
|
// Change: Re-assign `data` (even if it looks the same)
|
||||||
ng2ComponentInstance.data = {foo: 'qux'};
|
ng2ComponentInstance.data = {foo: 'qux'};
|
||||||
digest(adapter);
|
$digest(adapter);
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
expect(scopeOnChangesA.calls.count()).toBe(3);
|
expect(scopeOnChangesA.calls.count()).toBe(3);
|
||||||
@ -2631,7 +2631,7 @@ export function main() {
|
|||||||
expect(scopeDestroyListener).not.toHaveBeenCalled();
|
expect(scopeDestroyListener).not.toHaveBeenCalled();
|
||||||
|
|
||||||
ng2ComponentAInstance.destroyIt = true;
|
ng2ComponentAInstance.destroyIt = true;
|
||||||
digest(adapter);
|
$digest(adapter);
|
||||||
|
|
||||||
expect(scopeDestroyListener).toHaveBeenCalled();
|
expect(scopeDestroyListener).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
@ -2795,7 +2795,7 @@ export function main() {
|
|||||||
// (Should not propagate upwards.)
|
// (Should not propagate upwards.)
|
||||||
ng2ComponentBInstance.ng2BInputA = 'foo2';
|
ng2ComponentBInstance.ng2BInputA = 'foo2';
|
||||||
ng2ComponentBInstance.ng2BInputC = 'baz2';
|
ng2ComponentBInstance.ng2BInputC = 'baz2';
|
||||||
digest(adapter);
|
$digest(adapter);
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
expect(multiTrim(document.body.textContent))
|
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
|
// (Should propagate all the way up to `ng1ADataC` and back all the way down to
|
||||||
// `ng2BInputC`.)
|
// `ng2BInputC`.)
|
||||||
ng2ComponentBInstance.ng2BOutputC.emit('baz3');
|
ng2ComponentBInstance.ng2BOutputC.emit('baz3');
|
||||||
digest(adapter);
|
$digest(adapter);
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
expect(multiTrim(document.body.textContent))
|
expect(multiTrim(document.body.textContent))
|
||||||
@ -2815,7 +2815,7 @@ export function main() {
|
|||||||
// (Should not propagate upwards, only downwards.)
|
// (Should not propagate upwards, only downwards.)
|
||||||
ng1ControllerXInstance.ng1XInputA = 'foo4';
|
ng1ControllerXInstance.ng1XInputA = 'foo4';
|
||||||
ng1ControllerXInstance.ng1XInputB = {value: 'bar4'};
|
ng1ControllerXInstance.ng1XInputB = {value: 'bar4'};
|
||||||
digest(adapter);
|
$digest(adapter);
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
expect(multiTrim(document.body.textContent))
|
expect(multiTrim(document.body.textContent))
|
||||||
@ -2824,7 +2824,7 @@ export function main() {
|
|||||||
// Update `ng1XInputC`.
|
// Update `ng1XInputC`.
|
||||||
// (Should propagate upwards and downwards.)
|
// (Should propagate upwards and downwards.)
|
||||||
ng1ControllerXInstance.ng1XInputC = {value: 'baz5'};
|
ng1ControllerXInstance.ng1XInputC = {value: 'baz5'};
|
||||||
digest(adapter);
|
$digest(adapter);
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
expect(multiTrim(document.body.textContent))
|
expect(multiTrim(document.body.textContent))
|
||||||
@ -2833,7 +2833,7 @@ export function main() {
|
|||||||
// Update a property on `ng1XInputC`.
|
// Update a property on `ng1XInputC`.
|
||||||
// (Should propagate upwards and downwards.)
|
// (Should propagate upwards and downwards.)
|
||||||
ng1ControllerXInstance.ng1XInputC.value = 'baz6';
|
ng1ControllerXInstance.ng1XInputC.value = 'baz6';
|
||||||
digest(adapter);
|
$digest(adapter);
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
expect(multiTrim(document.body.textContent))
|
expect(multiTrim(document.body.textContent))
|
||||||
@ -2842,7 +2842,7 @@ export function main() {
|
|||||||
// Emit from `ng1XOutputA`.
|
// Emit from `ng1XOutputA`.
|
||||||
// (Should propagate upwards to `ng1ADataA` and back all the way down to `ng2BInputA`.)
|
// (Should propagate upwards to `ng1ADataA` and back all the way down to `ng2BInputA`.)
|
||||||
(ng1ControllerXInstance as any).ng1XOutputA({value: 'foo7'});
|
(ng1ControllerXInstance as any).ng1XOutputA({value: 'foo7'});
|
||||||
digest(adapter);
|
$digest(adapter);
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
expect(multiTrim(document.body.textContent))
|
expect(multiTrim(document.body.textContent))
|
||||||
@ -2852,7 +2852,7 @@ export function main() {
|
|||||||
// (Should propagate upwards to `ng1ADataB`, but not downwards,
|
// (Should propagate upwards to `ng1ADataB`, but not downwards,
|
||||||
// since `ng1XInputB` has been re-assigned (i.e. `ng2ADataB !== ng1XInputB`).)
|
// since `ng1XInputB` has been re-assigned (i.e. `ng2ADataB !== ng1XInputB`).)
|
||||||
(ng1ControllerXInstance as any).ng1XOutputB('bar8');
|
(ng1ControllerXInstance as any).ng1XOutputB('bar8');
|
||||||
digest(adapter);
|
$digest(adapter);
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
expect(multiTrim(document.body.textContent))
|
expect(multiTrim(document.body.textContent))
|
||||||
@ -2863,7 +2863,7 @@ export function main() {
|
|||||||
ng2ComponentAInstance.ng2ADataA = {value: 'foo9'};
|
ng2ComponentAInstance.ng2ADataA = {value: 'foo9'};
|
||||||
ng2ComponentAInstance.ng2ADataB = {value: 'bar9'};
|
ng2ComponentAInstance.ng2ADataB = {value: 'bar9'};
|
||||||
ng2ComponentAInstance.ng2ADataC = {value: 'baz9'};
|
ng2ComponentAInstance.ng2ADataC = {value: 'baz9'};
|
||||||
digest(adapter);
|
$digest(adapter);
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
expect(multiTrim(document.body.textContent))
|
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 {
|
export function html(html: string): Element {
|
||||||
// Don't return `body` itself, because using it as a `$rootElement` for ng1
|
// Don't return `body` itself, because using it as a `$rootElement` for ng1
|
||||||
// will attach `$injector` to it and that will affect subsequent tests.
|
// 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 {
|
export function multiTrim(text: string): string {
|
||||||
return text.replace(/\n/g, '').replace(/\s\s+/g, ' ').trim();
|
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();
|
||||||
|
}
|
||||||
|
@ -291,9 +291,11 @@ export function main() {
|
|||||||
|
|
||||||
it('should bind properties, events', async(() => {
|
it('should bind properties, events', async(() => {
|
||||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
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) => {
|
ng1Module.run(($rootScope: any) => {
|
||||||
|
$rootScope.name = 'world';
|
||||||
$rootScope.dataA = 'A';
|
$rootScope.dataA = 'A';
|
||||||
$rootScope.dataB = 'B';
|
$rootScope.dataB = 'B';
|
||||||
$rootScope.modelA = 'initModelA';
|
$rootScope.modelA = 'initModelA';
|
||||||
@ -364,9 +366,10 @@ export function main() {
|
|||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
assertChange('twoWayA', 'newA');
|
assertChange('twoWayA', 'newA');
|
||||||
|
assertChange('twoWayB', 'newB');
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
assertChange('twoWayB', 'newB');
|
assertChange('interpolate', 'Hello everyone');
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error('Called too many times! ' + JSON.stringify(changes));
|
throw new Error('Called too many times! ' + JSON.stringify(changes));
|
||||||
@ -381,7 +384,7 @@ export function main() {
|
|||||||
}).Class({constructor: function() {}});
|
}).Class({constructor: function() {}});
|
||||||
|
|
||||||
const element = html(`<div>
|
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"
|
bind-one-way-a="dataA" [one-way-b]="dataB"
|
||||||
bindon-two-way-a="modelA" [(two-way-b)]="modelB"
|
bindon-two-way-a="modelA" [(two-way-b)]="modelB"
|
||||||
on-event-a='eventA=$event' (event-b)="eventB=$event"></ng2>
|
on-event-a='eventA=$event' (event-b)="eventB=$event"></ng2>
|
||||||
@ -394,6 +397,15 @@ export function main() {
|
|||||||
'literal: Text; interpolate: Hello world; ' +
|
'literal: Text; interpolate: Hello world; ' +
|
||||||
'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (2) | ' +
|
'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (2) | ' +
|
||||||
'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;');
|
'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();
|
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(() => {
|
it('should allow attribute selectors for components in ng2', async(() => {
|
||||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => MyNg2Module));
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => MyNg2Module));
|
||||||
const ng1Module = angular.module('myExample', []);
|
const ng1Module = angular.module('myExample', []);
|
||||||
@ -1901,14 +1946,17 @@ function multiTrim(text: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function html(html: string): Element {
|
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;
|
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) {
|
if (div.childNodes.length === 1 && div.firstChild instanceof HTMLElement) {
|
||||||
return <Element>body.firstChild;
|
return div.firstChild;
|
||||||
}
|
}
|
||||||
|
|
||||||
return body;
|
return div;
|
||||||
}
|
}
|
||||||
|
|
||||||
function nodes(html: string) {
|
function nodes(html: string) {
|
||||||
|
@ -25,7 +25,9 @@ export const ROUTES = [
|
|||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [WorkerAppModule, RouterModule.forRoot(ROUTES, {useHash: true})],
|
imports: [WorkerAppModule, RouterModule.forRoot(ROUTES, {useHash: true})],
|
||||||
providers: [WORKER_APP_LOCATION_PROVIDERS],
|
providers: [
|
||||||
|
WORKER_APP_LOCATION_PROVIDERS,
|
||||||
|
],
|
||||||
bootstrap: [App],
|
bootstrap: [App],
|
||||||
declarations: [App, Start, Contact, About]
|
declarations: [App, Start, Contact, About]
|
||||||
})
|
})
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "angular-srcs",
|
"name": "angular-srcs",
|
||||||
"version": "2.4.7",
|
"version": "2.4.8",
|
||||||
"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",
|
||||||
|
@ -2,9 +2,10 @@
|
|||||||
|
|
||||||
set -e -o pipefail
|
set -e -o pipefail
|
||||||
|
|
||||||
|
echo 'travis_fold:start:ENV'
|
||||||
|
|
||||||
NODE_VERSION=5.4.1
|
NODE_VERSION=6.9.5
|
||||||
NPM_VERSION=3.5.3
|
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
|
CHROMIUM_VERSION=403382 # Chrome 53 linux stable, see https://www.chromium.org/developers/calendar
|
||||||
SAUCE_CONNECT_VERSION=4.3.11
|
SAUCE_CONNECT_VERSION=4.3.11
|
||||||
|
|
||||||
@ -74,3 +75,4 @@ if [[ ${TRAVIS} ]]; then
|
|||||||
export CHROME_BIN=${HOME}/.chrome/chromium/chrome-linux/chrome
|
export CHROME_BIN=${HOME}/.chrome/chromium/chrome-linux/chrome
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo 'travis_fold:end:ENV'
|
||||||
|
@ -2,6 +2,29 @@
|
|||||||
set -e -x
|
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 {
|
function publishRepo {
|
||||||
COMPONENT=$1
|
COMPONENT=$1
|
||||||
ARTIFACTS_DIR=$2
|
ARTIFACTS_DIR=$2
|
||||||
@ -88,7 +111,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`
|
LATEST_TAG=`getLatestTag`
|
||||||
|
|
||||||
publishRepo "${COMPONENT}" "${JS_BUILD_ARTIFACTS_DIR}"
|
publishRepo "${COMPONENT}" "${JS_BUILD_ARTIFACTS_DIR}"
|
||||||
done
|
done
|
||||||
|
3
tools/public_api_guard/common/index.d.ts
vendored
3
tools/public_api_guard/common/index.d.ts
vendored
@ -80,6 +80,9 @@ export declare class Location {
|
|||||||
static stripTrailingSlash(url: string): string;
|
static stripTrailingSlash(url: string): string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @experimental */
|
||||||
|
export declare const LOCATION_INITIALIZED: OpaqueToken;
|
||||||
|
|
||||||
/** @experimental */
|
/** @experimental */
|
||||||
export interface LocationChangeEvent {
|
export interface LocationChangeEvent {
|
||||||
type: string;
|
type: string;
|
||||||
|
@ -89,6 +89,10 @@ export declare const WORKER_APP_LOCATION_PROVIDERS: ({
|
|||||||
useFactory: (platformLocation: WebWorkerPlatformLocation, zone: NgZone) => () => Promise<boolean>;
|
useFactory: (platformLocation: WebWorkerPlatformLocation, zone: NgZone) => () => Promise<boolean>;
|
||||||
multi: boolean;
|
multi: boolean;
|
||||||
deps: (typeof NgZone | typeof PlatformLocation)[];
|
deps: (typeof NgZone | typeof PlatformLocation)[];
|
||||||
|
} | {
|
||||||
|
provide: OpaqueToken;
|
||||||
|
useFactory: (platformLocation: WebWorkerPlatformLocation) => Promise<any>;
|
||||||
|
deps: typeof PlatformLocation[];
|
||||||
})[];
|
})[];
|
||||||
|
|
||||||
/** @experimental */
|
/** @experimental */
|
||||||
|
@ -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
|
# create package.json so that npm install doesn't pollute any parent node_modules's directory
|
||||||
npm init --yes
|
npm init --yes
|
||||||
npm install ${LINKABLE_PKGS[*]}
|
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 install typescript@1.8.10
|
||||||
$(npm bin)/tsc --version
|
$(npm bin)/tsc --version
|
||||||
$(npm bin)/tsc -p tsconfig.json
|
$(npm bin)/tsc -p tsconfig.json
|
||||||
|
Reference in New Issue
Block a user