Compare commits

..

17 Commits
4.1.1 ... 4.1.2

Author SHA1 Message Date
62a8618536 docs: add changelog for 4.1.2 2017-05-10 15:44:28 -07:00
419fe0ca0d release: cut the 4.1.2 release 2017-05-10 15:42:11 -07:00
e46a65f8fd build: update key for pushing to PACKAGE-builds repos (#16667) 2017-05-09 17:34:35 -07:00
f0e1043a1d docs(*) fix dangling links in API docs (#16632)
* docs(animations): fix links to `Component` animations

* docs(core): fix links to `ReflectiveInjector` methods

The `resolve` and other methods were moved from the
`Injector` to the `ReflectiveInjector`.

* docs(core): fix links to `Renderer`

The local links were assuming that that methods were on the
current document (e.g. `RootRenderer`), but they are actually
on the `Renderer` class.

* docs(router): fix links to methods

* docs(forms): fix links to methods

* docs(core): fix links to methods

* docs(router): fix API page links and an internal link
2017-05-09 17:34:35 -07:00
c1938f5af1 docs(core): remove link to non-existent class in InjectableDecorator (#16653)
Closes #16640
2017-05-09 17:34:34 -07:00
915eae50b4 fix(router): fix redirect to a URL with a param having multiple values (#16376)
fixes #16310

PR Close #16376
2017-05-09 17:34:33 -07:00
68675e625a test(router): simplify redirect tests (#16376) 2017-05-09 17:34:32 -07:00
d0e1688514 fix(compiler): avoid a ...null spread in extraction (#16547)
This code only runs in ES5 mode in the test suite, so this is difficult to test.
However `updateFromTemplate` is being called with a spread operator, as
`...updateFromTemplate(...)`. The spread operator should fail on `null` values.
This change avoids the problem by always returning a (possibly empty) array.

PR Close #16547
2017-05-09 17:34:31 -07:00
ee6705af49 fix(core): detach projected views when a parent view is destroyed (#16592)
This also clarifies via a test 
that we only update projected views when the view is created or destroyed,
but not when it is attached/detached/moved.

Fixes #15578

PR Close #16592
2017-05-09 17:34:31 -07:00
9218812011 fix(core): projected views should be dirty checked when the declaring component is dirty checked. (#16592)
Previously a projected view was only dirty checked when the
component in which it was inserted was dirty checked.

This fix changes the behavior so that a view is also dirty checked if
the declaring component is dirty checked.

Note: This does not change the order of change detection,
only the fact whether a projected view is dirty checked or not.

Fixes #14321
2017-05-09 17:34:30 -07:00
ec77bf9fb0 docs(core): EventEmitter docs for isAsync defaults (#15780)
- solves #15758
2017-05-09 17:34:29 -07:00
fee5b2ad56 docs(animations): remove duplicate word (#16508) 2017-05-09 17:34:24 -07:00
9c70a3cfb1 fix(http): flatten metadata for @angular/http/testing
@angular/http/testing used to publish a metadata structure which paralleled
the .d.ts structure. This causes ngc to write incorrect imports for this
bundle when compiling providers using MockBackend and other http testing
classes.

This change restructures the @angular/http/testing build a bit, modeling it
after @angular/platform-browser-animations, and produces a FESM structure
that has flat metadata.

Fixes #15521.
2017-05-09 17:33:59 -07:00
ec3b6e9603 fix(http): introduce encodingHint for text() for better ArrayBuffer support
Currently, if a Response has an ArrayBuffer body and text() is called, Angular
attempts to convert the ArrayBuffer to a string. Doing this requires knowing
the encoding of the bytes in the buffer, which is context that we don't have.

Instead, we assume that the buffer is encoded in UTF-16, and attempt to process
it that way. Unfortunately the approach chosen (interpret buffer as Uint16Array and
create a Javascript string from each entry using String.fromCharCode) is incorrect
as it does not handle UTF-16 surrogate pairs. What Angular actually implements, then,
is UCS-2 decoding, which is equivalent to UTF-16 with characters restricted to the
base plane.

No standard way of decoding UTF-8 or UTF-16 exists in the browser today. APIs like
TextDecoder are only supported in a few browsers, and although hacks like using the
FileReader API with a Blob to force browsers to do content encoding detection and
decoding exist, they're slow and not compatible with the synchronous text() API.

Thus, this bug is fixed by introducing an encodingHint parameter to text(). The
default value of this parameter is 'legacy', indicating that the existing broken
behavior should be used - this prevents breaking existing apps. The only other
possible value of the hint is 'iso-8859' which interprets each byte of the buffer
with String.fromCharCode. UTF-8 and UTF-16 are not supported - it is up to the
consumer to get the ArrayBuffer and decode it themselves.

The parameter is a hint, as it's not always used (for example, if the conversion
to text doesn't involve an ArrayBuffer source). Additionally, this leaves the door
open for future implementations to perform more sophisticated encoding detection
and ignore the user-provided value if it can be proven to be incorrect.

Fixes #15932.

PR Close #16420
2017-05-09 17:33:59 -07:00
63066f7697 fix(http): honor RequestArgs.search and RequestArgs.params map type
Currently `new Request({search: ...})` is not honored, and
`new Request({params: {'x': 'y'}) doesn't work either, as this object would have
toString() called. This change allows both of these cases to work, as proved by
the 2 new tests.

Fixes #15761

PR Close #16392
2017-05-09 17:33:58 -07:00
5f6d0f2340 revert: fix: public API golden files (#16414)
This reverts commit dcaa11a88b.
2017-05-04 15:00:09 -07:00
4a0e93b03b revert: test(compiler-cli): add test for missingTranslation parameter
This reverts commit 109229246c.
2017-05-04 14:51:30 -07:00
36 changed files with 544 additions and 155 deletions

View File

@ -32,7 +32,7 @@ env:
global:
# GITHUB_TOKEN_ANGULAR=<github token, a personal access token of the angular-builds account, account access in valentine>
# This is needed for the e2e Travis matrix task to publish packages to github for continuous packages delivery.
- secure: "rNqXoy2gqjbF5tBXlRBy+oiYntO3BtzcxZuEtlLMzNaTNzC4dyMOFub0GkzIPWwOzkARoEU9Kv+bC97fDVbCBUKeyzzEqxqddUKhzRxeaYjsefJ6XeTvBvDxwo7wDwyxZSuWdBeGAe4eARVHm7ypsd+AlvqxtzjyS27TK2BzdL4="
- secure: "aCdHveZuY8AT4Jr1JoJB4LxZsnGWRe/KseZh1YXYe5UtufFCtTVHvUcLn0j2aLBF0KpdyS+hWf0i4np9jthKu2xPKriefoPgCMpisYeC0MFkwbmv+XlgkUbgkgVZMGiVyX7DCYXVahxIoOUjVMEDCbNiHTIrfEuyq24U3ok2tHc="
# FIREBASE_TOKEN
# This is needed for publishing builds to the "aio-staging" firebase site.
# TODO(i): the token was generated using the iminar@google account, we should switch to a shared/role-base account.

View File

@ -1,3 +1,19 @@
<a name="4.1.2"></a>
## [4.1.2](https://github.com/angular/angular/compare/4.1.1...4.1.2) (2017-05-10)
### Bug Fixes
* **compiler:** avoid a `...null` spread in extraction ([#16547](https://github.com/angular/angular/issues/16547)) ([d0e1688](https://github.com/angular/angular/commit/d0e1688))
* **core:** detach projected views when a parent view is destroyed ([#16592](https://github.com/angular/angular/issues/16592)) ([ee6705a](https://github.com/angular/angular/commit/ee6705a)), closes [#15578](https://github.com/angular/angular/issues/15578)
* **core:** projected views should be dirty checked when the declaring component is dirty checked. ([#16592](https://github.com/angular/angular/issues/16592)) ([9218812](https://github.com/angular/angular/commit/9218812)), closes [#14321](https://github.com/angular/angular/issues/14321)
* **http:** flatten metadata for [@angular](https://github.com/angular)/http/testing ([9c70a3c](https://github.com/angular/angular/commit/9c70a3c)), closes [#15521](https://github.com/angular/angular/issues/15521)
* **http:** honor RequestArgs.search and RequestArgs.params map type ([63066f7](https://github.com/angular/angular/commit/63066f7)), closes [#15761](https://github.com/angular/angular/issues/15761) [#16392](https://github.com/angular/angular/issues/16392)
* **http:** introduce encodingHint for text() for better ArrayBuffer support ([ec3b6e9](https://github.com/angular/angular/commit/ec3b6e9)), closes [#15932](https://github.com/angular/angular/issues/15932) [#16420](https://github.com/angular/angular/issues/16420)
* **router:** fix redirect to a URL with a param having multiple values ([#16376](https://github.com/angular/angular/issues/16376)) ([915eae5](https://github.com/angular/angular/commit/915eae5)), closes [#16310](https://github.com/angular/angular/issues/16310)
<a name="4.1.1"></a>
## [4.1.1](https://github.com/angular/angular/compare/4.1.0...4.1.1) (2017-05-04)

View File

@ -501,7 +501,7 @@ those callbacks like this:
The callbacks receive an `AnimationEvent` that contains contains useful properties such as
The callbacks receive an `AnimationEvent` that contains useful properties such as
`fromState`, `toState` and `totalTime`.
Those callbacks will fire whether or not an animation is picked up.

View File

@ -1,6 +1,6 @@
{
"name": "angular-srcs",
"version": "4.1.1",
"version": "4.1.2",
"private": true,
"branchPattern": "2.0.*",
"description": "Angular - a web framework for modern web apps",

View File

@ -120,7 +120,7 @@ export interface AnimationGroupMetadata extends AnimationMetadata { steps: Anima
/**
* `trigger` is an animation-specific function that is designed to be used inside of Angular's
animation DSL language. If this information is new, please navigate to the {@link
Component#animations-anchor component animations metadata page} to gain a better understanding of
Component#animations component animations metadata page} to gain a better understanding of
how animations in Angular are used.
*
* `trigger` Creates an animation trigger which will a list of {@link state state} and {@link
@ -128,7 +128,7 @@ export interface AnimationGroupMetadata extends AnimationMetadata { steps: Anima
changes.
*
* Triggers are registered within the component annotation data under the {@link
Component#animations-anchor animations section}. An animation trigger can be placed on an element
Component#animations animations section}. An animation trigger can be placed on an element
within a template by referencing the name of the trigger followed by the expression value that the
trigger is bound to (in the form of `[@triggerName]="expression"`.
*
@ -175,7 +175,7 @@ export function trigger(name: string, definitions: AnimationMetadata[]): Animati
/**
* `animate` is an animation-specific function that is designed to be used inside of Angular's
* animation DSL language. If this information is new, please navigate to the {@link
* Component#animations-anchor component animations metadata page} to gain a better understanding of
* Component#animations component animations metadata page} to gain a better understanding of
* how animations in Angular are used.
*
* `animate` specifies an animation step that will apply the provided `styles` data for a given
@ -226,7 +226,7 @@ export function animate(
/**
* `group` is an animation-specific function that is designed to be used inside of Angular's
* animation DSL language. If this information is new, please navigate to the {@link
* Component#animations-anchor component animations metadata page} to gain a better understanding of
* Component#animations component animations metadata page} to gain a better understanding of
* how animations in Angular are used.
*
* `group` specifies a list of animation steps that are all run in parallel. Grouped animations are
@ -261,7 +261,7 @@ export function group(steps: AnimationMetadata[]): AnimationGroupMetadata {
/**
* `sequence` is an animation-specific function that is designed to be used inside of Angular's
* animation DSL language. If this information is new, please navigate to the {@link
* Component#animations-anchor component animations metadata page} to gain a better understanding of
* Component#animations component animations metadata page} to gain a better understanding of
* how animations in Angular are used.
*
* `sequence` Specifies a list of animation steps that are run one by one. (`sequence` is used by
@ -299,7 +299,7 @@ export function sequence(steps: AnimationMetadata[]): AnimationSequenceMetadata
/**
* `style` is an animation-specific function that is designed to be used inside of Angular's
* animation DSL language. If this information is new, please navigate to the {@link
* Component#animations-anchor component animations metadata page} to gain a better understanding of
* Component#animations component animations metadata page} to gain a better understanding of
* how animations in Angular are used.
*
* `style` declares a key/value object containing CSS properties/styles that can then be used for
@ -347,7 +347,7 @@ export function style(
/**
* `state` is an animation-specific function that is designed to be used inside of Angular's
* animation DSL language. If this information is new, please navigate to the {@link
* Component#animations-anchor component animations metadata page} to gain a better understanding of
* Component#animations component animations metadata page} to gain a better understanding of
* how animations in Angular are used.
*
* `state` declares an animation state within the given trigger. When a state is active within a
@ -399,7 +399,7 @@ export function state(name: string, styles: AnimationStyleMetadata): AnimationSt
/**
* `keyframes` is an animation-specific function that is designed to be used inside of Angular's
* animation DSL language. If this information is new, please navigate to the {@link
* Component#animations-anchor component animations metadata page} to gain a better understanding of
* Component#animations component animations metadata page} to gain a better understanding of
* how animations in Angular are used.
*
* `keyframes` specifies a collection of {@link style style} entries each optionally characterized
@ -448,7 +448,7 @@ export function keyframes(steps: AnimationStyleMetadata[]): AnimationKeyframesSe
/**
* `transition` is an animation-specific function that is designed to be used inside of Angular's
* animation DSL language. If this information is new, please navigate to the {@link
* Component#animations-anchor component animations metadata page} to gain a better understanding of
* Component#animations component animations metadata page} to gain a better understanding of
* how animations in Angular are used.
*
* `transition` declares the {@link sequence sequence of animation steps} that will be run when the

View File

@ -29,7 +29,6 @@ function main() {
Promise.resolve()
.then(() => codeGenTest())
.then(() => codeGenTest(true))
.then(() => i18nTest())
.then(() => lazyRoutesTest())
.then(() => {
@ -43,9 +42,8 @@ function main() {
});
}
function codeGenTest(forceError = false) {
function codeGenTest() {
const basePath = path.join(__dirname, '../ngtools_src');
const srcPath = path.join(__dirname, '../src');
const project = path.join(basePath, 'tsconfig-build.json');
const readResources: string[] = [];
const wroteFiles: string[] = [];
@ -61,9 +59,6 @@ function codeGenTest(forceError = false) {
config.ngOptions.basePath = basePath;
console.log(`>>> running codegen for ${project}`);
if (forceError) {
console.log(`>>> asserting that missingTranslation param with error value throws`);
}
return __NGTOOLS_PRIVATE_API_2
.codeGen({
basePath,
@ -72,10 +67,9 @@ function codeGenTest(forceError = false) {
angularCompilerOptions: config.ngOptions,
// i18n options.
i18nFormat: 'xlf',
i18nFile: path.join(srcPath, 'messages.fi.xlf'),
locale: 'fi',
missingTranslation: forceError ? 'error' : 'ignore',
i18nFormat: undefined,
i18nFile: undefined,
locale: undefined,
readResource: (fileName: string) => {
readResources.push(fileName);
@ -107,17 +101,10 @@ function codeGenTest(forceError = false) {
console.log(`done, no errors.`);
})
.catch((e: Error) => {
if (forceError) {
assert(
e.message.match(`Missing translation for message`),
`Expected error message for missing translations`);
console.log(`done, error catched`);
} else {
console.error(e.stack);
console.error('Compilation failed');
throw e;
}
.catch((e: any) => {
console.error(e.stack);
console.error('Compilation failed');
throw e;
});
}
@ -178,7 +165,7 @@ function i18nTest() {
console.log(`done, no errors.`);
})
.catch((e: Error) => {
.catch((e: any) => {
console.error(e.stack);
console.error('Extraction failed');
throw e;

View File

@ -9,7 +9,7 @@
"ng-xi18n": "./src/extract_i18n.js"
},
"dependencies": {
"@angular/tsc-wrapped": "4.1.1",
"@angular/tsc-wrapped": "4.1.2",
"reflect-metadata": "^0.1.2",
"minimist": "^1.2.0"
},

View File

@ -26,7 +26,7 @@ export class MessageBundle {
private _implicitAttrs: {[k: string]: string[]}, private _locale: string|null = null) {}
updateFromTemplate(html: string, url: string, interpolationConfig: InterpolationConfig):
ParseError[]|null {
ParseError[] {
const htmlParserResult = this._htmlParser.parse(html, url, true, interpolationConfig);
if (htmlParserResult.errors.length) {
@ -41,7 +41,7 @@ export class MessageBundle {
}
this._messages.push(...i18nParserResult.messages);
return null;
return [];
}
// Return the message in the internal format

View File

@ -127,7 +127,7 @@ export interface InjectableDecorator {
*
* {@example core/di/ts/metadata_spec.ts region='Injectable'}
*
* {@link Injector} will throw {@link NoAnnotationError} when trying to instantiate a class that
* {@link Injector} will throw an error when trying to instantiate a class that
* does not have `@Injectable` marker, as shown in the example below.
*
* {@example core/di/ts/metadata_spec.ts region='InjectableThrows'}

View File

@ -113,7 +113,7 @@ export abstract class ReflectiveInjector implements Injector {
*
* This function is slower than the corresponding `fromResolvedProviders`
* because it needs to resolve the passed-in providers first.
* See {@link Injector#resolve} and {@link Injector#fromResolvedProviders}.
* See {@link ReflectiveInjector#resolve} and {@link ReflectiveInjector#fromResolvedProviders}.
*/
static resolveAndCreate(providers: Provider[], parent?: Injector): ReflectiveInjector {
const ResolvedReflectiveProviders = ReflectiveInjector.resolve(providers);
@ -190,7 +190,7 @@ export abstract class ReflectiveInjector implements Injector {
*
* This function is slower than the corresponding `createChildFromResolved`
* because it needs to resolve the passed-in providers first.
* See {@link Injector#resolve} and {@link Injector#createChildFromResolved}.
* See {@link ReflectiveInjector#resolve} and {@link ReflectiveInjector#createChildFromResolved}.
*/
abstract resolveAndCreateChild(providers: Provider[]): ReflectiveInjector;

View File

@ -64,8 +64,11 @@ export class EventEmitter<T> extends Subject<T> {
__isAsync: boolean;
/**
* Creates an instance of [EventEmitter], which depending on [isAsync],
* Creates an instance of {@link EventEmitter}, which depending on `isAsync`,
* delivers events synchronously or asynchronously.
*
* @param isAsync By default, events are delivered synchronously (default value: `false`).
* Set to `true` for asynchronous event delivery.
*/
constructor(isAsync: boolean = false) {
super();

View File

@ -61,19 +61,19 @@ export enum ViewEncapsulation {
* {@link Component}
*/
export class ViewMetadata {
/** {@link Component.templateUrl} */
/** {@link Component#templateUrl} */
templateUrl: string|undefined;
/** {@link Component.template} */
/** {@link Component#template} */
template: string|undefined;
/** {@link Component.stylesUrl} */
/** {@link Component#stylesUrl} */
styleUrls: string[]|undefined;
/** {@link Component.styles} */
/** {@link Component#styles} */
styles: string[]|undefined;
/** {@link Component.encapsulation} */
/** {@link Component#encapsulation} */
encapsulation: ViewEncapsulation|undefined;
/** {@link Component.animation} */
/** {@link Component#animation} */
animations: any[]|undefined;
/** {@link Component.interpolation} */
/** {@link Component#interpolation} */
interpolation: [string, string]|undefined;
constructor(

View File

@ -99,7 +99,8 @@ export const Renderer2Interceptor = new InjectionToken<Renderer2[]>('Renderer2In
*
* Use this service to bypass Angular's templating and make custom UI changes that can't be
* expressed declaratively. For example if you need to set a property or an attribute whose name is
* not statically known, use {@link #setElementProperty} or {@link #setElementAttribute}
* not statically known, use {@link Renderer#setElementProperty} or {@link
* Renderer#setElementAttribute}
* respectively.
*
* If you are implementing a custom renderer, you must implement this interface.

View File

@ -141,37 +141,38 @@ export const enum NodeFlags {
None = 0,
TypeElement = 1 << 0,
TypeText = 1 << 1,
ProjectedTemplate = 1 << 2,
CatRenderNode = TypeElement | TypeText,
TypeNgContent = 1 << 2,
TypePipe = 1 << 3,
TypePureArray = 1 << 4,
TypePureObject = 1 << 5,
TypePurePipe = 1 << 6,
TypeNgContent = 1 << 3,
TypePipe = 1 << 4,
TypePureArray = 1 << 5,
TypePureObject = 1 << 6,
TypePurePipe = 1 << 7,
CatPureExpression = TypePureArray | TypePureObject | TypePurePipe,
TypeValueProvider = 1 << 7,
TypeClassProvider = 1 << 8,
TypeFactoryProvider = 1 << 9,
TypeUseExistingProvider = 1 << 10,
LazyProvider = 1 << 11,
PrivateProvider = 1 << 12,
TypeDirective = 1 << 13,
Component = 1 << 14,
TypeValueProvider = 1 << 8,
TypeClassProvider = 1 << 9,
TypeFactoryProvider = 1 << 10,
TypeUseExistingProvider = 1 << 11,
LazyProvider = 1 << 12,
PrivateProvider = 1 << 13,
TypeDirective = 1 << 14,
Component = 1 << 15,
CatProvider = TypeValueProvider | TypeClassProvider | TypeFactoryProvider |
TypeUseExistingProvider | TypeDirective,
OnInit = 1 << 15,
OnDestroy = 1 << 16,
DoCheck = 1 << 17,
OnChanges = 1 << 18,
AfterContentInit = 1 << 19,
AfterContentChecked = 1 << 20,
AfterViewInit = 1 << 21,
AfterViewChecked = 1 << 22,
EmbeddedViews = 1 << 23,
ComponentView = 1 << 24,
TypeContentQuery = 1 << 25,
TypeViewQuery = 1 << 26,
StaticQuery = 1 << 27,
DynamicQuery = 1 << 28,
OnInit = 1 << 16,
OnDestroy = 1 << 17,
DoCheck = 1 << 18,
OnChanges = 1 << 19,
AfterContentInit = 1 << 20,
AfterContentChecked = 1 << 21,
AfterViewInit = 1 << 22,
AfterViewChecked = 1 << 23,
EmbeddedViews = 1 << 24,
ComponentView = 1 << 25,
TypeContentQuery = 1 << 26,
TypeViewQuery = 1 << 27,
StaticQuery = 1 << 28,
DynamicQuery = 1 << 29,
CatQuery = TypeContentQuery | TypeViewQuery,
// mutually exclusive values...
@ -328,7 +329,10 @@ export const enum ViewState {
FirstCheck = 1 << 1,
Attached = 1 << 2,
ChecksEnabled = 1 << 3,
Destroyed = 1 << 4,
IsProjectedView = 1 << 4,
CheckProjectedView = 1 << 5,
CheckProjectedViews = 1 << 6,
Destroyed = 1 << 7,
CatDetectChanges = Attached | ChecksEnabled,
CatInit = BeforeFirstCheck | CatDetectChanges

View File

@ -117,6 +117,14 @@ export function markParentViewsForCheck(view: ViewData) {
}
}
export function markParentViewsForCheckProjectedViews(view: ViewData, endView: ViewData) {
let currView: ViewData|null = view;
while (currView && currView !== endView) {
currView.state |= ViewState.CheckProjectedViews;
currView = currView.viewContainerParent || currView.parent;
}
}
export function dispatchEvent(
view: ViewData, nodeIndex: number, eventName: string, event: any): boolean {
const nodeDef = view.def.nodes[nodeIndex];

View File

@ -17,7 +17,8 @@ import {checkAndUpdateQuery, createQuery} from './query';
import {createTemplateData, createViewContainerData} from './refs';
import {checkAndUpdateTextDynamic, checkAndUpdateTextInline, createText} from './text';
import {ArgumentType, CheckType, ElementData, NodeData, NodeDef, NodeFlags, ProviderData, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewState, ViewUpdateFn, asElementData, asQueryList, asTextData} from './types';
import {NOOP, checkBindingNoChanges, isComponentView, resolveViewDefinition} from './util';
import {NOOP, checkBindingNoChanges, isComponentView, markParentViewsForCheckProjectedViews, resolveViewDefinition} from './util';
import {detachProjectedView} from './view_attach';
export function viewDef(
flags: ViewFlags, nodes: NodeDef[], updateDirectives?: ViewUpdateFn,
@ -314,12 +315,14 @@ function createViewNodes(view: ViewData) {
}
export function checkNoChangesView(view: ViewData) {
markProjectedViewsForCheck(view);
Services.updateDirectives(view, CheckType.CheckNoChanges);
execEmbeddedViewsAction(view, ViewAction.CheckNoChanges);
Services.updateRenderer(view, CheckType.CheckNoChanges);
execComponentViewsAction(view, ViewAction.CheckNoChanges);
// Note: We don't check queries for changes as we didn't do this in v2.x.
// TODO(tbosch): investigate if we can enable the check again in v5.x with a nicer error message.
view.state &= ~(ViewState.CheckProjectedViews | ViewState.CheckProjectedView);
}
export function checkAndUpdateView(view: ViewData) {
@ -329,6 +332,7 @@ export function checkAndUpdateView(view: ViewData) {
} else {
view.state &= ~ViewState.FirstCheck;
}
markProjectedViewsForCheck(view);
Services.updateDirectives(view, CheckType.CheckAndUpdate);
execEmbeddedViewsAction(view, ViewAction.CheckAndUpdate);
execQueriesAction(
@ -343,7 +347,6 @@ export function checkAndUpdateView(view: ViewData) {
execComponentViewsAction(view, ViewAction.CheckAndUpdate);
execQueriesAction(
view, NodeFlags.TypeViewQuery, NodeFlags.DynamicQuery, CheckType.CheckAndUpdate);
callLifecycleHooksChildrenFirst(
view, NodeFlags.AfterViewChecked |
(view.state & ViewState.FirstCheck ? NodeFlags.AfterViewInit : 0));
@ -351,6 +354,7 @@ export function checkAndUpdateView(view: ViewData) {
if (view.def.flags & ViewFlags.OnPush) {
view.state &= ~ViewState.ChecksEnabled;
}
view.state &= ~(ViewState.CheckProjectedViews | ViewState.CheckProjectedView);
}
export function checkAndUpdateNode(
@ -363,6 +367,31 @@ export function checkAndUpdateNode(
}
}
function markProjectedViewsForCheck(view: ViewData) {
const def = view.def;
if (!(def.nodeFlags & NodeFlags.ProjectedTemplate)) {
return;
}
for (let i = 0; i < def.nodes.length; i++) {
const nodeDef = def.nodes[i];
if (nodeDef.flags & NodeFlags.ProjectedTemplate) {
const projectedViews = asElementData(view, i).template._projectedViews;
if (projectedViews) {
for (let i = 0; i < projectedViews.length; i++) {
const projectedView = projectedViews[i];
projectedView.state |= ViewState.CheckProjectedView;
markParentViewsForCheckProjectedViews(projectedView, view);
}
}
} else if ((nodeDef.childFlags & NodeFlags.ProjectedTemplate) === 0) {
// a parent with leafs
// no child is a component,
// then skip the children
i += nodeDef.childCount;
}
}
}
function checkAndUpdateNodeInline(
view: ViewData, nodeDef: NodeDef, v0?: any, v1?: any, v2?: any, v3?: any, v4?: any, v5?: any,
v6?: any, v7?: any, v8?: any, v9?: any): boolean {
@ -474,6 +503,7 @@ export function destroyView(view: ViewData) {
view.disposables[i]();
}
}
detachProjectedView(view);
if (view.renderer.destroyNode) {
destroyViewNodes(view);
}
@ -498,7 +528,9 @@ function destroyViewNodes(view: ViewData) {
enum ViewAction {
CreateViewNodes,
CheckNoChanges,
CheckNoChangesProjectedViews,
CheckAndUpdate,
CheckAndUpdateProjectedViews,
Destroy
}
@ -547,18 +579,44 @@ function callViewAction(view: ViewData, action: ViewAction) {
const viewState = view.state;
switch (action) {
case ViewAction.CheckNoChanges:
if ((viewState & ViewState.CatDetectChanges) === ViewState.CatDetectChanges &&
(viewState & ViewState.Destroyed) === 0) {
checkNoChangesView(view);
if ((viewState & ViewState.Destroyed) === 0) {
if ((viewState & ViewState.CatDetectChanges) === ViewState.CatDetectChanges) {
checkNoChangesView(view);
} else if (viewState & ViewState.CheckProjectedViews) {
execProjectedViewsAction(view, ViewAction.CheckNoChangesProjectedViews);
}
}
break;
case ViewAction.CheckNoChangesProjectedViews:
if ((viewState & ViewState.Destroyed) === 0) {
if (viewState & ViewState.CheckProjectedView) {
checkNoChangesView(view);
} else if (viewState & ViewState.CheckProjectedViews) {
execProjectedViewsAction(view, action);
}
}
break;
case ViewAction.CheckAndUpdate:
if ((viewState & ViewState.CatDetectChanges) === ViewState.CatDetectChanges &&
(viewState & ViewState.Destroyed) === 0) {
checkAndUpdateView(view);
if ((viewState & ViewState.Destroyed) === 0) {
if ((viewState & ViewState.CatDetectChanges) === ViewState.CatDetectChanges) {
checkAndUpdateView(view);
} else if (viewState & ViewState.CheckProjectedViews) {
execProjectedViewsAction(view, ViewAction.CheckAndUpdateProjectedViews);
}
}
break;
case ViewAction.CheckAndUpdateProjectedViews:
if ((viewState & ViewState.Destroyed) === 0) {
if (viewState & ViewState.CheckProjectedView) {
checkAndUpdateView(view);
} else if (viewState & ViewState.CheckProjectedViews) {
execProjectedViewsAction(view, action);
}
}
break;
case ViewAction.Destroy:
// Note: destroyView recurses over all views,
// so we don't need to special case projected views here.
destroyView(view);
break;
case ViewAction.CreateViewNodes:
@ -567,6 +625,11 @@ function callViewAction(view: ViewData, action: ViewAction) {
}
}
function execProjectedViewsAction(view: ViewData, action: ViewAction) {
execEmbeddedViewsAction(view, action);
execComponentViewsAction(view, action);
}
function execQueriesAction(
view: ViewData, queryFlags: NodeFlags, staticDynamicQueryFlag: NodeFlags,
checkType: CheckType) {

View File

@ -6,8 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ElementData, Services, ViewData} from './types';
import {RenderNodeAction, declaredViewContainer, renderNode, visitRootRenderNodes} from './util';
import {ElementData, NodeDef, NodeFlags, Services, ViewData, ViewDefinition, ViewState} from './types';
import {RenderNodeAction, declaredViewContainer, isComponentView, renderNode, visitRootRenderNodes} from './util';
export function attachEmbeddedView(
parentView: ViewData, elementData: ElementData, viewIndex: number | undefined | null,
@ -18,14 +18,7 @@ export function attachEmbeddedView(
}
view.viewContainerParent = parentView;
addToArray(embeddedViews, viewIndex !, view);
const dvcElementData = declaredViewContainer(view);
if (dvcElementData && dvcElementData !== elementData) {
let projectedViews = dvcElementData.template._projectedViews;
if (!projectedViews) {
projectedViews = dvcElementData.template._projectedViews = [];
}
projectedViews.push(view);
}
attachProjectedView(elementData, view);
Services.dirtyParentQueries(view);
@ -33,6 +26,43 @@ export function attachEmbeddedView(
renderAttachEmbeddedView(elementData, prevView, view);
}
function attachProjectedView(vcElementData: ElementData, view: ViewData) {
const dvcElementData = declaredViewContainer(view);
if (!dvcElementData || dvcElementData === vcElementData ||
view.state & ViewState.IsProjectedView) {
return;
}
// Note: For performance reasons, we
// - add a view to template._projectedViews only 1x throughout its lifetime,
// and remove it not until the view is destroyed.
// (hard, as when a parent view is attached/detached we would need to attach/detach all
// nested projected views as well, even accross component boundaries).
// - don't track the insertion order of views in the projected views array
// (hard, as when the views of the same template are inserted different view containers)
view.state |= ViewState.IsProjectedView;
let projectedViews = dvcElementData.template._projectedViews;
if (!projectedViews) {
projectedViews = dvcElementData.template._projectedViews = [];
}
projectedViews.push(view);
// Note: we are changing the NodeDef here as we cannot calculate
// the fact whether a template is used for projection during compilation.
markNodeAsProjectedTemplate(view.parent !.def, view.parentNodeDef !);
}
function markNodeAsProjectedTemplate(viewDef: ViewDefinition, nodeDef: NodeDef) {
if (nodeDef.flags & NodeFlags.ProjectedTemplate) {
return;
}
viewDef.nodeFlags |= NodeFlags.ProjectedTemplate;
nodeDef.flags |= NodeFlags.ProjectedTemplate;
let parentNodeDef = nodeDef.parent;
while (parentNodeDef) {
parentNodeDef.childFlags |= NodeFlags.ProjectedTemplate;
parentNodeDef = parentNodeDef.parent;
}
}
export function detachEmbeddedView(elementData: ElementData, viewIndex?: number): ViewData|null {
const embeddedViews = elementData.viewContainer !._embeddedViews;
if (viewIndex == null || viewIndex >= embeddedViews.length) {
@ -45,12 +75,7 @@ export function detachEmbeddedView(elementData: ElementData, viewIndex?: number)
view.viewContainerParent = null;
removeFromArray(embeddedViews, viewIndex);
const dvcElementData = declaredViewContainer(view);
if (dvcElementData && dvcElementData !== elementData) {
const projectedViews = dvcElementData.template._projectedViews;
removeFromArray(projectedViews, projectedViews.indexOf(view));
}
// See attachProjectedView for why we don't update projectedViews here.
Services.dirtyParentQueries(view);
renderDetachView(view);
@ -58,6 +83,20 @@ export function detachEmbeddedView(elementData: ElementData, viewIndex?: number)
return view;
}
export function detachProjectedView(view: ViewData) {
if (!(view.state & ViewState.IsProjectedView)) {
return;
}
const dvcElementData = declaredViewContainer(view);
if (dvcElementData) {
const projectedViews = dvcElementData.template._projectedViews;
if (projectedViews) {
removeFromArray(projectedViews, projectedViews.indexOf(view));
Services.dirtyParentQueries(view);
}
}
}
export function moveEmbeddedView(
elementData: ElementData, oldViewIndex: number, newViewIndex: number): ViewData {
const embeddedViews = elementData.viewContainer !._embeddedViews;

View File

@ -8,11 +8,12 @@
import {ElementSchemaRegistry} from '@angular/compiler/src/schema/element_schema_registry';
import {TEST_COMPILER_PROVIDERS} from '@angular/compiler/testing/src/test_bindings';
import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, DebugElement, Directive, DoCheck, EventEmitter, HostBinding, Inject, Injectable, Input, OnChanges, OnDestroy, OnInit, Output, Pipe, PipeTransform, RenderComponentType, Renderer, RendererFactory2, RootRenderer, SimpleChange, SimpleChanges, TemplateRef, Type, ViewChild, ViewContainerRef, WrappedValue} from '@angular/core';
import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, DebugElement, Directive, DoCheck, EventEmitter, HostBinding, Inject, Injectable, Input, OnChanges, OnDestroy, OnInit, Output, Pipe, PipeTransform, RenderComponentType, Renderer, RendererFactory2, RootRenderer, SimpleChange, SimpleChanges, TemplateRef, Type, ViewChild, ViewContainerRef, WrappedValue} from '@angular/core';
import {ComponentFixture, TestBed, fakeAsync} from '@angular/core/testing';
import {By} from '@angular/platform-browser/src/dom/debug/by';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {DomElementSchemaRegistry} from '../../../compiler/index';
import {MockSchemaRegistry} from '../../../compiler/testing/index';
@ -1293,6 +1294,120 @@ export function main() {
ctx.detectChanges();
expect(renderLog.loggedValues).toEqual(['Tom']);
});
describe('projected views', () => {
let log: string[];
@Directive({selector: '[i]'})
class DummyDirective {
@Input()
i: any;
}
@Component({
selector: 'main-cmp',
template:
`<span [i]="log('start')"></span><outer-cmp><ng-template><span [i]="log('tpl')"></span></ng-template></outer-cmp>`
})
class MainComp {
constructor(public cdRef: ChangeDetectorRef) {}
log(id: string) { log.push(`main-${id}`); }
}
@Component({
selector: 'outer-cmp',
template:
`<span [i]="log('start')"></span><inner-cmp [outerTpl]="tpl"><ng-template><span [i]="log('tpl')"></span></ng-template></inner-cmp>`
})
class OuterComp {
@ContentChild(TemplateRef)
tpl: TemplateRef<any>;
constructor(public cdRef: ChangeDetectorRef) {}
log(id: string) { log.push(`outer-${id}`); }
}
@Component({
selector: 'inner-cmp',
template:
`<span [i]="log('start')"></span>><ng-container [ngTemplateOutlet]="outerTpl"></ng-container><ng-container [ngTemplateOutlet]="tpl"></ng-container>`
})
class InnerComp {
@ContentChild(TemplateRef)
tpl: TemplateRef<any>;
@Input()
outerTpl: TemplateRef<any>
constructor(public cdRef: ChangeDetectorRef) {}
log(id: string) { log.push(`inner-${id}`); }
}
let ctx: ComponentFixture<MainComp>;
let mainComp: MainComp;
let outerComp: OuterComp;
let innerComp: InnerComp;
beforeEach(() => {
log = [];
ctx = TestBed
.configureTestingModule(
{declarations: [MainComp, OuterComp, InnerComp, DummyDirective]})
.createComponent(MainComp);
mainComp = ctx.componentInstance;
outerComp = ctx.debugElement.query(By.directive(OuterComp)).injector.get(OuterComp);
innerComp = ctx.debugElement.query(By.directive(InnerComp)).injector.get(InnerComp);
});
it('should dirty check projected views in regular order', () => {
ctx.detectChanges(false);
expect(log).toEqual(
['main-start', 'outer-start', 'inner-start', 'main-tpl', 'outer-tpl']);
log = [];
ctx.detectChanges(false);
expect(log).toEqual(
['main-start', 'outer-start', 'inner-start', 'main-tpl', 'outer-tpl']);
});
it('should not dirty check projected views if neither the declaration nor the insertion place is dirty checked',
() => {
ctx.detectChanges(false);
log = [];
mainComp.cdRef.detach();
ctx.detectChanges(false);
expect(log).toEqual([]);
});
it('should dirty check projected views if the insertion place is dirty checked', () => {
ctx.detectChanges(false);
log = [];
innerComp.cdRef.detectChanges();
expect(log).toEqual(['inner-start', 'main-tpl', 'outer-tpl']);
});
it('should dirty check projected views if the declaration place is dirty checked', () => {
ctx.detectChanges(false);
log = [];
innerComp.cdRef.detach();
mainComp.cdRef.detectChanges();
expect(log).toEqual(['main-start', 'outer-start', 'main-tpl', 'outer-tpl']);
log = [];
outerComp.cdRef.detectChanges();
expect(log).toEqual(['outer-start', 'outer-tpl']);
log = [];
outerComp.cdRef.detach();
mainComp.cdRef.detectChanges();
expect(log).toEqual(['main-start', 'main-tpl']);
});
});
});
describe('class binding', () => {

View File

@ -536,6 +536,52 @@ export function main() {
expect(q.query.length).toBe(0);
});
// Note: This tests is just document our current behavior, which we do
// for performance reasons.
it('should not affected queries for projected templates if views are detached or moved', () => {
const template =
'<manual-projecting #q><ng-template let-x="x"><div [text]="x"></div></ng-template></manual-projecting>';
const view = createTestCmpAndDetectChanges(MyComp0, template);
const q = view.debugElement.children[0].references !['q'] as ManualProjecting;
expect(q.query.length).toBe(0);
const view1 = q.vc.createEmbeddedView(q.template, {'x': '1'});
const view2 = q.vc.createEmbeddedView(q.template, {'x': '2'});
view.detectChanges();
expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '2']);
q.vc.detach(1);
q.vc.detach(0);
view.detectChanges();
expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '2']);
q.vc.insert(view2);
q.vc.insert(view1);
view.detectChanges();
expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '2']);
});
it('should remove manually projected templates if their parent view is destroyed', () => {
const template = `
<manual-projecting #q><ng-template #tpl><div text="1"></div></ng-template></manual-projecting>
<div *ngIf="shouldShow">
<ng-container [ngTemplateOutlet]="tpl"></ng-container>
</div>
`;
const view = createTestCmp(MyComp0, template);
const q = view.debugElement.children[0].references !['q'];
view.componentInstance.shouldShow = true;
view.detectChanges();
expect(q.query.length).toBe(1);
view.componentInstance.shouldShow = false;
view.detectChanges();
expect(q.query.length).toBe(0);
});
it('should not throw if a content template is queried and created in the view during change detection',
() => {
@Component(

View File

@ -128,7 +128,7 @@ export const formArrayNameProvider: any = {
* status is re-calculated.
*
* **Add new controls**: You can add new controls to the {@link FormArray} dynamically by
* calling its {@link FormArray.push} method.
* calling its {@link FormArray#push} method.
* Ex: `this.form.get('cities').push(new FormControl());`
*
* ### Example

View File

@ -676,8 +676,8 @@ export class FormControl extends AbstractControl {
/**
* Patches the value of a control.
*
* This function is functionally the same as {@link FormControl.setValue} at this level.
* It exists for symmetry with {@link FormGroup.patchValue} on `FormGroups` and `FormArrays`,
* This function is functionally the same as {@link FormControl#setValue} at this level.
* It exists for symmetry with {@link FormGroup#patchValue} on `FormGroups` and `FormArrays`,
* where it does behave differently.
*/
patchValue(value: any, options: {
@ -842,7 +842,7 @@ export class FormGroup extends AbstractControl {
* Registers a control with the group's list of controls.
*
* This method does not update value or validity of the control, so for
* most cases you'll want to use {@link FormGroup.addControl} instead.
* most cases you'll want to use {@link FormGroup#addControl} instead.
*/
registerControl(name: string, control: AbstractControl): AbstractControl {
if (this.controls[name]) return this.controls[name];

View File

@ -37,14 +37,32 @@ export abstract class Body {
/**
* Returns the body as a string, presuming `toString()` can be called on the response body.
*
* When decoding an `ArrayBuffer`, the optional `encodingHint` parameter determines how the
* bytes in the buffer will be interpreted. Valid values are:
*
* - `legacy` - incorrectly interpret the bytes as UTF-16 (technically, UCS-2). Only characters
* in the Basic Multilingual Plane are supported, surrogate pairs are not handled correctly.
* In addition, the endianness of the 16-bit octet pairs in the `ArrayBuffer` is not taken
* into consideration. This is the default behavior to avoid breaking apps, but should be
* considered deprecated.
*
* - `iso-8859` - interpret the bytes as ISO-8859 (which can be used for ASCII encoded text).
*/
text(): string {
text(encodingHint: 'legacy'|'iso-8859' = 'legacy'): string {
if (this._body instanceof URLSearchParams) {
return this._body.toString();
}
if (this._body instanceof ArrayBuffer) {
return String.fromCharCode.apply(null, new Uint16Array(<ArrayBuffer>this._body));
switch (encodingHint) {
case 'legacy':
return String.fromCharCode.apply(null, new Uint16Array(this._body as ArrayBuffer));
case 'iso-8859':
return String.fromCharCode.apply(null, new Uint8Array(this._body as ArrayBuffer));
default:
throw new Error(`Invalid value for encodingHint: ${encodingHint}`);
}
}
if (this._body == null) {

View File

@ -42,6 +42,15 @@ export function getResponseURL(xhr: any): string|null {
return null;
}
export function stringToArrayBuffer8(input: String): ArrayBuffer {
const view = new Uint8Array(input.length);
for (let i = 0, strLen = input.length; i < strLen; i++) {
view[i] = input.charCodeAt(i);
}
return view.buffer;
}
export function stringToArrayBuffer(input: String): ArrayBuffer {
const view = new Uint16Array(input.length);
for (let i = 0, strLen = input.length; i < strLen; i++) {

View File

@ -76,8 +76,14 @@ export class Request extends Body {
// TODO: assert that url is present
const url = requestOptions.url;
this.url = requestOptions.url !;
if (requestOptions.params) {
const params = requestOptions.params.toString();
const paramsArg = requestOptions.params || requestOptions.search;
if (paramsArg) {
let params: string;
if (typeof paramsArg === 'object' && !(paramsArg instanceof URLSearchParams)) {
params = urlEncodeParams(paramsArg).toString();
} else {
params = paramsArg.toString();
}
if (params.length > 0) {
let prefix = '?';
if (this.url.indexOf('?') != -1) {
@ -163,8 +169,22 @@ export class Request extends Body {
}
}
function urlEncodeParams(params: {[key: string]: any}): URLSearchParams {
const searchParams = new URLSearchParams();
Object.keys(params).forEach(key => {
const value = params[key];
if (value && Array.isArray(value)) {
value.forEach(element => searchParams.append(key, element.toString()));
} else {
searchParams.append(key, value.toString());
}
});
return searchParams;
}
const noop = function() {};
const w = typeof window == 'object' ? window : noop;
const FormData = (w as any /** TODO #9100 */)['FormData'] || noop;
const Blob = (w as any /** TODO #9100 */)['Blob'] || noop;
export const ArrayBuffer = (w as any /** TODO #9100 */)['ArrayBuffer'] || noop;
export const ArrayBuffer: ArrayBufferConstructor =
(w as any /** TODO #9100 */)['ArrayBuffer'] || noop;

View File

@ -7,10 +7,12 @@
*/
import {describe, expect, it} from '@angular/core/testing/src/testing_internal';
import {ɵgetDOM as getDOM} from '@angular/platform-browser';
import {RequestOptions} from '../src/base_request_options';
import {ContentType} from '../src/enums';
import {Headers} from '../src/headers';
import {stringToArrayBuffer, stringToArrayBuffer8} from '../src/http_utils';
import {ArrayBuffer, Request} from '../src/static_request';
export function main() {
@ -109,5 +111,28 @@ export function main() {
expect(req.text()).toEqual('');
});
it('should use object params', () => {
const req = new Request({url: 'http://test.com', params: {'a': 3, 'b': ['x', 'y']}});
expect(req.url).toBe('http://test.com?a=3&b=x&b=y');
});
it('should use search if present', () => {
const req = new Request({url: 'http://test.com', search: 'a=1&b=2'});
expect(req.url).toBe('http://test.com?a=1&b=2');
});
if (getDOM().supportsWebAnimation()) {
it('should serialize an ArrayBuffer to string via legacy encoding', () => {
const str = '\u89d2\u5ea6';
expect(new Request({body: stringToArrayBuffer(str), url: '/'}).text()).toEqual(str);
});
it('should serialize an ArrayBuffer to string via iso-8859 encoding', () => {
const str = 'abcd';
expect(new Request({body: stringToArrayBuffer8(str), url: '/'}).text('iso-8859'))
.toEqual(str);
});
}
});
}

View File

@ -0,0 +1,14 @@
/**
* @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
*/
/**
* @module
* @description
* Entry point for all public APIs of the http testing package.
*/
export * from './src/testing';

View File

@ -8,9 +8,12 @@
}
},
"files": [
"index.ts"
"public_api.ts"
],
"angularCompilerOptions": {
"strictMetadataEmit": true
"annotateForClosureCompiler": true,
"strictMetadataEmit": true,
"flatModuleOutFile": "index.js",
"flatModuleId": "@angular/http/testing"
}
}

View File

@ -358,7 +358,13 @@ class ApplyRedirects {
private createQueryParams(redirectToParams: Params, actualParams: Params): Params {
const res: Params = {};
forEach(redirectToParams, (v: any, k: string) => {
res[k] = v.startsWith(':') ? actualParams[v.substring(1)] : v;
const copySourceValue = typeof v === 'string' && v.startsWith(':');
if (copySourceValue) {
const sourceName = v.substring(1);
res[k] = actualParams[sourceName];
} else {
res[k] = v;
}
});
return res;
}

View File

@ -72,7 +72,7 @@ import {UrlTree} from '../url_tree';
* - 'merge' merge the queryParams into the current queryParams
* - 'preserve' prserve the current queryParams
* - default / '' use the queryParams only
* same options for {@link NavigationExtras.queryParamsHandling}
* same options for {@link NavigationExtras#queryParamsHandling}
*
* ```
* <a [routerLink]="['/user/bob']" [queryParams]="{debug: true}" queryParamsHandling="merge">
@ -89,7 +89,7 @@ import {UrlTree} from '../url_tree';
*
* @ngModule RouterModule
*
* See {@link Router.createUrlTree} for more information.
* See {@link Router#createUrlTree} for more information.
*
* @stable
*/

View File

@ -32,13 +32,24 @@ describe('applyRedirects', () => {
],
},
],
'/a/b', (t: UrlTree) => { compareTrees(t, tree('/a/b')); });
'/a/b', (t: UrlTree) => { expectTreeToBe(t, '/a/b'); });
});
it('should add new segments when needed', () => {
checkRedirect(
[{path: 'a/b', redirectTo: 'a/b/c'}, {path: '**', component: ComponentC}], '/a/b',
(t: UrlTree) => { compareTrees(t, tree('/a/b/c')); });
(t: UrlTree) => { expectTreeToBe(t, '/a/b/c'); });
});
it('should support redirecting with to an URL with query parameters', () => {
const config: Routes = [
{path: 'single_value', redirectTo: '/dst?k=v1'},
{path: 'multiple_values', redirectTo: '/dst?k=v1&k=v2'},
{path: '**', component: ComponentA},
];
checkRedirect(config, 'single_value', (t: UrlTree) => expectTreeToBe(t, '/dst?k=v1'));
checkRedirect(config, 'multiple_values', (t: UrlTree) => expectTreeToBe(t, '/dst?k=v1&k=v2'));
});
it('should handle positional parameters', () => {
@ -47,7 +58,7 @@ describe('applyRedirects', () => {
{path: 'a/:aid/b/:bid', redirectTo: 'newa/:aid/newb/:bid'},
{path: '**', component: ComponentC}
],
'/a/1/b/2', (t: UrlTree) => { compareTrees(t, tree('/newa/1/newb/2')); });
'/a/1/b/2', (t: UrlTree) => { expectTreeToBe(t, '/newa/1/newb/2'); });
});
it('should throw when cannot handle a positional parameter', () => {
@ -61,7 +72,7 @@ describe('applyRedirects', () => {
it('should pass matrix parameters', () => {
checkRedirect(
[{path: 'a/:id', redirectTo: 'd/a/:id/e'}, {path: '**', component: ComponentC}],
'/a;p1=1/1;p2=2', (t: UrlTree) => { compareTrees(t, tree('/d/a;p1=1/1;p2=2/e')); });
'/a;p1=1/1;p2=2', (t: UrlTree) => { expectTreeToBe(t, '/d/a;p1=1/1;p2=2/e'); });
});
it('should handle preserve secondary routes', () => {
@ -70,7 +81,7 @@ describe('applyRedirects', () => {
{path: 'a/:id', redirectTo: 'd/a/:id/e'},
{path: 'c/d', component: ComponentA, outlet: 'aux'}, {path: '**', component: ComponentC}
],
'/a/1(aux:c/d)', (t: UrlTree) => { compareTrees(t, tree('/d/a/1/e(aux:c/d)')); });
'/a/1(aux:c/d)', (t: UrlTree) => { expectTreeToBe(t, '/d/a/1/e(aux:c/d)'); });
});
it('should redirect secondary routes', () => {
@ -80,7 +91,7 @@ describe('applyRedirects', () => {
{path: 'c/d', redirectTo: 'f/c/d/e', outlet: 'aux'},
{path: '**', component: ComponentC, outlet: 'aux'}
],
'/a/1(aux:c/d)', (t: UrlTree) => { compareTrees(t, tree('/a/1(aux:f/c/d/e)')); });
'/a/1(aux:c/d)', (t: UrlTree) => { expectTreeToBe(t, '/a/1(aux:f/c/d/e)'); });
});
it('should use the configuration of the route redirected to', () => {
@ -95,7 +106,7 @@ describe('applyRedirects', () => {
},
{path: 'c', redirectTo: 'a'}
],
'c/b', (t: UrlTree) => { compareTrees(t, tree('a/b')); });
'c/b', (t: UrlTree) => { expectTreeToBe(t, 'a/b'); });
});
it('should support redirects with both main and aux', () => {
@ -109,7 +120,7 @@ describe('applyRedirects', () => {
{path: 'b', redirectTo: 'cc', outlet: 'aux'}
]
}],
'a/(b//aux:b)', (t: UrlTree) => { compareTrees(t, tree('a/(bb//aux:cc)')); });
'a/(b//aux:b)', (t: UrlTree) => { expectTreeToBe(t, 'a/(bb//aux:cc)'); });
});
it('should support redirects with both main and aux (with a nested redirect)', () => {
@ -128,7 +139,7 @@ describe('applyRedirects', () => {
{path: 'b', redirectTo: 'cc/d', outlet: 'aux'}
]
}],
'a/(b//aux:b)', (t: UrlTree) => { compareTrees(t, tree('a/(bb//aux:cc/dd)')); });
'a/(b//aux:b)', (t: UrlTree) => { expectTreeToBe(t, 'a/(bb//aux:cc/dd)'); });
});
it('should redirect wild cards', () => {
@ -137,7 +148,7 @@ describe('applyRedirects', () => {
{path: '404', component: ComponentA},
{path: '**', redirectTo: '/404'},
],
'/a/1(aux:c/d)', (t: UrlTree) => { compareTrees(t, tree('/404')); });
'/a/1(aux:c/d)', (t: UrlTree) => { expectTreeToBe(t, '/404'); });
});
it('should support absolute redirects', () => {
@ -150,7 +161,7 @@ describe('applyRedirects', () => {
},
{path: '**', component: ComponentC}
],
'/a/b/1?b=2', (t: UrlTree) => { compareTrees(t, tree('/absolute/1?a=1&b=2#f1')); });
'/a/b/1?b=2', (t: UrlTree) => { expectTreeToBe(t, '/absolute/1?a=1&b=2#f1'); });
});
describe('lazy loading', () => {
@ -166,7 +177,7 @@ describe('applyRedirects', () => {
applyRedirects(testModule.injector, <any>loader, serializer, tree('a/b'), config)
.forEach(r => {
compareTrees(r, tree('/a/b'));
expectTreeToBe(r, '/a/b');
expect(config[0]._loadedConfig).toBe(loadedConfig);
});
});
@ -198,7 +209,7 @@ describe('applyRedirects', () => {
}];
applyRedirects(<any>injector, <any>loader, serializer, tree('a/b'), config).forEach(r => {
compareTrees(r, tree('/a/b'));
expectTreeToBe(r, '/a/b');
});
});
@ -279,8 +290,7 @@ describe('applyRedirects', () => {
[{path: 'a', component: ComponentA, canLoad: ['guard'], loadChildren: 'children'}];
applyRedirects(<any>injector, <any>loader, serializer, tree('a/b'), config)
.subscribe(
(r) => { compareTrees(r, tree('/a/b')); }, (e) => { throw 'Should not reach'; });
.subscribe((r) => { expectTreeToBe(r, '/a/b'); }, (e) => { throw 'Should not reach'; });
});
@ -293,7 +303,7 @@ describe('applyRedirects', () => {
[{path: '', pathMatch: 'full', redirectTo: '/a'}, {path: 'a', loadChildren: 'children'}];
applyRedirects(testModule.injector, <any>loader, serializer, tree(''), config).forEach(r => {
compareTrees(r, tree('a'));
expectTreeToBe(r, 'a');
expect(config[1]._loadedConfig).toBe(loadedConfig);
});
});
@ -318,7 +328,7 @@ describe('applyRedirects', () => {
applyRedirects(testModule.injector, <any>loader, serializer, tree('a?k2'), config)
.subscribe(
r => {
compareTrees(r, tree('a?k2'));
expectTreeToBe(r, 'a?k2');
expect(config[0]._loadedConfig).toBe(loadedConfig);
},
(e) => { throw 'Should not reach'; });
@ -373,7 +383,7 @@ describe('applyRedirects', () => {
},
{path: '', redirectTo: 'a'}
],
'b', (t: UrlTree) => { compareTrees(t, tree('a/b')); });
'b', (t: UrlTree) => { expectTreeToBe(t, 'a/b'); });
});
it('redirect from an empty path should work (absolute redirect)', () => {
@ -388,7 +398,7 @@ describe('applyRedirects', () => {
},
{path: '', redirectTo: '/a/b'}
],
'', (t: UrlTree) => { compareTrees(t, tree('a/b')); });
'', (t: UrlTree) => { expectTreeToBe(t, 'a/b'); });
});
it('should redirect empty path route only when terminal', () => {
@ -419,7 +429,7 @@ describe('applyRedirects', () => {
},
{path: '', redirectTo: 'a'}
],
'', (t: UrlTree) => { compareTrees(t, tree('a/b')); });
'', (t: UrlTree) => { expectTreeToBe(t, 'a/b'); });
});
it('redirect to an empty path should work', () => {
@ -428,7 +438,7 @@ describe('applyRedirects', () => {
{path: '', component: ComponentA, children: [{path: 'b', component: ComponentB}]},
{path: 'a', redirectTo: ''}
],
'a/b', (t: UrlTree) => { compareTrees(t, tree('b')); });
'a/b', (t: UrlTree) => { expectTreeToBe(t, 'b'); });
});
describe('aux split is in the middle', () => {
@ -442,7 +452,7 @@ describe('applyRedirects', () => {
{path: '', redirectTo: 'c', outlet: 'aux'}
]
}],
'a/b', (t: UrlTree) => { compareTrees(t, tree('a/(b//aux:c)')); });
'a/b', (t: UrlTree) => { expectTreeToBe(t, 'a/(b//aux:c)'); });
});
it('should create a new url segment (terminal)', () => {
@ -455,7 +465,7 @@ describe('applyRedirects', () => {
{path: '', pathMatch: 'full', redirectTo: 'c', outlet: 'aux'}
]
}],
'a/b', (t: UrlTree) => { compareTrees(t, tree('a/b')); });
'a/b', (t: UrlTree) => { expectTreeToBe(t, 'a/b'); });
});
});
@ -470,7 +480,7 @@ describe('applyRedirects', () => {
{path: '', redirectTo: 'c', outlet: 'aux'}
]
}],
'a', (t: UrlTree) => { compareTrees(t, tree('a/(b//aux:c)')); });
'a', (t: UrlTree) => { expectTreeToBe(t, 'a/(b//aux:c)'); });
});
it('should create a new child (terminal)', () => {
@ -483,7 +493,7 @@ describe('applyRedirects', () => {
{path: '', pathMatch: 'full', redirectTo: 'c', outlet: 'aux'}
]
}],
'a', (t: UrlTree) => { compareTrees(t, tree('a/(b//aux:c)')); });
'a', (t: UrlTree) => { expectTreeToBe(t, 'a/(b//aux:c)'); });
});
it('should work only only primary outlet', () => {
@ -495,7 +505,7 @@ describe('applyRedirects', () => {
{path: 'c', component: ComponentC, outlet: 'aux'}
]
}],
'a/(aux:c)', (t: UrlTree) => { compareTrees(t, tree('a/(b//aux:c)')); });
'a/(aux:c)', (t: UrlTree) => { expectTreeToBe(t, 'a/(b//aux:c)'); });
});
});
@ -515,7 +525,7 @@ describe('applyRedirects', () => {
{path: '', redirectTo: 'c', outlet: 'aux'}
]
}],
'a/(d//aux:e)', (t: UrlTree) => { compareTrees(t, tree('a/(b/d//aux:c/e)')); });
'a/(d//aux:e)', (t: UrlTree) => { expectTreeToBe(t, 'a/(b/d//aux:c/e)'); });
});
it('should not create a new child (terminal)', () => {
@ -545,7 +555,7 @@ describe('applyRedirects', () => {
it('should not error when no children matching and no url is left', () => {
checkRedirect(
[{path: 'a', component: ComponentA, children: [{path: 'b', component: ComponentB}]}],
'/a', (t: UrlTree) => { compareTrees(t, tree('a')); });
'/a', (t: UrlTree) => { expectTreeToBe(t, 'a'); });
});
it('should not error when no children matching and no url is left (aux routes)', () => {
@ -559,7 +569,7 @@ describe('applyRedirects', () => {
{path: 'c', component: ComponentC, outlet: 'aux'},
]
}],
'/a', (t: UrlTree) => { compareTrees(t, tree('a/(aux:c)')); });
'/a', (t: UrlTree) => { expectTreeToBe(t, 'a/(aux:c)'); });
});
it('should error when no children matching and some url is left', () => {
@ -588,7 +598,7 @@ describe('applyRedirects', () => {
component: ComponentA,
children: [{path: 'b', component: ComponentB}]
}] as any,
'/a/1/b', (t: UrlTree) => { compareTrees(t, tree('a/1/b')); });
'/a/1/b', (t: UrlTree) => { expectTreeToBe(t, 'a/1/b'); });
});
});
@ -600,7 +610,7 @@ describe('applyRedirects', () => {
{path: 'b/:id', component: ComponentB},
{path: 'c/:id', component: ComponentC, outlet: 'aux'}
],
'a/1;p=99', (t: UrlTree) => { compareTrees(t, tree('/b/1;p=99(aux:c/1;p=99)')); });
'a/1;p=99', (t: UrlTree) => { expectTreeToBe(t, '/b/1;p=99(aux:c/1;p=99)'); });
});
it('should work when using absolute redirects (wildcard)', () => {
@ -609,7 +619,7 @@ describe('applyRedirects', () => {
{path: '**', redirectTo: '/b(aux:c)'}, {path: 'b', component: ComponentB},
{path: 'c', component: ComponentC, outlet: 'aux'}
],
'a/1', (t: UrlTree) => { compareTrees(t, tree('/b(aux:c)')); });
'a/1', (t: UrlTree) => { expectTreeToBe(t, '/b(aux:c)'); });
});
it('should throw when using non-absolute redirects', () => {
@ -637,7 +647,8 @@ function tree(url: string): UrlTree {
return new DefaultUrlSerializer().parse(url);
}
function compareTrees(actual: UrlTree, expected: UrlTree): void {
function expectTreeToBe(actual: UrlTree, expectedUrl: string): void {
const expected = tree(expectedUrl);
const serializer = new DefaultUrlSerializer();
const error =
`"${serializer.serialize(actual)}" is not equal to "${serializer.serialize(expected)}"`;

View File

@ -18,7 +18,7 @@ let downgradeCount = 0;
/**
* @whatItDoes
*
* *Part of the [upgrade/static](/docs/ts/latest/api/#!?query=upgrade%2Fstatic)
* *Part of the [upgrade/static](api?query=upgrade%2Fstatic)
* library for hybrid upgrade apps that support AoT compilation*
*
* Allows an Angular component to be used from AngularJS.

View File

@ -12,7 +12,7 @@ import {INJECTOR_KEY} from './constants';
/**
* @whatItDoes
*
* *Part of the [upgrade/static](/docs/ts/latest/api/#!?query=upgrade%2Fstatic)
* *Part of the [upgrade/static](api?query=upgrade%2Fstatic)
* library for hybrid upgrade apps that support AoT compilation*
*
* Allow an Angular service to be accessible from AngularJS.

View File

@ -43,7 +43,7 @@ type LifecycleHook = '$doCheck' | '$onChanges' | '$onDestroy' | '$onInit' | '$po
/**
* @whatItDoes
*
* *Part of the [upgrade/static](/docs/ts/latest/api/#!?query=upgrade%2Fstatic)
* *Part of the [upgrade/static](api?query=upgrade%2Fstatic)
* library for hybrid upgrade apps that support AoT compilation*
*
* Allows an AngularJS component to be used from Angular.

View File

@ -18,7 +18,7 @@ import {angular1Providers, setTempInjectorRef} from './angular1_providers';
/**
* @whatItDoes
*
* *Part of the [upgrade/static](/docs/ts/latest/api/#!?query=upgrade%2Fstatic)
* *Part of the [upgrade/static](api?query=upgrade%2Fstatic)
* library for hybrid upgrade apps that support AoT compilation*
*
* Allows AngularJS and Angular components to be used together inside a hybrid upgrade
@ -88,6 +88,7 @@ import {angular1Providers, setTempInjectorRef} from './angular1_providers';
*
* {@example upgrade/static/ts/module.ts region='bootstrap'}
*
* {@a upgrading-an-angular-1-service}
*
* ## Upgrading an AngularJS service
*

View File

@ -1,6 +1,6 @@
{
"name": "@angular/tsc-wrapped",
"version": "4.1.1",
"version": "4.1.2",
"description": "Wraps the tsc CLI, allowing extensions.",
"homepage": "https://github.com/angular/angular/tree/master/tools/tsc-wrapped",
"bugs": "https://github.com/angular/angular/issues",

View File

@ -532,8 +532,8 @@ export interface ValidatorFn {
/** @stable */
export declare class Validators {
static compose(validators: (ValidatorFn | null | undefined)[]): ValidatorFn | null;
static compose(validators: null): null;
static compose(validators: (ValidatorFn | null | undefined)[]): ValidatorFn | null;
static composeAsync(validators: (AsyncValidatorFn | null)[]): AsyncValidatorFn | null;
static email(control: AbstractControl): ValidationErrors | null;
static maxLength(maxLength: number): ValidatorFn;