Compare commits
75 Commits
Author | SHA1 | Date | |
---|---|---|---|
380377139b | |||
9c7680ef69 | |||
69572ac2f1 | |||
76f53f929c | |||
ba52f2f252 | |||
e122f6bf0f | |||
453c758d1a | |||
015ca47336 | |||
f32e287812 | |||
9946ac5cc7 | |||
593e05dc97 | |||
da77b580c9 | |||
1733ea09bd | |||
1f4fa28fac | |||
c12e56ec0c | |||
4a5c8bd25f | |||
9c954740d1 | |||
11ed8f56ab | |||
a49acbf027 | |||
8e41910429 | |||
a4ab14bf74 | |||
ea4fc9b421 | |||
0956acee58 | |||
2ca67e1674 | |||
472666fc2b | |||
462316b0f1 | |||
96c2b2cc25 | |||
3d407fc010 | |||
64bd672e3a | |||
ef38676091 | |||
38be2b81c6 | |||
39a71eb0ec | |||
2fe6fb1163 | |||
b5afe51b26 | |||
170525a225 | |||
0c98f45105 | |||
e7025c9423 | |||
8f295287a2 | |||
030facc66a | |||
45af8f6752 | |||
33a79028be | |||
09226d96f8 | |||
6c3166e6e4 | |||
8df328b15a | |||
115f18fa06 | |||
511cd4d182 | |||
87d5d49530 | |||
933caacad3 | |||
efe9c4f35c | |||
5b0f9e2f51 | |||
462879887a | |||
dae0d0fd66 | |||
c7f750dd5a | |||
73de925551 | |||
547c22029a | |||
364642d58c | |||
7b67badc43 | |||
dc1662a447 | |||
b5f433626b | |||
dabaf858d9 | |||
bbc3c9ce0e | |||
1dcf1f484e | |||
583d2833db | |||
f502a768d3 | |||
16303ac487 | |||
6cdc3b5c12 | |||
5c46c493f2 | |||
e02c18049d | |||
e0ce5458a2 | |||
6a5ba0ec81 | |||
828c0d24eb | |||
22536442d6 | |||
845ea235ee | |||
21a4de999b | |||
82b34838bf |
71
CHANGELOG.md
71
CHANGELOG.md
@ -1,3 +1,74 @@
|
||||
<a name="2.2.3"></a>
|
||||
## [2.2.3](https://github.com/angular/angular/compare/2.2.2...2.2.3) (2016-11-23)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler:** Revert: fix versions of `@angular/tsc-wrapped` ([015ca47](https://github.com/angular/angular/commit/015ca47))
|
||||
* **animations:** Revert: blend in all previously transitioned styles into next animation if interrupted ([c12e56e](https://github.com/angular/angular/commit/c12e56e))
|
||||
|
||||
|
||||
<a name="2.2.2"></a>
|
||||
## [2.2.2](https://github.com/angular/angular/compare/2.2.1...2.2.2) (2016-11-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** blend in all previously transitioned styles into next animation if interrupted ([#13014](https://github.com/angular/angular/issues/13014)) ([ea4fc9b](https://github.com/angular/angular/commit/ea4fc9b)), closes [#13013](https://github.com/angular/angular/issues/13013)
|
||||
* **benchmarks:** use sanitized style values ([#12943](https://github.com/angular/angular/issues/12943)) ([33a7902](https://github.com/angular/angular/commit/33a7902))
|
||||
* **closure:** quote date pattern aliases ([#13012](https://github.com/angular/angular/issues/13012)) ([0956ace](https://github.com/angular/angular/commit/0956ace))
|
||||
* **compiler:** fix versions of `@angular/tsc-wrapped` ([2fe6fb1](https://github.com/angular/angular/commit/2fe6fb1))
|
||||
* **router:** add a banner file for the router ([#12919](https://github.com/angular/angular/issues/12919)) ([8df328b](https://github.com/angular/angular/commit/8df328b))
|
||||
* **router:** add a banner file for the router ([#12919](https://github.com/angular/angular/issues/12919)) ([511cd4d](https://github.com/angular/angular/commit/511cd4d))
|
||||
* **router:** removes a peer dependency from router to upgrade ([115f18f](https://github.com/angular/angular/commit/115f18f))
|
||||
* **router:** removes a peer dependency from router to upgrade ([87d5d49](https://github.com/angular/angular/commit/87d5d49))
|
||||
* **router:** support redirects to named outlets ([09226d9](https://github.com/angular/angular/commit/09226d9)), closes [#12740](https://github.com/angular/angular/issues/12740) [#9921](https://github.com/angular/angular/issues/9921)
|
||||
* **upgrade:** call ng1 lifecycle hooks ([#12875](https://github.com/angular/angular/issues/12875)) ([462316b](https://github.com/angular/angular/commit/462316b))
|
||||
|
||||
|
||||
|
||||
<a name="2.3.0-beta.0"></a>
|
||||
# [2.3.0-beta.0](https://github.com/angular/angular/compare/2.2.0...2.3.0-beta.0) (2016-11-17)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler:** assert xliff messages have translations ([7908679](https://github.com/angular/angular/commit/7908679)), closes [#12815](https://github.com/angular/angular/issues/12815) [#12604](https://github.com/angular/angular/issues/12604)
|
||||
* **compiler:** updates hash algo for xmb/xtb files ([2f14415](https://github.com/angular/angular/commit/2f14415))
|
||||
* **core:** fix placeholders handling in i18n. ([76e4911](https://github.com/angular/angular/commit/76e4911)), closes [#12512](https://github.com/angular/angular/issues/12512)
|
||||
* **core:** misc i18n fixes ([ed5e98d](https://github.com/angular/angular/commit/ed5e98d))
|
||||
* **core:** xmb serializer uses decimal messaged IDs ([08c038e](https://github.com/angular/angular/commit/08c038e)), closes [#12511](https://github.com/angular/angular/issues/12511)
|
||||
* **platform-browser:** enable AOT ([efbbefd](https://github.com/angular/angular/commit/efbbefd)), closes [#12783](https://github.com/angular/angular/issues/12783)
|
||||
|
||||
### Features
|
||||
|
||||
* **core:** add `attachView` / `detachView` to ApplicationRef ([9f7d32a](https://github.com/angular/angular/commit/9f7d32a)), closes [#9293](https://github.com/angular/angular/issues/9293)
|
||||
* **core:** expose `ViewRef` as `ChangeDetectorRef` ([1b5384e](https://github.com/angular/angular/commit/1b5384e)), closes [#12722](https://github.com/angular/angular/issues/12722)
|
||||
* **core:** implements a decimal fingerprint for i18n ([582550a](https://github.com/angular/angular/commit/582550a))
|
||||
* **router:** register router with ngprobe ([c2fae72](https://github.com/angular/angular/commit/c2fae72))
|
||||
* **router_link:** add skipLocationChange and replaceUrl inputs ([#12850](https://github.com/angular/angular/issues/12850)) ([46d1502](https://github.com/angular/angular/commit/46d1502))
|
||||
|
||||
Note: The 2.3.0-beta.0 release also contains all the changes present in the 2.2.1 release.
|
||||
|
||||
<a name="2.2.1"></a>
|
||||
## [2.2.1](https://github.com/angular/angular/compare/2.2.0...2.2.1) (2016-11-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** only pass in same typed players as previous players into web-animations ([#12907](https://github.com/angular/angular/issues/12907)) ([583d283](https://github.com/angular/angular/commit/583d283))
|
||||
* **animations:** retain styling when transition destinations are changed ([#12208](https://github.com/angular/angular/issues/12208)) ([5c46c49](https://github.com/angular/angular/commit/5c46c49)), closes [#9661](https://github.com/angular/angular/issues/9661)
|
||||
* **core:** support `ngTemplateOutlet` in production mode ([#12921](https://github.com/angular/angular/issues/12921)) ([4628798](https://github.com/angular/angular/commit/4628798)), closes [#12911](https://github.com/angular/angular/issues/12911)
|
||||
* **http:** correctly handle response body for 204 status code ([21a4de9](https://github.com/angular/angular/commit/21a4de9)), closes [#12830](https://github.com/angular/angular/issues/12830) [#12393](https://github.com/angular/angular/issues/12393)
|
||||
* **http:** return request url if it cannot be retrieved from response ([845ea23](https://github.com/angular/angular/commit/845ea23)), closes [#12837](https://github.com/angular/angular/issues/12837)
|
||||
* **upgrade:** make AoT ngUpgrade work with the testability API and resumeBootstrap() ([#12910](https://github.com/angular/angular/issues/12910)) ([dc1662a](https://github.com/angular/angular/commit/dc1662a))
|
||||
* **platform-browser:** fix disableDebugTools() ([#12918](https://github.com/angular/angular/issues/12918)) ([7b67bad](https://github.com/angular/angular/commit/7b67bad))
|
||||
* **router:** add a banner file for the router ([#12919](https://github.com/angular/angular/issues/12919)) ([364642d](https://github.com/angular/angular/commit/364642d))
|
||||
* **router:** removes a peer dependency from router to upgrade ([1dcf1f4](https://github.com/angular/angular/commit/1dcf1f4))
|
||||
* **forms** allow for null values in HTML select options bound with ngValue ([e0ce545](https://github.com/angular/angular/commit/e0ce545)), closes [#10349](https://github.com/angular/angular/issues/10349)
|
||||
* **router:** should not create a route state if navigation is canceled ([#12868](https://github.com/angular/angular/issues/12868)) ([dabaf85](https://github.com/angular/angular/commit/dabaf85)), closes [#12776](https://github.com/angular/angular/issues/12776)
|
||||
* **common:** select should allow for null values in HTML select options bound with ngValue ([e02c180](https://github.com/angular/angular/commit/e02c180)), closes [#12829](https://github.com/angular/angular/issues/12829)
|
||||
* **compiler-cli:** support ctorParams in function closure ([#12876](https://github.com/angular/angular/issues/12876)) ([6cdc3b5](https://github.com/angular/angular/commit/6cdc3b5))
|
||||
|
||||
|
||||
<a name="2.2.0"></a>
|
||||
# [2.2.0 upgrade-firebooster](https://github.com/angular/angular/compare/2.2.0-rc.0...2.2.0) (2016-11-14)
|
||||
|
||||
|
38
build.sh
38
build.sh
@ -20,6 +20,10 @@ PACKAGES=(core
|
||||
benchpress)
|
||||
BUILD_ALL=true
|
||||
BUNDLE=true
|
||||
VERSION_PREFIX=$(node -p "require('./package.json').version")
|
||||
VERSION_SUFFIX="-$(git log --oneline -1 | awk '{print $1}')"
|
||||
ROUTER_VERSION_PREFIX=$(node -p "require('./package.json').version.replace(/^2/, '3')")
|
||||
REMOVE_BENCHPRESS=false
|
||||
|
||||
for ARG in "$@"; do
|
||||
case "$ARG" in
|
||||
@ -31,6 +35,10 @@ for ARG in "$@"; do
|
||||
--bundle=*)
|
||||
BUNDLE=( "${ARG#--bundle=}" )
|
||||
;;
|
||||
--publish)
|
||||
VERSION_SUFFIX=""
|
||||
REMOVE_BENCHPRESS=true
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option $ARG."
|
||||
exit 1
|
||||
@ -38,6 +46,10 @@ for ARG in "$@"; do
|
||||
esac
|
||||
done
|
||||
|
||||
VERSION="${VERSION_PREFIX}${VERSION_SUFFIX}"
|
||||
ROUTER_VERSION="${ROUTER_VERSION_PREFIX}${VERSION_SUFFIX}"
|
||||
echo "====== BUILDING: Version ${VERSION} (Router ${ROUTER_VERSION})"
|
||||
|
||||
export NODE_PATH=${NODE_PATH}:$(pwd)/dist/all:$(pwd)/dist/tools
|
||||
TSC="node --max-old-space-size=3000 dist/tools/@angular/tsc-wrapped/src/main"
|
||||
UGLIFYJS=`pwd`/node_modules/.bin/uglifyjs
|
||||
@ -106,7 +118,13 @@ do
|
||||
UMD_ES5_MIN_PATH=${DESTDIR}/bundles/${PACKAGE}.umd.min.js
|
||||
UMD_STATIC_ES5_MIN_PATH=${DESTDIR}/bundles/${PACKAGE}-static.umd.min.js
|
||||
UMD_UPGRADE_ES5_MIN_PATH=${DESTDIR}/bundles/${PACKAGE}-upgrade.umd.min.js
|
||||
LICENSE_BANNER=${PWD}/modules/@angular/license-banner.txt
|
||||
|
||||
if [[ ${PACKAGE} != router ]]; then
|
||||
LICENSE_BANNER=${PWD}/modules/@angular/license-banner.txt
|
||||
fi
|
||||
if [[ ${PACKAGE} == router ]]; then
|
||||
LICENSE_BANNER=${PWD}/modules/@angular/router-license-banner.txt
|
||||
fi
|
||||
|
||||
rm -rf ${DESTDIR}
|
||||
|
||||
@ -191,8 +209,24 @@ do
|
||||
$UGLIFYJS -c --screw-ie8 --comments -o ${UMD_UPGRADE_ES5_MIN_PATH} ${UMD_UPGRADE_ES5_PATH}
|
||||
fi
|
||||
) 2>&1 | grep -v "as external dependency"
|
||||
|
||||
fi
|
||||
|
||||
(
|
||||
echo "====== VERSION: Updating version references"
|
||||
cd ${DESTDIR}
|
||||
echo "====== EXECUTE: perl -p -i -e \"s/0\.0\.0\-PLACEHOLDER/${VERSION}/g\" $""(grep -ril 0\.0\.0\-PLACEHOLDER .)"
|
||||
perl -p -i -e "s/0\.0\.0\-PLACEHOLDER/${VERSION}/g" $(grep -ril 0\.0\.0\-PLACEHOLDER .) < /dev/null 2> /dev/null
|
||||
echo "====== EXECUTE: perl -p -i -e \"s/0\.0\.0\-ROUTERPLACEHOLDER/${ROUTER_VERSION}/g\" $""(grep -ril 0\.0\.0\-ROUTERPLACEHOLDER .)"
|
||||
perl -p -i -e "s/0\.0\.0\-ROUTERPLACEHOLDER/${ROUTER_VERSION}/g" $(grep -ril 0\.0\.0\-ROUTERPLACEHOLDER .) < /dev/null 2> /dev/null
|
||||
)
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "====== Building examples: ./modules/@angular/examples/build.sh ====="
|
||||
./modules/@angular/examples/build.sh
|
||||
|
||||
if [[ ${REMOVE_BENCHPRESS} == true ]]; then
|
||||
echo ""
|
||||
echo "==== Removing benchpress from publication"
|
||||
rm -r dist/packages-dist/benchpress
|
||||
fi
|
||||
|
@ -4,7 +4,7 @@ machine:
|
||||
|
||||
dependencies:
|
||||
pre:
|
||||
- npm install -g npm
|
||||
- npm install -g npm@3.6.0
|
||||
|
||||
test:
|
||||
override:
|
||||
|
@ -34,34 +34,34 @@ export class Extractor {
|
||||
const programSymbols: StaticSymbol[] =
|
||||
extractProgramSymbols(this.program, this.staticReflector, this.reflectorHost, this.options);
|
||||
|
||||
return compiler
|
||||
.analyzeNgModules(programSymbols, {transitiveModules: true}, this.metadataResolver)
|
||||
.then(({files}) => {
|
||||
const errors: compiler.ParseError[] = [];
|
||||
const {ngModules, files} = compiler.analyzeAndValidateNgModules(
|
||||
programSymbols, {transitiveModules: true}, this.metadataResolver);
|
||||
return compiler.loadNgModuleDirectives(ngModules).then(() => {
|
||||
const errors: compiler.ParseError[] = [];
|
||||
|
||||
files.forEach(file => {
|
||||
const compMetas: compiler.CompileDirectiveMetadata[] = [];
|
||||
file.directives.forEach(directiveType => {
|
||||
const dirMeta = this.metadataResolver.getDirectiveMetadata(directiveType);
|
||||
if (dirMeta && dirMeta.isComponent) {
|
||||
compMetas.push(dirMeta);
|
||||
}
|
||||
});
|
||||
compMetas.forEach(compMeta => {
|
||||
const html = compMeta.template.template;
|
||||
const interpolationConfig =
|
||||
compiler.InterpolationConfig.fromArray(compMeta.template.interpolation);
|
||||
errors.push(
|
||||
...this.messageBundle.updateFromTemplate(html, file.srcUrl, interpolationConfig));
|
||||
});
|
||||
});
|
||||
|
||||
if (errors.length) {
|
||||
throw new Error(errors.map(e => e.toString()).join('\n'));
|
||||
files.forEach(file => {
|
||||
const compMetas: compiler.CompileDirectiveMetadata[] = [];
|
||||
file.directives.forEach(directiveType => {
|
||||
const dirMeta = this.metadataResolver.getDirectiveMetadata(directiveType);
|
||||
if (dirMeta && dirMeta.isComponent) {
|
||||
compMetas.push(dirMeta);
|
||||
}
|
||||
|
||||
return this.messageBundle;
|
||||
});
|
||||
compMetas.forEach(compMeta => {
|
||||
const html = compMeta.template.template;
|
||||
const interpolationConfig =
|
||||
compiler.InterpolationConfig.fromArray(compMeta.template.interpolation);
|
||||
errors.push(
|
||||
...this.messageBundle.updateFromTemplate(html, file.srcUrl, interpolationConfig));
|
||||
});
|
||||
});
|
||||
|
||||
if (errors.length) {
|
||||
throw new Error(errors.map(e => e.toString()).join('\n'));
|
||||
}
|
||||
|
||||
return this.messageBundle;
|
||||
});
|
||||
}
|
||||
|
||||
static create(
|
||||
|
@ -132,7 +132,6 @@ export class StaticReflector implements ReflectorReader {
|
||||
const ctor = (<any[]>ctorData).find(a => a['__symbolic'] == 'constructor');
|
||||
const parameterTypes = <any[]>this.simplify(type, ctor['parameters'] || []);
|
||||
const parameterDecorators = <any[]>this.simplify(type, ctor['parameterDecorators'] || []);
|
||||
|
||||
parameters = [];
|
||||
parameterTypes.forEach((paramType, index) => {
|
||||
const nestedResult: any[] = [];
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {StaticReflector, StaticReflectorHost, StaticSymbol} from '@angular/compiler-cli/src/static_reflector';
|
||||
import {HostListener, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core';
|
||||
import {HostListener, Inject, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core';
|
||||
import {MetadataCollector} from '@angular/tsc-wrapped';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
@ -410,6 +410,13 @@ describe('StaticReflector', () => {
|
||||
expect(props).toEqual({foo: []});
|
||||
});
|
||||
|
||||
it('should read ctor parameters with forwardRef', () => {
|
||||
const src = '/tmp/src/forward-ref.ts';
|
||||
const dep = host.getStaticSymbol(src, 'Dep');
|
||||
const props = reflector.parameters(host.getStaticSymbol(src, 'Forward'));
|
||||
expect(props).toEqual([[dep, new Inject(dep)]]);
|
||||
});
|
||||
|
||||
it('should report an error for invalid function calls', () => {
|
||||
expect(
|
||||
() =>
|
||||
@ -1068,6 +1075,18 @@ class MockReflectorHost implements StaticReflectorHost {
|
||||
providers: [ { provider: 'a', useValue: (() => 1)() }]
|
||||
})
|
||||
export class InvalidMetadata {}
|
||||
`,
|
||||
'/tmp/src/forward-ref.ts': `
|
||||
import {forwardRef} from 'angular2/core';
|
||||
import {Component} from 'angular2/src/core/metadata';
|
||||
import {Inject} from 'angular2/src/core/di/metadata';
|
||||
@Component({})
|
||||
export class Forward {
|
||||
constructor(@Inject(forwardRef(() => Dep)) d: Dep) {}
|
||||
}
|
||||
export class Dep {
|
||||
@Input f: Forward;
|
||||
}
|
||||
`
|
||||
};
|
||||
|
||||
|
@ -41,7 +41,9 @@ const _ANIMATION_TIME_VAR = o.variable('totalTime');
|
||||
const _ANIMATION_START_STATE_STYLES_VAR = o.variable('startStateStyles');
|
||||
const _ANIMATION_END_STATE_STYLES_VAR = o.variable('endStateStyles');
|
||||
const _ANIMATION_COLLECTED_STYLES = o.variable('collectedStyles');
|
||||
const EMPTY_MAP = o.literalMap([]);
|
||||
const _PREVIOUS_ANIMATION_PLAYERS = o.variable('previousPlayers');
|
||||
const _EMPTY_MAP = o.literalMap([]);
|
||||
const _EMPTY_ARRAY = o.literalArr([]);
|
||||
|
||||
class _AnimationBuilder implements AnimationAstVisitor {
|
||||
private _fnVarName: string;
|
||||
@ -110,10 +112,15 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
||||
_callAnimateMethod(
|
||||
ast: AnimationStepAst, startingStylesExpr: any, keyframesExpr: any,
|
||||
context: _AnimationBuilderContext) {
|
||||
let previousStylesValue: o.Expression = _EMPTY_ARRAY;
|
||||
if (context.isExpectingFirstAnimateStep) {
|
||||
previousStylesValue = _PREVIOUS_ANIMATION_PLAYERS;
|
||||
context.isExpectingFirstAnimateStep = false;
|
||||
}
|
||||
context.totalTransitionTime += ast.duration + ast.delay;
|
||||
return _ANIMATION_FACTORY_RENDERER_VAR.callMethod('animate', [
|
||||
_ANIMATION_FACTORY_ELEMENT_VAR, startingStylesExpr, keyframesExpr, o.literal(ast.duration),
|
||||
o.literal(ast.delay), o.literal(ast.easing)
|
||||
o.literal(ast.delay), o.literal(ast.easing), previousStylesValue
|
||||
]);
|
||||
}
|
||||
|
||||
@ -150,6 +157,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
||||
|
||||
context.totalTransitionTime = 0;
|
||||
context.isExpectingFirstStyleStep = true;
|
||||
context.isExpectingFirstAnimateStep = true;
|
||||
|
||||
const stateChangePreconditions: o.Expression[] = [];
|
||||
|
||||
@ -187,17 +195,16 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
||||
context.stateMap.registerState(DEFAULT_STATE, {});
|
||||
|
||||
const statements: o.Statement[] = [];
|
||||
statements.push(_ANIMATION_FACTORY_VIEW_CONTEXT
|
||||
.callMethod(
|
||||
'cancelActiveAnimation',
|
||||
statements.push(_PREVIOUS_ANIMATION_PLAYERS
|
||||
.set(_ANIMATION_FACTORY_VIEW_CONTEXT.callMethod(
|
||||
'getAnimationPlayers',
|
||||
[
|
||||
_ANIMATION_FACTORY_ELEMENT_VAR, o.literal(this.animationName),
|
||||
_ANIMATION_NEXT_STATE_VAR.equals(o.literal(EMPTY_STATE))
|
||||
])
|
||||
.toStmt());
|
||||
]))
|
||||
.toDeclStmt());
|
||||
|
||||
|
||||
statements.push(_ANIMATION_COLLECTED_STYLES.set(EMPTY_MAP).toDeclStmt());
|
||||
statements.push(_ANIMATION_COLLECTED_STYLES.set(_EMPTY_MAP).toDeclStmt());
|
||||
statements.push(_ANIMATION_PLAYER_VAR.set(o.NULL_EXPR).toDeclStmt());
|
||||
statements.push(_ANIMATION_TIME_VAR.set(o.literal(0)).toDeclStmt());
|
||||
|
||||
@ -223,17 +230,6 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
||||
|
||||
const RENDER_STYLES_FN = o.importExpr(resolveIdentifier(Identifiers.renderStyles));
|
||||
|
||||
// before we start any animation we want to clear out the starting
|
||||
// styles from the element's style property (since they were placed
|
||||
// there at the end of the last animation
|
||||
statements.push(RENDER_STYLES_FN
|
||||
.callFn([
|
||||
_ANIMATION_FACTORY_ELEMENT_VAR, _ANIMATION_FACTORY_RENDERER_VAR,
|
||||
o.importExpr(resolveIdentifier(Identifiers.clearStyles))
|
||||
.callFn([_ANIMATION_START_STATE_STYLES_VAR])
|
||||
])
|
||||
.toStmt());
|
||||
|
||||
ast.stateTransitions.forEach(transAst => statements.push(transAst.visit(this, context)));
|
||||
|
||||
// this check ensures that the animation factory always returns a player
|
||||
@ -269,6 +265,22 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
||||
])])
|
||||
.toStmt());
|
||||
|
||||
statements.push(o.importExpr(resolveIdentifier(Identifiers.AnimationSequencePlayer))
|
||||
.instantiate([_PREVIOUS_ANIMATION_PLAYERS])
|
||||
.callMethod('destroy', [])
|
||||
.toStmt());
|
||||
|
||||
// before we start any animation we want to clear out the starting
|
||||
// styles from the element's style property (since they were placed
|
||||
// there at the end of the last animation
|
||||
statements.push(RENDER_STYLES_FN
|
||||
.callFn([
|
||||
_ANIMATION_FACTORY_ELEMENT_VAR, _ANIMATION_FACTORY_RENDERER_VAR,
|
||||
o.importExpr(resolveIdentifier(Identifiers.clearStyles))
|
||||
.callFn([_ANIMATION_START_STATE_STYLES_VAR])
|
||||
])
|
||||
.toStmt());
|
||||
|
||||
statements.push(_ANIMATION_FACTORY_VIEW_CONTEXT
|
||||
.callMethod(
|
||||
'queueAnimation',
|
||||
@ -304,7 +316,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
||||
const lookupMap: any[] = [];
|
||||
Object.keys(context.stateMap.states).forEach(stateName => {
|
||||
const value = context.stateMap.states[stateName];
|
||||
let variableValue = EMPTY_MAP;
|
||||
let variableValue = _EMPTY_MAP;
|
||||
if (isPresent(value)) {
|
||||
const styleMap: any[] = [];
|
||||
Object.keys(value).forEach(key => { styleMap.push([key, o.literal(value[key])]); });
|
||||
@ -324,6 +336,7 @@ class _AnimationBuilderContext {
|
||||
stateMap = new _AnimationBuilderStateMap();
|
||||
endStateAnimateStep: AnimationStepAst = null;
|
||||
isExpectingFirstStyleStep = false;
|
||||
isExpectingFirstAnimateStep = false;
|
||||
totalTransitionTime = 0;
|
||||
}
|
||||
|
||||
|
@ -589,7 +589,7 @@ export interface CompileNgModuleDirectiveSummary extends CompileSummary {
|
||||
exportedDirectives: CompileIdentifierMetadata[];
|
||||
exportedPipes: CompileIdentifierMetadata[];
|
||||
exportedModules: CompileNgModuleDirectiveSummary[];
|
||||
loadingPromises: Promise<any>[];
|
||||
directiveLoaders: (() => Promise<void>)[];
|
||||
}
|
||||
|
||||
export type CompileNgModuleSummary =
|
||||
@ -661,7 +661,7 @@ export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
|
||||
exportedModules: this.exportedModules,
|
||||
exportedDirectives: this.exportedDirectives,
|
||||
exportedPipes: this.exportedPipes,
|
||||
loadingPromises: this.transitiveModule.loadingPromises
|
||||
directiveLoaders: this.transitiveModule.directiveLoaders
|
||||
};
|
||||
}
|
||||
|
||||
@ -682,7 +682,7 @@ export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
|
||||
exportedDirectives: this.exportedDirectives,
|
||||
exportedPipes: this.exportedPipes,
|
||||
exportedModules: this.exportedModules,
|
||||
loadingPromises: this.transitiveModule.loadingPromises
|
||||
directiveLoaders: this.transitiveModule.directiveLoaders
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -695,7 +695,7 @@ export class TransitiveCompileNgModuleMetadata {
|
||||
public modules: CompileNgModuleInjectorSummary[], public providers: CompileProviderMetadata[],
|
||||
public entryComponents: CompileIdentifierMetadata[],
|
||||
public directives: CompileIdentifierMetadata[], public pipes: CompileIdentifierMetadata[],
|
||||
public loadingPromises: Promise<any>[]) {
|
||||
public directiveLoaders: (() => Promise<void>)[]) {
|
||||
directives.forEach(dir => this.directivesSet.add(dir.reference));
|
||||
pipes.forEach(pipe => this.pipesSet.add(pipe.reference));
|
||||
}
|
||||
|
@ -6,7 +6,6 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ListWrapper} from '../../facade/collection';
|
||||
import * as ml from '../../ml_parser/ast';
|
||||
import {HtmlParser} from '../../ml_parser/html_parser';
|
||||
import {InterpolationConfig} from '../../ml_parser/interpolation_config';
|
||||
@ -162,7 +161,7 @@ class _WriteVisitor implements i18n.Visitor {
|
||||
|
||||
serialize(nodes: i18n.Node[]): xml.Node[] {
|
||||
this._isInIcu = false;
|
||||
return ListWrapper.flatten(nodes.map(node => node.visit(this)));
|
||||
return [].concat(...nodes.map(node => node.visit(this)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,6 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ListWrapper} from '../../facade/collection';
|
||||
import * as html from '../../ml_parser/ast';
|
||||
import * as i18n from '../i18n_ast';
|
||||
import {MessageBundle} from '../message_bundle';
|
||||
@ -121,6 +120,6 @@ class _Visitor implements i18n.Visitor {
|
||||
}
|
||||
|
||||
serialize(nodes: i18n.Node[]): xml.Node[] {
|
||||
return ListWrapper.flatten(nodes.map(node => node.visit(this)));
|
||||
return [].concat(...nodes.map(node => node.visit(this)));
|
||||
}
|
||||
}
|
||||
|
@ -146,97 +146,43 @@ export class CompileMetadataResolver {
|
||||
return;
|
||||
}
|
||||
directiveType = resolveForwardRef(directiveType);
|
||||
const dirMeta = this._directiveResolver.resolve(directiveType);
|
||||
if (!dirMeta) {
|
||||
return null;
|
||||
}
|
||||
let moduleUrl = staticTypeModuleUrl(directiveType);
|
||||
const nonNormalizedMetadata = this.getNonNormalizedDirectiveMetadata(directiveType);
|
||||
|
||||
const createDirectiveMetadata = (templateMeta: cpl.CompileTemplateMetadata) => {
|
||||
let changeDetectionStrategy: ChangeDetectionStrategy = null;
|
||||
let viewProviders: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
|
||||
let entryComponentMetadata: cpl.CompileIdentifierMetadata[] = [];
|
||||
let selector = dirMeta.selector;
|
||||
|
||||
if (dirMeta instanceof Component) {
|
||||
// Component
|
||||
changeDetectionStrategy = dirMeta.changeDetection;
|
||||
if (dirMeta.viewProviders) {
|
||||
viewProviders = this._getProvidersMetadata(
|
||||
dirMeta.viewProviders, entryComponentMetadata,
|
||||
`viewProviders for "${stringify(directiveType)}"`);
|
||||
}
|
||||
if (dirMeta.entryComponents) {
|
||||
entryComponentMetadata =
|
||||
flattenAndDedupeArray(dirMeta.entryComponents)
|
||||
.map((type) => this._getIdentifierMetadata(type, staticTypeModuleUrl(type)))
|
||||
.concat(entryComponentMetadata);
|
||||
}
|
||||
if (!selector) {
|
||||
selector = this._schemaRegistry.getDefaultComponentElementName();
|
||||
}
|
||||
} else {
|
||||
// Directive
|
||||
if (!selector) {
|
||||
throw new Error(`Directive ${stringify(directiveType)} has no selector, please add it!`);
|
||||
}
|
||||
}
|
||||
|
||||
let providers: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
|
||||
if (isPresent(dirMeta.providers)) {
|
||||
providers = this._getProvidersMetadata(
|
||||
dirMeta.providers, entryComponentMetadata,
|
||||
`providers for "${stringify(directiveType)}"`);
|
||||
}
|
||||
let queries: cpl.CompileQueryMetadata[] = [];
|
||||
let viewQueries: cpl.CompileQueryMetadata[] = [];
|
||||
if (isPresent(dirMeta.queries)) {
|
||||
queries = this._getQueriesMetadata(dirMeta.queries, false, directiveType);
|
||||
viewQueries = this._getQueriesMetadata(dirMeta.queries, true, directiveType);
|
||||
}
|
||||
|
||||
const meta = cpl.CompileDirectiveMetadata.create({
|
||||
selector: selector,
|
||||
exportAs: dirMeta.exportAs,
|
||||
isComponent: !!templateMeta,
|
||||
type: this._getTypeMetadata(directiveType, moduleUrl),
|
||||
template: templateMeta,
|
||||
changeDetection: changeDetectionStrategy,
|
||||
inputs: dirMeta.inputs,
|
||||
outputs: dirMeta.outputs,
|
||||
host: dirMeta.host,
|
||||
providers: providers,
|
||||
viewProviders: viewProviders,
|
||||
queries: queries,
|
||||
viewQueries: viewQueries,
|
||||
entryComponents: entryComponentMetadata
|
||||
const createDirectiveMetadata = (templateMetadata: cpl.CompileTemplateMetadata) => {
|
||||
const normalizedDirMeta = new cpl.CompileDirectiveMetadata({
|
||||
type: nonNormalizedMetadata.type,
|
||||
isComponent: nonNormalizedMetadata.isComponent,
|
||||
selector: nonNormalizedMetadata.selector,
|
||||
exportAs: nonNormalizedMetadata.exportAs,
|
||||
changeDetection: nonNormalizedMetadata.changeDetection,
|
||||
inputs: nonNormalizedMetadata.inputs,
|
||||
outputs: nonNormalizedMetadata.outputs,
|
||||
hostListeners: nonNormalizedMetadata.hostListeners,
|
||||
hostProperties: nonNormalizedMetadata.hostProperties,
|
||||
hostAttributes: nonNormalizedMetadata.hostAttributes,
|
||||
providers: nonNormalizedMetadata.providers,
|
||||
viewProviders: nonNormalizedMetadata.viewProviders,
|
||||
queries: nonNormalizedMetadata.queries,
|
||||
viewQueries: nonNormalizedMetadata.viewQueries,
|
||||
entryComponents: nonNormalizedMetadata.entryComponents,
|
||||
template: templateMetadata
|
||||
});
|
||||
this._directiveCache.set(directiveType, meta);
|
||||
this._directiveSummaryCache.set(directiveType, meta.toSummary());
|
||||
return meta;
|
||||
this._directiveCache.set(directiveType, normalizedDirMeta);
|
||||
this._directiveSummaryCache.set(directiveType, normalizedDirMeta.toSummary());
|
||||
return normalizedDirMeta;
|
||||
};
|
||||
|
||||
if (dirMeta instanceof Component) {
|
||||
// component
|
||||
moduleUrl = componentModuleUrl(this._reflector, directiveType, dirMeta);
|
||||
assertArrayOfStrings('styles', dirMeta.styles);
|
||||
assertArrayOfStrings('styleUrls', dirMeta.styleUrls);
|
||||
assertInterpolationSymbols('interpolation', dirMeta.interpolation);
|
||||
|
||||
const animations = dirMeta.animations ?
|
||||
dirMeta.animations.map(e => this.getAnimationEntryMetadata(e)) :
|
||||
null;
|
||||
|
||||
if (nonNormalizedMetadata.isComponent) {
|
||||
const templateMeta = this._directiveNormalizer.normalizeTemplate({
|
||||
componentType: directiveType,
|
||||
moduleUrl: moduleUrl,
|
||||
encapsulation: dirMeta.encapsulation,
|
||||
template: dirMeta.template,
|
||||
templateUrl: dirMeta.templateUrl,
|
||||
styles: dirMeta.styles,
|
||||
styleUrls: dirMeta.styleUrls,
|
||||
animations: animations,
|
||||
interpolation: dirMeta.interpolation
|
||||
moduleUrl: nonNormalizedMetadata.type.moduleUrl,
|
||||
encapsulation: nonNormalizedMetadata.template.encapsulation,
|
||||
template: nonNormalizedMetadata.template.template,
|
||||
templateUrl: nonNormalizedMetadata.template.templateUrl,
|
||||
styles: nonNormalizedMetadata.template.styles,
|
||||
styleUrls: nonNormalizedMetadata.template.styleUrls,
|
||||
animations: nonNormalizedMetadata.template.animations,
|
||||
interpolation: nonNormalizedMetadata.template.interpolation
|
||||
});
|
||||
if (templateMeta.syncResult) {
|
||||
createDirectiveMetadata(templateMeta.syncResult);
|
||||
@ -254,6 +200,96 @@ export class CompileMetadataResolver {
|
||||
}
|
||||
}
|
||||
|
||||
getNonNormalizedDirectiveMetadata(directiveType: any): cpl.CompileDirectiveMetadata {
|
||||
directiveType = resolveForwardRef(directiveType);
|
||||
const dirMeta = this._directiveResolver.resolve(directiveType);
|
||||
if (!dirMeta) {
|
||||
return null;
|
||||
}
|
||||
let moduleUrl = staticTypeModuleUrl(directiveType);
|
||||
let nonNormalizedTemplateMetadata: cpl.CompileTemplateMetadata;
|
||||
|
||||
if (dirMeta instanceof Component) {
|
||||
// component
|
||||
moduleUrl = componentModuleUrl(this._reflector, directiveType, dirMeta);
|
||||
assertArrayOfStrings('styles', dirMeta.styles);
|
||||
assertArrayOfStrings('styleUrls', dirMeta.styleUrls);
|
||||
assertInterpolationSymbols('interpolation', dirMeta.interpolation);
|
||||
|
||||
const animations = dirMeta.animations ?
|
||||
dirMeta.animations.map(e => this.getAnimationEntryMetadata(e)) :
|
||||
null;
|
||||
|
||||
nonNormalizedTemplateMetadata = new cpl.CompileTemplateMetadata({
|
||||
encapsulation: dirMeta.encapsulation,
|
||||
template: dirMeta.template,
|
||||
templateUrl: dirMeta.templateUrl,
|
||||
styles: dirMeta.styles,
|
||||
styleUrls: dirMeta.styleUrls,
|
||||
animations: animations,
|
||||
interpolation: dirMeta.interpolation
|
||||
});
|
||||
}
|
||||
|
||||
let changeDetectionStrategy: ChangeDetectionStrategy = null;
|
||||
let viewProviders: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
|
||||
let entryComponentMetadata: cpl.CompileIdentifierMetadata[] = [];
|
||||
let selector = dirMeta.selector;
|
||||
|
||||
if (dirMeta instanceof Component) {
|
||||
// Component
|
||||
changeDetectionStrategy = dirMeta.changeDetection;
|
||||
if (dirMeta.viewProviders) {
|
||||
viewProviders = this._getProvidersMetadata(
|
||||
dirMeta.viewProviders, entryComponentMetadata,
|
||||
`viewProviders for "${stringify(directiveType)}"`);
|
||||
}
|
||||
if (dirMeta.entryComponents) {
|
||||
entryComponentMetadata =
|
||||
flattenAndDedupeArray(dirMeta.entryComponents)
|
||||
.map((type) => this._getIdentifierMetadata(type, staticTypeModuleUrl(type)))
|
||||
.concat(entryComponentMetadata);
|
||||
}
|
||||
if (!selector) {
|
||||
selector = this._schemaRegistry.getDefaultComponentElementName();
|
||||
}
|
||||
} else {
|
||||
// Directive
|
||||
if (!selector) {
|
||||
throw new Error(`Directive ${stringify(directiveType)} has no selector, please add it!`);
|
||||
}
|
||||
}
|
||||
|
||||
let providers: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
|
||||
if (isPresent(dirMeta.providers)) {
|
||||
providers = this._getProvidersMetadata(
|
||||
dirMeta.providers, entryComponentMetadata, `providers for "${stringify(directiveType)}"`);
|
||||
}
|
||||
let queries: cpl.CompileQueryMetadata[] = [];
|
||||
let viewQueries: cpl.CompileQueryMetadata[] = [];
|
||||
if (isPresent(dirMeta.queries)) {
|
||||
queries = this._getQueriesMetadata(dirMeta.queries, false, directiveType);
|
||||
viewQueries = this._getQueriesMetadata(dirMeta.queries, true, directiveType);
|
||||
}
|
||||
|
||||
return cpl.CompileDirectiveMetadata.create({
|
||||
selector: selector,
|
||||
exportAs: dirMeta.exportAs,
|
||||
isComponent: !!nonNormalizedTemplateMetadata,
|
||||
type: this._getTypeMetadata(directiveType, moduleUrl),
|
||||
template: nonNormalizedTemplateMetadata,
|
||||
changeDetection: changeDetectionStrategy,
|
||||
inputs: dirMeta.inputs,
|
||||
outputs: dirMeta.outputs,
|
||||
host: dirMeta.host,
|
||||
providers: providers,
|
||||
viewProviders: viewProviders,
|
||||
queries: queries,
|
||||
viewQueries: viewQueries,
|
||||
entryComponents: entryComponentMetadata
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the metadata for the given directive.
|
||||
* This assumes `loadNgModuleMetadata` has been called first.
|
||||
@ -309,11 +345,20 @@ export class CompileMetadataResolver {
|
||||
loadNgModuleMetadata(moduleType: any, isSync: boolean, throwIfNotFound = true):
|
||||
{ngModule: cpl.CompileNgModuleMetadata, loading: Promise<any>} {
|
||||
const ngModule = this._loadNgModuleMetadata(moduleType, isSync, throwIfNotFound);
|
||||
const loading =
|
||||
ngModule ? Promise.all(ngModule.transitiveModule.loadingPromises) : Promise.resolve(null);
|
||||
const loading = ngModule ?
|
||||
Promise.all(ngModule.transitiveModule.directiveLoaders.map(loader => loader())) :
|
||||
Promise.resolve(null);
|
||||
return {ngModule, loading};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the NgModule metadata without loading the directives.
|
||||
*/
|
||||
getUnloadedNgModuleMetadata(moduleType: any, isSync: boolean, throwIfNotFound = true):
|
||||
cpl.CompileNgModuleMetadata {
|
||||
return this._loadNgModuleMetadata(moduleType, isSync, throwIfNotFound);
|
||||
}
|
||||
|
||||
private _loadNgModuleMetadata(moduleType: any, isSync: boolean, throwIfNotFound = true):
|
||||
cpl.CompileNgModuleMetadata {
|
||||
moduleType = resolveForwardRef(moduleType);
|
||||
@ -396,10 +441,8 @@ export class CompileMetadataResolver {
|
||||
transitiveModule.directives.push(declaredIdentifier);
|
||||
declaredDirectives.push(declaredIdentifier);
|
||||
this._addTypeToModule(declaredType, moduleType);
|
||||
const loadingPromise = this._loadDirectiveMetadata(declaredType, isSync);
|
||||
if (loadingPromise) {
|
||||
transitiveModule.loadingPromises.push(loadingPromise);
|
||||
}
|
||||
transitiveModule.directiveLoaders.push(
|
||||
() => this._loadDirectiveMetadata(declaredType, isSync));
|
||||
} else if (this._pipeResolver.isPipe(declaredType)) {
|
||||
transitiveModule.pipesSet.add(declaredType);
|
||||
transitiveModule.pipes.push(declaredIdentifier);
|
||||
@ -525,10 +568,10 @@ export class CompileMetadataResolver {
|
||||
const directives =
|
||||
flattenArray(transitiveExportedModules.map((ngModule) => ngModule.exportedDirectives));
|
||||
const pipes = flattenArray(transitiveExportedModules.map((ngModule) => ngModule.exportedPipes));
|
||||
const loadingPromises =
|
||||
ListWrapper.flatten(transitiveExportedModules.map(ngModule => ngModule.loadingPromises));
|
||||
const directiveLoaders =
|
||||
ListWrapper.flatten(transitiveExportedModules.map(ngModule => ngModule.directiveLoaders));
|
||||
return new cpl.TransitiveCompileNgModuleMetadata(
|
||||
transitiveModules, providers, entryComponents, directives, pipes, loadingPromises);
|
||||
transitiveModules, providers, entryComponents, directives, pipes, directiveLoaders);
|
||||
}
|
||||
|
||||
private _getIdentifierMetadata(type: Type<any>, moduleUrl: string):
|
||||
@ -584,20 +627,26 @@ export class CompileMetadataResolver {
|
||||
return pipeSummary;
|
||||
}
|
||||
|
||||
private _loadPipeMetadata(pipeType: Type<any>): void {
|
||||
pipeType = resolveForwardRef(pipeType);
|
||||
const pipeMeta = this._pipeResolver.resolve(pipeType);
|
||||
getOrLoadPipeMetadata(pipeType: any): cpl.CompilePipeMetadata {
|
||||
let pipeMeta = this._pipeCache.get(pipeType);
|
||||
if (!pipeMeta) {
|
||||
return null;
|
||||
pipeMeta = this._loadPipeMetadata(pipeType);
|
||||
}
|
||||
return pipeMeta;
|
||||
}
|
||||
|
||||
const meta = new cpl.CompilePipeMetadata({
|
||||
private _loadPipeMetadata(pipeType: any): cpl.CompilePipeMetadata {
|
||||
pipeType = resolveForwardRef(pipeType);
|
||||
const pipeAnnotation = this._pipeResolver.resolve(pipeType);
|
||||
|
||||
const pipeMeta = new cpl.CompilePipeMetadata({
|
||||
type: this._getTypeMetadata(pipeType, staticTypeModuleUrl(pipeType)),
|
||||
name: pipeMeta.name,
|
||||
pure: pipeMeta.pure
|
||||
name: pipeAnnotation.name,
|
||||
pure: pipeAnnotation.pure
|
||||
});
|
||||
this._pipeCache.set(pipeType, meta);
|
||||
this._pipeSummaryCache.set(pipeType, meta.toSummary());
|
||||
this._pipeCache.set(pipeType, pipeMeta);
|
||||
this._pipeSummaryCache.set(pipeType, pipeMeta.toSummary());
|
||||
return pipeMeta;
|
||||
}
|
||||
|
||||
private _getDependenciesMetadata(typeOrFunc: Type<any>|Function, dependencies: any[]):
|
||||
|
@ -27,17 +27,46 @@ export class SourceModule {
|
||||
constructor(public fileUrl: string, public moduleUrl: string, public source: string) {}
|
||||
}
|
||||
|
||||
export interface NgAnalyzedModules {
|
||||
ngModules: CompileNgModuleMetadata[];
|
||||
ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>;
|
||||
files: Array<{srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}>;
|
||||
symbolsMissingModule?: StaticSymbol[];
|
||||
}
|
||||
|
||||
// Returns all the source files and a mapping from modules to directives
|
||||
export function analyzeNgModules(
|
||||
programStaticSymbols: StaticSymbol[], options: {transitiveModules: boolean},
|
||||
metadataResolver: CompileMetadataResolver): Promise<{
|
||||
ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
|
||||
files: Array<{srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}>
|
||||
}> {
|
||||
return _loadNgModules(programStaticSymbols, options, metadataResolver).then(_analyzeNgModules);
|
||||
metadataResolver: CompileMetadataResolver): NgAnalyzedModules {
|
||||
const {ngModules, symbolsMissingModule} =
|
||||
_createNgModules(programStaticSymbols, options, metadataResolver);
|
||||
return _analyzeNgModules(ngModules, symbolsMissingModule);
|
||||
}
|
||||
|
||||
function _analyzeNgModules(ngModuleMetas: CompileNgModuleMetadata[]) {
|
||||
|
||||
export function analyzeAndValidateNgModules(
|
||||
programStaticSymbols: StaticSymbol[], options: {transitiveModules: boolean},
|
||||
metadataResolver: CompileMetadataResolver): NgAnalyzedModules {
|
||||
const result = analyzeNgModules(programStaticSymbols, options, metadataResolver);
|
||||
if (result.symbolsMissingModule && result.symbolsMissingModule.length) {
|
||||
const messages = result.symbolsMissingModule.map(
|
||||
s => `Cannot determine the module for class ${s.name} in ${s.filePath}!`);
|
||||
throw new Error(messages.join('\n'));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Wait for the directives in the given modules have been loaded
|
||||
export function loadNgModuleDirectives(ngModules: CompileNgModuleMetadata[]) {
|
||||
return Promise
|
||||
.all(ListWrapper.flatten(ngModules.map(
|
||||
(ngModule) => ngModule.transitiveModule.directiveLoaders.map(loader => loader()))))
|
||||
.then(() => {});
|
||||
}
|
||||
|
||||
function _analyzeNgModules(
|
||||
ngModuleMetas: CompileNgModuleMetadata[],
|
||||
symbolsMissingModule: StaticSymbol[]): NgAnalyzedModules {
|
||||
const moduleMetasByRef = new Map<any, CompileNgModuleMetadata>();
|
||||
ngModuleMetas.forEach((ngModule) => moduleMetasByRef.set(ngModule.type.reference, ngModule));
|
||||
const ngModuleByPipeOrDirective = new Map<StaticSymbol, CompileNgModuleMetadata>();
|
||||
@ -78,10 +107,11 @@ function _analyzeNgModules(ngModuleMetas: CompileNgModuleMetadata[]) {
|
||||
});
|
||||
|
||||
return {
|
||||
// map directive/pipe to module
|
||||
ngModuleByPipeOrDirective,
|
||||
// list modules and directives for every source file
|
||||
files,
|
||||
// map directive/pipe to module
|
||||
ngModuleByPipeOrDirective,
|
||||
// list modules and directives for every source file
|
||||
files,
|
||||
ngModules: ngModuleMetas, symbolsMissingModule
|
||||
};
|
||||
}
|
||||
|
||||
@ -100,13 +130,14 @@ export class OfflineCompiler {
|
||||
|
||||
compileModules(staticSymbols: StaticSymbol[], options: {transitiveModules: boolean}):
|
||||
Promise<SourceModule[]> {
|
||||
return analyzeNgModules(staticSymbols, options, this._metadataResolver)
|
||||
.then(({ngModuleByPipeOrDirective, files}) => {
|
||||
const sourceModules = files.map(
|
||||
file => this._compileSrcFile(
|
||||
file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.ngModules));
|
||||
return ListWrapper.flatten(sourceModules);
|
||||
});
|
||||
const {ngModuleByPipeOrDirective, files, ngModules} =
|
||||
analyzeAndValidateNgModules(staticSymbols, options, this._metadataResolver);
|
||||
return loadNgModuleDirectives(ngModules).then(() => {
|
||||
const sourceModules = files.map(
|
||||
file => this._compileSrcFile(
|
||||
file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.ngModules));
|
||||
return ListWrapper.flatten(sourceModules);
|
||||
});
|
||||
}
|
||||
|
||||
private _compileSrcFile(
|
||||
@ -328,22 +359,21 @@ function _splitTypescriptSuffix(path: string): string[] {
|
||||
// Load the NgModules and check
|
||||
// that all directives / pipes that are present in the program
|
||||
// are also declared by a module.
|
||||
function _loadNgModules(
|
||||
function _createNgModules(
|
||||
programStaticSymbols: StaticSymbol[], options: {transitiveModules: boolean},
|
||||
metadataResolver: CompileMetadataResolver): Promise<CompileNgModuleMetadata[]> {
|
||||
metadataResolver: CompileMetadataResolver):
|
||||
{ngModules: CompileNgModuleMetadata[], symbolsMissingModule: StaticSymbol[]} {
|
||||
const ngModules = new Map<any, CompileNgModuleMetadata>();
|
||||
const programPipesAndDirectives: StaticSymbol[] = [];
|
||||
const ngModulePipesAndDirective = new Set<StaticSymbol>();
|
||||
const loadingPromises: Promise<any>[] = [];
|
||||
|
||||
const addNgModule = (staticSymbol: any) => {
|
||||
if (ngModules.has(staticSymbol)) {
|
||||
return false;
|
||||
}
|
||||
const {ngModule, loading} = metadataResolver.loadNgModuleMetadata(staticSymbol, false, false);
|
||||
const ngModule = metadataResolver.getUnloadedNgModuleMetadata(staticSymbol, false, false);
|
||||
if (ngModule) {
|
||||
ngModules.set(ngModule.type.reference, ngModule);
|
||||
loadingPromises.push(loading);
|
||||
ngModule.declaredDirectives.forEach((dir) => ngModulePipesAndDirective.add(dir.reference));
|
||||
ngModule.declaredPipes.forEach((pipe) => ngModulePipesAndDirective.add(pipe.reference));
|
||||
if (options.transitiveModules) {
|
||||
@ -364,11 +394,5 @@ function _loadNgModules(
|
||||
const symbolsMissingModule =
|
||||
programPipesAndDirectives.filter(s => !ngModulePipesAndDirective.has(s));
|
||||
|
||||
if (symbolsMissingModule.length) {
|
||||
const messages = symbolsMissingModule.map(
|
||||
s => `Cannot determine the module for class ${s.name} in ${s.filePath}!`);
|
||||
throw new Error(messages.join('\n'));
|
||||
}
|
||||
|
||||
return Promise.all(loadingPromises).then(() => Array.from(ngModules.values()));
|
||||
return {ngModules: Array.from(ngModules.values()), symbolsMissingModule};
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ export class AnimationGroupPlayer implements AnimationPlayer {
|
||||
this._started = false;
|
||||
}
|
||||
|
||||
setPosition(p: any /** TODO #9100 */): void {
|
||||
setPosition(p: number): void {
|
||||
this._players.forEach(player => { player.setPosition(p); });
|
||||
}
|
||||
|
||||
@ -100,4 +100,6 @@ export class AnimationGroupPlayer implements AnimationPlayer {
|
||||
});
|
||||
return min;
|
||||
}
|
||||
|
||||
get players(): AnimationPlayer[] { return this._players; }
|
||||
}
|
||||
|
@ -56,6 +56,6 @@ export class NoOpAnimationPlayer implements AnimationPlayer {
|
||||
finish(): void { this._onFinish(); }
|
||||
destroy(): void {}
|
||||
reset(): void {}
|
||||
setPosition(p: any /** TODO #9100 */): void {}
|
||||
setPosition(p: number): void {}
|
||||
getPosition(): number { return 0; }
|
||||
}
|
||||
|
@ -104,7 +104,9 @@ export class AnimationSequencePlayer implements AnimationPlayer {
|
||||
}
|
||||
}
|
||||
|
||||
setPosition(p: any /** TODO #9100 */): void { this._players[0].setPosition(p); }
|
||||
setPosition(p: number): void { this._players[0].setPosition(p); }
|
||||
|
||||
getPosition(): number { return this._players[0].getPosition(); }
|
||||
|
||||
get players(): AnimationPlayer[] { return this._players; }
|
||||
}
|
||||
|
@ -84,6 +84,8 @@ export function balanceAnimationKeyframes(
|
||||
firstKeyframe.styles.styles.push(extraFirstKeyframeStyles);
|
||||
}
|
||||
|
||||
collectAndResolveStyles(collectedStyles, [finalStateStyles]);
|
||||
|
||||
return keyframes;
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ export class DebugDomRootRenderer implements RootRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
export class DebugDomRenderer implements Renderer {
|
||||
export class DebugDomRenderer {
|
||||
constructor(private _delegate: Renderer) {}
|
||||
|
||||
selectRootElement(selectorOrNode: string|any, debugInfo?: RenderDebugInfo): any {
|
||||
@ -150,7 +150,9 @@ export class DebugDomRenderer implements Renderer {
|
||||
|
||||
animate(
|
||||
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
|
||||
duration: number, delay: number, easing: string): AnimationPlayer {
|
||||
return this._delegate.animate(element, startingStyles, keyframes, duration, delay, easing);
|
||||
duration: number, delay: number, easing: string,
|
||||
previousPlayers: AnimationPlayer[] = []): AnimationPlayer {
|
||||
return this._delegate.animate(
|
||||
element, startingStyles, keyframes, duration, delay, easing, previousPlayers);
|
||||
}
|
||||
}
|
||||
|
@ -8,8 +8,9 @@
|
||||
import {AnimationGroupPlayer} from '../animation/animation_group_player';
|
||||
import {AnimationPlayer} from '../animation/animation_player';
|
||||
import {queueAnimation as queueAnimationGlobally} from '../animation/animation_queue';
|
||||
import {AnimationTransitionEvent} from '../animation/animation_transition_event';
|
||||
import {AnimationSequencePlayer} from '../animation/animation_sequence_player';
|
||||
import {ViewAnimationMap} from '../animation/view_animation_map';
|
||||
import {ListWrapper} from '../facade/collection';
|
||||
|
||||
export class AnimationViewContext {
|
||||
private _players = new ViewAnimationMap();
|
||||
@ -30,15 +31,26 @@ export class AnimationViewContext {
|
||||
this._players.set(element, animationName, player);
|
||||
}
|
||||
|
||||
cancelActiveAnimation(element: any, animationName: string, removeAllAnimations: boolean = false):
|
||||
void {
|
||||
getAnimationPlayers(element: any, animationName: string, removeAllAnimations: boolean = false):
|
||||
AnimationPlayer[] {
|
||||
const players: AnimationPlayer[] = [];
|
||||
if (removeAllAnimations) {
|
||||
this._players.findAllPlayersByElement(element).forEach(player => player.destroy());
|
||||
this._players.findAllPlayersByElement(element).forEach(
|
||||
player => { _recursePlayers(player, players); });
|
||||
} else {
|
||||
const player = this._players.find(element, animationName);
|
||||
if (player) {
|
||||
player.destroy();
|
||||
const currentPlayer = this._players.find(element, animationName);
|
||||
if (currentPlayer) {
|
||||
_recursePlayers(currentPlayer, players);
|
||||
}
|
||||
}
|
||||
return players;
|
||||
}
|
||||
}
|
||||
|
||||
function _recursePlayers(player: AnimationPlayer, collectedPlayers: AnimationPlayer[]) {
|
||||
if ((player instanceof AnimationGroupPlayer) || (player instanceof AnimationSequencePlayer)) {
|
||||
player.players.forEach(player => _recursePlayers(player, collectedPlayers));
|
||||
} else {
|
||||
collectedPlayers.push(player);
|
||||
}
|
||||
}
|
||||
|
@ -232,8 +232,10 @@ export abstract class AppView<T> {
|
||||
if (nextSibling) {
|
||||
this.visitRootNodesInternal(this._directRenderer.insertBefore, nextSibling);
|
||||
} else {
|
||||
this.visitRootNodesInternal(
|
||||
this._directRenderer.appendChild, this._directRenderer.parentElement(prevNode));
|
||||
const parentElement = this._directRenderer.parentElement(prevNode);
|
||||
if (parentElement) {
|
||||
this.visitRootNodesInternal(this._directRenderer.appendChild, parentElement);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.renderer.attachViewAfter(prevNode, this.flatRootNodes);
|
||||
|
@ -56,8 +56,12 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
|
||||
}
|
||||
|
||||
// API of tsickle for lowering decorators to properties on the class.
|
||||
if ((<any>type).ctorParameters) {
|
||||
const ctorParameters = (<any>type).ctorParameters;
|
||||
const tsickleCtorParams = (<any>type).ctorParameters;
|
||||
if (tsickleCtorParams) {
|
||||
// Newer tsickle uses a function closure
|
||||
// Retain the non-function case for compatibility with older tsickle
|
||||
const ctorParameters =
|
||||
typeof tsickleCtorParams === 'function' ? tsickleCtorParams() : tsickleCtorParams;
|
||||
const paramTypes = ctorParameters.map((ctorParam: any) => ctorParam && ctorParam.type);
|
||||
const paramAnnotations = ctorParameters.map(
|
||||
(ctorParam: any) =>
|
||||
|
@ -88,7 +88,8 @@ export abstract class Renderer {
|
||||
|
||||
abstract animate(
|
||||
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
|
||||
duration: number, delay: number, easing: string): AnimationPlayer;
|
||||
duration: number, delay: number, easing: string,
|
||||
previousPlayers?: AnimationPlayer[]): AnimationPlayer;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1854,6 +1854,8 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||
let animation = driver.log.pop();
|
||||
let kf = animation['keyframeLookup'];
|
||||
expect(kf[1]).toEqual([1, {'background': 'green'}]);
|
||||
let player = animation['player'];
|
||||
player.finish();
|
||||
|
||||
cmp.exp = 'blue';
|
||||
fixture.detectChanges();
|
||||
@ -1863,6 +1865,8 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||
kf = animation['keyframeLookup'];
|
||||
expect(kf[0]).toEqual([0, {'background': 'green'}]);
|
||||
expect(kf[1]).toEqual([1, {'background': 'grey'}]);
|
||||
player = animation['player'];
|
||||
player.finish();
|
||||
|
||||
cmp.exp = 'red';
|
||||
fixture.detectChanges();
|
||||
@ -1872,6 +1876,8 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||
kf = animation['keyframeLookup'];
|
||||
expect(kf[0]).toEqual([0, {'background': 'grey'}]);
|
||||
expect(kf[1]).toEqual([1, {'background': 'red'}]);
|
||||
player = animation['player'];
|
||||
player.finish();
|
||||
|
||||
cmp.exp = 'orange';
|
||||
fixture.detectChanges();
|
||||
@ -1881,6 +1887,8 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||
kf = animation['keyframeLookup'];
|
||||
expect(kf[0]).toEqual([0, {'background': 'red'}]);
|
||||
expect(kf[1]).toEqual([1, {'background': 'grey'}]);
|
||||
player = animation['player'];
|
||||
player.finish();
|
||||
}));
|
||||
|
||||
it('should seed in the origin animation state styles into the first animation step',
|
||||
@ -1911,6 +1919,44 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||
expect(animation['startingStyles']).toEqual({'height': '100px'});
|
||||
}));
|
||||
|
||||
it('should seed in the previous animation styles into the transition if the previous transition was interupted midway',
|
||||
fakeAsync(() => {
|
||||
TestBed.overrideComponent(DummyIfCmp, {
|
||||
set: {
|
||||
template: `
|
||||
<div class="target" [@status]="exp"></div>
|
||||
`,
|
||||
animations: [trigger(
|
||||
'status',
|
||||
[
|
||||
state('*', style({ opacity: 0 })),
|
||||
state('a', style({height: '100px', width: '200px'})),
|
||||
state('b', style({height: '1000px' })),
|
||||
transition('* => *', [
|
||||
animate(1000, style({ fontSize: '20px' })),
|
||||
animate(1000)
|
||||
])
|
||||
])]
|
||||
}
|
||||
});
|
||||
|
||||
const driver = TestBed.get(AnimationDriver) as MockAnimationDriver;
|
||||
const fixture = TestBed.createComponent(DummyIfCmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
|
||||
cmp.exp = 'a';
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
driver.log = [];
|
||||
|
||||
cmp.exp = 'b';
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
|
||||
const animation = driver.log[0];
|
||||
expect(animation['previousStyles']).toEqual({opacity: '0', fontSize: '*'});
|
||||
}));
|
||||
|
||||
it('should perform a state change even if there is no transition that is found',
|
||||
fakeAsync(() => {
|
||||
TestBed.overrideComponent(DummyIfCmp, {
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Component, Injectable, RenderComponentType, Renderer, RootRenderer} from '@angular/core';
|
||||
import {Component, ContentChild, Injectable, Input, RenderComponentType, Renderer, RootRenderer, TemplateRef} from '@angular/core';
|
||||
import {DebugDomRenderer} from '@angular/core/src/debug/debug_renderer';
|
||||
import {DirectRenderer} from '@angular/core/src/render/api';
|
||||
import {TestBed, inject} from '@angular/core/testing';
|
||||
@ -125,6 +125,46 @@ export function main() {
|
||||
const projectedNode = childHostEl.childNodes[1];
|
||||
expect(directRenderer.appendChild).toHaveBeenCalledWith(projectedNode, childHostEl);
|
||||
});
|
||||
|
||||
it('should support using structural directives with ngTemplateOutlet', () => {
|
||||
@Component({
|
||||
template:
|
||||
'<child [templateCtx]="templateCtx"><template let-shown="shown" #tpl><span *ngIf="shown">hello</span></template></child>'
|
||||
})
|
||||
class Parent {
|
||||
templateCtx = {shown: false};
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'child',
|
||||
template:
|
||||
'(<template [ngTemplateOutlet]="templateRef" [ngOutletContext]="templateCtx"></template>)'
|
||||
})
|
||||
class Child {
|
||||
@Input()
|
||||
templateCtx: any;
|
||||
|
||||
@ContentChild('tpl')
|
||||
templateRef: TemplateRef<any>;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [Parent, Child]});
|
||||
|
||||
let fixture = TestBed.createComponent(Parent);
|
||||
fixture.componentInstance.templateCtx.shown = false;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('()');
|
||||
fixture.destroy();
|
||||
|
||||
fixture = TestBed.createComponent(Parent);
|
||||
fixture.componentInstance.templateCtx.shown = true;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('(hello)');
|
||||
|
||||
fixture.componentInstance.templateCtx.shown = false;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('()');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -89,6 +89,25 @@ export function main() {
|
||||
const p = reflector.parameters(ClassWithoutDecorators);
|
||||
expect(p.length).toEqual(2);
|
||||
});
|
||||
|
||||
// See https://github.com/angular/tsickle/issues/261
|
||||
it('should read forwardRef down-leveled type', () => {
|
||||
class Dep {}
|
||||
class ForwardLegacy {
|
||||
constructor(d: Dep) {}
|
||||
// Older tsickle had a bug: wrote a forward reference
|
||||
static ctorParameters = [{type: Dep}];
|
||||
}
|
||||
expect(reflector.parameters(ForwardLegacy)).toEqual([[Dep]]);
|
||||
class Forward {
|
||||
constructor(d: Dep) {}
|
||||
// Newer tsickle generates a functionClosure
|
||||
static ctorParameters = () => [{type: ForwardDep}];
|
||||
}
|
||||
class ForwardDep {}
|
||||
expect(reflector.parameters(Forward)).toEqual([[ForwardDep]]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('propMetadata', () => {
|
||||
|
@ -5,8 +5,7 @@
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AnimationPlayer} from '@angular/core';
|
||||
import {AUTO_STYLE, AnimationPlayer} from '@angular/core';
|
||||
|
||||
export class MockAnimationPlayer implements AnimationPlayer {
|
||||
private _onDoneFns: Function[] = [];
|
||||
@ -16,8 +15,21 @@ export class MockAnimationPlayer implements AnimationPlayer {
|
||||
private _started = false;
|
||||
|
||||
public parentPlayer: AnimationPlayer = null;
|
||||
public previousStyles: {[styleName: string]: string | number} = {};
|
||||
|
||||
public log: any[] /** TODO #9100 */ = [];
|
||||
public log: any[] = [];
|
||||
|
||||
constructor(
|
||||
public startingStyles: {[key: string]: string | number} = {},
|
||||
public keyframes: Array<[number, {[style: string]: string | number}]> = [],
|
||||
previousPlayers: AnimationPlayer[] = []) {
|
||||
previousPlayers.forEach(player => {
|
||||
if (player instanceof MockAnimationPlayer) {
|
||||
const styles = player._captureStyles();
|
||||
Object.keys(styles).forEach(prop => this.previousStyles[prop] = styles[prop]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _onFinish(): void {
|
||||
if (!this._finished) {
|
||||
@ -67,6 +79,32 @@ export class MockAnimationPlayer implements AnimationPlayer {
|
||||
}
|
||||
}
|
||||
|
||||
setPosition(p: any /** TODO #9100 */): void {}
|
||||
setPosition(p: number): void {}
|
||||
getPosition(): number { return 0; }
|
||||
|
||||
private _captureStyles(): {[styleName: string]: string | number} {
|
||||
const captures: {[prop: string]: string | number} = {};
|
||||
|
||||
if (this.hasStarted()) {
|
||||
// when assembling the captured styles, it's important that
|
||||
// we build the keyframe styles in the following order:
|
||||
// {startingStyles, ... other styles within keyframes, ... previousStyles }
|
||||
Object.keys(this.startingStyles).forEach(prop => {
|
||||
captures[prop] = this.startingStyles[prop];
|
||||
});
|
||||
|
||||
this.keyframes.forEach(kf => {
|
||||
const [offset, styles] = kf;
|
||||
const newStyles: {[prop: string]: string | number} = {};
|
||||
Object.keys(styles).forEach(
|
||||
prop => { captures[prop] = this._finished ? styles[prop] : AUTO_STYLE; });
|
||||
});
|
||||
}
|
||||
|
||||
Object.keys(this.previousStyles).forEach(prop => {
|
||||
captures[prop] = this.previousStyles[prop];
|
||||
});
|
||||
|
||||
return captures;
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,9 @@
|
||||
writeScriptTag('/vendor/system.js');
|
||||
writeScriptTag('/vendor/Reflect.js');
|
||||
writeScriptTag('/_common/system-config.js');
|
||||
if (location.pathname.indexOf('/upgrade/') != -1) {
|
||||
writeScriptTag('/vendor/angular.js');
|
||||
}
|
||||
|
||||
function writeScriptTag(scriptUrl: string, onload: string = '') {
|
||||
document.write('<script src="' + scriptUrl + '" onload="' + onload + '"></script>');
|
||||
|
@ -6,6 +6,8 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||
import {AppModule} from './module';
|
||||
import * as module from './module';
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
||||
if (module.AppModule) {
|
||||
platformBrowserDynamic().bootstrapModule(module.AppModule);
|
||||
}
|
@ -19,6 +19,7 @@ System.config({
|
||||
'/vendor/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
|
||||
'@angular/router': '/vendor/@angular/router/bundles/router.umd.js',
|
||||
'@angular/upgrade': '/vendor/@angular/upgrade/bundles/upgrade.umd.js',
|
||||
'@angular/upgrade/static': '/vendor/@angular/upgrade/bundles/upgrade-static.umd.js',
|
||||
'rxjs': '/vendor/rxjs',
|
||||
},
|
||||
packages: {
|
||||
|
@ -20,6 +20,7 @@ mkdir $DIST/vendor/
|
||||
ln -s ../../../dist/packages-dist/ $DIST/vendor/@angular
|
||||
|
||||
for FILE in \
|
||||
../../../node_modules/angular/angular.js \
|
||||
../../../node_modules/zone.js/dist/zone.js \
|
||||
../../../node_modules/systemjs/dist/system.js \
|
||||
../../../node_modules/reflect-metadata/Reflect.js \
|
||||
@ -35,4 +36,6 @@ for MODULE in `find . -name module.ts`; do
|
||||
cp _common/*.html $FINAL_DIR_PATH
|
||||
cp $DIST/_common/*.js $FINAL_DIR_PATH
|
||||
cp $DIST/_common/*.js.map $FINAL_DIR_PATH
|
||||
|
||||
find `dirname $MODULE` -name \*.css -exec cp {} $FINAL_DIR_PATH \;
|
||||
done
|
||||
|
@ -18,7 +18,7 @@
|
||||
"target": "es5",
|
||||
"lib": ["es2015", "dom"],
|
||||
"skipLibCheck": true,
|
||||
"types": ["jasmine", "node"]
|
||||
"types": ["jasmine", "node", "angularjs"]
|
||||
},
|
||||
"include": [
|
||||
"./_common/*.ts",
|
||||
|
@ -0,0 +1,54 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {browser, by, element} from 'protractor';
|
||||
import {verifyNoBrowserErrors} from '../../../../_common/e2e_util';
|
||||
|
||||
function loadPage(url: string) {
|
||||
browser.ng12Hybrid = true;
|
||||
browser.rootEl = 'example-app';
|
||||
browser.get(url);
|
||||
}
|
||||
|
||||
describe('upgrade(static)', () => {
|
||||
beforeEach(() => { loadPage('/upgrade/static/ts/'); });
|
||||
afterEach(verifyNoBrowserErrors);
|
||||
|
||||
it('should render the `ng2-heroes` component', () => {
|
||||
expect(element(by.css('h1')).getText()).toEqual('Heroes');
|
||||
expect(element.all(by.css('p')).get(0).getText()).toEqual('There are 3 heroes.');
|
||||
});
|
||||
|
||||
it('should render 3 ng1-hero components', () => {
|
||||
const heroComponents = element.all(by.css('ng1-hero'));
|
||||
expect(heroComponents.count()).toEqual(3);
|
||||
});
|
||||
|
||||
it('should add a new hero when the "Add Hero" button is pressed', () => {
|
||||
const addHeroButton = element.all(by.css('button')).last();
|
||||
expect(addHeroButton.getText()).toEqual('Add Hero');
|
||||
addHeroButton.click();
|
||||
const heroComponents = element.all(by.css('ng1-hero'));
|
||||
expect(heroComponents.last().element(by.css('h2')).getText()).toEqual('Kamala Khan');
|
||||
});
|
||||
|
||||
it('should remove a hero when the "Remove" button is pressed', () => {
|
||||
let firstHero = element.all(by.css('ng1-hero')).get(0);
|
||||
expect(firstHero.element(by.css('h2')).getText()).toEqual('Superman');
|
||||
|
||||
const removeHeroButton = firstHero.element(by.css('button'));
|
||||
expect(removeHeroButton.getText()).toEqual('Remove');
|
||||
removeHeroButton.click();
|
||||
|
||||
const heroComponents = element.all(by.css('ng1-hero'));
|
||||
expect(heroComponents.count()).toEqual(2);
|
||||
|
||||
firstHero = element.all(by.css('ng1-hero')).get(0);
|
||||
expect(firstHero.element(by.css('h2')).getText()).toEqual('Wonder Woman');
|
||||
});
|
||||
});
|
184
modules/@angular/examples/upgrade/static/ts/module.ts
Normal file
184
modules/@angular/examples/upgrade/static/ts/module.ts
Normal file
@ -0,0 +1,184 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {Component, Directive, DoCheck, ElementRef, EventEmitter, Inject, Injectable, Injector, Input, NgModule, OnChanges, OnDestroy, OnInit, Output, SimpleChanges} from '@angular/core';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||
import {UpgradeComponent, UpgradeModule, downgradeComponent, downgradeInjectable} from '@angular/upgrade/static';
|
||||
|
||||
interface Hero {
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
// #docregion Angular 2 Stuff
|
||||
// #docregion ng2-heroes
|
||||
// This Angular 2 component will be "downgraded" to be used in Angular 1
|
||||
@Component({
|
||||
selector: 'ng2-heroes',
|
||||
// This template uses the upgraded `ng1-hero` component
|
||||
// Note that because its element is compiled by Angular 2+ we must use camelCased attribute names
|
||||
template: `<h1>Heroes</h1>
|
||||
<p><ng-content></ng-content></p>
|
||||
<div *ngFor="let hero of heroes">
|
||||
<ng1-hero [hero]="hero" (onRemove)="removeHero.emit(hero)"><strong>Super Hero</strong></ng1-hero>
|
||||
</div>
|
||||
<button (click)="addHero.emit()">Add Hero</button>`,
|
||||
})
|
||||
class Ng2HeroesComponent {
|
||||
@Input() heroes: Hero[];
|
||||
@Output() addHero = new EventEmitter();
|
||||
@Output() removeHero = new EventEmitter();
|
||||
}
|
||||
// #enddocregion
|
||||
|
||||
// #docregion ng2-heroes-service
|
||||
// This Angular 2 service will be "downgraded" to be used in Angular 1
|
||||
@Injectable()
|
||||
class HeroesService {
|
||||
heroes: Hero[] = [
|
||||
{name: 'superman', description: 'The man of steel'},
|
||||
{name: 'wonder woman', description: 'Princess of the Amazons'},
|
||||
{name: 'thor', description: 'The hammer-wielding god'}
|
||||
];
|
||||
|
||||
// #docregion use-ng1-upgraded-service
|
||||
constructor(@Inject('titleCase') titleCase: (v: string) => string) {
|
||||
// Change all the hero names to title case, using the "upgraded" Angular 1 service
|
||||
this.heroes.forEach((hero: Hero) => hero.name = titleCase(hero.name));
|
||||
}
|
||||
// #enddocregion
|
||||
|
||||
addHero() {
|
||||
this.heroes =
|
||||
this.heroes.concat([{name: 'Kamala Khan', description: 'Epic shape-shifting healer'}]);
|
||||
}
|
||||
|
||||
removeHero(hero: Hero) { this.heroes = this.heroes.filter((item: Hero) => item !== hero); }
|
||||
}
|
||||
// #enddocregion
|
||||
|
||||
// #docregion ng1-hero-wrapper
|
||||
// This Angular 2 directive will act as an interface to the "upgraded" Angular 1 component
|
||||
@Directive({selector: 'ng1-hero'})
|
||||
class Ng1HeroComponentWrapper extends UpgradeComponent implements OnInit, OnChanges, DoCheck,
|
||||
OnDestroy {
|
||||
// The names of the input and output properties here must match the names of the
|
||||
// `<` and `&` bindings in the Angular 1 component that is being wrapped
|
||||
@Input() hero: Hero;
|
||||
@Output() onRemove: EventEmitter<void>;
|
||||
constructor(@Inject(ElementRef) elementRef: ElementRef, @Inject(Injector) injector: Injector) {
|
||||
// We must pass the name of the directive as used by Angular 1 to the super
|
||||
super('ng1Hero', elementRef, injector);
|
||||
}
|
||||
|
||||
// For this class to work when compiled with AoT, we must implement these lifecycle hooks
|
||||
// because the AoT compiler will not realise that the super class implements them
|
||||
ngOnInit() { super.ngOnInit(); }
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) { super.ngOnChanges(changes); }
|
||||
|
||||
ngDoCheck() { super.ngDoCheck(); }
|
||||
|
||||
ngOnDestroy() { super.ngOnDestroy(); }
|
||||
}
|
||||
// #enddocregion
|
||||
|
||||
// #docregion ng2-module
|
||||
// This NgModule represents the Angular 2 pieces of the application
|
||||
@NgModule({
|
||||
declarations: [Ng2HeroesComponent, Ng1HeroComponentWrapper],
|
||||
providers: [
|
||||
HeroesService,
|
||||
// #docregion upgrade-ng1-service
|
||||
// Register an Angular 2+ provider whose value is the "upgraded" Angular 1 service
|
||||
{provide: 'titleCase', useFactory: (i: any) => i.get('titleCase'), deps: ['$injector']}
|
||||
// #enddocregion
|
||||
],
|
||||
// All components that are to be "downgraded" must be declared as `entryComponents`
|
||||
entryComponents: [Ng2HeroesComponent],
|
||||
// We must import `UpgradeModule` to get access to the Angular 1 core services
|
||||
imports: [BrowserModule, UpgradeModule]
|
||||
})
|
||||
class Ng2AppModule {
|
||||
ngDoBootstrap() { /* this is a placeholder to stop the boostrapper from complaining */
|
||||
}
|
||||
}
|
||||
// #enddocregion
|
||||
// #enddocregion
|
||||
|
||||
|
||||
// #docregion Angular 1 Stuff
|
||||
// #docregion ng1-module
|
||||
// This Angular 1 module represents the Angular 1 pieces of the application
|
||||
const ng1AppModule = angular.module('ng1AppModule', []);
|
||||
// #enddocregion
|
||||
|
||||
// #docregion ng1-hero
|
||||
// This Angular 1 component will be "upgraded" to be used in Angular 2+
|
||||
ng1AppModule.component('ng1Hero', {
|
||||
bindings: {hero: '<', onRemove: '&'},
|
||||
transclude: true,
|
||||
template: `<div class="title" ng-transclude></div>
|
||||
<h2>{{ $ctrl.hero.name }}</h2>
|
||||
<p>{{ $ctrl.hero.description }}</p>
|
||||
<button ng-click="$ctrl.onRemove()">Remove</button>`
|
||||
});
|
||||
// #enddocregion
|
||||
|
||||
// #docregion ng1-title-case-service
|
||||
// This Angular 1 service will be "upgraded" to be used in Angular 2+
|
||||
ng1AppModule.factory(
|
||||
'titleCase',
|
||||
() => (value: string) => value.replace(/((^|\s)[a-z])/g, (_, c) => c.toUpperCase()));
|
||||
// #enddocregion
|
||||
|
||||
// #docregion downgrade-ng2-heroes-service
|
||||
// Register an Angular 1 service, whose value is the "downgraded" Angular 2+ injectable.
|
||||
ng1AppModule.factory('heroesService', downgradeInjectable(HeroesService));
|
||||
// #enddocregion
|
||||
|
||||
// #docregion ng2-heroes-wrapper
|
||||
// This is directive will act as the interface to the "downgraded" Angular 2+ component
|
||||
ng1AppModule.directive(
|
||||
'ng2Heroes',
|
||||
downgradeComponent(
|
||||
// The inputs and outputs here must match the relevant names of the properties on the
|
||||
// "downgraded" component
|
||||
{component: Ng2HeroesComponent, inputs: ['heroes'], outputs: ['addHero', 'removeHero']}));
|
||||
// #enddocregion
|
||||
|
||||
// #docregion example-app
|
||||
// This is our top level application component
|
||||
ng1AppModule.component('exampleApp', {
|
||||
// We inject the "downgraded" HeroesService into this Angular 1 component
|
||||
// (We don't need the `HeroesService` type for Angular 1 DI - it just helps with TypeScript
|
||||
// compilation)
|
||||
controller: [
|
||||
'heroesService', function(heroesService: HeroesService) { this.heroesService = heroesService; }
|
||||
],
|
||||
// This template make use of the downgraded `ng2-heroes` component
|
||||
// Note that because its element is compiled by Angular 1 we must use kebab-case attributes for
|
||||
// inputs and outputs
|
||||
template: `<link rel="stylesheet" href="./styles.css">
|
||||
<ng2-heroes [heroes]="$ctrl.heroesService.heroes" (add-hero)="$ctrl.heroesService.addHero()" (remove-hero)="$ctrl.heroesService.removeHero($event)">
|
||||
There are {{ $ctrl.heroesService.heroes.length }} heroes.
|
||||
</ng2-heroes>`
|
||||
});
|
||||
// #enddocregion
|
||||
// #enddocregion
|
||||
|
||||
|
||||
// #docregion bootstrap
|
||||
// First we bootstrap the Angular 2 HybridModule
|
||||
// (We are using the dynamic browser platform as this example has not been compiled AoT)
|
||||
platformBrowserDynamic().bootstrapModule(Ng2AppModule).then(ref => {
|
||||
// Once Angular 2 bootstrap is complete then we bootstrap the Angular 1 module
|
||||
const upgrade = ref.injector.get(UpgradeModule) as UpgradeModule;
|
||||
upgrade.bootstrap(document.body, [ng1AppModule.name]);
|
||||
});
|
||||
// #enddocregion
|
17
modules/@angular/examples/upgrade/static/ts/styles.css
Normal file
17
modules/@angular/examples/upgrade/static/ts/styles.css
Normal file
@ -0,0 +1,17 @@
|
||||
ng2-heroes {
|
||||
border: solid black 2px;
|
||||
display: block;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
ng1-hero {
|
||||
border: solid green 2px;
|
||||
margin-top: 5px;
|
||||
padding: 5px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.title {
|
||||
background-color: blue;
|
||||
color: white;
|
||||
}
|
@ -44,7 +44,7 @@ const DATE_FORMATS_SPLIT =
|
||||
/((?:[^yMLdHhmsazZEwGjJ']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|J+|j+|m+|s+|a|z|Z|G+|w+))(.*)/;
|
||||
|
||||
const PATTERN_ALIASES: {[format: string]: DateFormatterFn} = {
|
||||
yMMMdjms: datePartGetterFactory(combine([
|
||||
'yMMMdjms': datePartGetterFactory(combine([
|
||||
digitCondition('year', 1),
|
||||
nameCondition('month', 3),
|
||||
digitCondition('day', 1),
|
||||
@ -52,23 +52,23 @@ const PATTERN_ALIASES: {[format: string]: DateFormatterFn} = {
|
||||
digitCondition('minute', 1),
|
||||
digitCondition('second', 1),
|
||||
])),
|
||||
yMdjm: datePartGetterFactory(combine([
|
||||
'yMdjm': datePartGetterFactory(combine([
|
||||
digitCondition('year', 1), digitCondition('month', 1), digitCondition('day', 1),
|
||||
digitCondition('hour', 1), digitCondition('minute', 1)
|
||||
])),
|
||||
yMMMMEEEEd: datePartGetterFactory(combine([
|
||||
'yMMMMEEEEd': datePartGetterFactory(combine([
|
||||
digitCondition('year', 1), nameCondition('month', 4), nameCondition('weekday', 4),
|
||||
digitCondition('day', 1)
|
||||
])),
|
||||
yMMMMd: datePartGetterFactory(
|
||||
'yMMMMd': datePartGetterFactory(
|
||||
combine([digitCondition('year', 1), nameCondition('month', 4), digitCondition('day', 1)])),
|
||||
yMMMd: datePartGetterFactory(
|
||||
'yMMMd': datePartGetterFactory(
|
||||
combine([digitCondition('year', 1), nameCondition('month', 3), digitCondition('day', 1)])),
|
||||
yMd: datePartGetterFactory(
|
||||
'yMd': datePartGetterFactory(
|
||||
combine([digitCondition('year', 1), digitCondition('month', 1), digitCondition('day', 1)])),
|
||||
jms: datePartGetterFactory(combine(
|
||||
'jms': datePartGetterFactory(combine(
|
||||
[digitCondition('hour', 1), digitCondition('second', 1), digitCondition('minute', 1)])),
|
||||
jm: datePartGetterFactory(combine([digitCondition('hour', 1), digitCondition('minute', 1)]))
|
||||
'jm': datePartGetterFactory(combine([digitCondition('hour', 1), digitCondition('minute', 1)]))
|
||||
};
|
||||
|
||||
const DATE_FORMATS: {[format: string]: DateFormatterFn} = {
|
||||
|
@ -56,22 +56,12 @@ export const TEMPLATE_DRIVEN_DIRECTIVES: Type<any>[] = [NgModel, NgModelGroup, N
|
||||
export const REACTIVE_DRIVEN_DIRECTIVES: Type<any>[] =
|
||||
[FormControlDirective, FormGroupDirective, FormControlName, FormGroupName, FormArrayName];
|
||||
|
||||
/**
|
||||
* A list of all the form directives.
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
export const FORM_DIRECTIVES: Type<any>[][] = [TEMPLATE_DRIVEN_DIRECTIVES, SHARED_FORM_DIRECTIVES];
|
||||
|
||||
/**
|
||||
* @stable
|
||||
*/
|
||||
export const REACTIVE_FORM_DIRECTIVES: Type<any>[][] =
|
||||
[REACTIVE_DRIVEN_DIRECTIVES, SHARED_FORM_DIRECTIVES];
|
||||
|
||||
/**
|
||||
* Internal module used for sharing directives between FormsModule and ReactiveFormsModule
|
||||
*/
|
||||
@NgModule({declarations: SHARED_FORM_DIRECTIVES, exports: SHARED_FORM_DIRECTIVES})
|
||||
@NgModule({
|
||||
declarations: SHARED_FORM_DIRECTIVES,
|
||||
exports: SHARED_FORM_DIRECTIVES,
|
||||
})
|
||||
export class InternalFormsSharedModule {
|
||||
}
|
||||
|
@ -6,13 +6,13 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Directive, ElementRef, Host, Input, OnDestroy, Optional, Renderer, forwardRef} from '@angular/core';
|
||||
import {Directive, ElementRef, Host, Input, OnDestroy, Optional, Provider, Renderer, forwardRef} from '@angular/core';
|
||||
|
||||
import {isPrimitive, looseIdentical} from '../facade/lang';
|
||||
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
|
||||
|
||||
export const SELECT_VALUE_ACCESSOR: any = {
|
||||
export const SELECT_VALUE_ACCESSOR: Provider = {
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => SelectControlValueAccessor),
|
||||
multi: true
|
||||
@ -115,8 +115,8 @@ export class SelectControlValueAccessor implements ControlValueAccessor {
|
||||
|
||||
/** @internal */
|
||||
_getOptionValue(valueString: string): any {
|
||||
const value = this._optionMap.get(_extractId(valueString));
|
||||
return value != null ? value : valueString;
|
||||
const id: string = _extractId(valueString);
|
||||
return this._optionMap.has(id) ? this._optionMap.get(id) : valueString;
|
||||
}
|
||||
}
|
||||
|
||||
@ -158,7 +158,7 @@ export class NgSelectOption implements OnDestroy {
|
||||
this._renderer.setElementProperty(this._element.nativeElement, 'value', value);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
ngOnDestroy(): void {
|
||||
if (this._select) {
|
||||
this._select._optionMap.delete(this.id);
|
||||
this._select.writeValue(this._select.value);
|
||||
|
@ -6,13 +6,13 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Directive, ElementRef, Host, Input, OnDestroy, OpaqueToken, Optional, Renderer, Type, forwardRef} from '@angular/core';
|
||||
import {Directive, ElementRef, Host, Input, OnDestroy, Optional, Provider, Renderer, forwardRef} from '@angular/core';
|
||||
|
||||
import {isPrimitive, looseIdentical} from '../facade/lang';
|
||||
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
|
||||
|
||||
export const SELECT_MULTIPLE_VALUE_ACCESSOR = {
|
||||
export const SELECT_MULTIPLE_VALUE_ACCESSOR: Provider = {
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => SelectMultipleControlValueAccessor),
|
||||
multi: true
|
||||
@ -121,8 +121,8 @@ export class SelectMultipleControlValueAccessor implements ControlValueAccessor
|
||||
|
||||
/** @internal */
|
||||
_getOptionValue(valueString: string): any {
|
||||
const opt = this._optionMap.get(_extractId(valueString));
|
||||
return opt ? opt._value : valueString;
|
||||
const id: string = _extractId(valueString);
|
||||
return this._optionMap.has(id) ? this._optionMap.get(id)._value : valueString;
|
||||
}
|
||||
}
|
||||
|
||||
@ -180,12 +180,10 @@ export class NgSelectMultipleOption implements OnDestroy {
|
||||
this._renderer.setElementProperty(this._element.nativeElement, 'selected', selected);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
ngOnDestroy(): void {
|
||||
if (this._select) {
|
||||
this._select._optionMap.delete(this.id);
|
||||
this._select.writeValue(this._select.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const SELECT_DIRECTIVES = [SelectMultipleControlValueAccessor, NgSelectMultipleOption];
|
||||
|
@ -5,10 +5,7 @@
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Directive, Input, OnChanges, SimpleChanges, forwardRef} from '@angular/core';
|
||||
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {AbstractControl} from '../model';
|
||||
import {NG_VALIDATORS, Validators} from '../validators';
|
||||
|
||||
@ -57,17 +54,17 @@ export const REQUIRED_VALIDATOR: any = {
|
||||
@Directive({
|
||||
selector: '[required][formControlName],[required][formControl],[required][ngModel]',
|
||||
providers: [REQUIRED_VALIDATOR],
|
||||
host: {'[attr.required]': 'required? "" : null'}
|
||||
host: {'[attr.required]': 'required ? "" : null'}
|
||||
})
|
||||
export class RequiredValidator implements Validator {
|
||||
private _required: boolean;
|
||||
private _onChange: () => void;
|
||||
|
||||
@Input()
|
||||
get required(): boolean { return this._required; }
|
||||
get required(): boolean /*| string*/ { return this._required; }
|
||||
|
||||
set required(value: boolean) {
|
||||
this._required = isPresent(value) && `${value}` !== 'false';
|
||||
this._required = value != null && value !== false && `${value}` !== 'false';
|
||||
if (this._onChange) this._onChange();
|
||||
}
|
||||
|
||||
@ -75,7 +72,7 @@ export class RequiredValidator implements Validator {
|
||||
return this.required ? Validators.required(c) : null;
|
||||
}
|
||||
|
||||
registerOnValidatorChange(fn: () => void) { this._onChange = fn; }
|
||||
registerOnValidatorChange(fn: () => void): void { this._onChange = fn; }
|
||||
}
|
||||
|
||||
/**
|
||||
@ -112,7 +109,7 @@ export const MIN_LENGTH_VALIDATOR: any = {
|
||||
@Directive({
|
||||
selector: '[minlength][formControlName],[minlength][formControl],[minlength][ngModel]',
|
||||
providers: [MIN_LENGTH_VALIDATOR],
|
||||
host: {'[attr.minlength]': 'minlength? minlength : null'}
|
||||
host: {'[attr.minlength]': 'minlength ? minlength : null'}
|
||||
})
|
||||
export class MinLengthValidator implements Validator,
|
||||
OnChanges {
|
||||
@ -121,12 +118,8 @@ export class MinLengthValidator implements Validator,
|
||||
|
||||
@Input() minlength: string;
|
||||
|
||||
private _createValidator() {
|
||||
this._validator = Validators.minLength(parseInt(this.minlength, 10));
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes['minlength']) {
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if ('minlength' in changes) {
|
||||
this._createValidator();
|
||||
if (this._onChange) this._onChange();
|
||||
}
|
||||
@ -136,7 +129,11 @@ export class MinLengthValidator implements Validator,
|
||||
return this.minlength == null ? null : this._validator(c);
|
||||
}
|
||||
|
||||
registerOnValidatorChange(fn: () => void) { this._onChange = fn; }
|
||||
registerOnValidatorChange(fn: () => void): void { this._onChange = fn; }
|
||||
|
||||
private _createValidator(): void {
|
||||
this._validator = Validators.minLength(parseInt(this.minlength, 10));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -162,7 +159,7 @@ export const MAX_LENGTH_VALIDATOR: any = {
|
||||
@Directive({
|
||||
selector: '[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]',
|
||||
providers: [MAX_LENGTH_VALIDATOR],
|
||||
host: {'[attr.maxlength]': 'maxlength? maxlength : null'}
|
||||
host: {'[attr.maxlength]': 'maxlength ? maxlength : null'}
|
||||
})
|
||||
export class MaxLengthValidator implements Validator,
|
||||
OnChanges {
|
||||
@ -171,22 +168,22 @@ export class MaxLengthValidator implements Validator,
|
||||
|
||||
@Input() maxlength: string;
|
||||
|
||||
private _createValidator() {
|
||||
this._validator = Validators.maxLength(parseInt(this.maxlength, 10));
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes['maxlength']) {
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if ('maxlength' in changes) {
|
||||
this._createValidator();
|
||||
if (this._onChange) this._onChange();
|
||||
}
|
||||
}
|
||||
|
||||
validate(c: AbstractControl): {[key: string]: any} {
|
||||
return isPresent(this.maxlength) ? this._validator(c) : null;
|
||||
return this.maxlength != null ? this._validator(c) : null;
|
||||
}
|
||||
|
||||
registerOnValidatorChange(fn: () => void) { this._onChange = fn; }
|
||||
registerOnValidatorChange(fn: () => void): void { this._onChange = fn; }
|
||||
|
||||
private _createValidator(): void {
|
||||
this._validator = Validators.maxLength(parseInt(this.maxlength, 10));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -220,20 +217,18 @@ export class PatternValidator implements Validator,
|
||||
private _validator: ValidatorFn;
|
||||
private _onChange: () => void;
|
||||
|
||||
@Input() pattern: string;
|
||||
@Input() pattern: string /*|RegExp*/;
|
||||
|
||||
private _createValidator() { this._validator = Validators.pattern(this.pattern); }
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes['pattern']) {
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if ('pattern' in changes) {
|
||||
this._createValidator();
|
||||
if (this._onChange) this._onChange();
|
||||
}
|
||||
}
|
||||
|
||||
validate(c: AbstractControl): {[key: string]: any} {
|
||||
return this.pattern ? this._validator(c) : null;
|
||||
}
|
||||
validate(c: AbstractControl): {[key: string]: any} { return this._validator(c); }
|
||||
|
||||
registerOnValidatorChange(fn: () => void) { this._onChange = fn; }
|
||||
registerOnValidatorChange(fn: () => void): void { this._onChange = fn; }
|
||||
|
||||
private _createValidator(): void { this._validator = Validators.pattern(this.pattern); }
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ export function main() {
|
||||
NgModelRadioForm, NgModelRangeForm, NgModelSelectForm, NgNoFormComp, InvalidNgModelNoName,
|
||||
NgModelOptionsStandalone, NgModelCustomComp, NgModelCustomWrapper,
|
||||
NgModelValidationBindings, NgModelMultipleValidators, NgAsyncValidator,
|
||||
NgModelAsyncValidation
|
||||
NgModelAsyncValidation, NgModelSelectWithNullForm
|
||||
],
|
||||
imports: [FormsModule]
|
||||
});
|
||||
@ -699,6 +699,28 @@ export function main() {
|
||||
expect(select.nativeElement.value).toEqual('2: Object');
|
||||
expect(secondNYC.nativeElement.selected).toBe(true);
|
||||
}));
|
||||
|
||||
it('should work with null option', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelSelectWithNullForm);
|
||||
const comp = fixture.componentInstance;
|
||||
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}];
|
||||
comp.selectedCity = null;
|
||||
fixture.detectChanges();
|
||||
|
||||
const select = fixture.debugElement.query(By.css('select'));
|
||||
|
||||
select.nativeElement.value = '2: Object';
|
||||
dispatchEvent(select.nativeElement, 'change');
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
expect(comp.selectedCity['name']).toEqual('NYC');
|
||||
|
||||
select.nativeElement.value = '0: null';
|
||||
dispatchEvent(select.nativeElement, 'change');
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
expect(comp.selectedCity).toEqual(null);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('custom value accessors', () => {
|
||||
@ -771,7 +793,7 @@ export function main() {
|
||||
expect(form.valid).toEqual(true);
|
||||
}));
|
||||
|
||||
it('should support optional fields with pattern validator', fakeAsync(() => {
|
||||
it('should support optional fields with string pattern validator', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelMultipleValidators);
|
||||
fixture.componentInstance.required = false;
|
||||
fixture.componentInstance.pattern = '[a-z]+';
|
||||
@ -793,6 +815,28 @@ export function main() {
|
||||
expect(form.control.hasError('pattern', ['tovalidate'])).toBeTruthy();
|
||||
}));
|
||||
|
||||
it('should support optional fields with RegExp pattern validator', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelMultipleValidators);
|
||||
fixture.componentInstance.required = false;
|
||||
fixture.componentInstance.pattern = /^[a-z]+$/;
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
|
||||
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
||||
const input = fixture.debugElement.query(By.css('input'));
|
||||
|
||||
input.nativeElement.value = '';
|
||||
dispatchEvent(input.nativeElement, 'input');
|
||||
fixture.detectChanges();
|
||||
expect(form.valid).toBeTruthy();
|
||||
|
||||
input.nativeElement.value = '1';
|
||||
dispatchEvent(input.nativeElement, 'input');
|
||||
fixture.detectChanges();
|
||||
expect(form.valid).toBeFalsy();
|
||||
expect(form.control.hasError('pattern', ['tovalidate'])).toBeTruthy();
|
||||
}));
|
||||
|
||||
it('should support optional fields with minlength validator', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelMultipleValidators);
|
||||
fixture.componentInstance.required = false;
|
||||
@ -1078,6 +1122,20 @@ class NgModelSelectForm {
|
||||
cities: any[] = [];
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'ng-model-select-null-form',
|
||||
template: `
|
||||
<select [(ngModel)]="selectedCity">
|
||||
<option *ngFor="let c of cities" [ngValue]="c"> {{c.name}} </option>
|
||||
<option [ngValue]="null">Unspecified</option>
|
||||
</select>
|
||||
`
|
||||
})
|
||||
class NgModelSelectWithNullForm {
|
||||
selectedCity: {[k: string]: string} = {};
|
||||
cities: any[] = [];
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'ng-model-custom-comp',
|
||||
template: `
|
||||
@ -1141,7 +1199,7 @@ class NgModelValidationBindings {
|
||||
class NgModelMultipleValidators {
|
||||
required: boolean;
|
||||
minLen: number;
|
||||
pattern: string;
|
||||
pattern: string|RegExp;
|
||||
}
|
||||
|
||||
@Directive({
|
||||
|
@ -7,15 +7,15 @@
|
||||
*/
|
||||
|
||||
import {Injectable} from '@angular/core';
|
||||
import {global} from '../facade/lang';
|
||||
|
||||
let _nextRequestId = 0;
|
||||
export const JSONP_HOME = '__ng_jsonp__';
|
||||
let _jsonpConnections: {[key: string]: any} = null;
|
||||
|
||||
function _getJsonpConnections(): {[key: string]: any} {
|
||||
const w: {[key: string]: any} = typeof window == 'object' ? window : {};
|
||||
if (_jsonpConnections === null) {
|
||||
_jsonpConnections = (<{[key: string]: any}>global)[JSONP_HOME] = {};
|
||||
_jsonpConnections = w[JSONP_HOME] = {};
|
||||
}
|
||||
return _jsonpConnections;
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ import {Observer} from 'rxjs/Observer';
|
||||
|
||||
import {ResponseOptions} from '../base_response_options';
|
||||
import {ReadyState, RequestMethod, ResponseType} from '../enums';
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {Connection, ConnectionBackend} from '../interfaces';
|
||||
import {Request} from '../static_request';
|
||||
import {Response} from '../static_response';
|
||||
@ -89,7 +88,7 @@ export class JSONPConnection_ extends JSONPConnection {
|
||||
if (!this._finished) {
|
||||
let responseOptions =
|
||||
new ResponseOptions({body: JSONP_ERR_NO_CALLBACK, type: ResponseType.Error, url});
|
||||
if (isPresent(baseResponseOptions)) {
|
||||
if (baseResponseOptions) {
|
||||
responseOptions = baseResponseOptions.merge(responseOptions);
|
||||
}
|
||||
responseObserver.error(new Response(responseOptions));
|
||||
@ -97,7 +96,7 @@ export class JSONPConnection_ extends JSONPConnection {
|
||||
}
|
||||
|
||||
let responseOptions = new ResponseOptions({body: this._responseData, url});
|
||||
if (isPresent(this.baseResponseOptions)) {
|
||||
if (this.baseResponseOptions) {
|
||||
responseOptions = this.baseResponseOptions.merge(responseOptions);
|
||||
}
|
||||
|
||||
@ -110,7 +109,7 @@ export class JSONPConnection_ extends JSONPConnection {
|
||||
this.readyState = ReadyState.Done;
|
||||
_dom.cleanup(script);
|
||||
let responseOptions = new ResponseOptions({body: error.message, type: ResponseType.Error});
|
||||
if (isPresent(baseResponseOptions)) {
|
||||
if (baseResponseOptions) {
|
||||
responseOptions = baseResponseOptions.merge(responseOptions);
|
||||
}
|
||||
responseObserver.error(new Response(responseOptions));
|
||||
@ -125,10 +124,7 @@ export class JSONPConnection_ extends JSONPConnection {
|
||||
this.readyState = ReadyState.Cancelled;
|
||||
script.removeEventListener('load', onLoad);
|
||||
script.removeEventListener('error', onError);
|
||||
if (isPresent(script)) {
|
||||
this._dom.cleanup(script);
|
||||
}
|
||||
|
||||
this._dom.cleanup(script);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@ -10,16 +10,13 @@ import {Injectable} from '@angular/core';
|
||||
import {__platform_browser_private__} from '@angular/platform-browser';
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
import {Observer} from 'rxjs/Observer';
|
||||
|
||||
import {ResponseOptions} from '../base_response_options';
|
||||
import {ContentType, ReadyState, RequestMethod, ResponseContentType, ResponseType} from '../enums';
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {Headers} from '../headers';
|
||||
import {getResponseURL, isSuccess} from '../http_utils';
|
||||
import {Connection, ConnectionBackend, XSRFStrategy} from '../interfaces';
|
||||
import {Request} from '../static_request';
|
||||
import {Response} from '../static_response';
|
||||
|
||||
import {BrowserXhr} from './browser_xhr';
|
||||
|
||||
const XSSI_PREFIX = /^\)\]\}',?\n/;
|
||||
@ -47,24 +44,29 @@ export class XHRConnection implements Connection {
|
||||
this.response = new Observable<Response>((responseObserver: Observer<Response>) => {
|
||||
const _xhr: XMLHttpRequest = browserXHR.build();
|
||||
_xhr.open(RequestMethod[req.method].toUpperCase(), req.url);
|
||||
if (isPresent(req.withCredentials)) {
|
||||
if (req.withCredentials != null) {
|
||||
_xhr.withCredentials = req.withCredentials;
|
||||
}
|
||||
// load event handler
|
||||
const onLoad = () => {
|
||||
// responseText is the old-school way of retrieving response (supported by IE8 & 9)
|
||||
// response/responseType properties were introduced in ResourceLoader Level2 spec (supported
|
||||
// by IE10)
|
||||
let body = _xhr.response === undefined ? _xhr.responseText : _xhr.response;
|
||||
// Implicitly strip a potential XSSI prefix.
|
||||
if (typeof body === 'string') body = body.replace(XSSI_PREFIX, '');
|
||||
const headers = Headers.fromResponseHeaderString(_xhr.getAllResponseHeaders());
|
||||
|
||||
const url = getResponseURL(_xhr);
|
||||
|
||||
// normalize IE9 bug (http://bugs.jquery.com/ticket/1450)
|
||||
let status: number = _xhr.status === 1223 ? 204 : _xhr.status;
|
||||
|
||||
let body: any = null;
|
||||
|
||||
// HTTP 204 means no content
|
||||
if (status !== 204) {
|
||||
// responseText is the old-school way of retrieving response (supported by IE8 & 9)
|
||||
// response/responseType properties were introduced in ResourceLoader Level2 spec
|
||||
// (supported by IE10)
|
||||
body = _xhr.response == null ? _xhr.responseText : _xhr.response;
|
||||
|
||||
// Implicitly strip a potential XSSI prefix.
|
||||
if (typeof body === 'string') {
|
||||
body = body.replace(XSSI_PREFIX, '');
|
||||
}
|
||||
}
|
||||
|
||||
// fix status code when it is 0 (0 status is undocumented).
|
||||
// Occurs when accessing file resources or on Android 4.1 stock browser
|
||||
// while retrieving files from application cache.
|
||||
@ -72,10 +74,13 @@ export class XHRConnection implements Connection {
|
||||
status = body ? 200 : 0;
|
||||
}
|
||||
|
||||
const statusText = _xhr.statusText || 'OK';
|
||||
const headers: Headers = Headers.fromResponseHeaderString(_xhr.getAllResponseHeaders());
|
||||
// IE 9 does not provide the way to get URL of response
|
||||
const url = getResponseURL(_xhr) || req.url;
|
||||
const statusText: string = _xhr.statusText || 'OK';
|
||||
|
||||
let responseOptions = new ResponseOptions({body, status, headers, statusText, url});
|
||||
if (isPresent(baseResponseOptions)) {
|
||||
if (baseResponseOptions != null) {
|
||||
responseOptions = baseResponseOptions.merge(responseOptions);
|
||||
}
|
||||
const response = new Response(responseOptions);
|
||||
@ -89,14 +94,14 @@ export class XHRConnection implements Connection {
|
||||
responseObserver.error(response);
|
||||
};
|
||||
// error event handler
|
||||
const onError = (err: any) => {
|
||||
const onError = (err: ErrorEvent) => {
|
||||
let responseOptions = new ResponseOptions({
|
||||
body: err,
|
||||
type: ResponseType.Error,
|
||||
status: _xhr.status,
|
||||
statusText: _xhr.statusText,
|
||||
});
|
||||
if (isPresent(baseResponseOptions)) {
|
||||
if (baseResponseOptions != null) {
|
||||
responseOptions = baseResponseOptions.merge(responseOptions);
|
||||
}
|
||||
responseObserver.error(new Response(responseOptions));
|
||||
@ -104,12 +109,12 @@ export class XHRConnection implements Connection {
|
||||
|
||||
this.setDetectedContentType(req, _xhr);
|
||||
|
||||
if (isPresent(req.headers)) {
|
||||
if (req.headers != null) {
|
||||
req.headers.forEach((values, name) => _xhr.setRequestHeader(name, values.join(',')));
|
||||
}
|
||||
|
||||
// Select the correct buffer type to store the response
|
||||
if (isPresent(req.responseType) && isPresent(_xhr.responseType)) {
|
||||
if (req.responseType != null && _xhr.responseType != null) {
|
||||
switch (req.responseType) {
|
||||
case ResponseContentType.ArrayBuffer:
|
||||
_xhr.responseType = 'arraybuffer';
|
||||
@ -141,9 +146,9 @@ export class XHRConnection implements Connection {
|
||||
});
|
||||
}
|
||||
|
||||
setDetectedContentType(req: any /** TODO #9100 */, _xhr: any /** TODO #9100 */) {
|
||||
setDetectedContentType(req: any /** TODO Request */, _xhr: any /** XMLHttpRequest */) {
|
||||
// Skip if a custom Content-Type header is provided
|
||||
if (isPresent(req.headers) && isPresent(req.headers.get('Content-Type'))) {
|
||||
if (req.headers != null && req.headers.get('Content-Type') != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -161,7 +166,7 @@ export class XHRConnection implements Connection {
|
||||
_xhr.setRequestHeader('content-type', 'text/plain');
|
||||
break;
|
||||
case ContentType.BLOB:
|
||||
let blob = req.blob();
|
||||
const blob = req.blob();
|
||||
if (blob.type) {
|
||||
_xhr.setRequestHeader('content-type', blob.type);
|
||||
}
|
||||
@ -185,7 +190,7 @@ export class CookieXSRFStrategy implements XSRFStrategy {
|
||||
constructor(
|
||||
private _cookieName: string = 'XSRF-TOKEN', private _headerName: string = 'X-XSRF-TOKEN') {}
|
||||
|
||||
configureRequest(req: Request) {
|
||||
configureRequest(req: Request): void {
|
||||
const xsrfToken = __platform_browser_private__.getDOM().getCookie(this._cookieName);
|
||||
if (xsrfToken) {
|
||||
req.headers.set(this._headerName, xsrfToken);
|
||||
|
@ -8,8 +8,6 @@
|
||||
|
||||
import {Injectable} from '@angular/core';
|
||||
|
||||
import {isPresent} from '../src/facade/lang';
|
||||
|
||||
import {RequestMethod, ResponseContentType} from './enums';
|
||||
import {Headers} from './headers';
|
||||
import {normalizeMethodName} from './http_utils';
|
||||
@ -77,16 +75,14 @@ export class RequestOptions {
|
||||
constructor(
|
||||
{method, headers, body, url, search, withCredentials,
|
||||
responseType}: RequestOptionsArgs = {}) {
|
||||
this.method = isPresent(method) ? normalizeMethodName(method) : null;
|
||||
this.headers = isPresent(headers) ? headers : null;
|
||||
this.body = isPresent(body) ? body : null;
|
||||
this.url = isPresent(url) ? url : null;
|
||||
this.search = isPresent(search) ?
|
||||
(typeof search === 'string' ? new URLSearchParams(<string>(search)) :
|
||||
<URLSearchParams>(search)) :
|
||||
null;
|
||||
this.withCredentials = isPresent(withCredentials) ? withCredentials : null;
|
||||
this.responseType = isPresent(responseType) ? responseType : null;
|
||||
this.method = method != null ? normalizeMethodName(method) : null;
|
||||
this.headers = headers != null ? headers : null;
|
||||
this.body = body != null ? body : null;
|
||||
this.url = url != null ? url : null;
|
||||
this.search =
|
||||
search != null ? (typeof search === 'string' ? new URLSearchParams(search) : search) : null;
|
||||
this.withCredentials = withCredentials != null ? withCredentials : null;
|
||||
this.responseType = responseType != null ? responseType : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -116,18 +112,18 @@ export class RequestOptions {
|
||||
*/
|
||||
merge(options?: RequestOptionsArgs): RequestOptions {
|
||||
return new RequestOptions({
|
||||
method: options && isPresent(options.method) ? options.method : this.method,
|
||||
headers: options && isPresent(options.headers) ? options.headers : this.headers,
|
||||
body: options && isPresent(options.body) ? options.body : this.body,
|
||||
url: options && isPresent(options.url) ? options.url : this.url,
|
||||
search: options && isPresent(options.search) ?
|
||||
method: options && options.method != null ? options.method : this.method,
|
||||
headers: options && options.headers != null ? options.headers : this.headers,
|
||||
body: options && options.body != null ? options.body : this.body,
|
||||
url: options && options.url != null ? options.url : this.url,
|
||||
search: options && options.search != null ?
|
||||
(typeof options.search === 'string' ? new URLSearchParams(options.search) :
|
||||
(<URLSearchParams>(options.search)).clone()) :
|
||||
options.search.clone()) :
|
||||
this.search,
|
||||
withCredentials: options && isPresent(options.withCredentials) ? options.withCredentials :
|
||||
this.withCredentials,
|
||||
responseType: options && isPresent(options.responseType) ? options.responseType :
|
||||
this.responseType
|
||||
withCredentials: options && options.withCredentials != null ? options.withCredentials :
|
||||
this.withCredentials,
|
||||
responseType: options && options.responseType != null ? options.responseType :
|
||||
this.responseType
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -8,8 +8,6 @@
|
||||
|
||||
import {Injectable} from '@angular/core';
|
||||
|
||||
import {isPresent} from '../src/facade/lang';
|
||||
|
||||
import {ResponseType} from './enums';
|
||||
import {Headers} from './headers';
|
||||
import {ResponseOptionsArgs} from './interfaces';
|
||||
@ -68,12 +66,12 @@ export class ResponseOptions {
|
||||
type: ResponseType;
|
||||
url: string;
|
||||
constructor({body, status, headers, statusText, type, url}: ResponseOptionsArgs = {}) {
|
||||
this.body = isPresent(body) ? body : null;
|
||||
this.status = isPresent(status) ? status : null;
|
||||
this.headers = isPresent(headers) ? headers : null;
|
||||
this.statusText = isPresent(statusText) ? statusText : null;
|
||||
this.type = isPresent(type) ? type : null;
|
||||
this.url = isPresent(url) ? url : null;
|
||||
this.body = body != null ? body : null;
|
||||
this.status = status != null ? status : null;
|
||||
this.headers = headers != null ? headers : null;
|
||||
this.statusText = statusText != null ? statusText : null;
|
||||
this.type = type != null ? type : null;
|
||||
this.url = url != null ? url : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -103,13 +101,12 @@ export class ResponseOptions {
|
||||
*/
|
||||
merge(options?: ResponseOptionsArgs): ResponseOptions {
|
||||
return new ResponseOptions({
|
||||
body: isPresent(options) && isPresent(options.body) ? options.body : this.body,
|
||||
status: isPresent(options) && isPresent(options.status) ? options.status : this.status,
|
||||
headers: isPresent(options) && isPresent(options.headers) ? options.headers : this.headers,
|
||||
statusText: isPresent(options) && isPresent(options.statusText) ? options.statusText :
|
||||
this.statusText,
|
||||
type: isPresent(options) && isPresent(options.type) ? options.type : this.type,
|
||||
url: isPresent(options) && isPresent(options.url) ? options.url : this.url,
|
||||
body: options && options.body != null ? options.body : this.body,
|
||||
status: options && options.status != null ? options.status : this.status,
|
||||
headers: options && options.headers != null ? options.headers : this.headers,
|
||||
statusText: options && options.statusText != null ? options.statusText : this.statusText,
|
||||
type: options && options.type != null ? options.type : this.type,
|
||||
url: options && options.url != null ? options.url : this.url,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {isJsObject, stringToArrayBuffer} from './http_utils';
|
||||
import {stringToArrayBuffer} from './http_utils';
|
||||
import {URLSearchParams} from './url_search_params';
|
||||
|
||||
|
||||
@ -51,7 +51,7 @@ export abstract class Body {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (isJsObject(this._body)) {
|
||||
if (typeof this._body === 'object') {
|
||||
return JSON.stringify(this._body, null, 2);
|
||||
}
|
||||
|
||||
|
@ -1 +0,0 @@
|
||||
../../facade/src
|
@ -8,7 +8,6 @@
|
||||
|
||||
import {Injectable} from '@angular/core';
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
import {isPresent} from '../src/facade/lang';
|
||||
import {BaseRequestOptions, RequestOptions} from './base_request_options';
|
||||
import {RequestMethod} from './enums';
|
||||
import {ConnectionBackend, RequestOptionsArgs} from './interfaces';
|
||||
@ -23,7 +22,7 @@ function mergeOptions(
|
||||
defaultOpts: BaseRequestOptions, providedOpts: RequestOptionsArgs, method: RequestMethod,
|
||||
url: string): RequestOptions {
|
||||
const newOptions = defaultOpts;
|
||||
if (isPresent(providedOpts)) {
|
||||
if (providedOpts) {
|
||||
// Hack so Dart can used named parameters
|
||||
return newOptions.merge(new RequestOptions({
|
||||
method: providedOpts.method || method,
|
||||
@ -35,11 +34,8 @@ function mergeOptions(
|
||||
responseType: providedOpts.responseType
|
||||
}));
|
||||
}
|
||||
if (isPresent(method)) {
|
||||
return newOptions.merge(new RequestOptions({method: method, url: url}));
|
||||
} else {
|
||||
return newOptions.merge(new RequestOptions({url: url}));
|
||||
}
|
||||
|
||||
return newOptions.merge(new RequestOptions({method, url}));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -49,5 +49,3 @@ export function stringToArrayBuffer(input: String): ArrayBuffer {
|
||||
}
|
||||
return view.buffer;
|
||||
}
|
||||
|
||||
export {isJsObject} from '../src/facade/lang';
|
||||
|
@ -6,8 +6,6 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {isPresent} from '../src/facade/lang';
|
||||
|
||||
import {Body} from './body';
|
||||
import {ContentType, RequestMethod, ResponseContentType} from './enums';
|
||||
import {Headers} from './headers';
|
||||
@ -78,7 +76,7 @@ export class Request extends Body {
|
||||
// TODO: assert that url is present
|
||||
const url = requestOptions.url;
|
||||
this.url = requestOptions.url;
|
||||
if (isPresent(requestOptions.search)) {
|
||||
if (requestOptions.search) {
|
||||
const search = requestOptions.search.toString();
|
||||
if (search.length > 0) {
|
||||
let prefix = '?';
|
||||
@ -93,7 +91,6 @@ export class Request extends Body {
|
||||
this.method = normalizeMethodName(requestOptions.method);
|
||||
// TODO(jeffbcross): implement behavior
|
||||
// Defaults to 'omit', consistent with browser
|
||||
// TODO(jeffbcross): implement behavior
|
||||
this.headers = new Headers(requestOptions.headers);
|
||||
this.contentType = this.detectContentType();
|
||||
this.withCredentials = requestOptions.withCredentials;
|
||||
|
@ -14,7 +14,6 @@ import {JSONPBackend, JSONPBackend_, JSONPConnection, JSONPConnection_} from '..
|
||||
import {BaseRequestOptions, RequestOptions} from '../../src/base_request_options';
|
||||
import {BaseResponseOptions, ResponseOptions} from '../../src/base_response_options';
|
||||
import {ReadyState, RequestMethod, ResponseType} from '../../src/enums';
|
||||
import {isPresent} from '../../src/facade/lang';
|
||||
import {Request} from '../../src/static_request';
|
||||
|
||||
let existingScripts: MockBrowserJsonp[] = [];
|
||||
@ -22,18 +21,14 @@ let existingScripts: MockBrowserJsonp[] = [];
|
||||
class MockBrowserJsonp extends BrowserJsonp {
|
||||
src: string;
|
||||
callbacks = new Map<string, (data: any) => any>();
|
||||
constructor() { super(); }
|
||||
|
||||
addEventListener(type: string, cb: (data: any) => any) { this.callbacks.set(type, cb); }
|
||||
|
||||
removeEventListener(type: string, cb: Function) { this.callbacks.delete(type); }
|
||||
|
||||
dispatchEvent(type: string, argument?: any) {
|
||||
if (!isPresent(argument)) {
|
||||
argument = {};
|
||||
}
|
||||
dispatchEvent(type: string, argument: any = {}) {
|
||||
const cb = this.callbacks.get(type);
|
||||
if (isPresent(cb)) {
|
||||
if (cb) {
|
||||
cb(argument);
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,6 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {AsyncTestCompleter, SpyObject, afterEach, beforeEach, beforeEachProviders, describe, expect, inject, it} from '@angular/core/testing/testing_internal';
|
||||
import {__platform_browser_private__} from '@angular/platform-browser';
|
||||
|
||||
import {BrowserXhr} from '../../src/backends/browser_xhr';
|
||||
import {CookieXSRFStrategy, XHRBackend, XHRConnection} from '../../src/backends/xhr_backend';
|
||||
import {BaseRequestOptions, RequestOptions} from '../../src/base_request_options';
|
||||
@ -486,6 +485,7 @@ export function main() {
|
||||
existingXHRs[0].setStatusCode(statusCode);
|
||||
existingXHRs[0].dispatchEvent('load');
|
||||
}));
|
||||
|
||||
it('should normalize IE\'s 1223 status code into 204',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
const statusCode = 1223;
|
||||
@ -502,6 +502,22 @@ export function main() {
|
||||
existingXHRs[0].dispatchEvent('load');
|
||||
}));
|
||||
|
||||
it('should ignore response body for 204 status code',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
const statusCode = 204;
|
||||
const connection = new XHRConnection(
|
||||
sampleRequest, new MockBrowserXHR(), new ResponseOptions({status: statusCode}));
|
||||
|
||||
connection.response.subscribe((res: Response) => {
|
||||
expect(res.text()).toBe('');
|
||||
async.done();
|
||||
});
|
||||
|
||||
existingXHRs[0].setStatusCode(statusCode);
|
||||
existingXHRs[0].setResponseText('Doge');
|
||||
existingXHRs[0].dispatchEvent('load');
|
||||
}));
|
||||
|
||||
it('should normalize responseText and response',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
const responseBody = 'Doge';
|
||||
@ -623,6 +639,21 @@ Connection: keep-alive`;
|
||||
existingXHRs[0].dispatchEvent('load');
|
||||
}));
|
||||
|
||||
it('should return request url if it cannot be retrieved from response',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
const statusCode = 200;
|
||||
const connection = new XHRConnection(
|
||||
sampleRequest, new MockBrowserXHR(), new ResponseOptions({status: statusCode}));
|
||||
|
||||
connection.response.subscribe((res: Response) => {
|
||||
expect(res.url).toEqual('https://google.com');
|
||||
async.done();
|
||||
});
|
||||
|
||||
existingXHRs[0].setStatusCode(statusCode);
|
||||
existingXHRs[0].dispatchEvent('load');
|
||||
}));
|
||||
|
||||
it('should set the status text property from the XMLHttpRequest instance if present',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
const statusText = 'test';
|
||||
|
@ -37,5 +37,7 @@ export function enableDebugTools<T>(ref: ComponentRef<T>): ComponentRef<T> {
|
||||
* @experimental All debugging apis are currently experimental.
|
||||
*/
|
||||
export function disableDebugTools(): void {
|
||||
delete context.ng.profiler;
|
||||
if (context.ng) {
|
||||
delete context.ng.profiler;
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,8 @@ import {AnimationKeyframe, AnimationStyles, NoOpAnimationPlayer} from '../privat
|
||||
class _NoOpAnimationDriver implements AnimationDriver {
|
||||
animate(
|
||||
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
|
||||
duration: number, delay: number, easing: string): AnimationPlayer {
|
||||
duration: number, delay: number, easing: string,
|
||||
previousPlayers: AnimationPlayer[] = []): AnimationPlayer {
|
||||
return new NoOpAnimationPlayer();
|
||||
}
|
||||
}
|
||||
@ -25,5 +26,6 @@ export abstract class AnimationDriver {
|
||||
static NOOP: AnimationDriver = new _NoOpAnimationDriver();
|
||||
abstract animate(
|
||||
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
|
||||
duration: number, delay: number, easing: string): AnimationPlayer;
|
||||
duration: number, delay: number, easing: string,
|
||||
previousPlayers?: AnimationPlayer[]): AnimationPlayer;
|
||||
}
|
||||
|
@ -260,9 +260,10 @@ export class DomRenderer implements Renderer {
|
||||
|
||||
animate(
|
||||
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
|
||||
duration: number, delay: number, easing: string): AnimationPlayer {
|
||||
duration: number, delay: number, easing: string,
|
||||
previousPlayers: AnimationPlayer[] = []): AnimationPlayer {
|
||||
return this._animationDriver.animate(
|
||||
element, startingStyles, keyframes, duration, delay, easing);
|
||||
element, startingStyles, keyframes, duration, delay, easing, previousPlayers);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AnimationPlayer} from '@angular/core';
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {AnimationKeyframe, AnimationStyles} from '../private_import_core';
|
||||
|
||||
@ -15,17 +16,18 @@ import {WebAnimationsPlayer} from './web_animations_player';
|
||||
export class WebAnimationsDriver implements AnimationDriver {
|
||||
animate(
|
||||
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
|
||||
duration: number, delay: number, easing: string): WebAnimationsPlayer {
|
||||
duration: number, delay: number, easing: string,
|
||||
previousPlayers: AnimationPlayer[] = []): WebAnimationsPlayer {
|
||||
let formattedSteps: {[key: string]: string | number}[] = [];
|
||||
let startingStyleLookup: {[key: string]: string | number} = {};
|
||||
if (isPresent(startingStyles) && startingStyles.styles.length > 0) {
|
||||
startingStyleLookup = _populateStyles(element, startingStyles, {});
|
||||
startingStyleLookup = _populateStyles(startingStyles, {});
|
||||
startingStyleLookup['offset'] = 0;
|
||||
formattedSteps.push(startingStyleLookup);
|
||||
}
|
||||
|
||||
keyframes.forEach((keyframe: AnimationKeyframe) => {
|
||||
const data = _populateStyles(element, keyframe.styles, startingStyleLookup);
|
||||
const data = _populateStyles(keyframe.styles, startingStyleLookup);
|
||||
data['offset'] = keyframe.offset;
|
||||
formattedSteps.push(data);
|
||||
});
|
||||
@ -52,13 +54,16 @@ export class WebAnimationsDriver implements AnimationDriver {
|
||||
playerOptions['easing'] = easing;
|
||||
}
|
||||
|
||||
return new WebAnimationsPlayer(element, formattedSteps, playerOptions);
|
||||
// there may be a chance a NoOp player is returned depending
|
||||
// on when the previous animation was cancelled
|
||||
previousPlayers = previousPlayers.filter(filterWebAnimationPlayerFn);
|
||||
return new WebAnimationsPlayer(
|
||||
element, formattedSteps, playerOptions, <WebAnimationsPlayer[]>previousPlayers);
|
||||
}
|
||||
}
|
||||
|
||||
function _populateStyles(
|
||||
element: any, styles: AnimationStyles,
|
||||
defaultStyles: {[key: string]: string | number}): {[key: string]: string | number} {
|
||||
function _populateStyles(styles: AnimationStyles, defaultStyles: {[key: string]: string | number}):
|
||||
{[key: string]: string | number} {
|
||||
const data: {[key: string]: string | number} = {};
|
||||
styles.styles.forEach(
|
||||
(entry) => { Object.keys(entry).forEach(prop => { data[prop] = entry[prop]; }); });
|
||||
@ -69,3 +74,7 @@ function _populateStyles(
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
function filterWebAnimationPlayerFn(player: AnimationPlayer) {
|
||||
return player instanceof WebAnimationsPlayer;
|
||||
}
|
||||
|
@ -7,6 +7,8 @@
|
||||
*/
|
||||
|
||||
import {AUTO_STYLE} from '@angular/core';
|
||||
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {AnimationPlayer} from '../private_import_core';
|
||||
|
||||
import {getDOM} from './dom_adapter';
|
||||
@ -21,13 +23,22 @@ export class WebAnimationsPlayer implements AnimationPlayer {
|
||||
private _finished = false;
|
||||
private _started = false;
|
||||
private _destroyed = false;
|
||||
private _finalKeyframe: {[key: string]: string | number};
|
||||
|
||||
public parentPlayer: AnimationPlayer = null;
|
||||
public previousStyles: {[styleName: string]: string | number};
|
||||
|
||||
constructor(
|
||||
public element: any, public keyframes: {[key: string]: string | number}[],
|
||||
public options: {[key: string]: string | number}) {
|
||||
public options: {[key: string]: string | number},
|
||||
previousPlayers: WebAnimationsPlayer[] = []) {
|
||||
this._duration = <number>options['duration'];
|
||||
|
||||
this.previousStyles = {};
|
||||
previousPlayers.forEach(player => {
|
||||
let styles = player._captureStyles();
|
||||
Object.keys(styles).forEach(prop => this.previousStyles[prop] = styles[prop]);
|
||||
});
|
||||
}
|
||||
|
||||
private _onFinish() {
|
||||
@ -44,14 +55,30 @@ export class WebAnimationsPlayer implements AnimationPlayer {
|
||||
|
||||
const keyframes = this.keyframes.map(styles => {
|
||||
const formattedKeyframe: {[key: string]: string | number} = {};
|
||||
Object.keys(styles).forEach(prop => {
|
||||
const value = styles[prop];
|
||||
formattedKeyframe[prop] = value == AUTO_STYLE ? _computeStyle(this.element, prop) : value;
|
||||
Object.keys(styles).forEach((prop, index) => {
|
||||
let value = styles[prop];
|
||||
if (value == AUTO_STYLE) {
|
||||
value = _computeStyle(this.element, prop);
|
||||
}
|
||||
if (value != undefined) {
|
||||
formattedKeyframe[prop] = value;
|
||||
}
|
||||
});
|
||||
return formattedKeyframe;
|
||||
});
|
||||
|
||||
const previousStyleProps = Object.keys(this.previousStyles);
|
||||
if (previousStyleProps.length) {
|
||||
let startingKeyframe = findStartingKeyframe(keyframes);
|
||||
previousStyleProps.forEach(prop => {
|
||||
if (isPresent(startingKeyframe[prop])) {
|
||||
startingKeyframe[prop] = this.previousStyles[prop];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this._player = this._triggerWebAnimation(this.element, keyframes, this.options);
|
||||
this._finalKeyframe = _copyKeyframeStyles(keyframes[keyframes.length - 1]);
|
||||
|
||||
// this is required so that the player doesn't start to animate right away
|
||||
this._resetDomPlayerState();
|
||||
@ -119,8 +146,47 @@ export class WebAnimationsPlayer implements AnimationPlayer {
|
||||
setPosition(p: number): void { this._player.currentTime = p * this.totalTime; }
|
||||
|
||||
getPosition(): number { return this._player.currentTime / this.totalTime; }
|
||||
|
||||
private _captureStyles(): {[prop: string]: string | number} {
|
||||
const styles: {[key: string]: string | number} = {};
|
||||
if (this.hasStarted()) {
|
||||
Object.keys(this._finalKeyframe).forEach(prop => {
|
||||
if (prop != 'offset') {
|
||||
styles[prop] =
|
||||
this._finished ? this._finalKeyframe[prop] : _computeStyle(this.element, prop);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return styles;
|
||||
}
|
||||
}
|
||||
|
||||
function _computeStyle(element: any, prop: string): string {
|
||||
return getDOM().getComputedStyle(element)[prop];
|
||||
}
|
||||
|
||||
function _copyKeyframeStyles(styles: {[style: string]: string | number}):
|
||||
{[style: string]: string | number} {
|
||||
const newStyles: {[style: string]: string | number} = {};
|
||||
Object.keys(styles).forEach(prop => {
|
||||
if (prop != 'offset') {
|
||||
newStyles[prop] = styles[prop];
|
||||
}
|
||||
});
|
||||
return newStyles;
|
||||
}
|
||||
|
||||
function findStartingKeyframe(keyframes: {[prop: string]: string | number}[]):
|
||||
{[prop: string]: string | number} {
|
||||
let startingKeyframe = keyframes[0];
|
||||
// it's important that we find the LAST keyframe
|
||||
// to ensure that style overidding is final.
|
||||
for (let i = 1; i < keyframes.length; i++) {
|
||||
const kf = keyframes[i];
|
||||
const offset = kf['offset'];
|
||||
if (offset !== 0) break;
|
||||
startingKeyframe = kf;
|
||||
}
|
||||
return startingKeyframe;
|
||||
}
|
||||
|
@ -6,13 +6,13 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal';
|
||||
import {AnimationPlayer} from '@angular/core';
|
||||
import {el} from '@angular/platform-browser/testing/browser_util';
|
||||
|
||||
import {DomAnimatePlayer} from '../../src/dom/dom_animate_player';
|
||||
import {WebAnimationsDriver} from '../../src/dom/web_animations_driver';
|
||||
import {WebAnimationsPlayer} from '../../src/dom/web_animations_player';
|
||||
import {AnimationKeyframe, AnimationStyles} from '../../src/private_import_core';
|
||||
import {AnimationKeyframe, AnimationStyles, NoOpAnimationPlayer} from '../../src/private_import_core';
|
||||
import {MockDomAnimatePlayer} from '../../testing/mock_dom_animate_player';
|
||||
|
||||
class ExtendedWebAnimationsDriver extends WebAnimationsDriver {
|
||||
@ -48,8 +48,7 @@ export function main() {
|
||||
it('should use a fill mode of `both`', () => {
|
||||
const startingStyles = _makeStyles({});
|
||||
const styles = [_makeKeyframe(0, {'color': 'green'}), _makeKeyframe(1, {'color': 'red'})];
|
||||
|
||||
const player = driver.animate(elm, startingStyles, styles, 1000, 1000, 'linear');
|
||||
const player = driver.animate(elm, startingStyles, styles, 1000, 1000, 'linear', []);
|
||||
const details = _formatOptions(player);
|
||||
const options = details['options'];
|
||||
expect(options['fill']).toEqual('both');
|
||||
@ -58,8 +57,7 @@ export function main() {
|
||||
it('should apply the provided easing', () => {
|
||||
const startingStyles = _makeStyles({});
|
||||
const styles = [_makeKeyframe(0, {'color': 'green'}), _makeKeyframe(1, {'color': 'red'})];
|
||||
|
||||
const player = driver.animate(elm, startingStyles, styles, 1000, 1000, 'ease-out');
|
||||
const player = driver.animate(elm, startingStyles, styles, 1000, 1000, 'ease-out', []);
|
||||
const details = _formatOptions(player);
|
||||
const options = details['options'];
|
||||
expect(options['easing']).toEqual('ease-out');
|
||||
@ -68,16 +66,32 @@ export function main() {
|
||||
it('should only apply the provided easing if present', () => {
|
||||
const startingStyles = _makeStyles({});
|
||||
const styles = [_makeKeyframe(0, {'color': 'green'}), _makeKeyframe(1, {'color': 'red'})];
|
||||
|
||||
const player = driver.animate(elm, startingStyles, styles, 1000, 1000, null);
|
||||
const player = driver.animate(elm, startingStyles, styles, 1000, 1000, null, []);
|
||||
const details = _formatOptions(player);
|
||||
const options = details['options'];
|
||||
const keys = Object.keys(options);
|
||||
expect(keys.indexOf('easing')).toEqual(-1);
|
||||
});
|
||||
|
||||
it('should only apply the provided easing if present', () => {
|
||||
const previousPlayers = [
|
||||
new NoOpAnimationPlayerWithStyles(),
|
||||
new NoOpAnimationPlayerWithStyles(),
|
||||
new NoOpAnimationPlayerWithStyles(),
|
||||
];
|
||||
const startingStyles = _makeStyles({});
|
||||
const styles = [_makeKeyframe(0, {}), _makeKeyframe(1, {})];
|
||||
const player = driver.animate(
|
||||
elm, startingStyles, styles, 1000, 1000, null, <AnimationPlayer[]>previousPlayers);
|
||||
expect(player.previousStyles).toEqual({});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class NoOpAnimationPlayerWithStyles extends NoOpAnimationPlayer {
|
||||
private _captureStyles() { return {color: 'red'}; }
|
||||
}
|
||||
|
||||
function _formatOptions(player: WebAnimationsPlayer): {[key: string]: any} {
|
||||
return {'element': player.element, 'keyframes': player.keyframes, 'options': player.options};
|
||||
}
|
||||
|
@ -6,7 +6,9 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {MockAnimationPlayer, beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal';
|
||||
import {AUTO_STYLE, AnimationPlayer} from '@angular/core';
|
||||
import {MockAnimationPlayer} from '@angular/core/testing/testing_internal';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
import {el} from '@angular/platform-browser/testing/browser_util';
|
||||
|
||||
import {DomAnimatePlayer} from '../../src/dom/dom_animate_player';
|
||||
@ -18,14 +20,16 @@ class ExtendedWebAnimationsPlayer extends WebAnimationsPlayer {
|
||||
|
||||
constructor(
|
||||
public element: HTMLElement, public keyframes: {[key: string]: string | number}[],
|
||||
public options: {[key: string]: string | number}) {
|
||||
super(element, keyframes, options);
|
||||
public options: {[key: string]: string | number},
|
||||
public previousPlayers: WebAnimationsPlayer[] = []) {
|
||||
super(element, keyframes, options, previousPlayers);
|
||||
}
|
||||
|
||||
get domPlayer() { return this._overriddenDomPlayer; }
|
||||
|
||||
/** @internal */
|
||||
_triggerWebAnimation(elm: any, keyframes: any[], options: any): DomAnimatePlayer {
|
||||
this._overriddenDomPlayer._capture('trigger', {elm, keyframes, options});
|
||||
return this._overriddenDomPlayer;
|
||||
}
|
||||
}
|
||||
@ -33,7 +37,7 @@ class ExtendedWebAnimationsPlayer extends WebAnimationsPlayer {
|
||||
export function main() {
|
||||
function makePlayer(): {[key: string]: any} {
|
||||
const someElm = el('<div></div>');
|
||||
const player = new ExtendedWebAnimationsPlayer(someElm, [], {});
|
||||
const player = new ExtendedWebAnimationsPlayer(someElm, [{}, {}], {}, []);
|
||||
player.init();
|
||||
return {'captures': player.domPlayer.captures, 'player': player};
|
||||
}
|
||||
@ -156,5 +160,72 @@ export function main() {
|
||||
player.destroy();
|
||||
expect(captures['cancel'].length).toBe(1);
|
||||
});
|
||||
|
||||
it('should resolve auto styles based on what is computed from the provided element', () => {
|
||||
const elm = el('<div></div>');
|
||||
document.body.appendChild(elm); // required for getComputedStyle() to work
|
||||
elm.style.opacity = '0.5';
|
||||
|
||||
const player = new ExtendedWebAnimationsPlayer(
|
||||
elm, [{opacity: AUTO_STYLE}, {opacity: '1'}], {duration: 1000}, []);
|
||||
|
||||
player.init();
|
||||
|
||||
const data = player.domPlayer.captures['trigger'][0];
|
||||
expect(data['keyframes']).toEqual([{opacity: '0.5'}, {opacity: '1'}]);
|
||||
});
|
||||
|
||||
describe('previousStyle', () => {
|
||||
it('should merge keyframe styles based on the previous styles passed in when the player has finished its operation',
|
||||
() => {
|
||||
const elm = el('<div></div>');
|
||||
const previousStyles = {width: '100px', height: '666px'};
|
||||
const previousPlayer =
|
||||
new ExtendedWebAnimationsPlayer(elm, [previousStyles, previousStyles], {}, []);
|
||||
previousPlayer.play();
|
||||
previousPlayer.finish();
|
||||
|
||||
const player = new ExtendedWebAnimationsPlayer(
|
||||
elm,
|
||||
[
|
||||
{width: '0px', height: '0px', opacity: 0, offset: 0},
|
||||
{width: '0px', height: '0px', opacity: 1, offset: 1}
|
||||
],
|
||||
{duration: 1000}, [previousPlayer]);
|
||||
|
||||
player.init();
|
||||
|
||||
const data = player.domPlayer.captures['trigger'][0];
|
||||
expect(data['keyframes']).toEqual([
|
||||
{width: '100px', height: '666px', opacity: 0, offset: 0},
|
||||
{width: '0px', height: '0px', opacity: 1, offset: 1}
|
||||
]);
|
||||
});
|
||||
|
||||
it('should properly calculate the previous styles for the player even when its currently playing',
|
||||
() => {
|
||||
if (!getDOM().supportsWebAnimation()) return;
|
||||
|
||||
const elm = el('<div></div>');
|
||||
document.body.appendChild(elm);
|
||||
|
||||
const fromStyles = {width: '100px', height: '666px'};
|
||||
const toStyles = {width: '50px', height: '333px'};
|
||||
const previousPlayer =
|
||||
new WebAnimationsPlayer(elm, [fromStyles, toStyles], {duration: 1000}, []);
|
||||
previousPlayer.play();
|
||||
previousPlayer.setPosition(0.5);
|
||||
previousPlayer.pause();
|
||||
|
||||
const newStyles = {width: '0px', height: '0px'};
|
||||
const player = new WebAnimationsPlayer(
|
||||
elm, [newStyles, newStyles], {duration: 1000}, [previousPlayer]);
|
||||
|
||||
player.init();
|
||||
|
||||
const data = player.previousStyles;
|
||||
expect(player.previousStyles).toEqual({width: '75px', height: '499.5px'});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -9,19 +9,29 @@
|
||||
import {AnimationPlayer} from '@angular/core';
|
||||
import {MockAnimationPlayer} from '@angular/core/testing/testing_internal';
|
||||
import {AnimationDriver} from '@angular/platform-browser';
|
||||
|
||||
import {ListWrapper} from './facade/collection';
|
||||
import {AnimationKeyframe, AnimationStyles} from './private_import_core';
|
||||
|
||||
export class MockAnimationDriver extends AnimationDriver {
|
||||
public log: {[key: string]: any}[] = [];
|
||||
animate(
|
||||
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
|
||||
duration: number, delay: number, easing: string): AnimationPlayer {
|
||||
const player = new MockAnimationPlayer();
|
||||
duration: number, delay: number, easing: string,
|
||||
previousPlayers: AnimationPlayer[] = []): AnimationPlayer {
|
||||
const mockPlayers = <MockAnimationPlayer[]>previousPlayers.filter(
|
||||
player => player instanceof MockAnimationPlayer);
|
||||
const normalizedStartingStyles = _serializeStyles(startingStyles);
|
||||
const normalizedKeyframes = _serializeKeyframes(keyframes);
|
||||
const player =
|
||||
new MockAnimationPlayer(normalizedStartingStyles, normalizedKeyframes, previousPlayers);
|
||||
|
||||
this.log.push({
|
||||
'element': element,
|
||||
'startingStyles': _serializeStyles(startingStyles),
|
||||
'startingStyles': normalizedStartingStyles,
|
||||
'previousStyles': player.previousStyles,
|
||||
'keyframes': keyframes,
|
||||
'keyframeLookup': _serializeKeyframes(keyframes),
|
||||
'keyframeLookup': normalizedKeyframes,
|
||||
'duration': duration,
|
||||
'delay': delay,
|
||||
'easing': easing,
|
||||
|
@ -206,9 +206,10 @@ export class ServerRenderer implements Renderer {
|
||||
|
||||
animate(
|
||||
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
|
||||
duration: number, delay: number, easing: string): AnimationPlayer {
|
||||
duration: number, delay: number, easing: string,
|
||||
previousPlayers: AnimationPlayer[] = []): AnimationPlayer {
|
||||
return this._animationDriver.animate(
|
||||
element, startingStyles, keyframes, duration, delay, easing);
|
||||
element, startingStyles, keyframes, duration, delay, easing, previousPlayers);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,7 +89,7 @@ export class MessageBasedRenderer {
|
||||
'animate',
|
||||
[
|
||||
RenderStoreObject, RenderStoreObject, PRIMITIVE, PRIMITIVE, PRIMITIVE, PRIMITIVE,
|
||||
PRIMITIVE, PRIMITIVE
|
||||
PRIMITIVE, PRIMITIVE, PRIMITIVE
|
||||
],
|
||||
this._animate.bind(this));
|
||||
|
||||
@ -248,8 +248,14 @@ export class MessageBasedRenderer {
|
||||
|
||||
private _animate(
|
||||
renderer: Renderer, element: any, startingStyles: any, keyframes: any[], duration: number,
|
||||
delay: number, easing: string, playerId: any) {
|
||||
const player = renderer.animate(element, startingStyles, keyframes, duration, delay, easing);
|
||||
delay: number, easing: string, previousPlayers: number[], playerId: any) {
|
||||
let normalizedPreviousPlayers: AnimationPlayer[];
|
||||
if (previousPlayers && previousPlayers.length) {
|
||||
normalizedPreviousPlayers =
|
||||
previousPlayers.map(playerId => this._renderStore.deserialize(playerId));
|
||||
}
|
||||
const player = renderer.animate(
|
||||
element, startingStyles, keyframes, duration, delay, easing, normalizedPreviousPlayers);
|
||||
this._renderStore.store(player, playerId);
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ import {MessageBus} from '../shared/message_bus';
|
||||
import {EVENT_CHANNEL, RENDERER_CHANNEL} from '../shared/messaging_api';
|
||||
import {RenderStore} from '../shared/render_store';
|
||||
import {ANIMATION_WORKER_PLAYER_PREFIX, RenderStoreObject, Serializer} from '../shared/serializer';
|
||||
|
||||
import {deserializeGenericEvent} from './event_deserializer';
|
||||
|
||||
@Injectable()
|
||||
@ -239,13 +240,16 @@ export class WebWorkerRenderer implements Renderer, RenderStoreObject {
|
||||
|
||||
animate(
|
||||
renderElement: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
|
||||
duration: number, delay: number, easing: string): AnimationPlayer {
|
||||
duration: number, delay: number, easing: string,
|
||||
previousPlayers: AnimationPlayer[] = []): AnimationPlayer {
|
||||
const playerId = this._rootRenderer.allocateId();
|
||||
const previousPlayerIds: number[] =
|
||||
previousPlayers.map(player => this._rootRenderer.renderStore.serialize(player));
|
||||
|
||||
this._runOnService('animate', [
|
||||
new FnArg(renderElement, RenderStoreObject), new FnArg(startingStyles, null),
|
||||
new FnArg(keyframes, null), new FnArg(duration, null), new FnArg(delay, null),
|
||||
new FnArg(easing, null), new FnArg(playerId, null)
|
||||
new FnArg(easing, null), new FnArg(previousPlayerIds, null), new FnArg(playerId, null)
|
||||
]);
|
||||
|
||||
const player = new _AnimationWorkerRendererPlayer(this._rootRenderer, renderElement);
|
||||
@ -325,7 +329,7 @@ export class WebWorkerRenderNode {
|
||||
animationPlayerEvents = new AnimationPlayerEmitter();
|
||||
}
|
||||
|
||||
class _AnimationWorkerRendererPlayer implements AnimationPlayer, RenderStoreObject {
|
||||
class _AnimationWorkerRendererPlayer implements RenderStoreObject {
|
||||
public parentPlayer: AnimationPlayer = null;
|
||||
|
||||
private _destroyed: boolean = false;
|
||||
|
@ -289,6 +289,30 @@ export function main() {
|
||||
|
||||
expect(player.log.indexOf('destroy') >= 0).toBe(true);
|
||||
}));
|
||||
|
||||
it('should properly transition to the next animation if the current one is cancelled',
|
||||
fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(AnimationCmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
|
||||
cmp.state = 'on';
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
|
||||
let player = <MockAnimationPlayer>uiDriver.log.shift()['player'];
|
||||
player.finish();
|
||||
player = <MockAnimationPlayer>uiDriver.log.shift()['player'];
|
||||
player.setPosition(0.5);
|
||||
|
||||
uiDriver.log = [];
|
||||
|
||||
cmp.state = 'off';
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
|
||||
const step = uiDriver.log.shift();
|
||||
expect(step['previousStyles']).toEqual({opacity: AUTO_STYLE, fontSize: AUTO_STYLE});
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
|
5
modules/@angular/router-license-banner.txt
Normal file
5
modules/@angular/router-license-banner.txt
Normal file
@ -0,0 +1,5 @@
|
||||
/**
|
||||
* @license Angular v0.0.0-ROUTERPLACEHOLDER
|
||||
* (c) 2010-2016 Google, Inc. https://angular.io/
|
||||
* License: MIT
|
||||
*/
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@angular/router",
|
||||
"version": "3.0.0-rc.1",
|
||||
"version": "0.0.0-ROUTERPLACEHOLDER",
|
||||
"description": "Angular - the routing library",
|
||||
"main": "bundles/router.umd.js",
|
||||
"module": "index.js",
|
||||
@ -24,7 +24,6 @@
|
||||
"@angular/core": "0.0.0-PLACEHOLDER",
|
||||
"@angular/common": "0.0.0-PLACEHOLDER",
|
||||
"@angular/platform-browser": "0.0.0-PLACEHOLDER",
|
||||
"@angular/upgrade": "0.0.0-PLACEHOLDER",
|
||||
"rxjs": "5.0.0-beta.12"
|
||||
}
|
||||
}
|
||||
|
@ -20,8 +20,8 @@ import {EmptyError} from 'rxjs/util/EmptyError';
|
||||
|
||||
import {Route, Routes, UrlMatchResult} from './config';
|
||||
import {LoadedRouterConfig, RouterConfigLoader} from './router_config_loader';
|
||||
import {NavigationCancelingError, PRIMARY_OUTLET, defaultUrlMatcher} from './shared';
|
||||
import {UrlSegment, UrlSegmentGroup, UrlTree} from './url_tree';
|
||||
import {NavigationCancelingError, PRIMARY_OUTLET, Params, defaultUrlMatcher} from './shared';
|
||||
import {UrlSegment, UrlSegmentGroup, UrlSerializer, UrlTree} from './url_tree';
|
||||
import {andObservables, forEach, merge, waitForMap, wrapIntoObservable} from './utils/collection';
|
||||
|
||||
class NoMatch {
|
||||
@ -29,7 +29,7 @@ class NoMatch {
|
||||
}
|
||||
|
||||
class AbsoluteRedirect {
|
||||
constructor(public segments: UrlSegment[]) {}
|
||||
constructor(public urlTree: UrlTree) {}
|
||||
}
|
||||
|
||||
function noMatch(segmentGroup: UrlSegmentGroup): Observable<UrlSegmentGroup> {
|
||||
@ -37,9 +37,15 @@ function noMatch(segmentGroup: UrlSegmentGroup): Observable<UrlSegmentGroup> {
|
||||
(obs: Observer<UrlSegmentGroup>) => obs.error(new NoMatch(segmentGroup)));
|
||||
}
|
||||
|
||||
function absoluteRedirect(segments: UrlSegment[]): Observable<UrlSegmentGroup> {
|
||||
function absoluteRedirect(newTree: UrlTree): Observable<any> {
|
||||
return new Observable<UrlSegmentGroup>(
|
||||
(obs: Observer<UrlSegmentGroup>) => obs.error(new AbsoluteRedirect(segments)));
|
||||
(obs: Observer<UrlSegmentGroup>) => obs.error(new AbsoluteRedirect(newTree)));
|
||||
}
|
||||
|
||||
function namedOutletsRedirect(redirectTo: string): Observable<any> {
|
||||
return new Observable<UrlSegmentGroup>(
|
||||
(obs: Observer<UrlSegmentGroup>) => obs.error(new Error(
|
||||
`Only absolute redirects can have named outlets. redirectTo: '${redirectTo}'`)));
|
||||
}
|
||||
|
||||
function canLoadFails(route: Route): Observable<LoadedRouterConfig> {
|
||||
@ -50,9 +56,9 @@ function canLoadFails(route: Route): Observable<LoadedRouterConfig> {
|
||||
|
||||
|
||||
export function applyRedirects(
|
||||
injector: Injector, configLoader: RouterConfigLoader, urlTree: UrlTree,
|
||||
config: Routes): Observable<UrlTree> {
|
||||
return new ApplyRedirects(injector, configLoader, urlTree, config).apply();
|
||||
injector: Injector, configLoader: RouterConfigLoader, urlSerializer: UrlSerializer,
|
||||
urlTree: UrlTree, config: Routes): Observable<UrlTree> {
|
||||
return new ApplyRedirects(injector, configLoader, urlSerializer, urlTree, config).apply();
|
||||
}
|
||||
|
||||
class ApplyRedirects {
|
||||
@ -60,21 +66,20 @@ class ApplyRedirects {
|
||||
|
||||
constructor(
|
||||
private injector: Injector, private configLoader: RouterConfigLoader,
|
||||
private urlTree: UrlTree, private config: Routes) {}
|
||||
private urlSerializer: UrlSerializer, private urlTree: UrlTree, private config: Routes) {}
|
||||
|
||||
apply(): Observable<UrlTree> {
|
||||
const expanded$ =
|
||||
this.expandSegmentGroup(this.injector, this.config, this.urlTree.root, PRIMARY_OUTLET);
|
||||
const urlTrees$ = map.call(
|
||||
expanded$, (rootSegmentGroup: UrlSegmentGroup) => this.createUrlTree(rootSegmentGroup));
|
||||
expanded$, (rootSegmentGroup: UrlSegmentGroup) => this.createUrlTree(
|
||||
rootSegmentGroup, this.urlTree.queryParams, this.urlTree.fragment));
|
||||
return _catch.call(urlTrees$, (e: any) => {
|
||||
if (e instanceof AbsoluteRedirect) {
|
||||
// after an absolute redirect we do not apply any more redirects!
|
||||
this.allowRedirects = false;
|
||||
const group =
|
||||
new UrlSegmentGroup([], {[PRIMARY_OUTLET]: new UrlSegmentGroup(e.segments, {})});
|
||||
// we need to run matching, so we can fetch all lazy-loaded modules
|
||||
return this.match(group);
|
||||
return this.match(e.urlTree);
|
||||
} else if (e instanceof NoMatch) {
|
||||
throw this.noMatchError(e);
|
||||
} else {
|
||||
@ -83,11 +88,12 @@ class ApplyRedirects {
|
||||
});
|
||||
}
|
||||
|
||||
private match(segmentGroup: UrlSegmentGroup): Observable<UrlTree> {
|
||||
private match(tree: UrlTree): Observable<UrlTree> {
|
||||
const expanded$ =
|
||||
this.expandSegmentGroup(this.injector, this.config, segmentGroup, PRIMARY_OUTLET);
|
||||
this.expandSegmentGroup(this.injector, this.config, tree.root, PRIMARY_OUTLET);
|
||||
const mapped$ = map.call(
|
||||
expanded$, (rootSegmentGroup: UrlSegmentGroup) => this.createUrlTree(rootSegmentGroup));
|
||||
expanded$, (rootSegmentGroup: UrlSegmentGroup) =>
|
||||
this.createUrlTree(rootSegmentGroup, tree.queryParams, tree.fragment));
|
||||
return _catch.call(mapped$, (e: any): Observable<UrlTree> => {
|
||||
if (e instanceof NoMatch) {
|
||||
throw this.noMatchError(e);
|
||||
@ -101,11 +107,12 @@ class ApplyRedirects {
|
||||
return new Error(`Cannot match any routes. URL Segment: '${e.segmentGroup}'`);
|
||||
}
|
||||
|
||||
private createUrlTree(rootCandidate: UrlSegmentGroup): UrlTree {
|
||||
private createUrlTree(rootCandidate: UrlSegmentGroup, queryParams: Params, fragment: string):
|
||||
UrlTree {
|
||||
const root = rootCandidate.segments.length > 0 ?
|
||||
new UrlSegmentGroup([], {[PRIMARY_OUTLET]: rootCandidate}) :
|
||||
rootCandidate;
|
||||
return new UrlTree(root, this.urlTree.queryParams, this.urlTree.fragment);
|
||||
return new UrlTree(root, queryParams, fragment);
|
||||
}
|
||||
|
||||
private expandSegmentGroup(
|
||||
@ -191,12 +198,14 @@ class ApplyRedirects {
|
||||
private expandWildCardWithParamsAgainstRouteUsingRedirect(
|
||||
injector: Injector, routes: Route[], route: Route,
|
||||
outlet: string): Observable<UrlSegmentGroup> {
|
||||
const newSegments = applyRedirectCommands([], route.redirectTo, {});
|
||||
const newTree = this.applyRedirectCommands([], route.redirectTo, {});
|
||||
if (route.redirectTo.startsWith('/')) {
|
||||
return absoluteRedirect(newSegments);
|
||||
return absoluteRedirect(newTree);
|
||||
} else {
|
||||
const group = new UrlSegmentGroup(newSegments, {});
|
||||
return this.expandSegment(injector, group, routes, newSegments, outlet, false);
|
||||
return mergeMap.call(this.lineralizeSegments(route, newTree), (newSegments: UrlSegment[]) => {
|
||||
const group = new UrlSegmentGroup(newSegments, {});
|
||||
return this.expandSegment(injector, group, routes, newSegments, outlet, false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -207,14 +216,16 @@ class ApplyRedirects {
|
||||
match(segmentGroup, route, segments);
|
||||
if (!matched) return noMatch(segmentGroup);
|
||||
|
||||
const newSegments =
|
||||
applyRedirectCommands(consumedSegments, route.redirectTo, <any>positionalParamSegments);
|
||||
const newTree = this.applyRedirectCommands(
|
||||
consumedSegments, route.redirectTo, <any>positionalParamSegments);
|
||||
if (route.redirectTo.startsWith('/')) {
|
||||
return absoluteRedirect(newSegments);
|
||||
return absoluteRedirect(newTree);
|
||||
} else {
|
||||
return this.expandSegment(
|
||||
injector, segmentGroup, routes, newSegments.concat(segments.slice(lastChild)), outlet,
|
||||
false);
|
||||
return mergeMap.call(this.lineralizeSegments(route, newTree), (newSegments: UrlSegment[]) => {
|
||||
return this.expandSegment(
|
||||
injector, segmentGroup, routes, newSegments.concat(segments.slice(lastChild)), outlet,
|
||||
false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -284,6 +295,92 @@ class ApplyRedirects {
|
||||
return of (new LoadedRouterConfig([], injector, null, null));
|
||||
}
|
||||
}
|
||||
|
||||
private lineralizeSegments(route: Route, urlTree: UrlTree): Observable<UrlSegment[]> {
|
||||
let res: UrlSegment[] = [];
|
||||
let c = urlTree.root;
|
||||
while (true) {
|
||||
res = res.concat(c.segments);
|
||||
if (c.numberOfChildren === 0) {
|
||||
return of (res);
|
||||
} else if (c.numberOfChildren > 1 || !c.children[PRIMARY_OUTLET]) {
|
||||
return namedOutletsRedirect(route.redirectTo);
|
||||
} else {
|
||||
c = c.children[PRIMARY_OUTLET];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private applyRedirectCommands(
|
||||
segments: UrlSegment[], redirectTo: string, posParams: {[k: string]: UrlSegment}): UrlTree {
|
||||
const t = this.urlSerializer.parse(redirectTo);
|
||||
return this.applyRedirectCreatreUrlTree(
|
||||
redirectTo, this.urlSerializer.parse(redirectTo), segments, posParams);
|
||||
}
|
||||
|
||||
private applyRedirectCreatreUrlTree(
|
||||
redirectTo: string, urlTree: UrlTree, segments: UrlSegment[],
|
||||
posParams: {[k: string]: UrlSegment}): UrlTree {
|
||||
const newRoot = this.createSegmentGroup(redirectTo, urlTree.root, segments, posParams);
|
||||
return new UrlTree(
|
||||
newRoot, this.createQueryParams(urlTree.queryParams, this.urlTree.queryParams),
|
||||
urlTree.fragment);
|
||||
}
|
||||
|
||||
private createQueryParams(redirectToParams: Params, actualParams: Params): Params {
|
||||
const res: Params = {};
|
||||
forEach(redirectToParams, (v: any, k: string) => {
|
||||
if (v.startsWith(':')) {
|
||||
res[k] = actualParams[v.substring(1)];
|
||||
} else {
|
||||
res[k] = v;
|
||||
}
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
private createSegmentGroup(
|
||||
redirectTo: string, group: UrlSegmentGroup, segments: UrlSegment[],
|
||||
posParams: {[k: string]: UrlSegment}): UrlSegmentGroup {
|
||||
const updatedSegments = this.createSegments(redirectTo, group.segments, segments, posParams);
|
||||
|
||||
let children: {[n: string]: UrlSegmentGroup} = {};
|
||||
forEach(group.children, (child: UrlSegmentGroup, name: string) => {
|
||||
children[name] = this.createSegmentGroup(redirectTo, child, segments, posParams);
|
||||
});
|
||||
|
||||
return new UrlSegmentGroup(updatedSegments, children);
|
||||
}
|
||||
|
||||
private createSegments(
|
||||
redirectTo: string, redirectToSegments: UrlSegment[], actualSegments: UrlSegment[],
|
||||
posParams: {[k: string]: UrlSegment}): UrlSegment[] {
|
||||
return redirectToSegments.map(
|
||||
s => s.path.startsWith(':') ? this.findPosParam(redirectTo, s, posParams) :
|
||||
this.findOrReturn(s, actualSegments));
|
||||
}
|
||||
|
||||
private findPosParam(
|
||||
redirectTo: string, redirectToUrlSegment: UrlSegment,
|
||||
posParams: {[k: string]: UrlSegment}): UrlSegment {
|
||||
const pos = posParams[redirectToUrlSegment.path.substring(1)];
|
||||
if (!pos)
|
||||
throw new Error(
|
||||
`Cannot redirect to '${redirectTo}'. Cannot find '${redirectToUrlSegment.path}'.`);
|
||||
return pos;
|
||||
}
|
||||
|
||||
private findOrReturn(redirectToUrlSegment: UrlSegment, actualSegments: UrlSegment[]): UrlSegment {
|
||||
let idx = 0;
|
||||
for (const s of actualSegments) {
|
||||
if (s.path === redirectToUrlSegment.path) {
|
||||
actualSegments.splice(idx);
|
||||
return s;
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
return redirectToUrlSegment;
|
||||
}
|
||||
}
|
||||
|
||||
function runGuards(injector: Injector, route: Route): Observable<boolean> {
|
||||
@ -328,46 +425,6 @@ function match(segmentGroup: UrlSegmentGroup, route: Route, segments: UrlSegment
|
||||
};
|
||||
}
|
||||
|
||||
function applyRedirectCommands(
|
||||
segments: UrlSegment[], redirectTo: string,
|
||||
posParams: {[k: string]: UrlSegment}): UrlSegment[] {
|
||||
const r = redirectTo.startsWith('/') ? redirectTo.substring(1) : redirectTo;
|
||||
if (r === '') {
|
||||
return [];
|
||||
} else {
|
||||
return createSegments(redirectTo, r.split('/'), segments, posParams);
|
||||
}
|
||||
}
|
||||
|
||||
function createSegments(
|
||||
redirectTo: string, parts: string[], segments: UrlSegment[],
|
||||
posParams: {[k: string]: UrlSegment}): UrlSegment[] {
|
||||
return parts.map(
|
||||
p => p.startsWith(':') ? findPosParam(p, posParams, redirectTo) :
|
||||
findOrCreateSegment(p, segments));
|
||||
}
|
||||
|
||||
function findPosParam(
|
||||
part: string, posParams: {[k: string]: UrlSegment}, redirectTo: string): UrlSegment {
|
||||
const paramName = part.substring(1);
|
||||
const pos = posParams[paramName];
|
||||
if (!pos) throw new Error(`Cannot redirect to '${redirectTo}'. Cannot find '${part}'.`);
|
||||
return pos;
|
||||
}
|
||||
|
||||
function findOrCreateSegment(part: string, segments: UrlSegment[]): UrlSegment {
|
||||
let idx = 0;
|
||||
for (const s of segments) {
|
||||
if (s.path === part) {
|
||||
segments.splice(idx);
|
||||
return s;
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
return new UrlSegment(part, {});
|
||||
}
|
||||
|
||||
|
||||
function split(
|
||||
segmentGroup: UrlSegmentGroup, consumedSegments: UrlSegment[], slicedSegments: UrlSegment[],
|
||||
config: Route[]) {
|
||||
|
@ -154,6 +154,9 @@ import {UrlSegment, UrlSegmentGroup} from './url_tree';
|
||||
* When navigating to `/team/11/user/jim`, the router will instantiate the wrapper component with
|
||||
* the user component in it.
|
||||
*
|
||||
* An empty path route inherits its parent's params and data. This is because it cannot have its
|
||||
* own params, and, as a result, it often uses its parent's params and data as its own.
|
||||
*
|
||||
* ### Matching Strategy
|
||||
*
|
||||
* By default the router will look at what is left in the url, and check if it starts with
|
||||
@ -219,7 +222,8 @@ import {UrlSegment, UrlSegmentGroup} from './url_tree';
|
||||
* has to have the primary and aux outlets defined.
|
||||
*
|
||||
* The router will also merge the `params`, `data`, and `resolve` of the componentless parent into
|
||||
* the `params`, `data`, and `resolve` of the children.
|
||||
* the `params`, `data`, and `resolve` of the children. This is done because there is no component
|
||||
* that can inject the activated route of the componentless parent.
|
||||
*
|
||||
* This is especially useful when child components are defined as follows:
|
||||
*
|
||||
|
@ -623,7 +623,8 @@ export class Router {
|
||||
Promise.resolve()
|
||||
.then(
|
||||
(_) => this.runNavigate(
|
||||
url, rawUrl, false, false, id, createEmptyState(url, this.rootComponentType)))
|
||||
url, rawUrl, false, false, id,
|
||||
createEmptyState(url, this.rootComponentType).snapshot))
|
||||
.then(resolve, reject);
|
||||
|
||||
} else {
|
||||
@ -634,7 +635,7 @@ export class Router {
|
||||
|
||||
private runNavigate(
|
||||
url: UrlTree, rawUrl: UrlTree, shouldPreventPushState: boolean, shouldReplaceUrl: boolean,
|
||||
id: number, precreatedState: RouterState): Promise<boolean> {
|
||||
id: number, precreatedState: RouterStateSnapshot): Promise<boolean> {
|
||||
if (id !== this.navigationId) {
|
||||
this.location.go(this.urlSerializer.serialize(this.currentUrlTree));
|
||||
this.routerEvents.next(new NavigationCancel(
|
||||
@ -644,68 +645,80 @@ export class Router {
|
||||
}
|
||||
|
||||
return new Promise((resolvePromise, rejectPromise) => {
|
||||
let state: RouterState;
|
||||
let navigationIsSuccessful: boolean;
|
||||
let preActivation: PreActivation;
|
||||
|
||||
let appliedUrl: UrlTree;
|
||||
|
||||
const storedState = this.currentRouterState;
|
||||
const storedUrl = this.currentUrlTree;
|
||||
|
||||
let routerState$: any;
|
||||
|
||||
// create an observable of the url and route state snapshot
|
||||
// this operation do not result in any side effects
|
||||
let urlAndSnapshot$: Observable<{appliedUrl: UrlTree, snapshot: RouterStateSnapshot}>;
|
||||
if (!precreatedState) {
|
||||
const redirectsApplied$ =
|
||||
applyRedirects(this.injector, this.configLoader, url, this.config);
|
||||
applyRedirects(this.injector, this.configLoader, this.urlSerializer, url, this.config);
|
||||
|
||||
const snapshot$ = mergeMap.call(redirectsApplied$, (u: UrlTree) => {
|
||||
appliedUrl = u;
|
||||
return recognize(
|
||||
this.rootComponentType, this.config, appliedUrl, this.serializeUrl(appliedUrl));
|
||||
});
|
||||
urlAndSnapshot$ = mergeMap.call(redirectsApplied$, (appliedUrl: UrlTree) => {
|
||||
return map.call(
|
||||
recognize(
|
||||
this.rootComponentType, this.config, appliedUrl, this.serializeUrl(appliedUrl)),
|
||||
(snapshot: any) => {
|
||||
|
||||
const emitRecognzied$ =
|
||||
map.call(snapshot$, (newRouterStateSnapshot: RouterStateSnapshot) => {
|
||||
this.routerEvents.next(new RoutesRecognized(
|
||||
id, this.serializeUrl(url), this.serializeUrl(appliedUrl),
|
||||
newRouterStateSnapshot));
|
||||
return newRouterStateSnapshot;
|
||||
});
|
||||
this.routerEvents.next(new RoutesRecognized(
|
||||
id, this.serializeUrl(url), this.serializeUrl(appliedUrl), snapshot));
|
||||
|
||||
routerState$ = map.call(emitRecognzied$, (routerStateSnapshot: RouterStateSnapshot) => {
|
||||
return createRouterState(routerStateSnapshot, this.currentRouterState);
|
||||
return {appliedUrl, snapshot};
|
||||
});
|
||||
});
|
||||
} else {
|
||||
appliedUrl = url;
|
||||
routerState$ = of (precreatedState);
|
||||
urlAndSnapshot$ = of ({appliedUrl: url, snapshot: precreatedState});
|
||||
}
|
||||
|
||||
const preactivation$ = map.call(routerState$, (newState: RouterState) => {
|
||||
state = newState;
|
||||
|
||||
// run preactivation: guards and data resolvers
|
||||
let preActivation: PreActivation;
|
||||
const preactivationTraverse$ = map.call(urlAndSnapshot$, ({appliedUrl, snapshot}: any) => {
|
||||
preActivation =
|
||||
new PreActivation(state.snapshot, this.currentRouterState.snapshot, this.injector);
|
||||
new PreActivation(snapshot, this.currentRouterState.snapshot, this.injector);
|
||||
preActivation.traverse(this.outletMap);
|
||||
return {appliedUrl, snapshot};
|
||||
});
|
||||
|
||||
const preactivation2$ = mergeMap.call(preactivation$, () => {
|
||||
const preactivationCheckGuards =
|
||||
mergeMap.call(preactivationTraverse$, ({appliedUrl, snapshot}: any) => {
|
||||
if (this.navigationId !== id) return of (false);
|
||||
|
||||
return map.call(preActivation.checkGuards(), (shouldActivate: boolean) => {
|
||||
return {appliedUrl: appliedUrl, snapshot: snapshot, shouldActivate: shouldActivate};
|
||||
});
|
||||
});
|
||||
|
||||
const preactivationResolveData$ = mergeMap.call(preactivationCheckGuards, (p: any) => {
|
||||
if (this.navigationId !== id) return of (false);
|
||||
|
||||
return preActivation.checkGuards();
|
||||
});
|
||||
|
||||
const resolveData$ = mergeMap.call(preactivation2$, (shouldActivate: boolean) => {
|
||||
if (this.navigationId !== id) return of (false);
|
||||
|
||||
if (shouldActivate) {
|
||||
return map.call(preActivation.resolveData(), () => shouldActivate);
|
||||
if (p.shouldActivate) {
|
||||
return map.call(preActivation.resolveData(), () => p);
|
||||
} else {
|
||||
return of (shouldActivate);
|
||||
return of (p);
|
||||
}
|
||||
});
|
||||
|
||||
resolveData$
|
||||
.forEach((shouldActivate: boolean) => {
|
||||
|
||||
// create router state
|
||||
// this operation has side effects => route state is being affected
|
||||
const routerState$ =
|
||||
map.call(preactivationResolveData$, ({appliedUrl, snapshot, shouldActivate}: any) => {
|
||||
if (shouldActivate) {
|
||||
const state = createRouterState(snapshot, this.currentRouterState);
|
||||
return {appliedUrl, state, shouldActivate};
|
||||
} else {
|
||||
return {appliedUrl, state: null, shouldActivate};
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// applied the new router state
|
||||
// this operation has side effects
|
||||
let navigationIsSuccessful: boolean;
|
||||
const storedState = this.currentRouterState;
|
||||
const storedUrl = this.currentUrlTree;
|
||||
|
||||
routerState$
|
||||
.forEach(({appliedUrl, state, shouldActivate}: any) => {
|
||||
if (!shouldActivate || id !== this.navigationId) {
|
||||
navigationIsSuccessful = false;
|
||||
return;
|
||||
@ -733,8 +746,8 @@ export class Router {
|
||||
() => {
|
||||
this.navigated = true;
|
||||
if (navigationIsSuccessful) {
|
||||
this.routerEvents.next(
|
||||
new NavigationEnd(id, this.serializeUrl(url), this.serializeUrl(appliedUrl)));
|
||||
this.routerEvents.next(new NavigationEnd(
|
||||
id, this.serializeUrl(url), this.serializeUrl(this.currentUrlTree)));
|
||||
resolvePromise(true);
|
||||
} else {
|
||||
this.resetUrlToCurrentUrlTree();
|
||||
|
@ -15,6 +15,7 @@ import {LoadedRouterConfig} from '../src/router_config_loader';
|
||||
import {DefaultUrlSerializer, UrlSegmentGroup, UrlTree, equalSegments} from '../src/url_tree';
|
||||
|
||||
describe('applyRedirects', () => {
|
||||
const serializer = new DefaultUrlSerializer();
|
||||
|
||||
it('should return the same url tree when no redirects', () => {
|
||||
checkRedirect(
|
||||
@ -38,7 +39,7 @@ describe('applyRedirects', () => {
|
||||
});
|
||||
|
||||
it('should throw when cannot handle a positional parameter', () => {
|
||||
applyRedirects(null, null, tree('/a/1'), [
|
||||
applyRedirects(null, null, serializer, tree('/a/1'), [
|
||||
{path: 'a/:id', redirectTo: 'a/:other'}
|
||||
]).subscribe(() => {}, (e) => {
|
||||
expect(e.message).toEqual('Cannot redirect to \'a/:other\'. Cannot find \':other\'.');
|
||||
@ -133,11 +134,11 @@ describe('applyRedirects', () => {
|
||||
{
|
||||
path: 'a',
|
||||
component: ComponentA,
|
||||
children: [{path: 'b/:id', redirectTo: '/absolute/:id'}]
|
||||
children: [{path: 'b/:id', redirectTo: '/absolute/:id?a=1&b=:b#f1'}]
|
||||
},
|
||||
{path: '**', component: ComponentC}
|
||||
],
|
||||
'/a/b/1', (t: UrlTree) => { compareTrees(t, tree('/absolute/1')); });
|
||||
'/a/b/1?b=2', (t: UrlTree) => { compareTrees(t, tree('/absolute/1?a=1&b=2#f1')); });
|
||||
});
|
||||
|
||||
describe('lazy loading', () => {
|
||||
@ -153,10 +154,11 @@ describe('applyRedirects', () => {
|
||||
};
|
||||
const config = [{path: 'a', component: ComponentA, loadChildren: 'children'}];
|
||||
|
||||
applyRedirects(<any>'providedInjector', <any>loader, tree('a/b'), config).forEach(r => {
|
||||
compareTrees(r, tree('/a/b'));
|
||||
expect((<any>config[0])._loadedConfig).toBe(loadedConfig);
|
||||
});
|
||||
applyRedirects(<any>'providedInjector', <any>loader, serializer, tree('a/b'), config)
|
||||
.forEach(r => {
|
||||
compareTrees(r, tree('/a/b'));
|
||||
expect((<any>config[0])._loadedConfig).toBe(loadedConfig);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle the case when the loader errors', () => {
|
||||
@ -165,9 +167,8 @@ describe('applyRedirects', () => {
|
||||
};
|
||||
const config = [{path: 'a', component: ComponentA, loadChildren: 'children'}];
|
||||
|
||||
applyRedirects(null, <any>loader, tree('a/b'), config).subscribe(() => {}, (e) => {
|
||||
expect(e.message).toEqual('Loading Error');
|
||||
});
|
||||
applyRedirects(null, <any>loader, serializer, tree('a/b'), config)
|
||||
.subscribe(() => {}, (e) => { expect(e.message).toEqual('Loading Error'); });
|
||||
});
|
||||
|
||||
it('should load when all canLoad guards return true', () => {
|
||||
@ -186,7 +187,7 @@ describe('applyRedirects', () => {
|
||||
loadChildren: 'children'
|
||||
}];
|
||||
|
||||
applyRedirects(<any>injector, <any>loader, tree('a/b'), config).forEach(r => {
|
||||
applyRedirects(<any>injector, <any>loader, serializer, tree('a/b'), config).forEach(r => {
|
||||
compareTrees(r, tree('/a/b'));
|
||||
});
|
||||
});
|
||||
@ -208,7 +209,7 @@ describe('applyRedirects', () => {
|
||||
loadChildren: 'children'
|
||||
}];
|
||||
|
||||
applyRedirects(<any>injector, <any>loader, tree('a/b'), config)
|
||||
applyRedirects(<any>injector, <any>loader, serializer, tree('a/b'), config)
|
||||
.subscribe(
|
||||
() => { throw 'Should not reach'; },
|
||||
(e) => {
|
||||
@ -234,7 +235,7 @@ describe('applyRedirects', () => {
|
||||
loadChildren: 'children'
|
||||
}];
|
||||
|
||||
applyRedirects(<any>injector, <any>loader, tree('a/b'), config)
|
||||
applyRedirects(<any>injector, <any>loader, serializer, tree('a/b'), config)
|
||||
.subscribe(
|
||||
() => { throw 'Should not reach'; }, (e) => { expect(e).toEqual('someError'); });
|
||||
});
|
||||
@ -251,7 +252,7 @@ describe('applyRedirects', () => {
|
||||
const config =
|
||||
[{path: 'a', component: ComponentA, canLoad: ['guard'], loadChildren: 'children'}];
|
||||
|
||||
applyRedirects(<any>injector, <any>loader, tree('a/b'), config)
|
||||
applyRedirects(<any>injector, <any>loader, serializer, tree('a/b'), config)
|
||||
.subscribe(
|
||||
(r) => { compareTrees(r, tree('/a/b')); }, (e) => { throw 'Should not reach'; });
|
||||
|
||||
@ -267,10 +268,11 @@ describe('applyRedirects', () => {
|
||||
const config =
|
||||
[{path: '', pathMatch: 'full', redirectTo: '/a'}, {path: 'a', loadChildren: 'children'}];
|
||||
|
||||
applyRedirects(<any>'providedInjector', <any>loader, tree(''), config).forEach(r => {
|
||||
compareTrees(r, tree('a'));
|
||||
expect((<any>config[1])._loadedConfig).toBe(loadedConfig);
|
||||
});
|
||||
applyRedirects(<any>'providedInjector', <any>loader, serializer, tree(''), config)
|
||||
.forEach(r => {
|
||||
compareTrees(r, tree('a'));
|
||||
expect((<any>config[1])._loadedConfig).toBe(loadedConfig);
|
||||
});
|
||||
});
|
||||
|
||||
it('should load the configuration only once', () => {
|
||||
@ -289,12 +291,13 @@ describe('applyRedirects', () => {
|
||||
|
||||
const config = [{path: 'a', loadChildren: 'children'}];
|
||||
|
||||
applyRedirects(<any>'providedInjector', <any>loader, tree('a?k1'), config).subscribe(r => {});
|
||||
applyRedirects(<any>'providedInjector', <any>loader, serializer, tree('a?k1'), config)
|
||||
.subscribe(r => {});
|
||||
|
||||
applyRedirects(<any>'providedInjector', <any>loader, tree('a?k2'), config)
|
||||
applyRedirects(<any>'providedInjector', <any>loader, serializer, tree('a?k2'), config)
|
||||
.subscribe(
|
||||
r => {
|
||||
compareTrees(r, tree('a'));
|
||||
compareTrees(r, tree('a?k2'));
|
||||
expect((<any>config[0])._loadedConfig).toBe(loadedConfig);
|
||||
},
|
||||
(e) => { throw 'Should not reach'; });
|
||||
@ -309,9 +312,8 @@ describe('applyRedirects', () => {
|
||||
|
||||
const config = [{path: '**', loadChildren: 'children'}];
|
||||
|
||||
applyRedirects(<any>'providedInjector', <any>loader, tree('xyz'), config).forEach(r => {
|
||||
expect((<any>config[0])._loadedConfig).toBe(loadedConfig);
|
||||
});
|
||||
applyRedirects(<any>'providedInjector', <any>loader, serializer, tree('xyz'), config)
|
||||
.forEach(r => { expect((<any>config[0])._loadedConfig).toBe(loadedConfig); });
|
||||
});
|
||||
|
||||
it('should load the configuration after a local redirect from a wildcard route', () => {
|
||||
@ -324,9 +326,8 @@ describe('applyRedirects', () => {
|
||||
const config =
|
||||
[{path: 'not-found', loadChildren: 'children'}, {path: '**', redirectTo: 'not-found'}];
|
||||
|
||||
applyRedirects(<any>'providedInjector', <any>loader, tree('xyz'), config).forEach(r => {
|
||||
expect((<any>config[0])._loadedConfig).toBe(loadedConfig);
|
||||
});
|
||||
applyRedirects(<any>'providedInjector', <any>loader, serializer, tree('xyz'), config)
|
||||
.forEach(r => { expect((<any>config[0])._loadedConfig).toBe(loadedConfig); });
|
||||
});
|
||||
|
||||
it('should load the configuration after an absolute redirect from a wildcard route', () => {
|
||||
@ -339,9 +340,8 @@ describe('applyRedirects', () => {
|
||||
const config =
|
||||
[{path: 'not-found', loadChildren: 'children'}, {path: '**', redirectTo: '/not-found'}];
|
||||
|
||||
applyRedirects(<any>'providedInjector', <any>loader, tree('xyz'), config).forEach(r => {
|
||||
expect((<any>config[0])._loadedConfig).toBe(loadedConfig);
|
||||
});
|
||||
applyRedirects(<any>'providedInjector', <any>loader, serializer, tree('xyz'), config)
|
||||
.forEach(r => { expect((<any>config[0])._loadedConfig).toBe(loadedConfig); });
|
||||
});
|
||||
});
|
||||
|
||||
@ -388,7 +388,7 @@ describe('applyRedirects', () => {
|
||||
{path: '', redirectTo: 'a', pathMatch: 'full'}
|
||||
];
|
||||
|
||||
applyRedirects(null, null, tree('b'), config)
|
||||
applyRedirects(null, null, serializer, tree('b'), config)
|
||||
.subscribe(
|
||||
(_) => { throw 'Should not be reached'; },
|
||||
e => { expect(e.message).toEqual('Cannot match any routes. URL Segment: \'b\''); });
|
||||
@ -518,7 +518,7 @@ describe('applyRedirects', () => {
|
||||
]
|
||||
}];
|
||||
|
||||
applyRedirects(null, null, tree('a/(d//aux:e)'), config)
|
||||
applyRedirects(null, null, serializer, tree('a/(d//aux:e)'), config)
|
||||
.subscribe(
|
||||
(_) => { throw 'Should not be reached'; },
|
||||
e => { expect(e.message).toEqual('Cannot match any routes. URL Segment: \'a\''); });
|
||||
@ -549,7 +549,7 @@ describe('applyRedirects', () => {
|
||||
|
||||
it('should error when no children matching and some url is left', () => {
|
||||
applyRedirects(
|
||||
null, null, tree('/a/c'),
|
||||
null, null, serializer, tree('/a/c'),
|
||||
[{path: 'a', component: ComponentA, children: [{path: 'b', component: ComponentB}]}])
|
||||
.subscribe(
|
||||
(_) => { throw 'Should not be reached'; },
|
||||
@ -576,10 +576,46 @@ describe('applyRedirects', () => {
|
||||
'/a/1/b', (t: UrlTree) => { compareTrees(t, tree('a/1/b')); });
|
||||
});
|
||||
});
|
||||
|
||||
describe('redirecting to named outlets', () => {
|
||||
it('should work when using absolute redirects', () => {
|
||||
checkRedirect(
|
||||
[
|
||||
{path: 'a/:id', redirectTo: '/b/:id(aux:c/:id)'},
|
||||
{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)')); });
|
||||
});
|
||||
|
||||
it('should work when using absolute redirects (wildcard)', () => {
|
||||
checkRedirect(
|
||||
[
|
||||
{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)')); });
|
||||
});
|
||||
|
||||
it('should throw when using non-absolute redirects', () => {
|
||||
applyRedirects(
|
||||
null, null, serializer, tree('a'),
|
||||
[
|
||||
{path: 'a', redirectTo: 'b(aux:c)'},
|
||||
])
|
||||
.subscribe(
|
||||
() => { throw new Error('should not be reached'); },
|
||||
(e) => {
|
||||
expect(e.message).toEqual(
|
||||
'Only absolute redirects can have named outlets. redirectTo: \'b(aux:c)\'');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function checkRedirect(config: Routes, url: string, callback: any): void {
|
||||
applyRedirects(null, null, tree(url), config).subscribe(callback, e => { throw e; });
|
||||
applyRedirects(null, null, new DefaultUrlSerializer(), tree(url), config)
|
||||
.subscribe(callback, e => { throw e; });
|
||||
}
|
||||
|
||||
function tree(url: string): UrlTree {
|
||||
@ -591,6 +627,8 @@ function compareTrees(actual: UrlTree, expected: UrlTree): void {
|
||||
const error =
|
||||
`"${serializer.serialize(actual)}" is not equal to "${serializer.serialize(expected)}"`;
|
||||
compareSegments(actual.root, expected.root, error);
|
||||
expect(actual.queryParams).toEqual(expected.queryParams);
|
||||
expect(actual.fragment).toEqual(expected.fragment);
|
||||
}
|
||||
|
||||
function compareSegments(actual: UrlSegmentGroup, expected: UrlSegmentGroup, error: string): void {
|
||||
|
@ -1156,8 +1156,6 @@ describe('Integration', () => {
|
||||
advance(fixture);
|
||||
expect(location.path()).toEqual('/initial');
|
||||
})));
|
||||
|
||||
// should not break the back button when trigger by initial navigation
|
||||
});
|
||||
|
||||
describe('guards', () => {
|
||||
@ -1380,6 +1378,11 @@ describe('Integration', () => {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
{
|
||||
provide: 'alwaysFalse',
|
||||
useValue:
|
||||
(c: any, a: ActivatedRouteSnapshot, b: RouterStateSnapshot) => { return false; }
|
||||
},
|
||||
]
|
||||
});
|
||||
});
|
||||
@ -1504,6 +1507,31 @@ describe('Integration', () => {
|
||||
advance(fixture);
|
||||
expect(location.path()).toEqual('/team/33/user/fedor');
|
||||
})));
|
||||
|
||||
it('should not create a route state if navigation is canceled',
|
||||
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
||||
const fixture = createRoot(router, RootCmp);
|
||||
|
||||
router.resetConfig([{
|
||||
path: 'main',
|
||||
component: TeamCmp,
|
||||
children: [
|
||||
{path: 'component1', component: SimpleCmp, canDeactivate: ['alwaysFalse']},
|
||||
{path: 'component2', component: SimpleCmp}
|
||||
]
|
||||
}]);
|
||||
|
||||
router.navigateByUrl('/main/component1');
|
||||
advance(fixture);
|
||||
|
||||
router.navigateByUrl('/main/component2');
|
||||
advance(fixture);
|
||||
|
||||
const teamCmp = fixture.debugElement.children[1].componentInstance;
|
||||
expect(teamCmp.route.firstChild.url.value[0].path).toEqual('component1');
|
||||
expect(location.path()).toEqual('/main/component1');
|
||||
})));
|
||||
|
||||
});
|
||||
|
||||
describe('should work when given a class', () => {
|
||||
|
@ -14,6 +14,12 @@ export interface ComponentInfo {
|
||||
outputs?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* A `PropertyBinding` represents a mapping between a property name
|
||||
* and an attribute name. It is parsed from a string of the form
|
||||
* `"prop: attr"`; or simply `"propAndAttr" where the property
|
||||
* and attribute have the same identifier.
|
||||
*/
|
||||
export class PropertyBinding {
|
||||
prop: string;
|
||||
attr: string;
|
||||
|
@ -13,8 +13,11 @@ export const $INJECTOR = '$injector';
|
||||
export const $PARSE = '$parse';
|
||||
export const $ROOT_SCOPE = '$rootScope';
|
||||
export const $SCOPE = '$scope';
|
||||
export const $PROVIDE = '$provide';
|
||||
export const $DELEGATE = '$delegate';
|
||||
export const $$TESTABILITY = '$$testability';
|
||||
|
||||
export const $COMPILE = '$compile';
|
||||
export const $TEMPLATE_CACHE = '$templateCache';
|
||||
export const $HTTP_BACKEND = '$httpBackend';
|
||||
export const $CONTROLLER = '$controller';
|
||||
export const $CONTROLLER = '$controller';
|
||||
|
@ -6,20 +6,67 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ComponentFactory, ComponentFactoryResolver, Injector} from '@angular/core';
|
||||
import {ComponentFactory, ComponentFactoryResolver, Injector, Type} from '@angular/core';
|
||||
|
||||
import * as angular from '../angular_js';
|
||||
|
||||
import {ComponentInfo} from './component_info';
|
||||
import {$INJECTOR, $PARSE, INJECTOR_KEY} from './constants';
|
||||
import {DowngradeComponentAdapter} from './downgrade_component_adapter';
|
||||
|
||||
let downgradeCount = 0;
|
||||
|
||||
/**
|
||||
* @whatItDoes
|
||||
*
|
||||
* *Part of the [upgrade/static](/docs/ts/latest/api/#!?query=upgrade%2Fstatic)
|
||||
* library for hybrid upgrade apps that support AoT compilation*
|
||||
*
|
||||
* Allows an Angular 2+ component to be used from Angular 1.
|
||||
*
|
||||
* @howToUse
|
||||
*
|
||||
* Let's assume that you have an Angular 2+ component called `ng2Heroes` that needs
|
||||
* to be made available in Angular 1 templates.
|
||||
*
|
||||
* {@example upgrade/static/ts/module.ts region="ng2-heroes"}
|
||||
*
|
||||
* We must create an Angular 1 [directive](https://docs.angularjs.org/guide/directive)
|
||||
* that will make this Angular 2+ component available inside Angular 1 templates.
|
||||
* The `downgradeComponent()` function returns a factory function that we
|
||||
* can use to define the Angular 1 directive that wraps the "downgraded" component.
|
||||
*
|
||||
* {@example upgrade/static/ts/module.ts region="ng2-heroes-wrapper"}
|
||||
*
|
||||
* In this example you can see that we must provide information about the component being
|
||||
* "downgraded". This is because once the AoT compiler has run, all metadata about the
|
||||
* component has been removed from the code, and so cannot be inferred.
|
||||
*
|
||||
* We must do the following:
|
||||
* * specify the Angular 2+ component class that is to be downgraded
|
||||
* * specify all inputs and outputs that the Angular 1 component expects
|
||||
*
|
||||
* @description
|
||||
*
|
||||
* A helper function that returns a factory function to be used for registering an
|
||||
* Angular 1 wrapper directive for "downgrading" an Angular 2+ component.
|
||||
*
|
||||
* The parameter contains information about the Component that is being downgraded:
|
||||
*
|
||||
* * `component: Type<any>`: The type of the Component that will be downgraded
|
||||
* * `inputs: string[]`: A collection of strings that specify what inputs the component accepts.
|
||||
* * `outputs: string[]`: A collection of strings that specify what outputs the component emits.
|
||||
*
|
||||
* The `inputs` and `outputs` are strings that map the names of properties to camelCased
|
||||
* attribute names. They are of the form `"prop: attr"`; or simply `"propAndAttr" where the
|
||||
* property and attribute have the same identifier.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export function downgradeComponent(info: ComponentInfo): angular.IInjectable {
|
||||
export function downgradeComponent(info: /* ComponentInfo */ {
|
||||
component: Type<any>;
|
||||
inputs?: string[];
|
||||
outputs?: string[];
|
||||
}): any /* angular.IInjectable */ {
|
||||
const idPrefix = `NG2_UPGRADE_${downgradeCount++}_`;
|
||||
let idCount = 0;
|
||||
|
||||
|
@ -10,14 +10,44 @@ import {Injector} from '@angular/core';
|
||||
import {INJECTOR_KEY} from './constants';
|
||||
|
||||
/**
|
||||
* Create an Angular 1 factory that will return an Angular 2 injectable thing
|
||||
* (e.g. service, pipe, component, etc)
|
||||
* @whatItDoes
|
||||
*
|
||||
* Usage:
|
||||
* *Part of the [upgrade/static](/docs/ts/latest/api/#!?query=upgrade%2Fstatic)
|
||||
* library for hybrid upgrade apps that support AoT compilation*
|
||||
*
|
||||
* ```
|
||||
* angular1Module.factory('someService', downgradeInjectable(SomeService))
|
||||
* ```
|
||||
* Allow an Angular 2+ service to be accessible from Angular 1.
|
||||
*
|
||||
* @howToUse
|
||||
*
|
||||
* First ensure that the service to be downgraded is provided in an {@link NgModule}
|
||||
* that will be part of the upgrade application. For example, let's assume we have
|
||||
* defined `HeroesService`
|
||||
*
|
||||
* {@example upgrade/static/ts/module.ts region="ng2-heroes-service"}
|
||||
*
|
||||
* and that we have included this in our upgrade app {@link NgModule}
|
||||
*
|
||||
* {@example upgrade/static/ts/module.ts region="ng2-module"}
|
||||
*
|
||||
* Now we can register the `downgradeInjectable` factory function for the service
|
||||
* on an Angular 1 module.
|
||||
*
|
||||
* {@example upgrade/static/ts/module.ts region="downgrade-ng2-heroes-service"}
|
||||
*
|
||||
* Inside an Angular 1 component's controller we can get hold of the
|
||||
* downgraded service via the name we gave when downgrading.
|
||||
*
|
||||
* {@example upgrade/static/ts/module.ts region="example-app"}
|
||||
*
|
||||
* @description
|
||||
*
|
||||
* Takes a `token` that identifies a service provided from Angular 2+.
|
||||
*
|
||||
* Returns a [factory function](https://docs.angularjs.org/guide/di) that can be
|
||||
* used to register the service on an Angular 1 module.
|
||||
*
|
||||
* The factory function provides access to the Angular 2+ service that
|
||||
* is identified by the `token` parameter.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
|
@ -43,6 +43,43 @@ interface IControllerInstance extends IBindingDestination {
|
||||
type LifecycleHook = '$onChanges' | '$onDestroy' | '$onInit' | '$postLink';
|
||||
|
||||
/**
|
||||
* @whatItDoes
|
||||
*
|
||||
* *Part of the [upgrade/static](/docs/ts/latest/api/#!?query=upgrade%2Fstatic)
|
||||
* library for hybrid upgrade apps that support AoT compilation*
|
||||
*
|
||||
* Allows an Angular 1 component to be used from Angular 2+.
|
||||
*
|
||||
* @howToUse
|
||||
*
|
||||
* Let's assume that you have an Angular 1 component called `ng1Hero` that needs
|
||||
* to be made available in Angular 2+ templates.
|
||||
*
|
||||
* {@example upgrade/static/ts/module.ts region="ng1-hero"}
|
||||
*
|
||||
* We must create a {@link Directive} that will make this Angular 1 component
|
||||
* available inside Angular 2+ templates.
|
||||
*
|
||||
* {@example upgrade/static/ts/module.ts region="ng1-hero-wrapper"}
|
||||
*
|
||||
* In this example you can see that we must derive from the {@link UpgradeComponent}
|
||||
* base class but also provide an {@link Directive `@Directive`} decorator. This is
|
||||
* because the AoT compiler requires that this information is statically available at
|
||||
* compile time.
|
||||
*
|
||||
* Note that we must do the following:
|
||||
* * specify the directive's selector (`ng1-hero`)
|
||||
* * specify all inputs and outputs that the Angular 1 component expects
|
||||
* * derive from `UpgradeComponent`
|
||||
* * call the base class from the constructor, passing
|
||||
* * the Angular 1 name of the component (`ng1Hero`)
|
||||
* * the {@link ElementRef} and {@link Injector} for the component wrapper
|
||||
*
|
||||
* @description
|
||||
*
|
||||
* A helper class that should be used as a base class for creating Angular directives
|
||||
* that wrap Angular 1 components that need to be "upgraded".
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export class UpgradeComponent implements OnInit, OnChanges, DoCheck, OnDestroy {
|
||||
@ -63,6 +100,22 @@ export class UpgradeComponent implements OnInit, OnChanges, DoCheck, OnDestroy {
|
||||
private controllerInstance: IControllerInstance = null;
|
||||
private bindingDestination: IBindingDestination = null;
|
||||
|
||||
/**
|
||||
* Create a new `UpgradeComponent` instance. You should not normally need to do this.
|
||||
* Instead you should derive a new class from this one and call the super constructor
|
||||
* from the base class.
|
||||
*
|
||||
* {@example upgrade/static/ts/module.ts region="ng1-hero-wrapper" }
|
||||
*
|
||||
* * The `name` parameter should be the name of the Angular 1 directive.
|
||||
* * The `elementRef` and `injector` parameters should be acquired from Angular by dependency
|
||||
* injection into the base class constructor.
|
||||
*
|
||||
* Note that we must manually implement lifecycle hooks that call through to the super class.
|
||||
* This is because, at the moment, the AoT compiler is not able to tell that the
|
||||
* `UpgradeComponent`
|
||||
* already implements them and so does not wire up calls to them at runtime.
|
||||
*/
|
||||
constructor(private name: string, private elementRef: ElementRef, private injector: Injector) {
|
||||
this.$injector = injector.get($INJECTOR);
|
||||
this.$compile = this.$injector.get($COMPILE);
|
||||
@ -77,7 +130,7 @@ export class UpgradeComponent implements OnInit, OnChanges, DoCheck, OnDestroy {
|
||||
this.bindings = this.initializeBindings(this.directive);
|
||||
this.linkFn = this.compileTemplate(this.directive);
|
||||
|
||||
// We ask for the Angular 1 scope from the Angular 2 injector, since
|
||||
// We ask for the Angular 1 scope from the Angular 2+ injector, since
|
||||
// we will put the new component scope onto the new injector for each component
|
||||
const $parentScope = injector.get($SCOPE);
|
||||
// QUESTION 1: Should we create an isolated scope if the scope is only true?
|
||||
|
@ -6,25 +6,141 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Injector, NgModule, NgZone} from '@angular/core';
|
||||
import {Injector, NgModule, NgZone, Testability} from '@angular/core';
|
||||
|
||||
import * as angular from '../angular_js';
|
||||
import {controllerKey} from '../util';
|
||||
|
||||
import {angular1Providers, setTempInjectorRef} from './angular1_providers';
|
||||
import {$INJECTOR, INJECTOR_KEY, UPGRADE_MODULE_NAME} from './constants';
|
||||
import {$$TESTABILITY, $DELEGATE, $INJECTOR, $PROVIDE, $ROOT_SCOPE, INJECTOR_KEY, UPGRADE_MODULE_NAME} from './constants';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The Ng1Module contains providers for the Ng1Adapter and all the core Angular 1 services;
|
||||
* and also holds the `bootstrapNg1()` method fo bootstrapping an upgraded Angular 1 app.
|
||||
* @whatItDoes
|
||||
*
|
||||
* *Part of the [upgrade/static](/docs/ts/latest/api/#!?query=upgrade%2Fstatic)
|
||||
* library for hybrid upgrade apps that support AoT compilation*
|
||||
*
|
||||
* Allows Angular 1 and Angular 2+ components to be used together inside a hybrid upgrade
|
||||
* application, which supports AoT compilation.
|
||||
*
|
||||
* Specifically, the classes and functions in the `upgrade/static` module allow the following:
|
||||
* 1. Creation of an Angular 2+ directive that wraps and exposes an Angular 1 component so
|
||||
* that it can be used in an Angular 2 template. See {@link UpgradeComponent}.
|
||||
* 2. Creation of an Angular 1 directive that wraps and exposes an Angular 2+ component so
|
||||
* that it can be used in an Angular 1 template. See {@link downgradeComponent}.
|
||||
* 3. Creation of an Angular 2+ root injector provider that wraps and exposes an Angular 1
|
||||
* service so that it can be injected into an Angular 2+ context. See
|
||||
* {@link UpgradeModule#upgrading-an-angular-1-service Upgrading an Angular 1 service} below.
|
||||
* 4. Creation of an Angular 1 service that wraps and exposes an Angular 2+ injectable
|
||||
* so that it can be injected into an Angular 1 context. See {@link downgradeInjectable}.
|
||||
* 3. Bootstrapping of a hybrid Angular application which contains both of the frameworks
|
||||
* coexisting in a single application. See the
|
||||
* {@link UpgradeModule#example example} below.
|
||||
*
|
||||
* ## Mental Model
|
||||
*
|
||||
* When reasoning about how a hybrid application works it is useful to have a mental model which
|
||||
* describes what is happening and explains what is happening at the lowest level.
|
||||
*
|
||||
* 1. There are two independent frameworks running in a single application, each framework treats
|
||||
* the other as a black box.
|
||||
* 2. Each DOM element on the page is owned exactly by one framework. Whichever framework
|
||||
* instantiated the element is the owner. Each framework only updates/interacts with its own
|
||||
* DOM elements and ignores others.
|
||||
* 3. Angular 1 directives always execute inside the Angular 1 framework codebase regardless of
|
||||
* where they are instantiated.
|
||||
* 4. Angular 2+ components always execute inside the Angular 2+ framework codebase regardless of
|
||||
* where they are instantiated.
|
||||
* 5. An Angular 1 component can be "upgraded"" to an Angular 2+ component. This is achieved by
|
||||
* defining an Angular 2+ directive, which bootstraps the Angular 1 component at its location
|
||||
* in the DOM. See {@link UpgradeComponent}.
|
||||
* 6. An Angular 2+ component can be "downgraded"" to an Angular 1 component. This is achieved by
|
||||
* defining an Angular 1 directive, which bootstraps the Angular 2+ component at its location
|
||||
* in the DOM. See {@link downgradeComponent}.
|
||||
* 7. Whenever an "upgraded"/"downgraded" component is instantiated the host element is owned by
|
||||
* the framework doing the instantiation. The other framework then instantiates and owns the
|
||||
* view for that component.
|
||||
* a. This implies that the component bindings will always follow the semantics of the
|
||||
* instantiation framework.
|
||||
* b. The DOM attributes are parsed by the framework that owns the current template. So
|
||||
* attributes
|
||||
* in Angular 1 templates must use kebab-case, while Angular 1 templates must use camelCase.
|
||||
* c. However the template binding syntax will always use the Angular 2+ style, e.g. square
|
||||
* brackets (`[...]`) for property binding.
|
||||
* 8. Angular 1 is always bootstrapped first and owns the root component.
|
||||
* 9. The new application is running in an Angular 2+ zone, and therefore it no longer needs calls
|
||||
* to
|
||||
* `$apply()`.
|
||||
*
|
||||
* @howToUse
|
||||
*
|
||||
* `import {UpgradeModule} from '@angular/upgrade/static';`
|
||||
*
|
||||
* ## Example
|
||||
* Import the {@link UpgradeModule} into your top level {@link NgModule Angular 2+ `NgModule`}.
|
||||
*
|
||||
* {@example upgrade/static/ts/module.ts region='ng2-module'}
|
||||
*
|
||||
* Then bootstrap the hybrid upgrade app's module, get hold of the {@link UpgradeModule} instance
|
||||
* and use it to bootstrap the top level [Angular 1
|
||||
* module](https://docs.angularjs.org/api/ng/type/angular.Module).
|
||||
*
|
||||
* {@example upgrade/static/ts/module.ts region='bootstrap'}
|
||||
*
|
||||
*
|
||||
* ## Upgrading an Angular 1 service
|
||||
*
|
||||
* There is no specific API for upgrading an Angular 1 service. Instead you should just follow the
|
||||
* following recipe:
|
||||
*
|
||||
* Let's say you have an Angular 1 service:
|
||||
*
|
||||
* {@example upgrade/static/ts/module.ts region="ng1-title-case-service"}
|
||||
*
|
||||
* Then you should define an Angular 2+ provider to be included in your {@link NgModule} `providers`
|
||||
* property.
|
||||
*
|
||||
* {@example upgrade/static/ts/module.ts region="upgrade-ng1-service"}
|
||||
*
|
||||
* Then you can use the "upgraded" Angular 1 service by injecting it into an Angular 2 component
|
||||
* or service.
|
||||
*
|
||||
* {@example upgrade/static/ts/module.ts region="use-ng1-upgraded-service"}
|
||||
*
|
||||
* @description
|
||||
*
|
||||
* This class is an `NgModule`, which you import to provide Angular 1 core services,
|
||||
* and has an instance method used to bootstrap the hybrid upgrade application.
|
||||
*
|
||||
* ## Core Angular 1 services
|
||||
* Importing this {@link NgModule} will add providers for the core
|
||||
* [Angular 1 services](https://docs.angularjs.org/api/ng/service) to the root injector.
|
||||
*
|
||||
* ## Bootstrap
|
||||
* The runtime instance of this class contains a {@link UpgradeModule#bootstrap `bootstrap()`}
|
||||
* method, which you use to bootstrap the top level Angular 1 module onto an element in the
|
||||
* DOM for the hybrid upgrade app.
|
||||
*
|
||||
* It also contains properties to access the {@link UpgradeModule#injector root injector}, the
|
||||
* bootstrap {@link NgZone} and the
|
||||
* [Angular 1 $injector](https://docs.angularjs.org/api/auto/service/$injector).
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@NgModule({providers: angular1Providers})
|
||||
export class UpgradeModule {
|
||||
public $injector: angular.IInjectorService;
|
||||
/**
|
||||
* The Angular 1 `$injector` for the upgrade application.
|
||||
*/
|
||||
public $injector: any /*angular.IInjectorService*/;
|
||||
|
||||
constructor(public injector: Injector, public ngZone: NgZone) {}
|
||||
constructor(
|
||||
/** The root {@link Injector} for the upgrade application. */
|
||||
public injector: Injector,
|
||||
/** The bootstrap zone for the upgrade application */
|
||||
public ngZone: NgZone) {}
|
||||
|
||||
/**
|
||||
* Bootstrap an Angular 1 application from this NgModule
|
||||
@ -32,7 +148,8 @@ export class UpgradeModule {
|
||||
* @param [modules] the Angular 1 modules to bootstrap for this application
|
||||
* @param [config] optional extra Angular 1 bootstrap configuration
|
||||
*/
|
||||
bootstrap(element: Element, modules: string[] = [], config?: angular.IAngularBootstrapConfig) {
|
||||
bootstrap(
|
||||
element: Element, modules: string[] = [], config?: any /*angular.IAngularBootstrapConfig*/) {
|
||||
// Create an ng1 module to bootstrap
|
||||
const upgradeModule =
|
||||
angular
|
||||
@ -40,6 +157,35 @@ export class UpgradeModule {
|
||||
|
||||
.value(INJECTOR_KEY, this.injector)
|
||||
|
||||
.config([
|
||||
$PROVIDE, $INJECTOR,
|
||||
($provide: angular.IProvideService, $injector: angular.IInjectorService) => {
|
||||
if ($injector.has($$TESTABILITY)) {
|
||||
$provide.decorator($$TESTABILITY, [
|
||||
$DELEGATE,
|
||||
(testabilityDelegate: angular.ITestabilityService) => {
|
||||
const originalWhenStable: Function = testabilityDelegate.whenStable;
|
||||
const injector = this.injector;
|
||||
// Cannot use arrow function below because we need the context
|
||||
const newWhenStable = function(callback: Function) {
|
||||
originalWhenStable.call(this, function() {
|
||||
const ng2Testability: Testability = injector.get(Testability);
|
||||
if (ng2Testability.isStable()) {
|
||||
callback.apply(this, arguments);
|
||||
} else {
|
||||
ng2Testability.whenStable(newWhenStable.bind(this, callback));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
testabilityDelegate.whenStable = newWhenStable;
|
||||
return testabilityDelegate;
|
||||
}
|
||||
]);
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
.run([
|
||||
$INJECTOR,
|
||||
($injector: angular.IInjectorService) => {
|
||||
@ -59,7 +205,22 @@ export class UpgradeModule {
|
||||
}
|
||||
]);
|
||||
|
||||
// Make sure resumeBootstrap() only exists if the current bootstrap is deferred
|
||||
const windowAngular = (window as any /** TODO #???? */)['angular'];
|
||||
windowAngular.resumeBootstrap = undefined;
|
||||
|
||||
// Bootstrap the angular 1 application inside our zone
|
||||
this.ngZone.run(() => { angular.bootstrap(element, [upgradeModule.name], config); });
|
||||
|
||||
// Patch resumeBootstrap() to run inside the ngZone
|
||||
if (windowAngular.resumeBootstrap) {
|
||||
const originalResumeBootstrap: () => void = windowAngular.resumeBootstrap;
|
||||
const ngZone = this.ngZone;
|
||||
windowAngular.resumeBootstrap = function() {
|
||||
let args = arguments;
|
||||
windowAngular.resumeBootstrap = originalResumeBootstrap;
|
||||
ngZone.run(() => { windowAngular.resumeBootstrap.apply(this, args); });
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -355,14 +355,14 @@ export class UpgradeAdapter {
|
||||
function(testabilityDelegate: angular.ITestabilityService) {
|
||||
|
||||
const originalWhenStable: Function = testabilityDelegate.whenStable;
|
||||
const newWhenStable = (callback: Function): void => {
|
||||
const whenStableContext: any = this;
|
||||
// Cannot use arrow function below because we need the context
|
||||
const newWhenStable = function(callback: Function) {
|
||||
originalWhenStable.call(this, function() {
|
||||
const ng2Testability: Testability = moduleRef.injector.get(Testability);
|
||||
if (ng2Testability.isStable()) {
|
||||
callback.apply(this, arguments);
|
||||
} else {
|
||||
ng2Testability.whenStable(newWhenStable.bind(whenStableContext, callback));
|
||||
ng2Testability.whenStable(newWhenStable.bind(this, callback));
|
||||
}
|
||||
});
|
||||
};
|
||||
@ -433,8 +433,9 @@ export class UpgradeAdapter {
|
||||
if (windowAngular.resumeBootstrap) {
|
||||
const originalResumeBootstrap: () => void = windowAngular.resumeBootstrap;
|
||||
windowAngular.resumeBootstrap = function() {
|
||||
let args = arguments;
|
||||
windowAngular.resumeBootstrap = originalResumeBootstrap;
|
||||
windowAngular.resumeBootstrap.apply(this, arguments);
|
||||
ngZone.run(() => { windowAngular.resumeBootstrap.apply(this, args); });
|
||||
resolve();
|
||||
};
|
||||
} else {
|
||||
|
@ -49,7 +49,8 @@ export class UpgradeNg1ComponentAdapterBuilder {
|
||||
],
|
||||
ngOnInit: function() { /* needs to be here for ng2 to properly detect it */ },
|
||||
ngOnChanges: function() { /* needs to be here for ng2 to properly detect it */ },
|
||||
ngDoCheck: function() { /* needs to be here for ng2 to properly detect it */ }
|
||||
ngDoCheck: function() { /* needs to be here for ng2 to properly detect it */ },
|
||||
ngOnDestroy: function() { /* needs to be here for ng2 to properly detect it */ },
|
||||
});
|
||||
}
|
||||
|
||||
@ -262,16 +263,18 @@ class UpgradeNg1ComponentAdapter implements OnInit, OnChanges, DoCheck {
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
for (const name in changes) {
|
||||
if ((<Object>changes).hasOwnProperty(name)) {
|
||||
const change: SimpleChange = changes[name];
|
||||
this.setComponentProperty(name, change.currentValue);
|
||||
}
|
||||
const ng1Changes: any = {};
|
||||
Object.keys(changes).forEach(name => {
|
||||
const change: SimpleChange = changes[name];
|
||||
this.setComponentProperty(name, change.currentValue);
|
||||
ng1Changes[this.propertyMap[name]] = change;
|
||||
});
|
||||
if (this.destinationObj.$onChanges) {
|
||||
this.destinationObj.$onChanges(ng1Changes);
|
||||
}
|
||||
}
|
||||
|
||||
ngDoCheck(): number {
|
||||
const count = 0;
|
||||
ngDoCheck() {
|
||||
const destinationObj = this.destinationObj;
|
||||
const lastValues = this.checkLastValues;
|
||||
const checkProperties = this.checkProperties;
|
||||
@ -287,7 +290,15 @@ class UpgradeNg1ComponentAdapter implements OnInit, OnChanges, DoCheck {
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
if (this.destinationObj.$doCheck && this.directive.controller) {
|
||||
this.destinationObj.$doCheck();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.destinationObj.$onDestroy && this.directive.controller) {
|
||||
this.destinationObj.$onDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
setComponentProperty(name: string, value: any) {
|
||||
|
@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import {NgModule, Testability, destroyPlatform} from '@angular/core';
|
||||
import {NgZone} from '@angular/core/src/zone/ng_zone';
|
||||
import {fakeAsync, tick} from '@angular/core/testing';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||
@ -28,7 +29,11 @@ export function main() {
|
||||
|
||||
it('should handle deferred bootstrap', fakeAsync(() => {
|
||||
let applicationRunning = false;
|
||||
const ng1Module = angular.module('ng1', []).run(() => { applicationRunning = true; });
|
||||
let stayedInTheZone: boolean;
|
||||
const ng1Module = angular.module('ng1', []).run(() => {
|
||||
applicationRunning = true;
|
||||
stayedInTheZone = NgZone.isInAngularZone();
|
||||
});
|
||||
|
||||
const element = html('<div></div>');
|
||||
window.name = 'NG_DEFER_BOOTSTRAP!' + window.name;
|
||||
@ -40,6 +45,7 @@ export function main() {
|
||||
expect(applicationRunning).toEqual(false);
|
||||
tick(100);
|
||||
expect(applicationRunning).toEqual(true);
|
||||
expect(stayedInTheZone).toEqual(true);
|
||||
}));
|
||||
|
||||
it('should wait for ng2 testability', fakeAsync(() => {
|
||||
|
@ -922,6 +922,127 @@ export function main() {
|
||||
});
|
||||
}));
|
||||
|
||||
it('should call $doCheck of components', async(() => {
|
||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
||||
const ng1Module = angular.module('ng1', []);
|
||||
const valueToFind = '$doCheck';
|
||||
|
||||
let spy = jasmine.createSpy('doCheck');
|
||||
|
||||
const ng1 = {
|
||||
bindings: {},
|
||||
template: '{{$ctrl.value}}',
|
||||
controller: Class({
|
||||
constructor: function() {},
|
||||
$doCheck: function() {
|
||||
this.value = valueToFind;
|
||||
spy();
|
||||
}
|
||||
})
|
||||
};
|
||||
ng1Module.component('ng1', ng1);
|
||||
|
||||
const Ng2 = Component({selector: 'ng2', template: '<ng1></ng1>'}).Class({
|
||||
constructor: function() {}
|
||||
});
|
||||
|
||||
const Ng2Module = NgModule({
|
||||
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
|
||||
imports: [BrowserModule],
|
||||
schemas: [NO_ERRORS_SCHEMA],
|
||||
}).Class({constructor: function() {}});
|
||||
|
||||
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||
|
||||
const element = html(`<div><ng2></ng2></div>`);
|
||||
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
||||
expect(multiTrim(document.body.textContent)).toEqual(valueToFind);
|
||||
expect(spy).toHaveBeenCalled();
|
||||
let count = spy.calls.count();
|
||||
setTimeout(() => {
|
||||
expect(spy.calls.count()).toBeGreaterThan(count);
|
||||
ref.dispose();
|
||||
}, 100);
|
||||
});
|
||||
}));
|
||||
|
||||
it('should call $onChanges of components', async(() => {
|
||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
||||
const ng1Module = angular.module('ng1', []);
|
||||
const valueToFind = '$onChanges init';
|
||||
const valueToChange = '$onChanges changed';
|
||||
|
||||
const ng1 = {
|
||||
bindings: {val: '<'},
|
||||
template: '{{$ctrl.value}}',
|
||||
controller: Class({
|
||||
constructor: function() {},
|
||||
$onChanges: function(changes: any) { this.value = changes.val.currentValue; }
|
||||
})
|
||||
};
|
||||
ng1Module.component('ng1', ng1);
|
||||
|
||||
const Ng2 = Component({selector: 'ng2', template: '<ng1 [val]="val"></ng1>'}).Class({
|
||||
constructor: function() { this.val = valueToFind; },
|
||||
ngOnInit: function() { setTimeout(() => { this.val = valueToChange; }, 100); }
|
||||
});
|
||||
|
||||
const Ng2Module = NgModule({
|
||||
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
|
||||
imports: [BrowserModule],
|
||||
schemas: [NO_ERRORS_SCHEMA],
|
||||
}).Class({constructor: function() {}});
|
||||
|
||||
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||
|
||||
const element = html(`<div><ng2></ng2></div>`);
|
||||
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
||||
expect(multiTrim(document.body.textContent)).toEqual(valueToFind);
|
||||
setTimeout(() => {
|
||||
expect(multiTrim(document.body.textContent)).toEqual(valueToChange);
|
||||
ref.dispose();
|
||||
}, 200);
|
||||
});
|
||||
}));
|
||||
|
||||
it('should call $onDestroy of components', async(() => {
|
||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
||||
const ng1Module = angular.module('ng1', []);
|
||||
|
||||
let spy = jasmine.createSpy('$onDestroy');
|
||||
|
||||
const ng1 = {
|
||||
bindings: {},
|
||||
template: '<div>ng1</div>',
|
||||
controller: function($rootScope: any) { this.$onDestroy = function() { spy(); }; }
|
||||
};
|
||||
ng1Module.component('ng1', ng1);
|
||||
|
||||
const Ng2 = Component({selector: 'ng2', template: '<ng1></ng1>'}).Class({
|
||||
constructor: function() {}
|
||||
});
|
||||
|
||||
const Ng2Module = NgModule({
|
||||
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
|
||||
imports: [BrowserModule],
|
||||
schemas: [NO_ERRORS_SCHEMA],
|
||||
}).Class({constructor: function() {}});
|
||||
|
||||
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||
|
||||
const element = html(`<div ng-if="!destroy"><ng2></ng2></div>`);
|
||||
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
||||
(<any>ref.ng1RootScope).destroy = false;
|
||||
setTimeout(() => {
|
||||
(<any>ref.ng1RootScope).destroy = true;
|
||||
setTimeout(() => {
|
||||
expect(spy).toHaveBeenCalled();
|
||||
ref.dispose();
|
||||
}, 100);
|
||||
}, 100);
|
||||
});
|
||||
}));
|
||||
|
||||
it('should bind input properties (<) of components', async(() => {
|
||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
||||
const ng1Module = angular.module('ng1', []);
|
||||
|
@ -7,15 +7,18 @@
|
||||
*/
|
||||
|
||||
import {Component, Input, NgModule} from '@angular/core';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {BrowserModule, DomSanitizer, SafeStyle} from '@angular/platform-browser';
|
||||
|
||||
import {TableCell, emptyTable} from '../util';
|
||||
|
||||
let trustedEmptyColor: SafeStyle;
|
||||
let trustedGreyColor: SafeStyle;
|
||||
|
||||
@Component({
|
||||
selector: 'largetable',
|
||||
template: `<table><tbody>
|
||||
<tr *ngFor="let row of data; trackBy: trackByIndex">
|
||||
<td *ngFor="let cell of row; trackBy: trackByIndex" [style.backgroundColor]="cell.row % 2 ? '' : 'grey'">
|
||||
<td *ngFor="let cell of row; trackBy: trackByIndex" [style.backgroundColor]="getColor(cell.row)">
|
||||
{{cell.value}}
|
||||
</td>
|
||||
</tr>
|
||||
@ -26,8 +29,14 @@ export class TableComponent {
|
||||
data: TableCell[][] = emptyTable;
|
||||
|
||||
trackByIndex(index: number, item: any) { return index; }
|
||||
|
||||
getColor(row: number) { return row % 2 ? trustedEmptyColor : trustedGreyColor; }
|
||||
}
|
||||
|
||||
@NgModule({imports: [BrowserModule], bootstrap: [TableComponent], declarations: [TableComponent]})
|
||||
export class AppModule {
|
||||
constructor(sanitizer: DomSanitizer) {
|
||||
trustedEmptyColor = sanitizer.bypassSecurityTrustStyle('');
|
||||
trustedGreyColor = sanitizer.bypassSecurityTrustStyle('grey');
|
||||
}
|
||||
}
|
||||
|
@ -7,20 +7,28 @@
|
||||
*/
|
||||
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {BrowserModule, DomSanitizer, SafeStyle} from '@angular/platform-browser';
|
||||
|
||||
import {TreeNode, emptyTree} from '../util';
|
||||
|
||||
let trustedEmptyColor: SafeStyle;
|
||||
let trustedGreyColor: SafeStyle;
|
||||
|
||||
@Component({
|
||||
selector: 'tree',
|
||||
inputs: ['data'],
|
||||
template:
|
||||
`<span [style.backgroundColor]="data.depth % 2 ? '' : 'grey'"> {{data.value}} </span><tree *ngIf='data.right != null' [data]='data.right'></tree><tree *ngIf='data.left != null' [data]='data.left'></tree>`
|
||||
`<span [style.backgroundColor]="bgColor"> {{data.value}} </span><tree *ngIf='data.right != null' [data]='data.right'></tree><tree *ngIf='data.left != null' [data]='data.left'></tree>`
|
||||
})
|
||||
export class TreeComponent {
|
||||
data: TreeNode = emptyTree;
|
||||
get bgColor() { return this.data.depth % 2 ? trustedEmptyColor : trustedGreyColor; }
|
||||
}
|
||||
|
||||
@NgModule({imports: [BrowserModule], bootstrap: [TreeComponent], declarations: [TreeComponent]})
|
||||
export class AppModule {
|
||||
constructor(sanitizer: DomSanitizer) {
|
||||
trustedEmptyColor = sanitizer.bypassSecurityTrustStyle('');
|
||||
trustedGreyColor = sanitizer.bypassSecurityTrustStyle('grey');
|
||||
}
|
||||
}
|
||||
|
@ -7,14 +7,16 @@
|
||||
*/
|
||||
|
||||
import {Component, Input, NgModule} from '@angular/core';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {BrowserModule, DomSanitizer, SafeStyle} from '@angular/platform-browser';
|
||||
|
||||
import {TreeNode, emptyTree, maxDepth} from '../util';
|
||||
|
||||
let trustedEmptyColor: SafeStyle;
|
||||
let trustedGreyColor: SafeStyle;
|
||||
|
||||
function createTreeComponent(level: number, isLeaf: boolean) {
|
||||
const nextTreeEl = `tree${level+1}`;
|
||||
let template =
|
||||
`<span [style.backgroundColor]="data.depth % 2 ? '' : 'grey'"> {{data.value}} </span>`;
|
||||
let template = `<span [style.backgroundColor]="bgColor"> {{data.value}} </span>`;
|
||||
if (!isLeaf) {
|
||||
template +=
|
||||
`<${nextTreeEl} [data]='data.right'></${nextTreeEl}><${nextTreeEl} [data]='data.left'></${nextTreeEl}>`;
|
||||
@ -24,6 +26,7 @@ function createTreeComponent(level: number, isLeaf: boolean) {
|
||||
class TreeComponent {
|
||||
@Input()
|
||||
data: TreeNode;
|
||||
get bgColor() { return this.data.depth % 2 ? trustedEmptyColor : trustedGreyColor; }
|
||||
}
|
||||
|
||||
return TreeComponent;
|
||||
@ -43,6 +46,10 @@ function createModule(): any {
|
||||
|
||||
@NgModule({imports: [BrowserModule], bootstrap: [RootTreeComponent], declarations: [components]})
|
||||
class AppModule {
|
||||
constructor(sanitizer: DomSanitizer) {
|
||||
trustedEmptyColor = sanitizer.bypassSecurityTrustStyle('');
|
||||
trustedGreyColor = sanitizer.bypassSecurityTrustStyle('grey');
|
||||
}
|
||||
}
|
||||
|
||||
return AppModule;
|
||||
|
@ -40,6 +40,29 @@ describe('WebWorkers Animations', function() {
|
||||
browser.wait(() => boxElm.getSize().then(sizes => sizes['width'] > 750), 1000);
|
||||
});
|
||||
|
||||
it('should cancel the animation midway and continue from where it left off', () => {
|
||||
browser.ignoreSynchronization = true;
|
||||
browser.get(URL);
|
||||
|
||||
waitForBootstrap();
|
||||
|
||||
const elem = element(by.css(selector + ' .box'));
|
||||
const btn = element(by.css(selector + ' button'));
|
||||
const getWidth = () => elem.getSize().then((sizes: any) => sizes['width']);
|
||||
|
||||
btn.click();
|
||||
|
||||
browser.sleep(250);
|
||||
|
||||
btn.click();
|
||||
|
||||
expect(getWidth()).toBeLessThan(600);
|
||||
|
||||
browser.sleep(500);
|
||||
|
||||
expect(getWidth()).toBeLessThan(50);
|
||||
});
|
||||
|
||||
function waitForBootstrap() {
|
||||
browser.wait(protractor.until.elementLocated(by.css(selector + ' .box')), 5000)
|
||||
.then(() => {}, () => {
|
||||
|
@ -20,7 +20,8 @@
|
||||
"lib": ["es5", "dom", "es2015.promise", "es2015.collection", "es2015.iterable"],
|
||||
"skipDefaultLibCheck": true,
|
||||
"skipLibCheck": true,
|
||||
"target": "es5"
|
||||
"target": "es5",
|
||||
"types": ["angularjs"]
|
||||
},
|
||||
"exclude": [
|
||||
"angular1_router",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "angular-srcs",
|
||||
"version": "2.2.0",
|
||||
"version": "2.2.3",
|
||||
"private": true,
|
||||
"branchPattern": "2.0.*",
|
||||
"description": "Angular 2 - a web framework for modern web apps",
|
||||
|
2
tools/public_api_guard/core/index.d.ts
vendored
2
tools/public_api_guard/core/index.d.ts
vendored
@ -749,7 +749,7 @@ export declare class RenderComponentType {
|
||||
|
||||
/** @experimental */
|
||||
export declare abstract class Renderer {
|
||||
abstract animate(element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[], duration: number, delay: number, easing: string): AnimationPlayer;
|
||||
abstract animate(element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[], duration: number, delay: number, easing: string, previousPlayers?: AnimationPlayer[]): AnimationPlayer;
|
||||
abstract attachViewAfter(node: any, viewRootNodes: any[]): void;
|
||||
abstract createElement(parentElement: any, name: string, debugInfo?: RenderDebugInfo): any;
|
||||
abstract createTemplateAnchor(parentElement: any, debugInfo?: RenderDebugInfo): any;
|
||||
|
@ -1,6 +1,6 @@
|
||||
/** @experimental */
|
||||
export declare abstract class AnimationDriver {
|
||||
abstract animate(element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[], duration: number, delay: number, easing: string): AnimationPlayer;
|
||||
abstract animate(element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[], duration: number, delay: number, easing: string, previousPlayers?: AnimationPlayer[]): AnimationPlayer;
|
||||
static NOOP: AnimationDriver;
|
||||
}
|
||||
|
||||
|
14
tools/public_api_guard/upgrade/static.d.ts
vendored
14
tools/public_api_guard/upgrade/static.d.ts
vendored
@ -1,5 +1,9 @@
|
||||
/** @experimental */
|
||||
export declare function downgradeComponent(info: ComponentInfo): angular.IInjectable;
|
||||
export declare function downgradeComponent(info: {
|
||||
component: Type<any>;
|
||||
inputs?: string[];
|
||||
outputs?: string[];
|
||||
}): any;
|
||||
|
||||
/** @experimental */
|
||||
export declare function downgradeInjectable(token: any): (string | ((i: Injector) => any))[];
|
||||
@ -15,9 +19,11 @@ export declare class UpgradeComponent implements OnInit, OnChanges, DoCheck, OnD
|
||||
|
||||
/** @experimental */
|
||||
export declare class UpgradeModule {
|
||||
$injector: angular.IInjectorService;
|
||||
$injector: any;
|
||||
injector: Injector;
|
||||
ngZone: NgZone;
|
||||
constructor(injector: Injector, ngZone: NgZone);
|
||||
bootstrap(element: Element, modules?: string[], config?: angular.IAngularBootstrapConfig): void;
|
||||
constructor(
|
||||
injector: Injector,
|
||||
ngZone: NgZone);
|
||||
bootstrap(element: Element, modules?: string[], config?: any): void;
|
||||
}
|
||||
|
Reference in New Issue
Block a user