Compare commits
142 Commits
5.0.1
...
5.1.0-beta
Author | SHA1 | Date | |
---|---|---|---|
78ba39bfe2 | |||
119034c642 | |||
6e8e3bd248 | |||
a460066972 | |||
05d96dc507 | |||
b489259a34 | |||
6b748835be | |||
d30ea61f0d | |||
0c47ea704e | |||
049c89645b | |||
bf22f2df88 | |||
880201681f | |||
63d26a1777 | |||
8b50ed083c | |||
3997d97806 | |||
200d92d030 | |||
dbec3ca716 | |||
f7c9b941cb | |||
f0764016f9 | |||
a99eb16320 | |||
e36bac9e90 | |||
196ce6d475 | |||
faa621218e | |||
169cedd43b | |||
567cc26b8e | |||
1d19d61970 | |||
03f080b7da | |||
26f82995f6 | |||
f1da1419fa | |||
5079d2d37c | |||
c7fd172ba7 | |||
dcf8840831 | |||
60c0b178af | |||
0899f4f8fc | |||
aed4a11d01 | |||
75cf70ae04 | |||
6b30fbf94e | |||
24f17f913a | |||
ebfa204af0 | |||
a28d616e10 | |||
613a9e3672 | |||
65d57a07e0 | |||
22946cfd40 | |||
9975486954 | |||
068348e9b1 | |||
1beab0da6a | |||
3a03ff6b2d | |||
feae55b264 | |||
0355142737 | |||
5b16ce9302 | |||
17ed14faea | |||
d156e72ad7 | |||
7186c9c839 | |||
a41558eb30 | |||
a91252a90c | |||
132c0719dc | |||
3db7112b89 | |||
2ea76cce31 | |||
b8bb2dd0f5 | |||
27ae0f9475 | |||
171dceb010 | |||
1ebc0d1e33 | |||
1d8e0758fa | |||
cd55643f85 | |||
486b8e6f69 | |||
215d373ebd | |||
4038b42396 | |||
9ab1f4a9c9 | |||
e9afc59a81 | |||
83c826c3f9 | |||
abfc41d661 | |||
11fd7eaf63 | |||
54eba606cb | |||
3337865913 | |||
f24397c5d0 | |||
9d52bf27de | |||
19fbfbc371 | |||
6578b30b77 | |||
10c1c2ba5a | |||
e18bfd1f3d | |||
3c2ddbe9ab | |||
a149605c9f | |||
b396029d39 | |||
040b376052 | |||
175c872514 | |||
71291aa2c0 | |||
415e75716a | |||
3216abee2e | |||
327ad02a28 | |||
1df0c9e1b0 | |||
280dadaaad | |||
c30eff898a | |||
ca129ba549 | |||
171ae154c2 | |||
4426c0f14e | |||
901436e46f | |||
6fbc2b3be0 | |||
93d23ddcc8 | |||
548a809a05 | |||
a161b4ab6d | |||
60b91656cd | |||
1858d99559 | |||
3a8665409d | |||
005a78bd83 | |||
7553ce9dfe | |||
42bfe4477f | |||
3bdbb18c8b | |||
13f8648a00 | |||
b6abcb2500 | |||
f1a9e1e361 | |||
54480f7dfc | |||
951bd33b09 | |||
bf57df3e04 | |||
420852e2f5 | |||
04eb80cc2b | |||
308fc8e328 | |||
9bb2939d68 | |||
2f63899be2 | |||
22c66f0e02 | |||
00bc80bb37 | |||
394d951883 | |||
98b26381f6 | |||
31797d3b50 | |||
957be960d2 | |||
18e9d86a3b | |||
a869aeecd2 | |||
eca822b756 | |||
17142a778a | |||
5adb7c9669 | |||
703fcda590 | |||
f16a7cd7e3 | |||
fde5f2fa14 | |||
d56724659f | |||
56b18ff063 | |||
7bfeac746e | |||
c92efc15fb | |||
b51d57deb8 | |||
c357b40dca | |||
a0ae120093 | |||
d3211a2468 | |||
adab4f3e49 | |||
82fed62af2 |
22
CHANGELOG.md
22
CHANGELOG.md
@ -1,3 +1,25 @@
|
||||
<a name="5.1.0-beta.0"></a>
|
||||
# [5.1.0-beta.0](https://github.com/angular/angular/compare/5.0.0-rc.4...5.1.0-beta.0) (2017-11-08)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler:** don't overwrite missingTranslation's value in JIT ([#19952](https://github.com/angular/angular/issues/19952)) ([799cbb9](https://github.com/angular/angular/commit/799cbb9))
|
||||
* **compiler:** report a reasonable error with invalid metadata ([#20062](https://github.com/angular/angular/issues/20062)) ([da22c48](https://github.com/angular/angular/commit/da22c48))
|
||||
* **compiler-cli:** don't report emit diagnostics when `--noEmitOnError` is off ([#20063](https://github.com/angular/angular/issues/20063)) ([8639995](https://github.com/angular/angular/commit/8639995))
|
||||
* **core:** `__symbol__` should return `__zone_symbol__` without zone.js loaded ([#19541](https://github.com/angular/angular/issues/19541)) ([678d1cf](https://github.com/angular/angular/commit/678d1cf))
|
||||
* **core:** should support event.stopImmediatePropagation ([#19222](https://github.com/angular/angular/issues/19222)) ([7083791](https://github.com/angular/angular/commit/7083791))
|
||||
* **platform-browser:** support Symbols in custom `jasmineToString()` method ([#19794](https://github.com/angular/angular/issues/19794)) ([5a6efa7](https://github.com/angular/angular/commit/5a6efa7))
|
||||
|
||||
### Features
|
||||
|
||||
* **compiler:** introduce `TestBed.overrideTemplateUsingTestingModule` ([a460066](https://github.com/angular/angular/commit/a460066)), closes [#19815](https://github.com/angular/angular/issues/19815)
|
||||
|
||||
### Reverts
|
||||
|
||||
* feat(elements): implement `[@angular](https://github.com/angular)/elements` [#19469](https://github.com/angular/angular/issues/19469) ([#20152](https://github.com/angular/angular/issues/20152)) ([3997d97](https://github.com/angular/angular/commit/3997d97))
|
||||
|
||||
|
||||
|
||||
<a name="5.0.1"></a>
|
||||
## [5.0.1](https://github.com/angular/angular/compare/5.0.0...5.0.1) (2017-11-08)
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "angular-srcs",
|
||||
"version": "5.0.1",
|
||||
"version": "5.1.0-beta.0",
|
||||
"private": true,
|
||||
"branchPattern": "2.0.*",
|
||||
"description": "Angular - a web framework for modern web apps",
|
||||
|
@ -81,7 +81,9 @@ export class Runner {
|
||||
{provide: WebDriverAdapter, useValue: adapter}
|
||||
]);
|
||||
|
||||
const sampler = injector.get(Sampler);
|
||||
// TODO: With TypeScript 2.5 injector.get does not infer correctly the
|
||||
// return type. Remove 'any' and investigate the issue.
|
||||
const sampler = injector.get(Sampler) as any;
|
||||
return sampler.sample();
|
||||
});
|
||||
}
|
||||
|
@ -41,7 +41,9 @@ export class SpyLocation implements Location {
|
||||
return currPath == givenPath + (query.length > 0 ? ('?' + query) : '');
|
||||
}
|
||||
|
||||
simulateUrlPop(pathname: string) { this._subject.emit({'url': pathname, 'pop': true}); }
|
||||
simulateUrlPop(pathname: string) {
|
||||
this._subject.emit({'url': pathname, 'pop': true, 'type': 'popstate'});
|
||||
}
|
||||
|
||||
simulateHashChange(pathname: string) {
|
||||
// Because we don't prevent the native event, the browser will independently update the path
|
||||
|
@ -116,8 +116,8 @@ export class MetadataCollector {
|
||||
function classMetadataOf(classDeclaration: ts.ClassDeclaration): ClassMetadata {
|
||||
const result: ClassMetadata = {__symbolic: 'class'};
|
||||
|
||||
function getDecorators(decorators: ts.Decorator[] | undefined): MetadataSymbolicExpression[]|
|
||||
undefined {
|
||||
function getDecorators(decorators: ReadonlyArray<ts.Decorator>| undefined):
|
||||
MetadataSymbolicExpression[]|undefined {
|
||||
if (decorators && decorators.length)
|
||||
return decorators.map(decorator => objFromDecorator(decorator));
|
||||
return undefined;
|
||||
|
@ -606,7 +606,8 @@ class AngularCompilerProgram implements Program {
|
||||
|
||||
private writeFile(
|
||||
outFileName: string, outData: string, writeByteOrderMark: boolean,
|
||||
onError?: (message: string) => void, genFile?: GeneratedFile, sourceFiles?: ts.SourceFile[]) {
|
||||
onError?: (message: string) => void, genFile?: GeneratedFile,
|
||||
sourceFiles?: ReadonlyArray<ts.SourceFile>) {
|
||||
// collect emittedLibrarySummaries
|
||||
let baseFile: ts.SourceFile|undefined;
|
||||
if (genFile) {
|
||||
@ -653,7 +654,8 @@ class AngularCompilerProgram implements Program {
|
||||
if (baseFile) {
|
||||
sourceFiles = sourceFiles ? [...sourceFiles, baseFile] : [baseFile];
|
||||
}
|
||||
this.host.writeFile(outFileName, outData, writeByteOrderMark, onError, sourceFiles);
|
||||
// TODO: remove any when TS 2.4 support is removed.
|
||||
this.host.writeFile(outFileName, outData, writeByteOrderMark, onError, sourceFiles as any);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,6 +42,7 @@ export class JitCompiler {
|
||||
private _compiledDirectiveWrapperCache = new Map<Type, Type>();
|
||||
private _compiledNgModuleCache = new Map<Type, object>();
|
||||
private _sharedStylesheetCount = 0;
|
||||
private _addedAotSummaries = new Set<() => any[]>();
|
||||
|
||||
constructor(
|
||||
private _metadataResolver: CompileMetadataResolver, private _templateParser: TemplateParser,
|
||||
@ -74,10 +75,25 @@ export class JitCompiler {
|
||||
|
||||
loadAotSummaries(summaries: () => any[]) {
|
||||
this.clearCache();
|
||||
flattenSummaries(summaries).forEach((summary) => {
|
||||
this._summaryResolver.addSummary(
|
||||
{symbol: summary.type.reference, metadata: null, type: summary});
|
||||
});
|
||||
this._addAotSummaries(summaries);
|
||||
}
|
||||
|
||||
private _addAotSummaries(fn: () => any[]) {
|
||||
if (this._addedAotSummaries.has(fn)) {
|
||||
return;
|
||||
}
|
||||
this._addedAotSummaries.add(fn);
|
||||
const summaries = fn();
|
||||
for (let i = 0; i < summaries.length; i++) {
|
||||
const entry = summaries[i];
|
||||
if (typeof entry === 'function') {
|
||||
this._addAotSummaries(entry);
|
||||
} else {
|
||||
const summary = entry as CompileTypeSummary;
|
||||
this._summaryResolver.addSummary(
|
||||
{symbol: summary.type.reference, metadata: null, type: summary});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hasAotSummary(ref: Type) { return !!this._summaryResolver.resolveSummary(ref); }
|
||||
@ -200,6 +216,7 @@ export class JitCompiler {
|
||||
}
|
||||
|
||||
clearCache(): void {
|
||||
// Note: don't clear the _addedAotSummaries, as they don't change!
|
||||
this._metadataResolver.clearCache();
|
||||
this._compiledTemplateCache.clear();
|
||||
this._compiledHostTemplateCache.clear();
|
||||
@ -335,25 +352,6 @@ function assertComponent(meta: CompileDirectiveMetadata) {
|
||||
}
|
||||
}
|
||||
|
||||
function flattenSummaries(
|
||||
fn: () => any[], out: CompileTypeSummary[] = [],
|
||||
seen = new Set<() => any[]>()): CompileTypeSummary[] {
|
||||
if (seen.has(fn)) {
|
||||
return out;
|
||||
}
|
||||
seen.add(fn);
|
||||
const summaries = fn();
|
||||
for (let i = 0; i < summaries.length; i++) {
|
||||
const entry = summaries[i];
|
||||
if (typeof entry === 'function') {
|
||||
flattenSummaries(entry, out, seen);
|
||||
} else {
|
||||
out.push(entry);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function createOutputContext(): OutputContext {
|
||||
const importExpr = (symbol: any) =>
|
||||
ir.importExpr({name: identifierName(symbol), moduleName: null, runtime: symbol});
|
||||
|
@ -20,5 +20,5 @@ export {DirectRenderer as ɵDirectRenderer, RenderDebugInfo as ɵRenderDebugInfo
|
||||
export {global as ɵglobal, looseIdentical as ɵlooseIdentical, stringify as ɵstringify} from './util';
|
||||
export {makeDecorator as ɵmakeDecorator} from './util/decorators';
|
||||
export {isObservable as ɵisObservable, isPromise as ɵisPromise} from './util/lang';
|
||||
export {clearProviderOverrides as ɵclearProviderOverrides, overrideProvider as ɵoverrideProvider} from './view/index';
|
||||
export {clearOverrides as ɵclearOverrides, overrideComponentView as ɵoverrideComponentView, overrideProvider as ɵoverrideProvider} from './view/index';
|
||||
export {NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR as ɵNOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR} from './view/provider';
|
||||
|
@ -7,11 +7,12 @@
|
||||
*/
|
||||
|
||||
import {Injector} from '../di/injector';
|
||||
import {ComponentFactory} from '../linker/component_factory';
|
||||
import {NgModuleFactory, NgModuleRef} from '../linker/ng_module_factory';
|
||||
import {Type} from '../type';
|
||||
|
||||
import {initServicesIfNeeded} from './services';
|
||||
import {NgModuleDefinitionFactory, ProviderOverride, Services} from './types';
|
||||
import {NgModuleDefinitionFactory, ProviderOverride, Services, ViewDefinition} from './types';
|
||||
import {resolveDefinition} from './util';
|
||||
|
||||
export function overrideProvider(override: ProviderOverride) {
|
||||
@ -19,9 +20,14 @@ export function overrideProvider(override: ProviderOverride) {
|
||||
return Services.overrideProvider(override);
|
||||
}
|
||||
|
||||
export function clearProviderOverrides() {
|
||||
export function overrideComponentView(comp: Type<any>, componentFactory: ComponentFactory<any>) {
|
||||
initServicesIfNeeded();
|
||||
return Services.clearProviderOverrides();
|
||||
return Services.overrideComponentView(comp, componentFactory);
|
||||
}
|
||||
|
||||
export function clearOverrides() {
|
||||
initServicesIfNeeded();
|
||||
return Services.clearOverrides();
|
||||
}
|
||||
|
||||
// Attention: this function is called as top level function.
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
export {anchorDef, elementDef} from './element';
|
||||
export {clearProviderOverrides, createNgModuleFactory, overrideProvider} from './entrypoint';
|
||||
export {clearOverrides, createNgModuleFactory, overrideComponentView, overrideProvider} from './entrypoint';
|
||||
export {ngContentDef} from './ng_content';
|
||||
export {moduleDef, moduleProvideDef} from './ng_module';
|
||||
export {directiveDef, pipeDef, providerDef} from './provider';
|
||||
|
@ -10,6 +10,7 @@ import {isDevMode} from '../application_ref';
|
||||
import {DebugElement, DebugNode, EventListener, getDebugNode, indexDebugNode, removeDebugNodeFromIndex} from '../debug/debug_node';
|
||||
import {Injector} from '../di';
|
||||
import {ErrorHandler} from '../error_handler';
|
||||
import {ComponentFactory} from '../linker/component_factory';
|
||||
import {NgModuleRef} from '../linker/ng_module_factory';
|
||||
import {Renderer2, RendererFactory2, RendererStyleFlags2, RendererType2} from '../render/api';
|
||||
import {Sanitizer} from '../security';
|
||||
@ -18,9 +19,9 @@ import {Type} from '../type';
|
||||
import {isViewDebugError, viewDestroyedError, viewWrappedDebugError} from './errors';
|
||||
import {resolveDep} from './provider';
|
||||
import {dirtyParentQueries, getQueryValue} from './query';
|
||||
import {createInjector, createNgModuleRef} from './refs';
|
||||
import {createInjector, createNgModuleRef, getComponentViewDefinitionFactory} from './refs';
|
||||
import {ArgumentType, BindingFlags, CheckType, DebugContext, DepDef, ElementData, NgModuleDefinition, NgModuleProviderDef, NodeDef, NodeFlags, NodeLogger, ProviderOverride, RootData, Services, ViewData, ViewDefinition, ViewState, asElementData, asPureExpressionData} from './types';
|
||||
import {NOOP, isComponentView, renderNode, splitDepsDsl, viewParentEl} from './util';
|
||||
import {NOOP, isComponentView, renderNode, resolveDefinition, splitDepsDsl, viewParentEl} from './util';
|
||||
import {checkAndUpdateNode, checkAndUpdateView, checkNoChangesNode, checkNoChangesView, createComponentView, createEmbeddedView, createRootView, destroyView} from './view';
|
||||
|
||||
|
||||
@ -38,7 +39,8 @@ export function initServicesIfNeeded() {
|
||||
Services.createComponentView = services.createComponentView;
|
||||
Services.createNgModuleRef = services.createNgModuleRef;
|
||||
Services.overrideProvider = services.overrideProvider;
|
||||
Services.clearProviderOverrides = services.clearProviderOverrides;
|
||||
Services.overrideComponentView = services.overrideComponentView;
|
||||
Services.clearOverrides = services.clearOverrides;
|
||||
Services.checkAndUpdateView = services.checkAndUpdateView;
|
||||
Services.checkNoChangesView = services.checkNoChangesView;
|
||||
Services.destroyView = services.destroyView;
|
||||
@ -58,7 +60,8 @@ function createProdServices() {
|
||||
createComponentView: createComponentView,
|
||||
createNgModuleRef: createNgModuleRef,
|
||||
overrideProvider: NOOP,
|
||||
clearProviderOverrides: NOOP,
|
||||
overrideComponentView: NOOP,
|
||||
clearOverrides: NOOP,
|
||||
checkAndUpdateView: checkAndUpdateView,
|
||||
checkNoChangesView: checkNoChangesView,
|
||||
destroyView: destroyView,
|
||||
@ -84,7 +87,8 @@ function createDebugServices() {
|
||||
createComponentView: debugCreateComponentView,
|
||||
createNgModuleRef: debugCreateNgModuleRef,
|
||||
overrideProvider: debugOverrideProvider,
|
||||
clearProviderOverrides: debugClearProviderOverrides,
|
||||
overrideComponentView: debugOverrideComponentView,
|
||||
clearOverrides: debugClearOverrides,
|
||||
checkAndUpdateView: debugCheckAndUpdateView,
|
||||
checkNoChangesView: debugCheckNoChangesView,
|
||||
destroyView: debugDestroyView,
|
||||
@ -139,10 +143,15 @@ function debugCreateEmbeddedView(
|
||||
|
||||
function debugCreateComponentView(
|
||||
parentView: ViewData, nodeDef: NodeDef, viewDef: ViewDefinition, hostElement: any): ViewData {
|
||||
const defWithOverride = applyProviderOverridesToView(viewDef);
|
||||
const overrideComponentView =
|
||||
viewDefOverrides.get(nodeDef.element !.componentProvider !.provider !.token);
|
||||
if (overrideComponentView) {
|
||||
viewDef = overrideComponentView;
|
||||
} else {
|
||||
viewDef = applyProviderOverridesToView(viewDef);
|
||||
}
|
||||
return callWithDebugContext(
|
||||
DebugAction.create, createComponentView, null,
|
||||
[parentView, nodeDef, defWithOverride, hostElement]);
|
||||
DebugAction.create, createComponentView, null, [parentView, nodeDef, viewDef, hostElement]);
|
||||
}
|
||||
|
||||
function debugCreateNgModuleRef(
|
||||
@ -153,13 +162,21 @@ function debugCreateNgModuleRef(
|
||||
}
|
||||
|
||||
const providerOverrides = new Map<any, ProviderOverride>();
|
||||
const viewDefOverrides = new Map<any, ViewDefinition>();
|
||||
|
||||
function debugOverrideProvider(override: ProviderOverride) {
|
||||
providerOverrides.set(override.token, override);
|
||||
}
|
||||
|
||||
function debugClearProviderOverrides() {
|
||||
function debugOverrideComponentView(comp: any, compFactory: ComponentFactory<any>) {
|
||||
const hostViewDef = resolveDefinition(getComponentViewDefinitionFactory(compFactory));
|
||||
const compViewDef = resolveDefinition(hostViewDef.nodes[0].element !.componentView !);
|
||||
viewDefOverrides.set(comp, compViewDef);
|
||||
}
|
||||
|
||||
function debugClearOverrides() {
|
||||
providerOverrides.clear();
|
||||
viewDefOverrides.clear();
|
||||
}
|
||||
|
||||
// Notes about the algorithm:
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
import {Injector} from '../di';
|
||||
import {ErrorHandler} from '../error_handler';
|
||||
import {ComponentFactory} from '../linker/component_factory';
|
||||
import {NgModuleRef} from '../linker/ng_module_factory';
|
||||
import {QueryList} from '../linker/query_list';
|
||||
import {TemplateRef} from '../linker/template_ref';
|
||||
@ -16,6 +17,7 @@ import {Renderer2, RendererFactory2, RendererType2} from '../render/api';
|
||||
import {Sanitizer, SecurityContext} from '../security';
|
||||
import {Type} from '../type';
|
||||
|
||||
|
||||
// -------------------------------------
|
||||
// Defs
|
||||
// -------------------------------------
|
||||
@ -522,7 +524,8 @@ export interface Services {
|
||||
moduleType: Type<any>, parent: Injector, bootstrapComponents: Type<any>[],
|
||||
def: NgModuleDefinition): NgModuleRef<any>;
|
||||
overrideProvider(override: ProviderOverride): void;
|
||||
clearProviderOverrides(): void;
|
||||
overrideComponentView(compType: Type<any>, compFactory: ComponentFactory<any>): void;
|
||||
clearOverrides(): void;
|
||||
checkAndUpdateView(view: ViewData): void;
|
||||
checkNoChangesView(view: ViewData): void;
|
||||
destroyView(view: ViewData): void;
|
||||
@ -547,7 +550,8 @@ export const Services: Services = {
|
||||
createComponentView: undefined !,
|
||||
createNgModuleRef: undefined !,
|
||||
overrideProvider: undefined !,
|
||||
clearProviderOverrides: undefined !,
|
||||
overrideComponentView: undefined !,
|
||||
clearOverrides: undefined !,
|
||||
checkAndUpdateView: undefined !,
|
||||
checkNoChangesView: undefined !,
|
||||
destroyView: undefined !,
|
||||
|
@ -11,10 +11,12 @@ import {CompileMetadataResolver} from '@angular/compiler/src/metadata_resolver';
|
||||
import {MockResourceLoader} from '@angular/compiler/testing/src/resource_loader_mock';
|
||||
import {Component, Directive, Injectable, NgModule, Pipe, Type} from '@angular/core';
|
||||
import {TestBed, async, getTestBed} from '@angular/core/testing';
|
||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
|
||||
export function main() {
|
||||
describe('Jit Summaries', () => {
|
||||
let instances: Map<any, Base>;
|
||||
let summaries: () => any[];
|
||||
|
||||
class SomeDep {}
|
||||
|
||||
@ -69,7 +71,7 @@ export function main() {
|
||||
TestBed.configureCompiler({providers: [{provide: ResourceLoader, useValue: resourceLoader}]});
|
||||
TestBed.configureTestingModule({imports: [SomeModule], providers: [SomeDep]});
|
||||
|
||||
TestBed.compileComponents().then(() => {
|
||||
let summariesPromise = TestBed.compileComponents().then(() => {
|
||||
const metadataResolver = TestBed.get(CompileMetadataResolver) as CompileMetadataResolver;
|
||||
const summaries = [
|
||||
metadataResolver.getNgModuleSummary(SomeModule),
|
||||
@ -83,10 +85,12 @@ export function main() {
|
||||
metadataResolver.getInjectableSummary(SomeService)
|
||||
];
|
||||
clearMetadata();
|
||||
resetTestEnvironmentWithSummaries(() => summaries);
|
||||
TestBed.resetTestingModule();
|
||||
return () => summaries;
|
||||
});
|
||||
|
||||
resourceLoader.flush();
|
||||
return summariesPromise;
|
||||
}
|
||||
|
||||
function setMetadata(resourceLoader: MockResourceLoader) {
|
||||
@ -123,12 +127,14 @@ export function main() {
|
||||
|
||||
beforeEach(async(() => {
|
||||
instances = new Map<any, any>();
|
||||
createSummaries();
|
||||
createSummaries().then(s => summaries = s);
|
||||
}));
|
||||
|
||||
afterEach(() => { resetTestEnvironmentWithSummaries(); });
|
||||
|
||||
it('should use directive metadata from summaries', () => {
|
||||
resetTestEnvironmentWithSummaries(summaries);
|
||||
|
||||
@Component({template: '<div someDir></div>'})
|
||||
class TestComp {
|
||||
}
|
||||
@ -140,6 +146,8 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should use pipe metadata from summaries', () => {
|
||||
resetTestEnvironmentWithSummaries(summaries);
|
||||
|
||||
@Component({template: '{{1 | somePipe}}'})
|
||||
class TestComp {
|
||||
}
|
||||
@ -150,6 +158,8 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should use Service metadata from summaries', () => {
|
||||
resetTestEnvironmentWithSummaries(summaries);
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [SomeService, SomeDep],
|
||||
});
|
||||
@ -158,6 +168,8 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should use NgModule metadata from summaries', () => {
|
||||
resetTestEnvironmentWithSummaries(summaries);
|
||||
|
||||
TestBed
|
||||
.configureTestingModule(
|
||||
{providers: [SomeDep], declarations: [TestComp3], imports: [SomeModule]})
|
||||
@ -170,12 +182,16 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should allow to create private components from imported NgModule summaries', () => {
|
||||
resetTestEnvironmentWithSummaries(summaries);
|
||||
|
||||
TestBed.configureTestingModule({providers: [SomeDep], imports: [SomeModule]})
|
||||
.createComponent(SomePrivateComponent);
|
||||
expectInstanceCreated(SomePrivateComponent);
|
||||
});
|
||||
|
||||
it('should throw when trying to mock a type with a summary', () => {
|
||||
resetTestEnvironmentWithSummaries(summaries);
|
||||
|
||||
TestBed.resetTestingModule();
|
||||
expect(() => TestBed.overrideComponent(SomePrivateComponent, {add: {}}).compileComponents())
|
||||
.toThrowError(
|
||||
@ -190,5 +206,47 @@ export function main() {
|
||||
expect(() => TestBed.overrideModule(SomeModule, {add: {}}).compileComponents())
|
||||
.toThrowError('SomeModule was AOT compiled, so its metadata cannot be changed.');
|
||||
});
|
||||
|
||||
it('should allow to add summaries via configureTestingModule', () => {
|
||||
resetTestEnvironmentWithSummaries();
|
||||
|
||||
@Component({template: '<div someDir></div>'})
|
||||
class TestComp {
|
||||
}
|
||||
|
||||
TestBed
|
||||
.configureTestingModule({
|
||||
providers: [SomeDep],
|
||||
declarations: [TestComp, SomeDirective],
|
||||
aotSummaries: summaries
|
||||
})
|
||||
.createComponent(TestComp);
|
||||
expectInstanceCreated(SomeDirective);
|
||||
});
|
||||
|
||||
it('should allow to override a provider', () => {
|
||||
resetTestEnvironmentWithSummaries(summaries);
|
||||
|
||||
const overwrittenValue = {};
|
||||
|
||||
const fixture =
|
||||
TestBed.overrideProvider(SomeDep, {useFactory: () => overwrittenValue, deps: []})
|
||||
.configureTestingModule({providers: [SomeDep], imports: [SomeModule]})
|
||||
.createComponent(SomePublicComponent);
|
||||
|
||||
expect(fixture.componentInstance.dep).toBe(overwrittenValue);
|
||||
});
|
||||
|
||||
it('should allow to override a template', () => {
|
||||
resetTestEnvironmentWithSummaries(summaries);
|
||||
|
||||
TestBed.overrideTemplateUsingTestingModule(SomePublicComponent, 'overwritten');
|
||||
|
||||
const fixture = TestBed.configureTestingModule({providers: [SomeDep], imports: [SomeModule]})
|
||||
.createComponent(SomePublicComponent);
|
||||
expectInstanceCreated(SomePublicComponent);
|
||||
|
||||
expect(fixture.nativeElement).toHaveText('overwritten');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ApplicationInitStatus, CompilerOptions, Component, Directive, InjectionToken, Injector, ModuleWithComponentFactories, NgModule, NgModuleFactory, NgModuleRef, NgZone, Optional, Pipe, PlatformRef, Provider, SchemaMetadata, SkipSelf, Type, ɵDepFlags as DepFlags, ɵNodeFlags as NodeFlags, ɵclearProviderOverrides as clearProviderOverrides, ɵoverrideProvider as overrideProvider, ɵstringify as stringify} from '@angular/core';
|
||||
import {ApplicationInitStatus, CompilerOptions, Component, Directive, InjectionToken, Injector, ModuleWithComponentFactories, NgModule, NgModuleFactory, NgModuleRef, NgZone, Optional, Pipe, PlatformRef, Provider, SchemaMetadata, SkipSelf, StaticProvider, Type, ɵDepFlags as DepFlags, ɵNodeFlags as NodeFlags, ɵclearOverrides as clearOverrides, ɵgetComponentViewDefinitionFactory as getComponentViewDefinitionFactory, ɵoverrideComponentView as overrideComponentView, ɵoverrideProvider as overrideProvider, ɵstringify as stringify} from '@angular/core';
|
||||
|
||||
import {AsyncTestCompleter} from './async_test_completer';
|
||||
import {ComponentFixture} from './component_fixture';
|
||||
@ -45,6 +45,7 @@ export type TestModuleMetadata = {
|
||||
declarations?: any[],
|
||||
imports?: any[],
|
||||
schemas?: Array<SchemaMetadata|any[]>,
|
||||
aotSummaries?: () => any[],
|
||||
};
|
||||
|
||||
/**
|
||||
@ -141,15 +142,29 @@ export class TestBed implements Injector {
|
||||
return TestBed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the template of the given component, compiling the template
|
||||
* in the context of the TestingModule.
|
||||
*
|
||||
* Note: This works for JIT and AOTed components as well.
|
||||
*/
|
||||
static overrideTemplateUsingTestingModule(component: Type<any>, template: string):
|
||||
typeof TestBed {
|
||||
getTestBed().overrideTemplateUsingTestingModule(component, template);
|
||||
return TestBed;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Overwrites all providers for the given token with the given provider definition.
|
||||
*
|
||||
* Note: This works for JIT and AOTed components as well.
|
||||
*/
|
||||
static overrideProvider(token: any, provider: {
|
||||
useFactory: Function,
|
||||
deps: any[],
|
||||
}): void;
|
||||
static overrideProvider(token: any, provider: {useValue: any;}): void;
|
||||
}): typeof TestBed;
|
||||
static overrideProvider(token: any, provider: {useValue: any;}): typeof TestBed;
|
||||
static overrideProvider(token: any, provider: {
|
||||
useFactory?: Function,
|
||||
useValue?: any,
|
||||
@ -205,7 +220,9 @@ export class TestBed implements Injector {
|
||||
private _schemas: Array<SchemaMetadata|any[]> = [];
|
||||
private _activeFixtures: ComponentFixture<any>[] = [];
|
||||
|
||||
private _aotSummaries: () => any[] = () => [];
|
||||
private _testEnvAotSummaries: () => any[] = () => [];
|
||||
private _aotSummaries: Array<() => any[]> = [];
|
||||
private _templateOverrides: Array<{component: Type<any>, templateOf: Type<any>}> = [];
|
||||
|
||||
platform: PlatformRef = null !;
|
||||
|
||||
@ -232,7 +249,7 @@ export class TestBed implements Injector {
|
||||
this.platform = platform;
|
||||
this.ngModule = ngModule;
|
||||
if (aotSummaries) {
|
||||
this._aotSummaries = aotSummaries;
|
||||
this._testEnvAotSummaries = aotSummaries;
|
||||
}
|
||||
}
|
||||
|
||||
@ -245,11 +262,13 @@ export class TestBed implements Injector {
|
||||
this.resetTestingModule();
|
||||
this.platform = null !;
|
||||
this.ngModule = null !;
|
||||
this._aotSummaries = () => [];
|
||||
this._testEnvAotSummaries = () => [];
|
||||
}
|
||||
|
||||
resetTestingModule() {
|
||||
clearProviderOverrides();
|
||||
clearOverrides();
|
||||
this._aotSummaries = [];
|
||||
this._templateOverrides = [];
|
||||
this._compiler = null !;
|
||||
this._moduleOverrides = [];
|
||||
this._componentOverrides = [];
|
||||
@ -293,6 +312,9 @@ export class TestBed implements Injector {
|
||||
if (moduleDef.schemas) {
|
||||
this._schemas.push(...moduleDef.schemas);
|
||||
}
|
||||
if (moduleDef.aotSummaries) {
|
||||
this._aotSummaries.push(moduleDef.aotSummaries);
|
||||
}
|
||||
}
|
||||
|
||||
compileComponents(): Promise<any> {
|
||||
@ -327,6 +349,11 @@ export class TestBed implements Injector {
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const {component, templateOf} of this._templateOverrides) {
|
||||
const compFactory = this._compiler.getComponentFactory(templateOf);
|
||||
overrideComponentView(component, compFactory);
|
||||
}
|
||||
|
||||
const ngZone = new NgZone({enableLongStackTrace: true});
|
||||
const ngZoneInjector =
|
||||
Injector.create([{provide: NgZone, useValue: ngZone}], this.platform.injector);
|
||||
@ -339,7 +366,8 @@ export class TestBed implements Injector {
|
||||
|
||||
private _createCompilerAndModule(): Type<any> {
|
||||
const providers = this._providers.concat([{provide: TestBed, useValue: this}]);
|
||||
const declarations = this._declarations;
|
||||
const declarations =
|
||||
[...this._declarations, ...this._templateOverrides.map(entry => entry.templateOf)];
|
||||
const imports = [this.ngModule, this._imports];
|
||||
const schemas = this._schemas;
|
||||
|
||||
@ -350,7 +378,9 @@ export class TestBed implements Injector {
|
||||
const compilerFactory: TestingCompilerFactory =
|
||||
this.platform.injector.get(TestingCompilerFactory);
|
||||
this._compiler = compilerFactory.createTestingCompiler(this._compilerOptions);
|
||||
this._compiler.loadAotSummaries(this._aotSummaries);
|
||||
for (const summary of [this._testEnvAotSummaries, ...this._aotSummaries]) {
|
||||
this._compiler.loadAotSummaries(summary);
|
||||
}
|
||||
this._moduleOverrides.forEach((entry) => this._compiler.overrideModule(entry[0], entry[1]));
|
||||
this._componentOverrides.forEach(
|
||||
(entry) => this._compiler.overrideComponent(entry[0], entry[1]));
|
||||
@ -470,6 +500,16 @@ export class TestBed implements Injector {
|
||||
overrideProvider({token, flags, deps, value, deprecatedBehavior: deprecated});
|
||||
}
|
||||
|
||||
overrideTemplateUsingTestingModule(component: Type<any>, template: string) {
|
||||
this._assertNotInstantiated('overrideTemplateUsingTestingModule', 'override template');
|
||||
|
||||
@Component({selector: 'empty', template})
|
||||
class OverrideComponent {
|
||||
}
|
||||
|
||||
this._templateOverrides.push({component, templateOf: OverrideComponent});
|
||||
}
|
||||
|
||||
createComponent<T>(component: Type<T>): ComponentFixture<T> {
|
||||
this._initIfNeeded();
|
||||
const componentFactory = this._compiler.getComponentFactory(component);
|
||||
|
@ -179,7 +179,13 @@ export function create(info: any /* ts.server.PluginCreateInfo */): ts.LanguageS
|
||||
}
|
||||
|
||||
function completionToEntry(c: Completion): ts.CompletionEntry {
|
||||
return {kind: c.kind, name: c.name, sortText: c.sort, kindModifiers: ''};
|
||||
return {
|
||||
// TODO: remove any and fix type error.
|
||||
kind: c.kind as any,
|
||||
name: c.name,
|
||||
sortText: c.sort,
|
||||
kindModifiers: ''
|
||||
};
|
||||
}
|
||||
|
||||
function diagnosticToDiagnostic(d: Diagnostic, file: ts.SourceFile): ts.Diagnostic {
|
||||
@ -294,9 +300,10 @@ export function create(info: any /* ts.server.PluginCreateInfo */): ts.LanguageS
|
||||
fileName: loc.fileName,
|
||||
textSpan: {start: loc.span.start, length: loc.span.end - loc.span.start},
|
||||
name: '',
|
||||
kind: 'definition',
|
||||
// TODO: remove any and fix type error.
|
||||
kind: 'definition' as any,
|
||||
containerName: loc.fileName,
|
||||
containerKind: 'file'
|
||||
containerKind: 'file' as any,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -712,6 +712,60 @@ export function main() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('overrideTemplateUsingTestingModule', () => {
|
||||
it('should compile the template in the context of the testing module', () => {
|
||||
@Component({selector: 'comp', template: 'a'})
|
||||
class MyComponent {
|
||||
prop = 'some prop';
|
||||
}
|
||||
|
||||
let testDir: TestDir|undefined;
|
||||
|
||||
@Directive({selector: '[test]'})
|
||||
class TestDir {
|
||||
constructor() { testDir = this; }
|
||||
|
||||
@Input('test')
|
||||
test: string;
|
||||
}
|
||||
|
||||
TestBed.overrideTemplateUsingTestingModule(
|
||||
MyComponent, '<div [test]="prop">Hello world!</div>');
|
||||
|
||||
const fixture = TestBed.configureTestingModule({declarations: [MyComponent, TestDir]})
|
||||
.createComponent(MyComponent);
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('Hello world!');
|
||||
expect(testDir).toBeAnInstanceOf(TestDir);
|
||||
expect(testDir !.test).toBe('some prop');
|
||||
});
|
||||
|
||||
it('should throw if the TestBed is already created', () => {
|
||||
@Component({selector: 'comp', template: 'a'})
|
||||
class MyComponent {
|
||||
}
|
||||
|
||||
TestBed.get(Injector);
|
||||
|
||||
expect(() => TestBed.overrideTemplateUsingTestingModule(MyComponent, 'b'))
|
||||
.toThrowError(
|
||||
/Cannot override template when the test module has already been instantiated/);
|
||||
});
|
||||
|
||||
it('should reset overrides when the testing module is resetted', () => {
|
||||
@Component({selector: 'comp', template: 'a'})
|
||||
class MyComponent {
|
||||
}
|
||||
|
||||
TestBed.overrideTemplateUsingTestingModule(MyComponent, 'b');
|
||||
|
||||
const fixture = TestBed.resetTestingModule()
|
||||
.configureTestingModule({declarations: [MyComponent]})
|
||||
.createComponent(MyComponent);
|
||||
expect(fixture.nativeElement).toHaveText('a');
|
||||
});
|
||||
});
|
||||
|
||||
describe('setting up the compiler', () => {
|
||||
|
||||
describe('providers', () => {
|
||||
|
@ -43,12 +43,12 @@ declare let Zone: any;
|
||||
*/
|
||||
export interface NavigationExtras {
|
||||
/**
|
||||
* Enables relative navigation from the current ActivatedRoute.
|
||||
*
|
||||
* Configuration:
|
||||
*
|
||||
* ```
|
||||
* [{
|
||||
* Enables relative navigation from the current ActivatedRoute.
|
||||
*
|
||||
* Configuration:
|
||||
*
|
||||
* ```
|
||||
* [{
|
||||
* path: 'parent',
|
||||
* component: ParentComponent,
|
||||
* children: [{
|
||||
@ -59,92 +59,92 @@ export interface NavigationExtras {
|
||||
* component: ChildComponent
|
||||
* }]
|
||||
* }]
|
||||
* ```
|
||||
*
|
||||
* Navigate to list route from child route:
|
||||
*
|
||||
* ```
|
||||
* @Component({...})
|
||||
* class ChildComponent {
|
||||
* ```
|
||||
*
|
||||
* Navigate to list route from child route:
|
||||
*
|
||||
* ```
|
||||
* @Component({...})
|
||||
* class ChildComponent {
|
||||
* constructor(private router: Router, private route: ActivatedRoute) {}
|
||||
*
|
||||
* go() {
|
||||
* this.router.navigate(['../list'], { relativeTo: this.route });
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
* ```
|
||||
*/
|
||||
relativeTo?: ActivatedRoute|null;
|
||||
|
||||
/**
|
||||
* Sets query parameters to the URL.
|
||||
*
|
||||
* ```
|
||||
* // Navigate to /results?page=1
|
||||
* this.router.navigate(['/results'], { queryParams: { page: 1 } });
|
||||
* ```
|
||||
*/
|
||||
* Sets query parameters to the URL.
|
||||
*
|
||||
* ```
|
||||
* // Navigate to /results?page=1
|
||||
* this.router.navigate(['/results'], { queryParams: { page: 1 } });
|
||||
* ```
|
||||
*/
|
||||
queryParams?: Params|null;
|
||||
|
||||
/**
|
||||
* Sets the hash fragment for the URL.
|
||||
*
|
||||
* ```
|
||||
* // Navigate to /results#top
|
||||
* this.router.navigate(['/results'], { fragment: 'top' });
|
||||
* ```
|
||||
*/
|
||||
* Sets the hash fragment for the URL.
|
||||
*
|
||||
* ```
|
||||
* // Navigate to /results#top
|
||||
* this.router.navigate(['/results'], { fragment: 'top' });
|
||||
* ```
|
||||
*/
|
||||
fragment?: string;
|
||||
|
||||
/**
|
||||
* Preserves the query parameters for the next navigation.
|
||||
*
|
||||
* deprecated, use `queryParamsHandling` instead
|
||||
*
|
||||
* ```
|
||||
* // Preserve query params from /results?page=1 to /view?page=1
|
||||
* this.router.navigate(['/view'], { preserveQueryParams: true });
|
||||
* ```
|
||||
*
|
||||
* @deprecated since v4
|
||||
*/
|
||||
* Preserves the query parameters for the next navigation.
|
||||
*
|
||||
* deprecated, use `queryParamsHandling` instead
|
||||
*
|
||||
* ```
|
||||
* // Preserve query params from /results?page=1 to /view?page=1
|
||||
* this.router.navigate(['/view'], { preserveQueryParams: true });
|
||||
* ```
|
||||
*
|
||||
* @deprecated since v4
|
||||
*/
|
||||
preserveQueryParams?: boolean;
|
||||
|
||||
/**
|
||||
* config strategy to handle the query parameters for the next navigation.
|
||||
*
|
||||
* ```
|
||||
* // from /results?page=1 to /view?page=1&page=2
|
||||
* this.router.navigate(['/view'], { queryParams: { page: 2 }, queryParamsHandling: "merge" });
|
||||
* ```
|
||||
*/
|
||||
* config strategy to handle the query parameters for the next navigation.
|
||||
*
|
||||
* ```
|
||||
* // from /results?page=1 to /view?page=1&page=2
|
||||
* this.router.navigate(['/view'], { queryParams: { page: 2 }, queryParamsHandling: "merge" });
|
||||
* ```
|
||||
*/
|
||||
queryParamsHandling?: QueryParamsHandling|null;
|
||||
/**
|
||||
* Preserves the fragment for the next navigation
|
||||
*
|
||||
* ```
|
||||
* // Preserve fragment from /results#top to /view#top
|
||||
* this.router.navigate(['/view'], { preserveFragment: true });
|
||||
* ```
|
||||
*/
|
||||
* Preserves the fragment for the next navigation
|
||||
*
|
||||
* ```
|
||||
* // Preserve fragment from /results#top to /view#top
|
||||
* this.router.navigate(['/view'], { preserveFragment: true });
|
||||
* ```
|
||||
*/
|
||||
preserveFragment?: boolean;
|
||||
/**
|
||||
* Navigates without pushing a new state into history.
|
||||
*
|
||||
* ```
|
||||
* // Navigate silently to /view
|
||||
* this.router.navigate(['/view'], { skipLocationChange: true });
|
||||
* ```
|
||||
*/
|
||||
* Navigates without pushing a new state into history.
|
||||
*
|
||||
* ```
|
||||
* // Navigate silently to /view
|
||||
* this.router.navigate(['/view'], { skipLocationChange: true });
|
||||
* ```
|
||||
*/
|
||||
skipLocationChange?: boolean;
|
||||
/**
|
||||
* Navigates while replacing the current state in history.
|
||||
*
|
||||
* ```
|
||||
* // Navigate to /view
|
||||
* this.router.navigate(['/view'], { replaceUrl: true });
|
||||
* ```
|
||||
*/
|
||||
* Navigates while replacing the current state in history.
|
||||
*
|
||||
* ```
|
||||
* // Navigate to /view
|
||||
* this.router.navigate(['/view'], { replaceUrl: true });
|
||||
* ```
|
||||
*/
|
||||
replaceUrl?: boolean;
|
||||
}
|
||||
|
||||
@ -241,6 +241,14 @@ export class Router {
|
||||
|
||||
routeReuseStrategy: RouteReuseStrategy = new DefaultRouteReuseStrategy();
|
||||
|
||||
/**
|
||||
* Define what the router should do if it receives a navigation request to the current URL.
|
||||
* By default, the router will ignore this navigation. However, this prevents features such
|
||||
* as a "refresh" button. Use this option to configure the behavior when navigating to the
|
||||
* current URL. Default is 'ignore'.
|
||||
*/
|
||||
onSameUrlNavigation: 'reload'|'ignore' = 'ignore';
|
||||
|
||||
/**
|
||||
* Creates the router service.
|
||||
*/
|
||||
@ -518,11 +526,18 @@ export class Router {
|
||||
|
||||
// Because of a bug in IE and Edge, the location class fires two events (popstate and
|
||||
// hashchange) every single time. The second one should be ignored. Otherwise, the URL will
|
||||
// flicker.
|
||||
// flicker. Handles the case when a popstate was emitted first.
|
||||
if (lastNavigation && source == 'hashchange' && lastNavigation.source === 'popstate' &&
|
||||
lastNavigation.rawUrl.toString() === rawUrl.toString()) {
|
||||
return Promise.resolve(true); // return value is not used
|
||||
}
|
||||
// Because of a bug in IE and Edge, the location class fires two events (popstate and
|
||||
// hashchange) every single time. The second one should be ignored. Otherwise, the URL will
|
||||
// flicker. Handles the case when a hashchange was emitted first.
|
||||
if (lastNavigation && source == 'popstate' && lastNavigation.source === 'hashchange' &&
|
||||
lastNavigation.rawUrl.toString() === rawUrl.toString()) {
|
||||
return Promise.resolve(true); // return value is not used
|
||||
}
|
||||
|
||||
let resolve: any = null;
|
||||
let reject: any = null;
|
||||
@ -545,7 +560,8 @@ export class Router {
|
||||
const url = this.urlHandlingStrategy.extract(rawUrl);
|
||||
const urlTransition = !this.navigated || url.toString() !== this.currentUrlTree.toString();
|
||||
|
||||
if (urlTransition && this.urlHandlingStrategy.shouldProcessUrl(rawUrl)) {
|
||||
if ((this.onSameUrlNavigation === 'reload' ? true : urlTransition) &&
|
||||
this.urlHandlingStrategy.shouldProcessUrl(rawUrl)) {
|
||||
(this.events as Subject<Event>).next(new NavigationStart(id, this.serializeUrl(url)));
|
||||
Promise.resolve()
|
||||
.then(
|
||||
@ -573,10 +589,9 @@ export class Router {
|
||||
}
|
||||
|
||||
private runNavigate(
|
||||
url: UrlTree, rawUrl: UrlTree, shouldPreventPushState: boolean, shouldReplaceUrl: boolean,
|
||||
id: number, precreatedState: RouterStateSnapshot|null): Promise<boolean> {
|
||||
url: UrlTree, rawUrl: UrlTree, skipLocationChange: boolean, replaceUrl: boolean, id: number,
|
||||
precreatedState: RouterStateSnapshot|null): Promise<boolean> {
|
||||
if (id !== this.navigationId) {
|
||||
this.location.go(this.urlSerializer.serialize(this.currentUrlTree));
|
||||
(this.events as Subject<Event>)
|
||||
.next(new NavigationCancel(
|
||||
id, this.serializeUrl(url),
|
||||
@ -698,9 +713,9 @@ export class Router {
|
||||
|
||||
(this as{routerState: RouterState}).routerState = state;
|
||||
|
||||
if (!shouldPreventPushState) {
|
||||
if (!skipLocationChange) {
|
||||
const path = this.urlSerializer.serialize(this.rawUrlTree);
|
||||
if (this.location.isCurrentPathEqualTo(path) || shouldReplaceUrl) {
|
||||
if (this.location.isCurrentPathEqualTo(path) || replaceUrl) {
|
||||
this.location.replaceState(path);
|
||||
} else {
|
||||
this.location.go(path);
|
||||
@ -748,14 +763,13 @@ export class Router {
|
||||
(this as{routerState: RouterState}).routerState = storedState;
|
||||
this.currentUrlTree = storedUrl;
|
||||
this.rawUrlTree = this.urlHandlingStrategy.merge(this.currentUrlTree, rawUrl);
|
||||
this.location.replaceState(this.serializeUrl(this.rawUrlTree));
|
||||
this.resetUrlToCurrentUrlTree();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private resetUrlToCurrentUrlTree(): void {
|
||||
const path = this.urlSerializer.serialize(this.rawUrlTree);
|
||||
this.location.replaceState(path);
|
||||
this.location.replaceState(this.urlSerializer.serialize(this.rawUrlTree));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,12 +129,15 @@ export class RouterModule {
|
||||
* Creates a module with all the router providers and directives. It also optionally sets up an
|
||||
* application listener to perform an initial navigation.
|
||||
*
|
||||
* Options:
|
||||
* Options (see {@link ExtraOptions}):
|
||||
* * `enableTracing` makes the router log all its internal events to the console.
|
||||
* * `useHash` enables the location strategy that uses the URL fragment instead of the history
|
||||
* API.
|
||||
* * `initialNavigation` disables the initial navigation.
|
||||
* * `errorHandler` provides a custom error handler.
|
||||
* * `preloadingStrategy` configures a preloading strategy (see {@link PreloadAllModules}).
|
||||
* * `onSameUrlNavigation` configures how the router handles navigation to the current URL. See
|
||||
* {@link ExtraOptions} for more details.
|
||||
*/
|
||||
static forRoot(routes: Routes, config?: ExtraOptions): ModuleWithProviders {
|
||||
return {
|
||||
@ -267,6 +270,14 @@ export interface ExtraOptions {
|
||||
* Configures a preloading strategy. See {@link PreloadAllModules}.
|
||||
*/
|
||||
preloadingStrategy?: any;
|
||||
|
||||
/**
|
||||
* Define what the router should do if it receives a navigation request to the current URL.
|
||||
* By default, the router will ignore this navigation. However, this prevents features such
|
||||
* as a "refresh" button. Use this option to configure the behavior when navigating to the
|
||||
* current URL. Default is 'ignore'.
|
||||
*/
|
||||
onSameUrlNavigation?: 'reload'|'ignore';
|
||||
}
|
||||
|
||||
export function setupRouter(
|
||||
@ -299,6 +310,10 @@ export function setupRouter(
|
||||
});
|
||||
}
|
||||
|
||||
if (opts.onSameUrlNavigation) {
|
||||
router.onSameUrlNavigation = opts.onSameUrlNavigation;
|
||||
}
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,7 @@ import {ComponentFixture, TestBed, fakeAsync, inject, tick} from '@angular/core/
|
||||
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
import {ActivatedRoute, ActivatedRouteSnapshot, ActivationEnd, ActivationStart, CanActivate, CanDeactivate, ChildActivationEnd, ChildActivationStart, DetachedRouteHandle, Event, GuardsCheckEnd, GuardsCheckStart, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, PRIMARY_OUTLET, ParamMap, Params, PreloadAllModules, PreloadingStrategy, Resolve, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, RouteReuseStrategy, Router, RouterEvent, RouterModule, RouterPreloader, RouterStateSnapshot, RoutesRecognized, RunGuardsAndResolvers, UrlHandlingStrategy, UrlSegmentGroup, UrlTree} from '@angular/router';
|
||||
import {SpyLocation} from 'common/testing';
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
import {Observer} from 'rxjs/Observer';
|
||||
import {of } from 'rxjs/observable/of';
|
||||
@ -50,6 +51,89 @@ describe('Integration', () => {
|
||||
expect(fixture.nativeElement).toHaveText('route');
|
||||
})));
|
||||
|
||||
describe('navigation', function() {
|
||||
it('should navigate to the current URL', fakeAsync(inject([Router], (router: Router) => {
|
||||
router.onSameUrlNavigation = 'reload';
|
||||
router.resetConfig([
|
||||
{path: '', component: SimpleCmp},
|
||||
{path: 'simple', component: SimpleCmp},
|
||||
]);
|
||||
|
||||
const fixture = createRoot(router, RootCmp);
|
||||
const events: Event[] = [];
|
||||
router.events.subscribe(e => onlyNavigationStartAndEnd(e) && events.push(e));
|
||||
|
||||
router.navigateByUrl('/simple');
|
||||
tick();
|
||||
|
||||
router.navigateByUrl('/simple');
|
||||
tick();
|
||||
|
||||
expectEvents(events, [
|
||||
[NavigationStart, '/simple'], [NavigationEnd, '/simple'], [NavigationStart, '/simple'],
|
||||
[NavigationEnd, '/simple']
|
||||
]);
|
||||
})));
|
||||
|
||||
it('should not pollute browser history when replaceUrl is set to true',
|
||||
fakeAsync(inject([Router, Location], (router: Router, location: SpyLocation) => {
|
||||
router.resetConfig([
|
||||
{path: '', component: SimpleCmp}, {path: 'a', component: SimpleCmp},
|
||||
{path: 'b', component: SimpleCmp}
|
||||
]);
|
||||
|
||||
const fixture = createRoot(router, RootCmp);
|
||||
|
||||
router.navigateByUrl('/a', {replaceUrl: true});
|
||||
router.navigateByUrl('/b', {replaceUrl: true});
|
||||
tick();
|
||||
|
||||
expect(location.urlChanges).toEqual(['replace: /', 'replace: /b']);
|
||||
})));
|
||||
|
||||
it('should skip navigation if another navigation is already scheduled',
|
||||
fakeAsync(inject([Router, Location], (router: Router, location: SpyLocation) => {
|
||||
router.resetConfig([
|
||||
{path: '', component: SimpleCmp}, {path: 'a', component: SimpleCmp},
|
||||
{path: 'b', component: SimpleCmp}
|
||||
]);
|
||||
|
||||
const fixture = createRoot(router, RootCmp);
|
||||
|
||||
router.navigate(
|
||||
['/a'], {queryParams: {a: true}, queryParamsHandling: 'merge', replaceUrl: true});
|
||||
router.navigate(
|
||||
['/b'], {queryParams: {b: true}, queryParamsHandling: 'merge', replaceUrl: true});
|
||||
tick();
|
||||
|
||||
/**
|
||||
* Why do we have '/b?b=true' and not '/b?a=true&b=true'?
|
||||
*
|
||||
* This is because the router has the right to stop a navigation mid-flight if another
|
||||
* navigation has been already scheduled. This is why we can use a top-level guard
|
||||
* to perform redirects. Calling `navigate` in such a guard will stop the navigation, and
|
||||
* the components won't be instantiated.
|
||||
*
|
||||
* This is a fundamental property of the router: it only cares about its latest state.
|
||||
*
|
||||
* This means that components should only map params to something else, not reduce them.
|
||||
* In other words, the following component is asking for trouble:
|
||||
*
|
||||
* ```
|
||||
* class MyComponent {
|
||||
* constructor(a: ActivatedRoute) {
|
||||
* a.params.scan(...)
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* This also means "queryParamsHandling: 'merge'" should only be used to merge with
|
||||
* long-living query parameters (e.g., debug).
|
||||
*/
|
||||
expect(router.url).toEqual('/b?b=true');
|
||||
})));
|
||||
});
|
||||
|
||||
describe('should execute navigations serially', () => {
|
||||
let log: any[] = [];
|
||||
|
||||
@ -428,7 +512,7 @@ describe('Integration', () => {
|
||||
}]);
|
||||
|
||||
const recordedEvents: any[] = [];
|
||||
router.events.forEach(e => e instanceof RouterEvent && recordedEvents.push(e));
|
||||
router.events.forEach(e => onlyNavigationStartAndEnd(e) && recordedEvents.push(e));
|
||||
|
||||
router.navigateByUrl('/team/22/user/victor');
|
||||
advance(fixture);
|
||||
@ -442,15 +526,8 @@ describe('Integration', () => {
|
||||
expect(fixture.nativeElement).toHaveText('team 22 [ user fedor, right: ]');
|
||||
|
||||
expectEvents(recordedEvents, [
|
||||
[NavigationStart, '/team/22/user/victor'], [RoutesRecognized, '/team/22/user/victor'],
|
||||
[GuardsCheckStart, '/team/22/user/victor'], [GuardsCheckEnd, '/team/22/user/victor'],
|
||||
[ResolveStart, '/team/22/user/victor'], [ResolveEnd, '/team/22/user/victor'],
|
||||
[NavigationEnd, '/team/22/user/victor'],
|
||||
|
||||
[NavigationStart, '/team/22/user/fedor'], [RoutesRecognized, '/team/22/user/fedor'],
|
||||
[GuardsCheckStart, '/team/22/user/fedor'], [GuardsCheckEnd, '/team/22/user/fedor'],
|
||||
[ResolveStart, '/team/22/user/fedor'], [ResolveEnd, '/team/22/user/fedor'],
|
||||
[NavigationEnd, '/team/22/user/fedor']
|
||||
[NavigationStart, '/team/22/user/victor'], [NavigationEnd, '/team/22/user/victor'],
|
||||
[NavigationStart, '/team/22/user/fedor'], [NavigationEnd, '/team/22/user/fedor']
|
||||
]);
|
||||
})));
|
||||
|
||||
@ -3638,6 +3715,10 @@ function expectEvents(events: Event[], pairs: any[]) {
|
||||
}
|
||||
}
|
||||
|
||||
function onlyNavigationStartAndEnd(e: Event): boolean {
|
||||
return e instanceof NavigationStart || e instanceof NavigationEnd;
|
||||
}
|
||||
|
||||
@Component(
|
||||
{selector: 'link-cmp', template: `<a routerLink="/team/33/simple" [target]="'_self'">link</a>`})
|
||||
class StringLinkCmp {
|
||||
|
19
tools/public_api_guard/core/testing.d.ts
vendored
19
tools/public_api_guard/core/testing.d.ts
vendored
@ -71,13 +71,13 @@ export declare class TestBed implements Injector {
|
||||
}): void;
|
||||
configureTestingModule(moduleDef: TestModuleMetadata): void;
|
||||
createComponent<T>(component: Type<T>): ComponentFixture<T>;
|
||||
deprecatedOverrideProvider(token: any, provider: {
|
||||
useValue: any;
|
||||
}): void;
|
||||
/** @deprecated */ deprecatedOverrideProvider(token: any, provider: {
|
||||
useFactory: Function;
|
||||
deps: any[];
|
||||
}): void;
|
||||
deprecatedOverrideProvider(token: any, provider: {
|
||||
useValue: any;
|
||||
}): void;
|
||||
execute(tokens: any[], fn: Function, context?: any): any;
|
||||
get(token: any, notFoundValue?: any): any;
|
||||
/** @experimental */ initTestEnvironment(ngModule: Type<any> | Type<any>[], platform: PlatformRef, aotSummaries?: () => any[]): void;
|
||||
@ -92,6 +92,7 @@ export declare class TestBed implements Injector {
|
||||
overrideProvider(token: any, provider: {
|
||||
useValue: any;
|
||||
}): void;
|
||||
overrideTemplateUsingTestingModule(component: Type<any>, template: string): void;
|
||||
/** @experimental */ resetTestEnvironment(): void;
|
||||
resetTestingModule(): void;
|
||||
static compileComponents(): Promise<any>;
|
||||
@ -101,13 +102,13 @@ export declare class TestBed implements Injector {
|
||||
}): typeof TestBed;
|
||||
static configureTestingModule(moduleDef: TestModuleMetadata): typeof TestBed;
|
||||
static createComponent<T>(component: Type<T>): ComponentFixture<T>;
|
||||
static deprecatedOverrideProvider(token: any, provider: {
|
||||
useValue: any;
|
||||
}): void;
|
||||
/** @deprecated */ static deprecatedOverrideProvider(token: any, provider: {
|
||||
useFactory: Function;
|
||||
deps: any[];
|
||||
}): void;
|
||||
static deprecatedOverrideProvider(token: any, provider: {
|
||||
useValue: any;
|
||||
}): void;
|
||||
static get(token: any, notFoundValue?: any): any;
|
||||
/** @experimental */ static initTestEnvironment(ngModule: Type<any> | Type<any>[], platform: PlatformRef, aotSummaries?: () => any[]): TestBed;
|
||||
static overrideComponent(component: Type<any>, override: MetadataOverride<Component>): typeof TestBed;
|
||||
@ -117,11 +118,12 @@ export declare class TestBed implements Injector {
|
||||
static overrideProvider(token: any, provider: {
|
||||
useFactory: Function;
|
||||
deps: any[];
|
||||
}): void;
|
||||
}): typeof TestBed;
|
||||
static overrideProvider(token: any, provider: {
|
||||
useValue: any;
|
||||
}): void;
|
||||
}): typeof TestBed;
|
||||
static overrideTemplate(component: Type<any>, template: string): typeof TestBed;
|
||||
static overrideTemplateUsingTestingModule(component: Type<any>, template: string): typeof TestBed;
|
||||
/** @experimental */ static resetTestEnvironment(): void;
|
||||
static resetTestingModule(): typeof TestBed;
|
||||
}
|
||||
@ -137,6 +139,7 @@ export declare type TestModuleMetadata = {
|
||||
declarations?: any[];
|
||||
imports?: any[];
|
||||
schemas?: Array<SchemaMetadata | any[]>;
|
||||
aotSummaries?: () => any[];
|
||||
};
|
||||
|
||||
/** @experimental */
|
||||
|
2
tools/public_api_guard/router/router.d.ts
vendored
2
tools/public_api_guard/router/router.d.ts
vendored
@ -126,6 +126,7 @@ export interface ExtraOptions {
|
||||
enableTracing?: boolean;
|
||||
errorHandler?: ErrorHandler;
|
||||
initialNavigation?: InitialNavigation;
|
||||
onSameUrlNavigation?: 'reload' | 'ignore';
|
||||
preloadingStrategy?: any;
|
||||
useHash?: boolean;
|
||||
}
|
||||
@ -327,6 +328,7 @@ export declare class Router {
|
||||
errorHandler: ErrorHandler;
|
||||
readonly events: Observable<Event>;
|
||||
navigated: boolean;
|
||||
onSameUrlNavigation: 'reload' | 'ignore';
|
||||
routeReuseStrategy: RouteReuseStrategy;
|
||||
readonly routerState: RouterState;
|
||||
readonly url: string;
|
||||
|
Reference in New Issue
Block a user