diff --git a/aio/content/navigation.json b/aio/content/navigation.json
index 6a3ece8bce..a2f02e63c1 100644
--- a/aio/content/navigation.json
+++ b/aio/content/navigation.json
@@ -40,6 +40,12 @@
"hidden": true
},
+ {
+ "url": "guide/webpack",
+ "title": "Webpack: 简介",
+ "hidden": true
+ },
+
{
"url": "guide/quickstart",
"title": "快速上手",
@@ -165,7 +171,7 @@
{
"url": "guide/forms",
"title": "模板驱动表单",
- "tooltip": "A form creates a cohesive, effective, and compelling data entry experience. An Angular form coordinates a set of data-bound user controls, tracks changes, validates input, and presents errors."
+ "tooltip": "表单可以创建集中、高效、引人注目的输入体验。Angular 表单可以协调一组数据绑定控件,跟踪变更,验证输入,并表达错误信息。"
},
{
"url": "guide/form-validation",
@@ -381,7 +387,7 @@
"tooltip": "我们的联系方式、LOGO 和品牌"
},
{
- "url": "https://blog.angularjs.org/",
+ "url": "https://blog.angular.io/",
"title": "博客",
"tooltip": "Angular 官方博客"
}
diff --git a/aio/firebase.json b/aio/firebase.json
index b4710b8c87..628a255a3a 100644
--- a/aio/firebase.json
+++ b/aio/firebase.json
@@ -6,26 +6,27 @@
"public": "dist",
"cleanUrls": true,
"redirects": [
- // cli-quickstart.html glossary.html, quickstart.html, http.html, style-guide.html, styleguide
+ // cli-quickstart.html, glossary.html, quickstart.html, server-communication.html, style-guide.html
{"type": 301, "source": "/docs/ts/latest/cli-quickstart.html", "destination": "/guide/quickstart"},
- {"type": 301, "source": "/guide/cli-quickstart", "destination": "/guide/quickstart"},
{"type": 301, "source": "/docs/ts/latest/glossary.html", "destination": "/guide/glossary"},
{"type": 301, "source": "/docs/ts/latest/quickstart.html", "destination": "/guide/quickstart"},
{"type": 301, "source": "/docs/ts/latest/guide/server-communication.html", "destination": "/guide/http"},
{"type": 301, "source": "/docs/ts/latest/guide/style-guide.html", "destination": "/guide/styleguide"},
- {"type": 301, "source": "/styleguide", "destination": "/guide/styleguide"},
- // cookbook/component-communication.html
+ // guide/cli-quickstart, styleguide
+ {"type": 301, "source": "/guide/cli-quickstart", "destination": "/guide/quickstart"},
+ {"type": 301, "source": "/styleguide", "destination": "/guide/styleguide"},
+
+ // cookbook/a1-a2-quick-reference.html, cookbook/component-communication.html, cookbook/dependency-injection.html
+ {"type": 301, "source": "/docs/ts/latest/cookbook/a1-a2-quick-reference.html", "destination": "/guide/ajs-quick-reference"},
{"type": 301, "source": "/docs/ts/latest/cookbook/component-communication.html", "destination": "/guide/component-interaction"},
+ {"type": 301, "source": "/docs/ts/latest/cookbook/dependency-injection.html", "destination": "/guide/dependency-injection-in-action"},
// cookbook, cookbook/, cookbook/index.html
{"type": 301, "source": "/docs/ts/latest/cookbook", "destination": "/docs"},
{"type": 301, "source": "/docs/ts/latest/cookbook/", "destination": "/docs"},
{"type": 301, "source": "/docs/ts/latest/cookbook/index.html", "destination": "/docs"},
- // cookbook/dependency-injection.html
- {"type": 301, "source": "/docs/ts/latest/cookbook/dependency-injection.html", "destination": "/guide/dependency-injection-in-action"},
-
// cookbook/*.html
{"type": 301, "source": "/docs/ts/latest/cookbook/:cookbook.html", "destination": "/guide/:cookbook"},
diff --git a/aio/karma.conf.js b/aio/karma.conf.js
index f89999269a..c2d83c7804 100644
--- a/aio/karma.conf.js
+++ b/aio/karma.conf.js
@@ -16,15 +16,8 @@ module.exports = function (config) {
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
files: [
- { pattern: './node_modules/@angular/material/prebuilt-themes/indigo-pink.css', included: true },
- { pattern: './src/test.ts', watched: false }
+ { pattern: './node_modules/@angular/material/prebuilt-themes/indigo-pink.css', included: true }
],
- preprocessors: {
- './src/test.ts': ['@angular/cli']
- },
- mime: {
- 'text/x-typescript': ['ts','tsx']
- },
coverageIstanbulReporter: {
reports: [ 'html', 'lcovonly' ],
fixWebpackSourcePaths: true
@@ -32,14 +25,13 @@ module.exports = function (config) {
angularCli: {
environment: 'dev'
},
- reporters: config.angularCli && config.angularCli.codeCoverage
- ? ['progress', 'coverage-istanbul']
- : ['progress', 'kjhtml'],
+ reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
+ browserNoActivityTimeout: 60000,
singleRun: false
});
};
diff --git a/aio/package.json b/aio/package.json
index 6881103fdb..7309a5c78a 100644
--- a/aio/package.json
+++ b/aio/package.json
@@ -9,7 +9,7 @@
"ng": "yarn check-env && ng",
"start": "yarn check-env && ng serve",
"prebuild": "yarn check-env && yarn setup",
- "build": "ng build -prod -sm -vc=false",
+ "build": "ng build --target=production --environment=stable -sm -bo",
"postbuild": "yarn sw-manifest && yarn sw-copy",
"lint": "yarn check-env && yarn docs-lint && ng lint && yarn example-lint",
"test": "yarn check-env && ng test",
@@ -22,8 +22,7 @@
"example-e2e": "node ./tools/examples/run-example-e2e",
"example-lint": "tslint -c \"content/examples/tslint.json\" \"content/examples/**/*.ts\" -e \"content/examples/styleguide/**/*.avoid.ts\"",
"deploy-preview": "scripts/deploy-preview.sh",
- "deploy-staging": "scripts/deploy-to-firebase.sh staging",
- "deploy-production": "scripts/deploy-to-firebase.sh production",
+ "deploy-production": "scripts/deploy-to-firebase.sh",
"check-env": "node scripts/check-environment",
"payload-size": "scripts/payload.sh",
"predocs": "rimraf src/generated/{docs,*.json}",
@@ -31,6 +30,7 @@
"docs-watch": "node tools/transforms/authors-package/watchr.js",
"docs-lint": "eslint --ignore-path=\"tools/transforms/.eslintignore\" tools/transforms",
"docs-test": "node tools/transforms/test.js",
+ "tools-test": "./scripts/deploy-to-firebase.test.sh && yarn docs-test",
"serve-and-sync": "concurrently --kill-others \"yarn docs-watch\" \"yarn start\"",
"~~update-webdriver": "webdriver-manager update --standalone false --gecko false",
"boilerplate:add": "node ./tools/examples/example-boilerplate add",
@@ -40,7 +40,7 @@
"generate-zips": "node ./tools/example-zipper/generateZips",
"sw-manifest": "ngu-sw-manifest --dist dist --in ngsw-manifest.json --out dist/ngsw-manifest.json",
"sw-copy": "cp node_modules/@angular/service-worker/bundles/worker-basic.min.js dist/",
- "postinstall": "node tools/cli-patches/patch.js && uglifyjs node_modules/lunr/lunr.js -c -m -o src/assets/js/lunr.min.js --source-map",
+ "postinstall": "uglifyjs node_modules/lunr/lunr.js -c -m -o src/assets/js/lunr.min.js --source-map",
"build-ie-polyfills": "node node_modules/webpack/bin/webpack.js -p src/ie-polyfills.js src/generated/ie-polyfills.min.js"
},
"engines": {
@@ -66,14 +66,13 @@
"core-js": "^2.4.1",
"jasmine": "^2.6.0",
"ng-pwa-tools": "^0.0.10",
- "ngo": "angular/ngo",
"rxjs": "^5.2.0",
"tslib": "^1.7.1",
"web-animations-js": "^2.2.5",
"zone.js": "^0.8.12"
},
"devDependencies": {
- "@angular/cli": "angular/cli-builds#webpack-next",
+ "@angular/cli": "1.3.0-rc.3",
"@angular/compiler-cli": "^4.3.1",
"@types/jasmine": "^2.5.52",
"@types/node": "~6.0.60",
diff --git a/aio/scripts/deploy-to-firebase.sh b/aio/scripts/deploy-to-firebase.sh
index a14ba8965b..1542067de0 100755
--- a/aio/scripts/deploy-to-firebase.sh
+++ b/aio/scripts/deploy-to-firebase.sh
@@ -3,32 +3,82 @@
# WARNING: FIREBASE_TOKEN should NOT be printed.
set +x -eu -o pipefail
+# Only deploy if this not a PR. PRs are deployed early in `build.sh`.
+if [[ $TRAVIS_PULL_REQUEST != "false" ]]; then
+ echo "Skipping deploy because this is a PR build."
+ exit 0
+fi
-readonly deployEnv=$1
+# Do not deploy if the current commit is not the latest on its branch.
+readonly LATEST_COMMIT=$(git ls-remote origin $TRAVIS_BRANCH | cut -c1-40)
+if [[ $TRAVIS_COMMIT != $LATEST_COMMIT ]]; then
+ echo "Skipping deploy because $TRAVIS_COMMIT is not the latest commit ($LATEST_COMMIT)."
+ exit 0
+fi
+
+# The deployment mode is computed based on the branch we are building
+if [[ $TRAVIS_BRANCH == master ]]; then
+ readonly deployEnv=next
+elif [[ $TRAVIS_BRANCH == $STABLE_BRANCH ]]; then
+ readonly deployEnv=stable
+else
+ # Extract the major versions from the branches, e.g. the 4 from 4.3.x
+ readonly majorVersion=${TRAVIS_BRANCH%%.*}
+ readonly majorVersionStable=${STABLE_BRANCH%%.*}
+
+ # Do not deploy if the major version is not less than the stable branch major version
+ if [[ $majorVersion -ge $majorVersionStable ]]; then
+ echo "Skipping deploy of branch \"${TRAVIS_BRANCH}\" to firebase."
+ echo "We only deploy archive branches with the major version less than the stable branch: \"${STABLE_BRANCH}\""
+ exit 0
+ fi
+
+ # Find the branch that has highest minor version for the given `$majorVersion`
+ readonly mostRecentMinorVersion=$(
+ # List the branches that start with the major version
+ git ls-remote origin refs/heads/${majorVersion}.*.x |
+ # Extract the version number
+ awk -F'/' '{print $3}' |
+ # Sort by the minor version
+ sort -t. -k 2,2n |
+ # Get the highest version
+ tail -n1
+ )
+
+ # Do not deploy as it is not the latest branch for the given major version
+ if [[ $TRAVIS_BRANCH != $mostRecentMinorVersion ]]; then
+ echo "Skipping deploy of branch \"${TRAVIS_BRANCH}\" to firebase."
+ echo "There is a more recent branch with the same major version: \"${mostRecentMinorVersion}\""
+ exit 0
+ fi
+
+ readonly deployEnv=archive
+fi
case $deployEnv in
- staging)
- readonly buildEnv=stage
+ next)
readonly projectId=aio-staging
- readonly deployedUrl=https://$projectId.firebaseapp.com/
+ readonly deployedUrl=https://next.angular.io/
readonly firebaseToken=$FIREBASE_TOKEN
;;
- production)
- readonly buildEnv=prod
+ stable)
readonly projectId=angular-io
readonly deployedUrl=https://angular.io/
readonly firebaseToken=$FIREBASE_TOKEN
;;
- *)
- echo "Unknown deployment environment ('$deployEnv'). Expected 'staging' or 'production'."
- exit 1
+ archive)
+ readonly projectId=angular-io-${majorVersion}
+ readonly deployedUrl=https://v${majorVersion}.angular.io/
+ readonly firebaseToken=$FIREBASE_TOKEN
;;
esac
-# Do not deploy if the current commit is not the latest on its branch.
-readonly LATEST_COMMIT=$(git ls-remote origin $TRAVIS_BRANCH | cut -c1-40)
-if [ $TRAVIS_COMMIT != $LATEST_COMMIT ]; then
- echo "Skipping deploy because $TRAVIS_COMMIT is not the latest commit ($LATEST_COMMIT)."
+echo "Git branch : $TRAVIS_BRANCH"
+echo "Build/deploy mode : $deployEnv"
+echo "Firebase project : $projectId"
+echo "Deployment URL : $deployedUrl"
+
+if [[ $1 == "--dry-run" ]]; then
exit 0
fi
@@ -37,7 +87,10 @@ fi
cd "`dirname $0`/.."
# Build the app
- yarn build -- --env=$buildEnv
+ yarn build -- --env=$deployEnv
+
+ # Include any mode-specific files
+ cp -rf src/extra-files/$deployEnv/. dist/
# Check payload size
yarn payload-size
diff --git a/aio/scripts/deploy-to-firebase.test.sh b/aio/scripts/deploy-to-firebase.test.sh
new file mode 100755
index 0000000000..dd9a3235fd
--- /dev/null
+++ b/aio/scripts/deploy-to-firebase.test.sh
@@ -0,0 +1,158 @@
+#!/usr/bin/env bash
+
+function check {
+ if [[ $1 == $2 ]]; then
+ echo Pass
+ exit 0
+ fi
+ echo Fail
+ echo ---- Expected ----
+ echo "$2"
+ echo ---- Actual ----
+ echo "$1"
+ exit 1
+}
+
+(
+ echo ===== master - skip deploy - pull request
+ actual=$(
+ export TRAVIS_PULL_REQUEST=true
+ `dirname $0`/deploy-to-firebase.sh --dry-run
+ )
+ expected="Skipping deploy because this is a PR build."
+ check "$actual" "$expected"
+)
+
+(
+ echo ===== master - deploy success
+ actual=$(
+ export TRAVIS_PULL_REQUEST=false
+ export TRAVIS_BRANCH=master
+ export TRAVIS_COMMIT=$(git ls-remote origin master | cut -c-40)
+ export FIREBASE_TOKEN=XXXXX
+ `dirname $0`/deploy-to-firebase.sh --dry-run
+ )
+ expected="Git branch : master
+Build/deploy mode : next
+Firebase project : aio-staging
+Deployment URL : https://next.angular.io/"
+ check "$actual" "$expected"
+)
+
+(
+ echo ===== master - skip deploy - commit not HEAD
+ actual=$(
+ export TRAVIS_PULL_REQUEST=false
+ export TRAVIS_BRANCH=master
+ export TRAVIS_COMMIT=DUMMY_TEST_COMMIT
+ `dirname $0`/deploy-to-firebase.sh --dry-run
+ )
+ expected="Skipping deploy because DUMMY_TEST_COMMIT is not the latest commit ($(git ls-remote origin master | cut -c1-40))."
+ check "$actual" "$expected"
+)
+
+(
+ echo ===== stable - deploy success
+ actual=$(
+ export TRAVIS_PULL_REQUEST=false
+ export TRAVIS_BRANCH=4.3.x
+ export STABLE_BRANCH=4.3.x
+ export TRAVIS_COMMIT=$(git ls-remote origin 4.3.x | cut -c-40)
+ export FIREBASE_TOKEN=XXXXX
+ `dirname $0`/deploy-to-firebase.sh --dry-run
+ )
+ expected="Git branch : 4.3.x
+Build/deploy mode : stable
+Firebase project : angular-io
+Deployment URL : https://angular.io/"
+ check "$actual" "$expected"
+)
+
+(
+ echo ===== stable - skip deploy - commit not HEAD
+ actual=$(
+ export TRAVIS_PULL_REQUEST=false
+ export TRAVIS_BRANCH=4.3.x
+ export STABLE_BRANCH=4.3.x
+ export TRAVIS_COMMIT=DUMMY_TEST_COMMIT
+ `dirname $0`/deploy-to-firebase.sh --dry-run
+ )
+ expected="Skipping deploy because DUMMY_TEST_COMMIT is not the latest commit ($(git ls-remote origin 4.3.x | cut -c1-40))."
+ check "$actual" "$expected"
+)
+
+(
+ echo ===== archive - deploy success
+ actual=$(
+ export TRAVIS_PULL_REQUEST=false
+ export TRAVIS_BRANCH=2.4.x
+ export STABLE_BRANCH=4.3.x
+ export TRAVIS_COMMIT=$(git ls-remote origin 2.4.x | cut -c-40)
+ export FIREBASE_TOKEN=XXXXX
+ `dirname $0`/deploy-to-firebase.sh --dry-run
+ )
+ expected="Git branch : 2.4.x
+Build/deploy mode : archive
+Firebase project : angular-io-2
+Deployment URL : https://v2.angular.io/"
+ check "$actual" "$expected"
+)
+
+(
+ echo ===== archive - skip deploy - commit not HEAD
+ actual=$(
+ export TRAVIS_PULL_REQUEST=false
+ export TRAVIS_BRANCH=2.4.x
+ export STABLE_BRANCH=4.3.x
+ export TRAVIS_COMMIT=DUMMY_TEST_COMMIT
+ export FIREBASE_TOKEN=XXXXX
+ `dirname $0`/deploy-to-firebase.sh --dry-run
+ )
+ expected="Skipping deploy because DUMMY_TEST_COMMIT is not the latest commit ($(git ls-remote origin 2.4.x | cut -c1-40))."
+ check "$actual" "$expected"
+)
+
+(
+ echo ===== archive - skip deploy - major version too high, lower minor
+ actual=$(
+ export TRAVIS_PULL_REQUEST=false
+ export TRAVIS_BRANCH=2.1.x
+ export STABLE_BRANCH=2.2.x
+ export TRAVIS_COMMIT=$(git ls-remote origin 2.1.x | cut -c-40)
+ export FIREBASE_TOKEN=XXXXX
+ `dirname $0`/deploy-to-firebase.sh --dry-run
+ )
+ expected="Skipping deploy of branch \"2.1.x\" to firebase.
+We only deploy archive branches with the major version less than the stable branch: \"2.2.x\""
+ check "$actual" "$expected"
+)
+
+(
+ echo ===== archive - skip deploy - major version too high, higher minor
+ actual=$(
+ export TRAVIS_PULL_REQUEST=false
+ export TRAVIS_BRANCH=2.4.x
+ export STABLE_BRANCH=2.2.x
+ export TRAVIS_COMMIT=$(git ls-remote origin 2.1.x | cut -c-40)
+ export FIREBASE_TOKEN=XXXXX
+ `dirname $0`/deploy-to-firebase.sh --dry-run
+ )
+ expected="Skipping deploy of branch \"2.1.x\" to firebase.
+We only deploy archive branches with the major version less than the stable branch: \"2.2.x\""
+ check "$actual" "$expected"
+)
+
+(
+ echo ===== archive - skip deploy - minor version too low
+ actual=$(
+ export TRAVIS_PULL_REQUEST=false
+ export TRAVIS_BRANCH=2.1.x
+ export STABLE_BRANCH=4.3.x
+ export TRAVIS_COMMIT=$(git ls-remote origin 2.1.x | cut -c-40)
+ export FIREBASE_TOKEN=XXXXX
+ `dirname $0`/deploy-to-firebase.sh --dry-run
+ )
+ expected="Skipping deploy of branch \"2.1.x\" to firebase.
+There is a more recent branch with the same major version: \"2.4.x\""
+ check "$actual" "$expected"
+)
diff --git a/aio/scripts/payload.sh b/aio/scripts/payload.sh
index 0dc0bd9c30..aacd7c615e 100755
--- a/aio/scripts/payload.sh
+++ b/aio/scripts/payload.sh
@@ -61,7 +61,8 @@ else
# Nothing changed in aio/
exit 0
fi
-payloadData="$payloadData\"change\": \"$change\""
+message=$(echo $TRAVIS_COMMIT_MESSAGE | sed 's/"/\\"/g' | sed 's/\\/\\\\/g')
+payloadData="$payloadData\"change\": \"$change\", \"message\": \"$message\""
payloadData="{${payloadData}}"
diff --git a/aio/src/app/app.component.html b/aio/src/app/app.component.html
index 22127e12c2..8f42887b50 100644
--- a/aio/src/app/app.component.html
+++ b/aio/src/app/app.component.html
@@ -21,12 +21,13 @@
-
+
diff --git a/aio/src/app/app.component.spec.ts b/aio/src/app/app.component.spec.ts
index c69ffe6fdd..ea67b3bd6e 100644
--- a/aio/src/app/app.component.spec.ts
+++ b/aio/src/app/app.component.spec.ts
@@ -12,6 +12,7 @@ import { of } from 'rxjs/observable/of';
import { AppComponent } from './app.component';
import { AppModule } from './app.module';
import { DocViewerComponent } from 'app/layout/doc-viewer/doc-viewer.component';
+import { Deployment } from 'app/shared/deployment.service';
import { GaService } from 'app/shared/ga.service';
import { LocationService } from 'app/shared/location.service';
import { Logger } from 'app/shared/logger.service';
@@ -273,26 +274,49 @@ describe('AppComponent', () => {
describe('SideNav version selector', () => {
let selectElement: DebugElement;
let selectComponent: SelectComponent;
- beforeEach(() => {
+
+ function setupSelectorForTesting(mode?: string) {
+ createTestingModule('a/b', mode);
+ initializeTest();
component.onResize(sideBySideBreakPoint + 1); // side-by-side
selectElement = fixture.debugElement.query(By.directive(SelectComponent));
selectComponent = selectElement.componentInstance;
+ }
+
+ it('should select the version that matches the deploy mode', () => {
+ setupSelectorForTesting();
+ expect(selectComponent.selected.title).toContain('stable');
+ setupSelectorForTesting('next');
+ expect(selectComponent.selected.title).toContain('next');
+ setupSelectorForTesting('archive');
+ expect(selectComponent.selected.title).toContain('v4');
});
- it('should pick first (current) version by default', () => {
- expect(selectComponent.selected.title).toEqual(component.versionInfo.raw);
+ it('should add the current raw version string to the selected version', () => {
+ setupSelectorForTesting();
+ expect(selectComponent.selected.title).toContain(`(v${component.versionInfo.raw})`);
+ setupSelectorForTesting('next');
+ expect(selectComponent.selected.title).toContain(`(v${component.versionInfo.raw})`);
+ setupSelectorForTesting('archive');
+ expect(selectComponent.selected.title).toContain(`(v${component.versionInfo.raw})`);
});
// Older docs versions have an href
- it('should navigate when change to a version with an href', () => {
- selectElement.triggerEventHandler('change', { option: component.docVersions[1] as Option, index: 1});
- expect(locationService.go).toHaveBeenCalledWith(TestHttp.docVersions[0].url);
+ it('should navigate when change to a version with a url', () => {
+ setupSelectorForTesting();
+ const versionWithUrlIndex = component.docVersions.findIndex(v => !!v.url);
+ const versionWithUrl = component.docVersions[versionWithUrlIndex];
+ selectElement.triggerEventHandler('change', { option: versionWithUrl, index: versionWithUrlIndex});
+ expect(locationService.go).toHaveBeenCalledWith(versionWithUrl.url);
});
// The current docs version should not have an href
// This may change when we perfect our docs versioning approach
- it('should not navigate when change to a version without an href', () => {
- selectElement.triggerEventHandler('change', { option: component.docVersions[0] as Option, index: 0});
+ it('should not navigate when change to a version without a url', () => {
+ setupSelectorForTesting();
+ const versionWithoutUrlIndex = component.docVersions.findIndex(v => !v.url);
+ const versionWithoutUrl = component.docVersions[versionWithoutUrlIndex];
+ selectElement.triggerEventHandler('change', { option: versionWithoutUrl, index: versionWithoutUrlIndex});
expect(locationService.go).not.toHaveBeenCalled();
});
});
@@ -332,10 +356,6 @@ describe('AppComponent', () => {
});
describe('hostClasses', () => {
- let host: DebugElement;
- beforeEach(() => {
- host = fixture.debugElement;
- });
it('should set the css classes of the host container based on the current doc and navigation view', () => {
locationService.go('guide/pipes');
@@ -359,7 +379,7 @@ describe('AppComponent', () => {
});
it('should set the css class of the host container based on the open/closed state of the side nav', () => {
- const sideNav = host.query(By.directive(MdSidenav));
+ const sideNav = fixture.debugElement.query(By.directive(MdSidenav));
locationService.go('guide/pipes');
fixture.detectChanges();
@@ -376,7 +396,14 @@ describe('AppComponent', () => {
checkHostClass('sidenav', 'open');
});
+ it('should set the css class of the host container based on the initial deployment mode', () => {
+ createTestingModule('a/b', 'archive');
+ initializeTest();
+ checkHostClass('mode', 'archive');
+ });
+
function checkHostClass(type, value) {
+ const host = fixture.debugElement;
const classes = host.properties['className'];
const classArray = classes.split(' ').filter(c => c.indexOf(`${type}-`) === 0);
expect(classArray.length).toBeLessThanOrEqual(1, `"${classes}" should have only one class matching ${type}-*`);
@@ -623,7 +650,25 @@ describe('AppComponent', () => {
describe('footer', () => {
it('should have version number', () => {
const versionEl: HTMLElement = fixture.debugElement.query(By.css('aio-footer')).nativeElement;
- expect(versionEl.textContent).toContain(TestHttp.versionFull);
+ expect(versionEl.textContent).toContain(TestHttp.versionInfo.full);
+ });
+ });
+
+ describe('deployment banner', () => {
+ it('should show a message if the deployment mode is "archive"', () => {
+ createTestingModule('a/b', 'archive');
+ initializeTest();
+ fixture.detectChanges();
+ const banner: HTMLElement = fixture.debugElement.query(By.css('aio-mode-banner')).nativeElement;
+ expect(banner.textContent).toContain('archived documentation for Angular v4');
+ });
+
+ it('should show no message if the deployment mode is not "archive"', () => {
+ createTestingModule('a/b', 'stable');
+ initializeTest();
+ fixture.detectChanges();
+ const banner: HTMLElement = fixture.debugElement.query(By.css('aio-mode-banner')).nativeElement;
+ expect(banner.textContent.trim()).toEqual('');
});
});
@@ -720,6 +765,97 @@ describe('AppComponent', () => {
});
});
+ describe('archive redirection', () => {
+ it('should redirect to `docs` if deployment mode is `archive` and not at a docs page', () => {
+ createTestingModule('', 'archive');
+ initializeTest();
+ expect(TestBed.get(LocationService).replace).toHaveBeenCalledWith('docs');
+
+ createTestingModule('resources', 'archive');
+ initializeTest();
+ expect(TestBed.get(LocationService).replace).toHaveBeenCalledWith('docs');
+
+ createTestingModule('guide/aot-compiler', 'archive');
+ initializeTest();
+ expect(TestBed.get(LocationService).replace).not.toHaveBeenCalled();
+
+ createTestingModule('tutorial', 'archive');
+ initializeTest();
+ expect(TestBed.get(LocationService).replace).not.toHaveBeenCalled();
+
+ createTestingModule('tutorial/toh-pt1', 'archive');
+ initializeTest();
+ expect(TestBed.get(LocationService).replace).not.toHaveBeenCalled();
+
+ createTestingModule('docs', 'archive');
+ initializeTest();
+ expect(TestBed.get(LocationService).replace).not.toHaveBeenCalled();
+
+ createTestingModule('api', 'archive');
+ initializeTest();
+ expect(TestBed.get(LocationService).replace).not.toHaveBeenCalled();
+ });
+
+ it('should redirect to `docs` if deployment mode is `next` and not at a docs page', () => {
+ createTestingModule('', 'next');
+ initializeTest();
+ expect(TestBed.get(LocationService).replace).toHaveBeenCalledWith('docs');
+
+ createTestingModule('resources', 'next');
+ initializeTest();
+ expect(TestBed.get(LocationService).replace).toHaveBeenCalledWith('docs');
+
+ createTestingModule('guide/aot-compiler', 'next');
+ initializeTest();
+ expect(TestBed.get(LocationService).replace).not.toHaveBeenCalled();
+
+ createTestingModule('tutorial', 'next');
+ initializeTest();
+ expect(TestBed.get(LocationService).replace).not.toHaveBeenCalled();
+
+ createTestingModule('tutorial/toh-pt1', 'next');
+ initializeTest();
+ expect(TestBed.get(LocationService).replace).not.toHaveBeenCalled();
+
+ createTestingModule('docs', 'next');
+ initializeTest();
+ expect(TestBed.get(LocationService).replace).not.toHaveBeenCalled();
+
+ createTestingModule('api', 'next');
+ initializeTest();
+ expect(TestBed.get(LocationService).replace).not.toHaveBeenCalled();
+ });
+
+ it('should not redirect to `docs` if deployment mode is `stable` and not at a docs page', () => {
+ createTestingModule('', 'stable');
+ initializeTest();
+ expect(TestBed.get(LocationService).replace).not.toHaveBeenCalled();
+
+ createTestingModule('resources', 'stable');
+ initializeTest();
+ expect(TestBed.get(LocationService).replace).not.toHaveBeenCalled();
+
+ createTestingModule('guide/aot-compiler', 'stable');
+ initializeTest();
+ expect(TestBed.get(LocationService).replace).not.toHaveBeenCalled();
+
+ createTestingModule('tutorial', 'stable');
+ initializeTest();
+ expect(TestBed.get(LocationService).replace).not.toHaveBeenCalled();
+
+ createTestingModule('tutorial/toh-pt1', 'stable');
+ initializeTest();
+ expect(TestBed.get(LocationService).replace).not.toHaveBeenCalled();
+
+ createTestingModule('docs', 'stable');
+ initializeTest();
+ expect(TestBed.get(LocationService).replace).not.toHaveBeenCalled();
+
+ createTestingModule('api', 'stable');
+ initializeTest();
+ expect(TestBed.get(LocationService).replace).not.toHaveBeenCalled();
+ });
+ });
});
describe('with mocked DocViewer', () => {
@@ -883,7 +1019,8 @@ describe('AppComponent', () => {
//// test helpers ////
-function createTestingModule(initialUrl: string) {
+function createTestingModule(initialUrl: string, mode: string = 'stable') {
+ const mockLocationService = new MockLocationService(initialUrl);
TestBed.resetTestingModule();
TestBed.configureTestingModule({
imports: [ AppModule ],
@@ -891,9 +1028,14 @@ function createTestingModule(initialUrl: string) {
{ provide: APP_BASE_HREF, useValue: '/' },
{ provide: GaService, useClass: TestGaService },
{ provide: Http, useClass: TestHttp },
- { provide: LocationService, useFactory: () => new MockLocationService(initialUrl) },
+ { provide: LocationService, useFactory: () => mockLocationService },
{ provide: Logger, useClass: MockLogger },
{ provide: SearchService, useClass: MockSearchService },
+ { provide: Deployment, useFactory: () => {
+ const deployment = new Deployment(mockLocationService as any);
+ deployment.mode = mode;
+ return deployment;
+ }},
]
});
}
@@ -908,7 +1050,21 @@ class TestSearchService {
}
class TestHttp {
- static versionFull = '4.0.0-local+sha.73808dd';
+
+ static versionInfo = {
+ raw: '4.0.0-rc.6',
+ major: 4,
+ minor: 0,
+ patch: 0,
+ prerelease: [ 'local' ],
+ build: 'sha.73808dd',
+ version: '4.0.0-local',
+ codeName: 'snapshot',
+ isSnapshot: true,
+ full: '4.0.0-local+sha.73808dd',
+ branch: 'master',
+ commitSHA: '73808dd38b5ccd729404936834d1568bd066de81'
+ };
static docVersions: NavigationNode[] = [
{ title: 'v2', url: 'https://v2.angular.cn' }
@@ -951,22 +1107,7 @@ class TestHttp {
],
"docVersions": TestHttp.docVersions,
- "__versionInfo": {
- "raw": "4.0.0-rc.6",
- "major": 4,
- "minor": 0,
- "patch": 0,
- "prerelease": [
- "local"
- ],
- "build": "sha.73808dd",
- "version": "4.0.0-local",
- "codeName": "snapshot",
- "isSnapshot": true,
- "full": TestHttp.versionFull,
- "branch": "master",
- "commitSHA": "73808dd38b5ccd729404936834d1568bd066de81"
- }
+ "__versionInfo": TestHttp.versionInfo,
};
get(url: string) {
diff --git a/aio/src/app/app.component.ts b/aio/src/app/app.component.ts
index 3ba0293a4b..84c283ded8 100644
--- a/aio/src/app/app.component.ts
+++ b/aio/src/app/app.component.ts
@@ -5,6 +5,7 @@ import { MdSidenav } from '@angular/material';
import { CurrentNodes, NavigationService, NavigationViews, NavigationNode, VersionInfo } from 'app/navigation/navigation.service';
import { DocumentService, DocumentContents } from 'app/documents/document.service';
import { DocViewerComponent } from 'app/layout/doc-viewer/doc-viewer.component';
+import { Deployment } from 'app/shared/deployment.service';
import { LocationService } from 'app/shared/location.service';
import { NavMenuComponent } from 'app/layout/nav-menu/nav-menu.component';
import { ScrollService } from 'app/shared/scroll.service';
@@ -99,6 +100,7 @@ export class AppComponent implements OnInit {
sidenav: MdSidenav;
constructor(
+ public deployment: Deployment,
private documentService: DocumentService,
private hostElement: ElementRef,
private locationService: LocationService,
@@ -127,6 +129,11 @@ export class AppComponent implements OnInit {
});
this.locationService.currentPath.subscribe(path => {
+ // Redirect to docs if we are in not in stable mode and are not hitting a docs page
+ // (i.e. we have arrived at a marketing page)
+ if (this.deployment.mode !== 'stable' && !/^(docs$|api$|guide|tutorial)/.test(path)) {
+ this.locationService.replace('docs');
+ }
if (path === this.currentPath) {
// scroll only if on same page (most likely a change to the hash)
this.autoScroll();
@@ -158,12 +165,24 @@ export class AppComponent implements OnInit {
// Compute the version picker list from the current version and the versions in the navigation map
combineLatest(
- this.navigationService.versionInfo.map(versionInfo => ({ title: versionInfo.raw, url: null })),
- this.navigationService.navigationViews.map(views => views['docVersions']),
- (currentVersion, otherVersions) => [currentVersion, ...otherVersions])
- .subscribe(versions => {
- this.docVersions = versions;
- this.currentDocVersion = this.docVersions[0];
+ this.navigationService.versionInfo,
+ this.navigationService.navigationViews.map(views => views['docVersions']))
+ .subscribe(([versionInfo, versions]) => {
+ // TODO(pbd): consider whether we can lookup the stable and next versions from the internet
+ const computedVersions = [
+ { title: 'next', url: 'https://next.angular.io' },
+ { title: 'stable', url: 'https://angular.io' },
+ ];
+ if (this.deployment.mode === 'archive') {
+ computedVersions.push({ title: `v${versionInfo.major}`, url: null });
+ }
+ this.docVersions = [...computedVersions, ...versions];
+
+ // Find the current version - eithers title matches the current deployment mode
+ // or its title matches the major version of the current version info
+ this.currentDocVersion = this.docVersions.find(version =>
+ version.title === this.deployment.mode || version.title === `v${versionInfo.major}`);
+ this.currentDocVersion.title += ` (v${versionInfo.raw})`;
});
this.navigationService.navigationViews.subscribe(views => {
@@ -256,12 +275,13 @@ export class AppComponent implements OnInit {
}
updateHostClasses() {
+ const mode = `mode-${this.deployment.mode}`;
const sideNavOpen = `sidenav-${this.sidenav.opened ? 'open' : 'closed'}`;
const pageClass = `page-${this.pageId}`;
const folderClass = `folder-${this.folderId}`;
const viewClasses = Object.keys(this.currentNodes || {}).map(view => `view-${view}`).join(' ');
- this.hostClasses = `${sideNavOpen} ${pageClass} ${folderClass} ${viewClasses}`;
+ this.hostClasses = `${mode} ${sideNavOpen} ${pageClass} ${folderClass} ${viewClasses}`;
}
// Dynamically change height of table of contents container
diff --git a/aio/src/app/app.module.ts b/aio/src/app/app.module.ts
index 73e0154342..ca9545ab5b 100644
--- a/aio/src/app/app.module.ts
+++ b/aio/src/app/app.module.ts
@@ -26,8 +26,10 @@ import { SwUpdatesModule } from 'app/sw-updates/sw-updates.module';
import { AppComponent } from 'app/app.component';
import { ApiService } from 'app/embedded/api/api.service';
import { CustomMdIconRegistry, SVG_ICONS } from 'app/shared/custom-md-icon-registry';
+import { Deployment } from 'app/shared/deployment.service';
import { DocViewerComponent } from 'app/layout/doc-viewer/doc-viewer.component';
import { DtComponent } from 'app/layout/doc-viewer/dt.component';
+import { ModeBannerComponent } from 'app/layout/mode-banner/mode-banner.component';
import { EmbeddedModule } from 'app/embedded/embedded.module';
import { GaService } from 'app/shared/ga.service';
import { Logger } from 'app/shared/logger.service';
@@ -90,14 +92,16 @@ export const svgIconProviders = [
DocViewerComponent,
DtComponent,
FooterComponent,
- TopMenuComponent,
+ ModeBannerComponent,
NavMenuComponent,
NavItemComponent,
SearchResultsComponent,
SearchBoxComponent,
+ TopMenuComponent,
],
providers: [
ApiService,
+ Deployment,
DocumentService,
GaService,
Logger,
diff --git a/aio/src/app/layout/mode-banner/mode-banner.component.ts b/aio/src/app/layout/mode-banner/mode-banner.component.ts
new file mode 100644
index 0000000000..366a0154e9
--- /dev/null
+++ b/aio/src/app/layout/mode-banner/mode-banner.component.ts
@@ -0,0 +1,16 @@
+import { Component, Input } from '@angular/core';
+import { VersionInfo } from 'app/navigation/navigation.service';
+
+@Component({
+ selector: 'aio-mode-banner',
+ template: `
+
+ This is the
archived documentation for Angular v{{version?.major}}.
+ Please visit
angular.io to see documentation for the current version of Angular.
+
+ `
+})
+export class ModeBannerComponent {
+ @Input() mode: string;
+ @Input() version: VersionInfo;
+}
diff --git a/aio/src/app/shared/deployment.service.spec.ts b/aio/src/app/shared/deployment.service.spec.ts
new file mode 100644
index 0000000000..34df4ec92e
--- /dev/null
+++ b/aio/src/app/shared/deployment.service.spec.ts
@@ -0,0 +1,32 @@
+import { ReflectiveInjector } from '@angular/core';
+import { environment } from 'environments/environment';
+import { LocationService } from 'app/shared/location.service';
+import { MockLocationService } from 'testing/location.service';
+import { Deployment } from './deployment.service';
+
+describe('Deployment service', () => {
+ describe('mode', () => {
+ it('should get the mode from the environment', () => {
+ environment.mode = 'foo';
+ const deployment = getInjector().get(Deployment);
+ expect(deployment.mode).toEqual('foo');
+ });
+
+ it('should get the mode from the `mode` query parameter if available', () => {
+ const injector = getInjector();
+
+ const locationService: MockLocationService = injector.get(LocationService);
+ locationService.search.and.returnValue({ mode: 'bar' });
+
+ const deployment = injector.get(Deployment);
+ expect(deployment.mode).toEqual('bar');
+ });
+ });
+});
+
+function getInjector() {
+ return ReflectiveInjector.resolveAndCreate([
+ Deployment,
+ { provide: LocationService, useFactory: () => new MockLocationService('') }
+ ]);
+}
diff --git a/aio/src/app/shared/deployment.service.ts b/aio/src/app/shared/deployment.service.ts
new file mode 100644
index 0000000000..6a3f0ee79a
--- /dev/null
+++ b/aio/src/app/shared/deployment.service.ts
@@ -0,0 +1,17 @@
+import { Injectable } from '@angular/core';
+import { LocationService } from 'app/shared/location.service';
+import { environment } from 'environments/environment';
+
+/**
+ * Information about the deployment of this application.
+ */
+@Injectable()
+export class Deployment {
+ /**
+ * The deployment mode set from the environment provided at build time;
+ * or overridden by the `mode` query parameter: e.g. `...?mode=archive`
+ */
+ mode: string = this.location.search()['mode'] || environment.mode;
+
+ constructor(private location: LocationService) {}
+};
diff --git a/aio/src/app/shared/location.service.ts b/aio/src/app/shared/location.service.ts
index 00d042ad70..52ecacfa5c 100644
--- a/aio/src/app/shared/location.service.ts
+++ b/aio/src/app/shared/location.service.ts
@@ -55,6 +55,10 @@ export class LocationService {
window.location.assign(url);
}
+ replace(url: string) {
+ window.location.replace(url);
+ }
+
private stripSlashes(url: string) {
return url.replace(/^\/+/, '').replace(/\/+(\?|#|$)/, '$1');
}
diff --git a/aio/src/environments/environment.archive.ts b/aio/src/environments/environment.archive.ts
new file mode 100644
index 0000000000..5100c50a77
--- /dev/null
+++ b/aio/src/environments/environment.archive.ts
@@ -0,0 +1,6 @@
+// This is for archived sites, which are hosted at https://vX.angular.io, where X is the major Angular version.
+export const environment = {
+ gaId: 'UA-8594346-15', // Production id (since it is linked from the main site)
+ production: true,
+ mode: 'archive'
+};
diff --git a/aio/src/environments/environment.next.ts b/aio/src/environments/environment.next.ts
new file mode 100644
index 0000000000..63682ea65e
--- /dev/null
+++ b/aio/src/environments/environment.next.ts
@@ -0,0 +1,6 @@
+// This is for the staging site, which is hosted at https://next.angular.io (and https://aio-staging.firebaseapp.org)
+export const environment = {
+ gaId: 'UA-8594346-15', // Production id (since it is linked from the main site)
+ production: true,
+ mode: 'next'
+};
diff --git a/aio/src/environments/environment.prod.ts b/aio/src/environments/environment.stable.ts
similarity index 57%
rename from aio/src/environments/environment.prod.ts
rename to aio/src/environments/environment.stable.ts
index 94acf34f85..47e330840b 100644
--- a/aio/src/environments/environment.prod.ts
+++ b/aio/src/environments/environment.stable.ts
@@ -1,5 +1,6 @@
// This is for the production site, which is hosted at https://angular.io
export const environment = {
- gaId: 'UA-80456300-1',
- production: true
+ gaId: 'UA-80456300-1', // Production id
+ production: true,
+ mode: 'stable'
};
diff --git a/aio/src/environments/environment.stage.ts b/aio/src/environments/environment.stage.ts
deleted file mode 100644
index 97a992c88f..0000000000
--- a/aio/src/environments/environment.stage.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-// This is for the staging site, which is hosted at https://aio-staging.firebaseapp.org
-export const environment = {
- gaId: 'UA-8594346-26',
- production: true
-};
diff --git a/aio/src/environments/environment.ts b/aio/src/environments/environment.ts
index 165a718f55..77d62f0968 100644
--- a/aio/src/environments/environment.ts
+++ b/aio/src/environments/environment.ts
@@ -13,6 +13,7 @@ import 'core-js/es7/reflect';
export const environment = {
- gaId: 'UA-8594346-26', // Staging site
- production: false
+ gaId: 'UA-8594346-26', // Development id
+ production: false,
+ mode: 'stable'
};
diff --git a/aio/src/extra-files/README.md b/aio/src/extra-files/README.md
new file mode 100644
index 0000000000..2aad208a30
--- /dev/null
+++ b/aio/src/extra-files/README.md
@@ -0,0 +1,9 @@
+# Extra files folder
+
+This folder is used for extra files that should be included in deployments to firebase.
+
+After the AIO application had been built and before it is deployed all files and folders
+inside the folder with the same name as the current deployment mode (next, stable, archive)
+will be copied to the `dist` folder.
+
+See the `scripts/deploy-to-firebase.sh` script for more detail.
\ No newline at end of file
diff --git a/aio/src/extra-files/archive/robots.txt b/aio/src/extra-files/archive/robots.txt
new file mode 100644
index 0000000000..77470cb39f
--- /dev/null
+++ b/aio/src/extra-files/archive/robots.txt
@@ -0,0 +1,2 @@
+User-agent: *
+Disallow: /
\ No newline at end of file
diff --git a/aio/src/extra-files/next/robots.txt b/aio/src/extra-files/next/robots.txt
new file mode 100644
index 0000000000..77470cb39f
--- /dev/null
+++ b/aio/src/extra-files/next/robots.txt
@@ -0,0 +1,2 @@
+User-agent: *
+Disallow: /
\ No newline at end of file
diff --git a/aio/src/styles/2-modules/_deploy-theme.scss b/aio/src/styles/2-modules/_deploy-theme.scss
new file mode 100644
index 0000000000..3858b66ce7
--- /dev/null
+++ b/aio/src/styles/2-modules/_deploy-theme.scss
@@ -0,0 +1,50 @@
+
+aio-shell.mode-archive {
+
+ .mat-toolbar.mat-primary, footer {
+ background: linear-gradient(145deg,#263238,#78909C);
+ }
+
+ .vertical-menu-item {
+ &.selected, &:hover {
+ color: #263238;
+ }
+ }
+
+ .toc-inner ul.toc-list li.active a {
+ color: #263238;
+
+ &:before {
+ background-color: #263238;
+ }
+ }
+
+ .toc-inner ul.toc-list li:hover a {
+ color: #263238;
+ }
+}
+
+aio-shell.mode-next {
+
+ .mat-toolbar.mat-primary, footer {
+ background: linear-gradient(145deg,#DD0031,#C3002F);
+ }
+
+ .vertical-menu-item {
+ &.selected, &:hover {
+ color: #DD0031;
+ }
+ }
+
+ .toc-inner ul.toc-list li.active a {
+ color: #DD0031;
+
+ &:before {
+ background-color: #DD0031;
+ }
+ }
+
+ .toc-inner ul.toc-list li:hover a {
+ color: #DD0031;
+ }
+}
diff --git a/aio/src/styles/2-modules/_modules-dir.scss b/aio/src/styles/2-modules/_modules-dir.scss
index c145331196..4828b05e68 100644
--- a/aio/src/styles/2-modules/_modules-dir.scss
+++ b/aio/src/styles/2-modules/_modules-dir.scss
@@ -29,3 +29,4 @@
@import 'subsection';
@import 'toc';
@import 'select-menu';
+ @import 'deploy-theme';
diff --git a/aio/src/testing/location.service.ts b/aio/src/testing/location.service.ts
index 2656769b95..1e03a69626 100644
--- a/aio/src/testing/location.service.ts
+++ b/aio/src/testing/location.service.ts
@@ -10,6 +10,7 @@ export class MockLocationService {
go = jasmine.createSpy('Location.go').and
.callFake((url: string) => this.urlSubject.next(url));
goExternal = jasmine.createSpy('Location.goExternal');
+ replace = jasmine.createSpy('Location.replace');
handleAnchorClick = jasmine.createSpy('Location.handleAnchorClick')
.and.returnValue(false); // prevent click from causing a browser navigation
diff --git a/aio/tools/cli-patches/ngo.patch b/aio/tools/cli-patches/ngo.patch
deleted file mode 100644
index 44c74b987f..0000000000
--- a/aio/tools/cli-patches/ngo.patch
+++ /dev/null
@@ -1,14 +0,0 @@
---- node_modules/@angular/cli/models/webpack-configs/production.js 2017-05-12 14:30:22.000000000 -0700
-+++ node_modules/@angular/cli/models/webpack-configs/production.js 2017-05-12 14:32:23.000000000 -0700
-@@ -68,6 +68,11 @@
- }
- return {
- entry: entryPoints,
-+ module: {
-+ rules: [
-+ {"test": /\.js$/, "use": {loader: "ngo/webpack-loader", options: { sourceMap: true }}},
-+ ]
-+ },
- plugins: [
- new webpack.EnvironmentPlugin({
- 'NODE_ENV': 'production'
diff --git a/aio/tools/cli-patches/patch.js b/aio/tools/cli-patches/patch.js
deleted file mode 100644
index b5735eee16..0000000000
--- a/aio/tools/cli-patches/patch.js
+++ /dev/null
@@ -1,12 +0,0 @@
-const fs = require('fs');
-const sh = require('shelljs');
-
-PATCH_LOCK = 'node_modules/@angular/cli/models/webpack-configs/.patched';
-
-if (!fs.existsSync(PATCH_LOCK)) {
- sh.exec('patch -p0 -i tools/cli-patches/ngo.patch');
- sh.exec('patch -p0 -i tools/cli-patches/purify.patch');
- sh.exec('patch -p0 -i tools/cli-patches/scope-hoisting.patch');
- sh.exec('patch -p0 -i tools/cli-patches/uglify-config.patch');
- sh.touch(PATCH_LOCK);
-}
diff --git a/aio/tools/cli-patches/purify.patch b/aio/tools/cli-patches/purify.patch
deleted file mode 100644
index fbe8484169..0000000000
--- a/aio/tools/cli-patches/purify.patch
+++ /dev/null
@@ -1,10 +0,0 @@
---- node_modules/@angular/cli/models/webpack-configs/production.js 2017-05-11 12:10:46.000000000 -0700
-+++ node_modules/@angular/cli/models/webpack-configs/production.js 2017-05-11 12:10:11.000000000 -0700
-@@ -73,6 +73,7 @@
- 'NODE_ENV': 'production'
- }),
- new webpack.HashedModuleIdsPlugin(),
-+ new (require("ngo").PurifyPlugin)(),
- new webpack.optimize.UglifyJsPlugin({
- mangle: { screw_ie8: true },
- compress: { screw_ie8: true, warnings: buildOptions.verbose },
diff --git a/aio/tools/cli-patches/scope-hoisting.patch b/aio/tools/cli-patches/scope-hoisting.patch
deleted file mode 100644
index 15c05ce456..0000000000
--- a/aio/tools/cli-patches/scope-hoisting.patch
+++ /dev/null
@@ -1,10 +0,0 @@
---- node_modules/@angular/cli/models/webpack-configs/production.js 2017-05-24 15:36:43.000000000 -0700
-+++ node_modules/@angular/cli/models/webpack-configs/production.js 2017-05-24 15:37:04.000000000 -0700
-@@ -85,6 +85,7 @@
- 'NODE_ENV': 'production'
- }),
- new webpack.HashedModuleIdsPlugin(),
-+ new webpack.optimize.ModuleConcatenationPlugin(),
- new (require("ngo").PurifyPlugin)(),
- new webpack.optimize.UglifyJsPlugin({
- mangle: true,
diff --git a/aio/tools/cli-patches/uglify-config.patch b/aio/tools/cli-patches/uglify-config.patch
deleted file mode 100644
index 3d17d3c9d4..0000000000
--- a/aio/tools/cli-patches/uglify-config.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- node_modules/@angular/cli/models/webpack-configs/production.js 2017-05-24 15:36:43.000000000 -0700
-+++ node_modules/@angular/cli/models/webpack-configs/production.js 2017-05-24 15:37:04.000000000 -0700
-@@ -82,7 +82,7 @@
- new (require("purify/purify-webpack-plugin"))(),
- new webpack.optimize.UglifyJsPlugin({
- mangle: { screw_ie8: true },
-- compress: { screw_ie8: true, warnings: buildOptions.verbose },
-+ compress: { screw_ie8: true, warnings: buildOptions.verbose, pure_getters: true },
- sourceMap: buildOptions.sourcemaps,
- comments: false
- })
diff --git a/aio/tools/transforms/angular-base-package/post-processors/h1-checker.js b/aio/tools/transforms/angular-base-package/post-processors/h1-checker.js
index ee5356b5e9..e43517e571 100644
--- a/aio/tools/transforms/angular-base-package/post-processors/h1-checker.js
+++ b/aio/tools/transforms/angular-base-package/post-processors/h1-checker.js
@@ -37,5 +37,5 @@ function getText(h1) {
(node.properties.ariaHidden === 'true' || node.properties['aria-hidden'] === 'true')
));
- return toString(cleaned);
-}
\ No newline at end of file
+ return cleaned ? toString(cleaned) : '';
+}
diff --git a/aio/tools/transforms/angular-base-package/post-processors/h1-checker.spec.js b/aio/tools/transforms/angular-base-package/post-processors/h1-checker.spec.js
index 00ee5490ff..4dd2537df4 100644
--- a/aio/tools/transforms/angular-base-package/post-processors/h1-checker.spec.js
+++ b/aio/tools/transforms/angular-base-package/post-processors/h1-checker.spec.js
@@ -70,4 +70,14 @@ describe('h1Checker postprocessor', () => {
processor.$process([doc]);
expect(doc.vFile.title).toEqual('What is Angular?');
});
-});
\ No newline at end of file
+
+ it('should not break if the h1 is empty (except for an aria-hidden anchor)', () => {
+ const doc = {
+ docType: 'a',
+ renderedContent: `
+
+ `
+ };
+ expect(() => processor.$process([doc])).not.toThrow();
+ });
+});
diff --git a/aio/yarn.lock b/aio/yarn.lock
index c7be4d5000..9e32cc0b32 100644
--- a/aio/yarn.lock
+++ b/aio/yarn.lock
@@ -2,6 +2,15 @@
# yarn lockfile v1
+"@angular-devkit/build-optimizer@0.0.3":
+ version "0.0.3"
+ resolved "https://registry.yarnpkg.com/@angular-devkit/build-optimizer/-/build-optimizer-0.0.3.tgz#092bdf732b79a779ce540f9bb99d6590dd971204"
+ dependencies:
+ loader-utils "^1.1.0"
+ magic-string "^0.19.1"
+ source-map "^0.5.6"
+ typescript "^2.3.3"
+
"@angular/animations@^4.3.1":
version "4.3.1"
resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-4.3.1.tgz#1f7e0bb803efc21c608246e6765a1c647f3d1a5f"
@@ -14,51 +23,54 @@
dependencies:
tslib "^1.7.1"
-"@angular/cli@angular/cli-builds#webpack-next":
- version "1.2.0-beta.0-7c33dd4"
- resolved "https://codeload.github.com/angular/cli-builds/tar.gz/b4bb5968c04c92fd816c434044e28bbfc60b296c"
+"@angular/cli@1.3.0-rc.3":
+ version "1.3.0-rc.3"
+ resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-1.3.0-rc.3.tgz#5a6999382f956b6109d3042569659972bca38a63"
dependencies:
+ "@angular-devkit/build-optimizer" "0.0.3"
"@ngtools/json-schema" "1.1.0"
- "@ngtools/webpack" "https://github.com/angular/ngtools-webpack-builds.git#7c33dd4"
+ "@ngtools/webpack" "1.6.0-rc.3"
autoprefixer "^6.5.3"
- chalk "^1.1.3"
+ chalk "^2.0.1"
+ circular-dependency-plugin "^3.0.0"
common-tags "^1.3.1"
+ core-object "^3.1.0"
css-loader "^0.28.1"
cssnano "^3.10.0"
- debug "^2.1.3"
denodeify "^1.2.1"
diff "^3.1.0"
ember-cli-normalize-entity-name "^1.0.0"
ember-cli-string-utils "^1.0.0"
exports-loader "^0.6.3"
- extract-text-webpack-plugin "^2.1.0"
+ extract-text-webpack-plugin "3.0.0"
file-loader "^0.10.0"
- fs-extra "^2.0.0"
+ fs-extra "^4.0.0"
get-caller-file "^1.0.0"
glob "^7.0.3"
- html-webpack-plugin "^2.19.0"
+ heimdalljs "^0.2.4"
+ heimdalljs-logger "^0.1.9"
+ html-webpack-plugin "^2.29.0"
inflection "^1.7.0"
inquirer "^3.0.0"
isbinaryfile "^3.0.0"
istanbul-instrumenter-loader "^2.0.0"
- json-loader "^0.5.4"
+ karma-source-map-support "^1.2.0"
less "^2.7.2"
- less-loader "^4.0.2"
+ less-loader "^4.0.5"
license-webpack-plugin "^0.4.2"
lodash "^4.11.1"
memory-fs "^0.4.1"
minimatch "^3.0.3"
node-modules-path "^1.0.0"
nopt "^4.0.1"
- opn "4.0.2"
+ opn "~5.1.0"
portfinder "~1.0.12"
postcss-loader "^1.3.3"
postcss-url "^5.1.2"
raw-loader "^0.5.1"
resolve "^1.1.7"
- rimraf "^2.5.3"
rsvp "^3.0.17"
- rxjs "^5.0.1"
+ rxjs "^5.4.2"
sass-loader "^6.0.3"
script-loader "^0.7.0"
semver "^5.1.0"
@@ -68,14 +80,14 @@
stylus "^0.54.5"
stylus-loader "^3.0.1"
temp "0.8.3"
- typescript ">=2.0.0 <2.4.0"
+ typescript ">=2.0.0 <2.5.0"
url-loader "^0.5.7"
walk-sync "^0.3.1"
- webpack "3.0.0-rc.1"
- webpack-dev-middleware "^1.10.2"
- webpack-dev-server "~2.4.5"
- webpack-merge "^2.4.0"
- zone.js "^0.8.4"
+ webpack "~3.4.1"
+ webpack-dev-middleware "^1.11.0"
+ webpack-dev-server "~2.5.1"
+ webpack-merge "^4.1.0"
+ zone.js "^0.8.14"
optionalDependencies:
node-sass "^4.3.0"
@@ -166,13 +178,12 @@
version "1.1.0"
resolved "https://registry.yarnpkg.com/@ngtools/json-schema/-/json-schema-1.1.0.tgz#c3a0c544d62392acc2813a42c8a0dc6f58f86922"
-"@ngtools/webpack@https://github.com/angular/ngtools-webpack-builds.git#7c33dd4":
- version "1.4.0-7c33dd4"
- resolved "https://github.com/angular/ngtools-webpack-builds.git#7c33dd4"
+"@ngtools/webpack@1.6.0-rc.3":
+ version "1.6.0-rc.3"
+ resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-1.6.0-rc.3.tgz#1cc0885c5075f66ac322e68eaf256bff172dd134"
dependencies:
- enhanced-resolve "^3.1.0"
loader-utils "^1.0.2"
- magic-string "^0.19.0"
+ magic-string "^0.22.3"
source-map "^0.5.6"
"@types/jasmine@^2.5.52":
@@ -280,14 +291,14 @@ ajv-keywords@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.0.tgz#a296e17f7bfae7c1ce4f7e0de53d29cb32162df0"
-ajv@^4.11.2, ajv@^4.7.0, ajv@^4.9.1:
+ajv@^4.7.0, ajv@^4.9.1:
version "4.11.8"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536"
dependencies:
co "^4.6.0"
json-stable-stringify "^1.0.1"
-ajv@^5.1.5:
+ajv@^5.0.0, ajv@^5.1.5:
version "5.1.5"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.1.5.tgz#8734931b601f00d4feef7c65738d77d1b65d1f68"
dependencies:
@@ -353,6 +364,12 @@ ansi-styles@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
+ansi-styles@^3.1.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88"
+ dependencies:
+ color-convert "^1.9.0"
+
any-promise@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
@@ -451,7 +468,7 @@ array-flatten@1.1.1, array-flatten@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
-array-flatten@2.1.1:
+array-flatten@2.1.1, array-flatten@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.1.tgz#426bb9da84090c1838d812c8150af20a8331e296"
@@ -549,12 +566,18 @@ async@^1.3.0, async@^1.4.0, async@^1.5.2:
version "1.5.2"
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
-async@^2.0.0, async@^2.1.2, async@^2.1.4, async@^2.1.5:
+async@^2.0.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/async/-/async-2.4.0.tgz#4990200f18ea5b837c2cc4f8c031a6985c385611"
dependencies:
lodash "^4.14.0"
+async@^2.1.2, async@^2.1.4, async@^2.1.5, async@^2.4.1:
+ version "2.5.0"
+ resolved "https://registry.yarnpkg.com/async/-/async-2.5.0.tgz#843190fd6b7357a0b9e1c956edddd5ec8462b54d"
+ dependencies:
+ lodash "^4.14.0"
+
async@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/async/-/async-1.0.0.tgz#f8fc04ca3a13784ade9e1641af98578cfbd647a9"
@@ -772,6 +795,17 @@ body-parser@^1.16.1:
raw-body "~2.2.0"
type-is "~1.6.15"
+bonjour@^3.5.0:
+ version "3.5.0"
+ resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5"
+ dependencies:
+ array-flatten "^2.1.0"
+ deep-equal "^1.0.1"
+ dns-equal "^1.0.0"
+ dns-txt "^2.0.2"
+ multicast-dns "^6.0.1"
+ multicast-dns-service-types "^1.1.0"
+
boolbase@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
@@ -899,6 +933,10 @@ buffer-equal-constant-time@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
+buffer-indexof@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.0.tgz#f54f647c4f4e25228baa656a2e57e43d5f270982"
+
buffer-shims@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51"
@@ -971,7 +1009,7 @@ camelcase@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a"
-camelcase@^4.0.0:
+camelcase@^4.0.0, camelcase@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
@@ -1037,6 +1075,14 @@ chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3:
strip-ansi "^3.0.0"
supports-color "^2.0.0"
+chalk@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.0.1.tgz#dbec49436d2ae15f536114e76d14656cdbc0f44d"
+ dependencies:
+ ansi-styles "^3.1.0"
+ escape-string-regexp "^1.0.5"
+ supports-color "^4.0.0"
+
change-case@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/change-case/-/change-case-3.0.0.tgz#6c9c8e35f8790870a82b6b0745be8c3cbef9b081"
@@ -1084,7 +1130,7 @@ character-reference-invalid@^1.0.0:
version "0.0.2"
resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
-chokidar@^1.4.1, chokidar@^1.4.3, chokidar@^1.6.0:
+chokidar@^1.4.1, chokidar@^1.6.0, chokidar@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468"
dependencies:
@@ -1113,6 +1159,10 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
dependencies:
inherits "^2.0.1"
+circular-dependency-plugin@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/circular-dependency-plugin/-/circular-dependency-plugin-3.0.0.tgz#9b68692e35b0e3510998d0164b6ae5011bea5760"
+
circular-json@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.1.tgz#be8b36aefccde8b3ca7aa2d6afc07a37242c0d2d"
@@ -1236,7 +1286,7 @@ collapse-white-space@^1.0.0, collapse-white-space@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.2.tgz#9c463fb9c6d190d2dcae21a356a01bcae9eeef6d"
-color-convert@^1.3.0:
+color-convert@^1.3.0, color-convert@^1.9.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.0.tgz#1accf97dd739b983bf994d56fec8f95853641b7a"
dependencies:
@@ -1502,6 +1552,12 @@ core-js@^2.2.0, core-js@^2.4.0, core-js@^2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.4.1.tgz#4de911e667b0eae9124e34254b53aea6fc618d3e"
+core-object@^3.1.0:
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/core-object/-/core-object-3.1.3.tgz#df399b3311bdb0c909e8aae8929fc3c1c4b25880"
+ dependencies:
+ chalk "^1.1.3"
+
core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
@@ -1594,7 +1650,7 @@ cross-spawn@^4.0.0:
lru-cache "^4.0.1"
which "^1.2.9"
-cross-spawn@^5.1.0:
+cross-spawn@^5.0.1, cross-spawn@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
dependencies:
@@ -1796,7 +1852,7 @@ date-now@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
-debug@*, debug@2, debug@2.6.7, debug@^2.1.1, debug@^2.1.3, debug@^2.2.0, debug@^2.6.3:
+debug@*, debug@2, debug@2.6.7, debug@^2.1.1, debug@^2.2.0, debug@^2.6.3:
version "2.6.7"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.7.tgz#92bad1f6d05bbb6bba22cca88bcd0ec894c2861e"
dependencies:
@@ -1830,6 +1886,10 @@ decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
+deep-equal@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
+
deep-extend@~0.4.0:
version "0.4.2"
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f"
@@ -1860,6 +1920,17 @@ del@^2.0.2, del@^2.2.0:
pinkie-promise "^2.0.0"
rimraf "^2.2.8"
+del@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/del/-/del-3.0.0.tgz#53ecf699ffcbcb39637691ab13baf160819766e5"
+ dependencies:
+ globby "^6.1.0"
+ is-path-cwd "^1.0.0"
+ is-path-in-cwd "^1.0.0"
+ p-map "^1.1.1"
+ pify "^3.0.0"
+ rimraf "^2.2.8"
+
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
@@ -1992,6 +2063,23 @@ directory-encoder@^0.7.2:
handlebars "^1.3.0"
img-stats "^0.5.2"
+dns-equal@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d"
+
+dns-packet@^1.0.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.1.1.tgz#2369d45038af045f3898e6fa56862aed3f40296c"
+ dependencies:
+ ip "^1.1.0"
+ safe-buffer "^5.0.1"
+
+dns-txt@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/dns-txt/-/dns-txt-2.0.2.tgz#b91d806f5d27188e4ab3e7d107d881a1cc4642b6"
+ dependencies:
+ buffer-indexof "^1.0.0"
+
doctrine@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.0.0.tgz#c73d8d2909d22291e1a007a395804da8b665fe63"
@@ -2221,14 +2309,14 @@ engine.io@1.8.3:
engine.io-parser "1.3.2"
ws "1.1.2"
-enhanced-resolve@^3.0.0, enhanced-resolve@^3.1.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.1.0.tgz#9f4b626f577245edcf4b2ad83d86e17f4f421dec"
+enhanced-resolve@^3.4.0:
+ version "3.4.1"
+ resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz#0421e339fd71419b3da13d129b3979040230476e"
dependencies:
graceful-fs "^4.1.2"
memory-fs "^0.4.0"
object-assign "^4.0.1"
- tapable "^0.2.5"
+ tapable "^0.2.7"
ensure-posix-path@^1.0.0:
version "1.0.2"
@@ -2468,6 +2556,18 @@ execa@^0.4.0:
path-key "^1.0.0"
strip-eof "^1.0.0"
+execa@^0.7.0:
+ version "0.7.0"
+ resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777"
+ dependencies:
+ cross-spawn "^5.0.1"
+ get-stream "^3.0.0"
+ is-stream "^1.1.0"
+ npm-run-path "^2.0.0"
+ p-finally "^1.0.0"
+ signal-exit "^3.0.0"
+ strip-eof "^1.0.0"
+
exit-code@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/exit-code/-/exit-code-1.0.2.tgz#ce165811c9f117af6a5f882940b96ae7f9aecc34"
@@ -2570,14 +2670,14 @@ extglob@^0.3.1:
dependencies:
is-extglob "^1.0.0"
-extract-text-webpack-plugin@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/extract-text-webpack-plugin/-/extract-text-webpack-plugin-2.1.0.tgz#69315b885f876dbf96d3819f6a9f1cca7aebf159"
+extract-text-webpack-plugin@3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/extract-text-webpack-plugin/-/extract-text-webpack-plugin-3.0.0.tgz#90caa7907bc449f335005e3ac7532b41b00de612"
dependencies:
- ajv "^4.11.2"
- async "^2.1.2"
- loader-utils "^1.0.2"
- webpack-sources "^0.1.0"
+ async "^2.4.1"
+ loader-utils "^1.1.0"
+ schema-utils "^0.3.0"
+ webpack-sources "^1.0.1"
extsprintf@1.0.2:
version "1.0.2"
@@ -2687,6 +2787,12 @@ find-up@^1.0.0:
path-exists "^2.0.0"
pinkie-promise "^2.0.0"
+find-up@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7"
+ dependencies:
+ locate-path "^2.0.0"
+
findup-sync@~0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-0.3.0.tgz#37930aa5d816b777c03445e1966cc6790a4c0b16"
@@ -2824,13 +2930,21 @@ fs-extra@^0.30.0:
path-is-absolute "^1.0.0"
rimraf "^2.2.8"
-fs-extra@^2.0.0, fs-extra@^2.1.2:
+fs-extra@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-2.1.2.tgz#046c70163cef9aad46b0e4a7fa467fb22d71de35"
dependencies:
graceful-fs "^4.1.2"
jsonfile "^2.1.0"
+fs-extra@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.0.tgz#414fb4ca2d2170ba0014159d3a8aec3303418d9e"
+ dependencies:
+ graceful-fs "^4.1.2"
+ jsonfile "^3.0.0"
+ universalify "^0.1.0"
+
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
@@ -3128,6 +3242,10 @@ has-flag@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa"
+has-flag@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51"
+
has-unicode@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
@@ -3238,6 +3356,19 @@ header-case@^1.0.0:
no-case "^2.2.0"
upper-case "^1.1.3"
+heimdalljs-logger@^0.1.9:
+ version "0.1.9"
+ resolved "https://registry.yarnpkg.com/heimdalljs-logger/-/heimdalljs-logger-0.1.9.tgz#d76ada4e45b7bb6f786fc9c010a68eb2e2faf176"
+ dependencies:
+ debug "^2.2.0"
+ heimdalljs "^0.2.0"
+
+heimdalljs@^0.2.0, heimdalljs@^0.2.4:
+ version "0.2.5"
+ resolved "https://registry.yarnpkg.com/heimdalljs/-/heimdalljs-0.2.5.tgz#6aa54308eee793b642cff9cf94781445f37730ac"
+ dependencies:
+ rsvp "~3.2.1"
+
hmac-drbg@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
@@ -3298,9 +3429,9 @@ html-void-elements@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-1.0.1.tgz#f929bea267a19e3535950502ca12c159f1b559af"
-html-webpack-plugin@^2.19.0:
- version "2.28.0"
- resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-2.28.0.tgz#2e7863b57e5fd48fe263303e2ffc934c3064d009"
+html-webpack-plugin@^2.29.0:
+ version "2.29.0"
+ resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-2.29.0.tgz#e987f421853d3b6938c8c4c8171842e5fd17af23"
dependencies:
bluebird "^3.4.7"
html-minifier "^3.2.3"
@@ -3535,6 +3666,12 @@ inquirer@^3.0.0:
strip-ansi "^3.0.0"
through "^2.3.6"
+internal-ip@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-1.2.0.tgz#ae9fbf93b984878785d50a8de1b356956058cf5c"
+ dependencies:
+ meow "^3.3.0"
+
interpret@^1.0.0:
version "1.0.3"
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.3.tgz#cbc35c62eeee73f19ab7b10a801511401afc0f90"
@@ -3549,6 +3686,10 @@ invert-kv@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
+ip@^1.1.0:
+ version "1.1.5"
+ resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a"
+
ipaddr.js@1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.3.0.tgz#1e03a52fdad83a8bbb2b25cbf4998b4cffcd3dec"
@@ -3779,6 +3920,10 @@ is-word-character@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-word-character/-/is-word-character-1.0.0.tgz#a3a9e5ddad70c5c2ee36f4a9cfc9a53f44535247"
+is-wsl@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
+
isarray@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
@@ -4025,6 +4170,12 @@ jsonfile@^2.1.0:
optionalDependencies:
graceful-fs "^4.1.6"
+jsonfile@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-3.0.1.tgz#a5ecc6f65f53f662c4415c7675a0331d0992ec66"
+ optionalDependencies:
+ graceful-fs "^4.1.6"
+
jsonify@~0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
@@ -4105,6 +4256,12 @@ karma-jasmine@^1.0.2, karma-jasmine@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/karma-jasmine/-/karma-jasmine-1.1.0.tgz#22e4c06bf9a182e5294d1f705e3733811b810acf"
+karma-source-map-support@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/karma-source-map-support/-/karma-source-map-support-1.2.0.tgz#1bf81e7bb4b089627ab352ec4179e117c406a540"
+ dependencies:
+ source-map-support "^0.4.1"
+
karma@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/karma/-/karma-1.7.0.tgz#6f7a1a406446fa2e187ec95398698f4cee476269"
@@ -4211,9 +4368,9 @@ lcid@^1.0.0:
dependencies:
invert-kv "^1.0.0"
-less-loader@^4.0.2:
- version "4.0.3"
- resolved "https://registry.yarnpkg.com/less-loader/-/less-loader-4.0.3.tgz#d1e6462ca2f090c11248455e14b8dda4616d0521"
+less-loader@^4.0.5:
+ version "4.0.5"
+ resolved "https://registry.yarnpkg.com/less-loader/-/less-loader-4.0.5.tgz#ae155a7406cac6acd293d785587fcff0f478c4dd"
dependencies:
clone "^2.1.1"
loader-utils "^1.1.0"
@@ -4279,6 +4436,15 @@ load-json-file@^1.0.0:
pinkie-promise "^2.0.0"
strip-bom "^2.0.0"
+load-json-file@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8"
+ dependencies:
+ graceful-fs "^4.1.2"
+ parse-json "^2.2.0"
+ pify "^2.0.0"
+ strip-bom "^3.0.0"
+
loader-runner@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2"
@@ -4300,6 +4466,13 @@ loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.0:
emojis-list "^2.0.0"
json5 "^0.5.0"
+locate-path@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
+ dependencies:
+ p-locate "^2.0.0"
+ path-exists "^3.0.0"
+
lodash._isnative@~2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/lodash._isnative/-/lodash._isnative-2.4.1.tgz#3ea6404b784a7be836c7b57580e1cdf79b14832c"
@@ -4451,12 +4624,18 @@ macaddress@^0.2.8:
version "0.2.8"
resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12"
-magic-string@^0.19.0, magic-string@^0.19.1:
+magic-string@^0.19.1:
version "0.19.1"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.19.1.tgz#14d768013caf2ec8fdea16a49af82fc377e75201"
dependencies:
vlq "^0.2.1"
+magic-string@^0.22.3:
+ version "0.22.4"
+ resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.22.4.tgz#31039b4e40366395618c1d6cf8193c53917475ff"
+ dependencies:
+ vlq "^0.2.1"
+
make-dir@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.0.0.tgz#97a011751e91dd87cfadef58832ebb04936de978"
@@ -4528,6 +4707,12 @@ media-typer@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
+mem@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76"
+ dependencies:
+ mimic-fn "^1.0.0"
+
memory-fs@^0.4.0, memory-fs@^0.4.1, memory-fs@~0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552"
@@ -4535,7 +4720,7 @@ memory-fs@^0.4.0, memory-fs@^0.4.1, memory-fs@~0.4.1:
errno "^0.1.3"
readable-stream "^2.0.1"
-meow@^3.7.0:
+meow@^3.3.0, meow@^3.7.0:
version "3.7.0"
resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb"
dependencies:
@@ -4680,6 +4865,17 @@ ms@^0.7.1:
version "0.7.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.3.tgz#708155a5e44e33f5fd0fc53e81d0d40a91be1fff"
+multicast-dns-service-types@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901"
+
+multicast-dns@^6.0.1:
+ version "6.1.1"
+ resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.1.1.tgz#6e7de86a570872ab17058adea7160bbeca814dde"
+ dependencies:
+ dns-packet "^1.0.1"
+ thunky "^0.1.0"
+
mute-stream@0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0"
@@ -4730,21 +4926,16 @@ ng-pwa-tools@^0.0.10:
sha1 "^1.1.1"
ts-node "^3.0.2"
-ngo@angular/ngo:
- version "0.0.11"
- resolved "https://codeload.github.com/angular/ngo/tar.gz/09980bf1006a20963a7273467c20d28216035d16"
- dependencies:
- loader-utils "^1.1.0"
- magic-string "^0.19.1"
- source-map "^0.5.6"
- typescript "^2.3.3"
-
no-case@^2.2.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.1.tgz#7aeba1c73a52184265554b7dc03baf720df80081"
dependencies:
lower-case "^1.1.1"
+node-forge@0.6.33:
+ version "0.6.33"
+ resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.6.33.tgz#463811879f573d45155ad6a9f43dc296e8e85ebc"
+
node-gyp@^3.3.1:
version "3.6.1"
resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.6.1.tgz#19561067ff185464aded478212681f47fd578cbc"
@@ -4899,6 +5090,12 @@ npm-run-path@^1.0.0:
dependencies:
path-key "^1.0.0"
+npm-run-path@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
+ dependencies:
+ path-key "^2.0.0"
+
"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0, npmlog@^4.0.2:
version "4.1.0"
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.0.tgz#dc59bee85f64f00ed424efb2af0783df25d1c0b5"
@@ -5022,6 +5219,12 @@ opn@4.0.2:
object-assign "^4.0.1"
pinkie-promise "^2.0.0"
+opn@~5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/opn/-/opn-5.1.0.tgz#72ce2306a17dbea58ff1041853352b4a8fc77519"
+ dependencies:
+ is-wsl "^1.1.0"
+
optimist@0.6.x, optimist@^0.6.1, optimist@~0.6.0, optimist@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
@@ -5079,6 +5282,14 @@ os-locale@^1.4.0:
dependencies:
lcid "^1.0.0"
+os-locale@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2"
+ dependencies:
+ execa "^0.7.0"
+ lcid "^1.0.0"
+ mem "^1.1.0"
+
os-tmpdir@^1.0.0, os-tmpdir@~1.0.0, os-tmpdir@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
@@ -5090,6 +5301,24 @@ osenv@0, osenv@^0.1.0, osenv@^0.1.4:
os-homedir "^1.0.0"
os-tmpdir "^1.0.0"
+p-finally@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
+
+p-limit@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.1.0.tgz#b07ff2d9a5d88bec806035895a2bab66a27988bc"
+
+p-locate@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43"
+ dependencies:
+ p-limit "^1.1.0"
+
+p-map@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.1.1.tgz#05f5e4ae97a068371bc2a5cc86bfbdbc19c4ae7a"
+
package-json@^1.0.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/package-json/-/package-json-1.2.0.tgz#c8ecac094227cdf76a316874ed05e27cc939a0e0"
@@ -5221,6 +5450,10 @@ path-exists@^2.0.0:
dependencies:
pinkie-promise "^2.0.0"
+path-exists@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
+
path-is-absolute@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
@@ -5233,6 +5466,10 @@ path-key@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/path-key/-/path-key-1.0.0.tgz#5d53d578019646c0d68800db4e146e6bdc2ac7af"
+path-key@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
+
path-parse@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1"
@@ -5255,6 +5492,12 @@ path-type@^1.0.0:
pify "^2.0.0"
pinkie-promise "^2.0.0"
+path-type@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73"
+ dependencies:
+ pify "^2.0.0"
+
pbkdf2@^3.0.3:
version "3.0.12"
resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.12.tgz#be36785c5067ea48d806ff923288c5f750b6b8a2"
@@ -5273,6 +5516,10 @@ pify@^2.0.0, pify@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
+pify@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
+
pinkie-promise@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
@@ -5775,6 +6022,13 @@ read-pkg-up@^1.0.1:
find-up "^1.0.0"
read-pkg "^1.0.0"
+read-pkg-up@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be"
+ dependencies:
+ find-up "^2.0.0"
+ read-pkg "^2.0.0"
+
read-pkg@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28"
@@ -5783,6 +6037,14 @@ read-pkg@^1.0.0:
normalize-package-data "^2.3.2"
path-type "^1.0.0"
+read-pkg@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8"
+ dependencies:
+ load-json-file "^2.0.0"
+ normalize-package-data "^2.3.2"
+ path-type "^2.0.0"
+
readable-stream@1.0, readable-stream@~1.0.2, readable-stream@~1.0.24, readable-stream@~1.0.26:
version "1.0.34"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c"
@@ -6138,7 +6400,7 @@ right-align@^0.1.1:
dependencies:
align-text "^0.1.1"
-rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.5.2, rimraf@^2.5.3, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1:
+rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.1.tgz#c2338ec643df7a1b7fe5c54fa86f57428a55f33d"
dependencies:
@@ -6171,6 +6433,10 @@ rsvp@^3.0.17, rsvp@^3.0.18, rsvp@^3.1.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.5.0.tgz#a62c573a4ae4e1dfd0697ebc6242e79c681eaa34"
+rsvp@~3.2.1:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.2.1.tgz#07cb4a5df25add9e826ebc67dcc9fd89db27d84a"
+
run-async@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/run-async/-/run-async-0.1.0.tgz#c8ad4a5e110661e402a7d21b530e009f25f8e389"
@@ -6195,12 +6461,18 @@ rx@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782"
-rxjs@^5.0.1, rxjs@^5.2.0:
+rxjs@^5.2.0:
version "5.4.0"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.4.0.tgz#a7db14ab157f9d7aac6a56e655e7a3860d39bf26"
dependencies:
symbol-observable "^1.0.1"
+rxjs@^5.4.2:
+ version "5.4.2"
+ resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.4.2.tgz#2a3236fcbf03df57bae06fd6972fd99e5c08fcf7"
+ dependencies:
+ symbol-observable "^1.0.1"
+
safe-buffer@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7"
@@ -6263,6 +6535,12 @@ scandirectory@^2.5.0:
safefs "^3.1.2"
taskgroup "^4.0.5"
+schema-utils@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.3.0.tgz#f5877222ce3e931edae039f17eb3716e7137f8cf"
+ dependencies:
+ ajv "^5.0.0"
+
script-loader@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/script-loader/-/script-loader-0.7.0.tgz#685dc7e7069e0dee7a92674f0ebc5b0f55baa5ec"
@@ -6299,6 +6577,12 @@ selenium-webdriver@^2.53.2:
ws "^1.0.1"
xml2js "0.4.4"
+selfsigned@^1.9.1:
+ version "1.9.1"
+ resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.9.1.tgz#cdda4492d70d486570f87c65546023558e1dfa5a"
+ dependencies:
+ node-forge "0.6.33"
+
semver-diff@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36"
@@ -6527,7 +6811,7 @@ sort-keys@^1.0.0:
dependencies:
is-plain-obj "^1.0.0"
-source-list-map@^0.1.7, source-list-map@~0.1.7:
+source-list-map@^0.1.7:
version "0.1.8"
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-0.1.8.tgz#c550b2ab5427f6b3f21f5afead88c4f5587b2106"
@@ -6543,7 +6827,7 @@ source-map-loader@^0.2.0:
loader-utils "~0.2.2"
source-map "~0.1.33"
-source-map-support@^0.4.0, source-map-support@^0.4.15, source-map-support@^0.4.2, source-map-support@~0.4.0:
+source-map-support@^0.4.0, source-map-support@^0.4.1, source-map-support@^0.4.15, source-map-support@^0.4.2, source-map-support@~0.4.0:
version "0.4.15"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.15.tgz#03202df65c06d2bd8c7ec2362a193056fef8d3b1"
dependencies:
@@ -6848,12 +7132,18 @@ supports-color@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
-supports-color@^3.1.0, supports-color@^3.1.1, supports-color@^3.1.2, supports-color@^3.2.3:
+supports-color@^3.1.1, supports-color@^3.1.2, supports-color@^3.2.3:
version "3.2.3"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6"
dependencies:
has-flag "^1.0.0"
+supports-color@^4.0.0, supports-color@^4.2.1:
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.2.1.tgz#65a4bb2631e90e02420dba5554c375a4754bb836"
+ dependencies:
+ has-flag "^2.0.0"
+
svgo@^0.7.0:
version "0.7.2"
resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5"
@@ -6892,9 +7182,9 @@ table@^3.7.8:
slice-ansi "0.0.4"
string-width "^2.0.0"
-tapable@^0.2.5, tapable@~0.2.5:
- version "0.2.6"
- resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.6.tgz#206be8e188860b514425375e6f1ae89bfb01fd8d"
+tapable@^0.2.7:
+ version "0.2.7"
+ resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.7.tgz#e46c0daacbb2b8a98b9b0cea0f4052105817ed5c"
tar-pack@^3.4.0:
version "3.4.0"
@@ -6979,6 +7269,10 @@ through2@2.0.1:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
+thunky@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/thunky/-/thunky-0.1.0.tgz#bf30146824e2b6e67b0f2d7a4ac8beb26908684e"
+
timed-out@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-2.0.0.tgz#f38b0ae81d3747d628001f41dafc652ace671c0a"
@@ -7210,7 +7504,7 @@ typedarray@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
-typescript@2.3.2, "typescript@>=2.0.0 <2.4.0":
+typescript@2.3.2, "typescript@>=2.0.0 <2.5.0":
version "2.3.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.3.2.tgz#f0f045e196f69a72f06b25fd3bd39d01c3ce9984"
@@ -7218,7 +7512,7 @@ typescript@^2.3.3, typescript@^2.3.4:
version "2.4.1"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.4.1.tgz#c3ccb16ddaa0b2314de031e7e6fee89e5ba346bc"
-uglify-js@^2.6, uglify-js@^2.8.27, uglify-js@~2.8.22:
+uglify-js@^2.6, uglify-js@~2.8.22:
version "2.8.28"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.28.tgz#e335032df9bb20dcb918f164589d5af47f38834a"
dependencies:
@@ -7227,6 +7521,15 @@ uglify-js@^2.6, uglify-js@^2.8.27, uglify-js@~2.8.22:
optionalDependencies:
uglify-to-browserify "~1.0.0"
+uglify-js@^2.8.29:
+ version "2.8.29"
+ resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd"
+ dependencies:
+ source-map "~0.5.1"
+ yargs "~3.10.0"
+ optionalDependencies:
+ uglify-to-browserify "~1.0.0"
+
uglify-js@^3.0.15:
version "3.0.15"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.0.15.tgz#aacb323a846b234602270dead8a32441a8806f42"
@@ -7246,6 +7549,14 @@ uglify-to-browserify@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7"
+uglifyjs-webpack-plugin@^0.4.6:
+ version "0.4.6"
+ resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-0.4.6.tgz#b951f4abb6bd617e66f63eb891498e391763e309"
+ dependencies:
+ source-map "^0.5.6"
+ uglify-js "^2.8.29"
+ webpack-sources "^1.0.1"
+
uid-number@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81"
@@ -7380,6 +7691,10 @@ universal-analytics@^0.3.9:
request "2.x"
underscore "1.x"
+universalify@^0.1.0:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.1.tgz#fa71badd4437af4c148841e3b3b165f9e9e590b7"
+
unpipe@1.0.0, unpipe@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
@@ -7609,12 +7924,12 @@ walkdir@^0.0.11:
version "0.0.11"
resolved "https://registry.yarnpkg.com/walkdir/-/walkdir-0.0.11.tgz#a16d025eb931bd03b52f308caed0f40fcebe9532"
-watchpack@^1.3.1:
- version "1.3.1"
- resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.3.1.tgz#7d8693907b28ce6013e7f3610aa2a1acf07dad87"
+watchpack@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.4.0.tgz#4a1472bcbb952bd0a9bb4036801f954dfb39faac"
dependencies:
async "^2.1.2"
- chokidar "^1.4.3"
+ chokidar "^1.7.0"
graceful-fs "^4.1.2"
watchr@^3.0.1:
@@ -7670,50 +7985,47 @@ webidl-conversions@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.1.tgz#8015a17ab83e7e1b311638486ace81da6ce206a0"
-webpack-dev-middleware@^1.10.2:
- version "1.10.2"
- resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.10.2.tgz#2e252ce1dfb020dbda1ccb37df26f30ab014dbd1"
+webpack-dev-middleware@^1.11.0:
+ version "1.11.0"
+ resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.11.0.tgz#09691d0973a30ad1f82ac73a12e2087f0a4754f9"
dependencies:
memory-fs "~0.4.1"
mime "^1.3.4"
path-is-absolute "^1.0.0"
range-parser "^1.0.3"
-webpack-dev-server@~2.4.5:
- version "2.4.5"
- resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-2.4.5.tgz#31384ce81136be1080b4b4cde0eb9b90e54ee6cf"
+webpack-dev-server@~2.5.1:
+ version "2.5.1"
+ resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-2.5.1.tgz#a02e726a87bb603db5d71abb7d6d2649bf10c769"
dependencies:
ansi-html "0.0.7"
+ bonjour "^3.5.0"
chokidar "^1.6.0"
compression "^1.5.2"
connect-history-api-fallback "^1.3.0"
+ del "^3.0.0"
express "^4.13.3"
html-entities "^1.2.0"
http-proxy-middleware "~0.17.4"
+ internal-ip "^1.2.0"
opn "4.0.2"
portfinder "^1.0.9"
+ selfsigned "^1.9.1"
serve-index "^1.7.2"
sockjs "0.3.18"
sockjs-client "1.1.2"
spdy "^3.4.1"
strip-ansi "^3.0.0"
supports-color "^3.1.1"
- webpack-dev-middleware "^1.10.2"
+ webpack-dev-middleware "^1.11.0"
yargs "^6.0.0"
-webpack-merge@^2.4.0:
- version "2.6.1"
- resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-2.6.1.tgz#f1d801d2c5d39f83ffec9f119240b3e3be994a1c"
+webpack-merge@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.1.0.tgz#6ad72223b3e0b837e531e4597c199f909361511e"
dependencies:
lodash "^4.17.4"
-webpack-sources@^0.1.0:
- version "0.1.5"
- resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-0.1.5.tgz#aa1f3abf0f0d74db7111c40e500b84f966640750"
- dependencies:
- source-list-map "~0.1.7"
- source-map "~0.5.3"
-
webpack-sources@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.0.1.tgz#c7356436a4d13123be2e2426a05d1dad9cbe65cf"
@@ -7721,16 +8033,16 @@ webpack-sources@^1.0.1:
source-list-map "^2.0.0"
source-map "~0.5.3"
-webpack@3.0.0-rc.1:
- version "3.0.0-rc.1"
- resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.0.0-rc.1.tgz#41319cda8040f53177ce999a5d8a333e4f264d75"
+webpack@~3.4.1:
+ version "3.4.1"
+ resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.4.1.tgz#4c3f4f3fb318155a4db0cb6a36ff05c5697418f4"
dependencies:
acorn "^5.0.0"
acorn-dynamic-import "^2.0.0"
ajv "^5.1.5"
ajv-keywords "^2.0.0"
async "^2.1.2"
- enhanced-resolve "^3.0.0"
+ enhanced-resolve "^3.4.0"
escope "^3.6.0"
interpret "^1.0.0"
json-loader "^0.5.4"
@@ -7741,12 +8053,12 @@ webpack@3.0.0-rc.1:
mkdirp "~0.5.0"
node-libs-browser "^2.0.0"
source-map "^0.5.3"
- supports-color "^3.1.0"
- tapable "~0.2.5"
- uglify-js "^2.8.27"
- watchpack "^1.3.1"
+ supports-color "^4.2.1"
+ tapable "^0.2.7"
+ uglifyjs-webpack-plugin "^0.4.6"
+ watchpack "^1.4.0"
webpack-sources "^1.0.1"
- yargs "^6.0.0"
+ yargs "^8.0.2"
websocket-driver@>=0.5.1:
version "0.6.5"
@@ -7790,6 +8102,10 @@ which-module@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f"
+which-module@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
+
which@1, which@^1.2.1, which@^1.2.8, which@^1.2.9:
version "1.2.14"
resolved "https://registry.yarnpkg.com/which/-/which-1.2.14.tgz#9a87c4378f03e827cecaf1acdf56c736c01c14e5"
@@ -7984,6 +8300,12 @@ yargs-parser@^5.0.0:
dependencies:
camelcase "^3.0.0"
+yargs-parser@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-7.0.0.tgz#8d0ac42f16ea55debd332caf4c4038b3e3f5dfd9"
+ dependencies:
+ camelcase "^4.1.0"
+
yargs@3.32.0, yargs@^3.32.0:
version "3.32.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.32.0.tgz#03088e9ebf9e756b69751611d2a5ef591482c995"
@@ -8032,6 +8354,24 @@ yargs@^7.0.2:
y18n "^3.2.1"
yargs-parser "^5.0.0"
+yargs@^8.0.2:
+ version "8.0.2"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-8.0.2.tgz#6299a9055b1cefc969ff7e79c1d918dceb22c360"
+ dependencies:
+ camelcase "^4.1.0"
+ cliui "^3.2.0"
+ decamelize "^1.1.1"
+ get-caller-file "^1.0.1"
+ os-locale "^2.0.0"
+ read-pkg-up "^2.0.0"
+ require-directory "^2.1.1"
+ require-main-filename "^1.0.1"
+ set-blocking "^2.0.0"
+ string-width "^2.0.0"
+ which-module "^2.0.0"
+ y18n "^3.2.1"
+ yargs-parser "^7.0.0"
+
yargs@~3.10.0:
version "3.10.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1"
@@ -8068,6 +8408,10 @@ zip-stream@~0.6.0:
lodash "~3.10.1"
readable-stream "~1.0.26"
-zone.js@^0.8.12, zone.js@^0.8.4:
+zone.js@^0.8.12:
version "0.8.12"
resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.8.12.tgz#86ff5053c98aec291a0bf4bbac501d694a05cfbb"
+
+zone.js@^0.8.14:
+ version "0.8.14"
+ resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.8.14.tgz#0c4db24b178232274ccb43f78c99db7f3642b6cf"
diff --git a/docs/TRIAGE_AND_LABELS.md b/docs/TRIAGE_AND_LABELS.md
index 86736ef745..50da21211b 100644
--- a/docs/TRIAGE_AND_LABELS.md
+++ b/docs/TRIAGE_AND_LABELS.md
@@ -50,7 +50,7 @@ What kind of problem is this?
* `type: RFC / discussion / question`
* `type: bug`
-* `type: chore`
+* `type: docs`
* `type: feature`
* `type: performance`
* `type: refactor`
@@ -108,16 +108,31 @@ closing or reviewing PRs is a top priority ahead of other ongoing work.
Every triaged PR must have a `pr_action` label assigned to it and an assignee:
-* `pr_action: review` - work is complete and comment is needed from the assignee.
-* `pr_action: cleanup` - more work is needed from the current assignee.
-* `pr_action: discuss` - discussion is needed, to be led by the current assignee.
-* `pr_action: merge` - the PR should be merged. Add this to a PR when you would like to
- trigger automatic merging following a successful build. This is described in [COMMITTER.md](COMMITTER.md).
+* `PR action: review` - work is complete and comment is needed from the assignee.
+* `PR action: cleanup` - more work is needed from the current assignee.
+* `PR action: discuss` - discussion is needed, to be led by the current assignee.
+* `PR action: merge` - the PR is ready to be merged by the caretaker.
In addition, PRs can have the following states:
-* `pr_state: WIP` - PR is experimental or rapidly changing. Not ready for review or triage.
-* `pr_state: blocked` - PR is blocked on an issue or other PR. Not ready for review or triage.
+* `PR state: WIP` - PR is experimental or rapidly changing. Not ready for review or triage.
+* `PR state: blocked` - PR is blocked on an issue or other PR. Not ready for review or triage.
+
+
+## PR Target
+
+In our git workflow, we merge changes either to the `master` branch, the most recent patch branch (e.g. `4.3.x`), or to both.
+
+The decision about the target must be done by the PR author and/or reviewer. This decision is then honored when the PR is being merged.
+
+To communicate the target we use the following labels:
+
+* `PR target: master-only`
+* `PR target: patch-only`
+* `PR target: master & patch`
+* `PR target: TBD` - the target is yet to be determined
+
+If a PR is missing the "PR target" label, or if the label is set to "TBD" when the PR is sent to the caretaker, the caretaker should reject the PR and request the appropriate target label to be applied before the PR is merged.
## PR Approvals
diff --git a/modules/e2e_util/perf_util.ts b/modules/e2e_util/perf_util.ts
index f49dd92475..d5673ff587 100644
--- a/modules/e2e_util/perf_util.ts
+++ b/modules/e2e_util/perf_util.ts
@@ -11,7 +11,7 @@ const yargs = require('yargs');
const nodeUuid = require('node-uuid');
import * as fs from 'fs-extra';
-import {SeleniumWebDriverAdapter, Options, JsonFileReporter, Validator, RegressionSlopeValidator, ConsoleReporter, SizeValidator, MultiReporter, MultiMetric, Runner, Provider} from '@angular/benchpress';
+import {SeleniumWebDriverAdapter, Options, JsonFileReporter, Validator, RegressionSlopeValidator, ConsoleReporter, SizeValidator, MultiReporter, MultiMetric, Runner, StaticProvider} from '@angular/benchpress';
import {readCommandLine as readE2eCommandLine, openBrowser} from './e2e_util';
let cmdArgs: {'sample-size': number, 'force-gc': boolean, 'dryrun': boolean, 'bundles': boolean};
@@ -59,7 +59,7 @@ function createBenchpressRunner(): Runner {
}
const resultsFolder = './dist/benchmark_results';
fs.ensureDirSync(resultsFolder);
- const providers: Provider[] = [
+ const providers: StaticProvider[] = [
SeleniumWebDriverAdapter.PROTRACTOR_PROVIDERS,
{provide: Options.FORCE_GC, useValue: cmdArgs['force-gc']},
{provide: Options.DEFAULT_DESCRIPTION, useValue: {'runId': runId}}, JsonFileReporter.PROVIDERS,
diff --git a/modules/playground/README.md b/modules/playground/README.md
index 0f8e32d516..213a5faafb 100644
--- a/modules/playground/README.md
+++ b/modules/playground/README.md
@@ -1,6 +1,8 @@
# How to run the examples locally
+```
$ cp -r ./modules/playground ./dist/all/
$ ./node_modules/.bin/tsc -p modules --emitDecoratorMetadata -w
$ gulp serve
$ open http://localhost:8000/all/playground/src/hello_world/index.html?bundles=false
+```
\ No newline at end of file
diff --git a/package.json b/package.json
index f5bbab4e8b..41e326ee97 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "angular-srcs",
- "version": "5.0.0-beta.1",
+ "version": "5.0.0-beta.2",
"private": true,
"branchPattern": "2.0.*",
"description": "Angular - a web framework for modern web apps",
diff --git a/packages/animations/browser/src/dsl/animation_ast.ts b/packages/animations/browser/src/dsl/animation_ast.ts
index 54e7ffec86..0a8b1309cd 100644
--- a/packages/animations/browser/src/dsl/animation_ast.ts
+++ b/packages/animations/browser/src/dsl/animation_ast.ts
@@ -82,6 +82,7 @@ export class AnimateAst extends Ast {
export class StyleAst extends Ast {
public isEmptyStep = false;
+ public containsDynamicStyles = false;
constructor(
public styles: (ɵStyleData|string)[], public easing: string|null,
diff --git a/packages/animations/browser/src/dsl/animation_ast_builder.ts b/packages/animations/browser/src/dsl/animation_ast_builder.ts
index d373ab145c..3fc8d50d65 100644
--- a/packages/animations/browser/src/dsl/animation_ast_builder.ts
+++ b/packages/animations/browser/src/dsl/animation_ast_builder.ts
@@ -8,7 +8,7 @@
import {AUTO_STYLE, AnimateTimings, AnimationAnimateChildMetadata, AnimationAnimateMetadata, AnimationAnimateRefMetadata, AnimationGroupMetadata, AnimationKeyframesSequenceMetadata, AnimationMetadata, AnimationMetadataType, AnimationOptions, AnimationQueryMetadata, AnimationQueryOptions, AnimationReferenceMetadata, AnimationSequenceMetadata, AnimationStaggerMetadata, AnimationStateMetadata, AnimationStyleMetadata, AnimationTransitionMetadata, AnimationTriggerMetadata, style, ɵStyleData} from '@angular/animations';
import {getOrSetAsInMap} from '../render/shared';
-import {ENTER_SELECTOR, LEAVE_SELECTOR, NG_ANIMATING_SELECTOR, NG_TRIGGER_SELECTOR, copyObj, normalizeAnimationEntry, resolveTiming, validateStyleParams} from '../util';
+import {ENTER_SELECTOR, LEAVE_SELECTOR, NG_ANIMATING_SELECTOR, NG_TRIGGER_SELECTOR, SUBSTITUTION_EXPR_START, copyObj, extractStyleParams, iteratorToArray, normalizeAnimationEntry, resolveTiming, validateStyleParams} from '../util';
import {AnimateAst, AnimateChildAst, AnimateRefAst, Ast, DynamicTimingAst, GroupAst, KeyframesAst, QueryAst, ReferenceAst, SequenceAst, StaggerAst, StateAst, StyleAst, TimingAst, TransitionAst, TriggerAst} from './animation_ast';
import {AnimationDslVisitor, visitAnimationNode} from './animation_dsl_visitor';
@@ -112,7 +112,35 @@ export class AnimationAstBuilderVisitor implements AnimationDslVisitor {
}
visitState(metadata: AnimationStateMetadata, context: AnimationAstBuilderContext): StateAst {
- return new StateAst(metadata.name, this.visitStyle(metadata.styles, context));
+ const styleAst = this.visitStyle(metadata.styles, context);
+ const astParams = (metadata.options && metadata.options.params) || null;
+ if (styleAst.containsDynamicStyles) {
+ const missingSubs = new Set
();
+ const params = astParams || {};
+ styleAst.styles.forEach(value => {
+ if (isObject(value)) {
+ const stylesObj = value as any;
+ Object.keys(stylesObj).forEach(prop => {
+ extractStyleParams(stylesObj[prop]).forEach(sub => {
+ if (!params.hasOwnProperty(sub)) {
+ missingSubs.add(sub);
+ }
+ });
+ });
+ }
+ });
+ if (missingSubs.size) {
+ const missingSubsArr = iteratorToArray(missingSubs.values());
+ context.errors.push(
+ `state("${metadata.name}", ...) must define default values for all the following style substitutions: ${missingSubsArr.join(', ')}`);
+ }
+ }
+
+ const stateAst = new StateAst(metadata.name, styleAst);
+ if (astParams) {
+ stateAst.options = {params: astParams};
+ }
+ return stateAst;
}
visitTransition(metadata: AnimationTransitionMetadata, context: AnimationAstBuilderContext):
@@ -201,11 +229,12 @@ export class AnimationAstBuilderVisitor implements AnimationDslVisitor {
} else {
styles.push(styleTuple as ɵStyleData);
}
- })
+ });
} else {
styles.push(metadata.styles);
}
+ let containsDynamicStyles = false;
let collectedEasing: string|null = null;
styles.forEach(styleData => {
if (isObject(styleData)) {
@@ -215,9 +244,21 @@ export class AnimationAstBuilderVisitor implements AnimationDslVisitor {
collectedEasing = easing as string;
delete styleMap['easing'];
}
+ if (!containsDynamicStyles) {
+ for (let prop in styleMap) {
+ const value = styleMap[prop];
+ if (value.toString().indexOf(SUBSTITUTION_EXPR_START) >= 0) {
+ containsDynamicStyles = true;
+ break;
+ }
+ }
+ }
}
});
- return new StyleAst(styles, collectedEasing, metadata.offset);
+
+ const ast = new StyleAst(styles, collectedEasing, metadata.offset);
+ ast.containsDynamicStyles = containsDynamicStyles;
+ return ast;
}
private _validateStyleAst(ast: StyleAst, context: AnimationAstBuilderContext): void {
diff --git a/packages/animations/browser/src/dsl/animation_transition_factory.ts b/packages/animations/browser/src/dsl/animation_transition_factory.ts
index 390f77d32f..4eea313a8b 100644
--- a/packages/animations/browser/src/dsl/animation_transition_factory.ts
+++ b/packages/animations/browser/src/dsl/animation_transition_factory.ts
@@ -9,38 +9,51 @@ import {AnimationOptions, ɵStyleData} from '@angular/animations';
import {AnimationDriver} from '../render/animation_driver';
import {getOrSetAsInMap} from '../render/shared';
-import {iteratorToArray, mergeAnimationOptions} from '../util';
+import {copyObj, interpolateParams, iteratorToArray, mergeAnimationOptions} from '../util';
-import {TransitionAst} from './animation_ast';
+import {StyleAst, TransitionAst} from './animation_ast';
import {buildAnimationTimelines} from './animation_timeline_builder';
import {TransitionMatcherFn} from './animation_transition_expr';
import {AnimationTransitionInstruction, createTransitionInstruction} from './animation_transition_instruction';
import {ElementInstructionMap} from './element_instruction_map';
+const EMPTY_OBJECT = {};
+
export class AnimationTransitionFactory {
constructor(
private _triggerName: string, public ast: TransitionAst,
- private _stateStyles: {[stateName: string]: ɵStyleData}) {}
+ private _stateStyles: {[stateName: string]: AnimationStateStyles}) {}
match(currentState: any, nextState: any): boolean {
return oneOrMoreTransitionsMatch(this.ast.matchers, currentState, nextState);
}
+ buildStyles(stateName: string, params: {[key: string]: any}, errors: any[]) {
+ const backupStateStyler = this._stateStyles['*'];
+ const stateStyler = this._stateStyles[stateName];
+ const backupStyles = backupStateStyler ? backupStateStyler.buildStyles(params, errors) : {};
+ return stateStyler ? stateStyler.buildStyles(params, errors) : backupStyles;
+ }
+
build(
driver: AnimationDriver, element: any, currentState: any, nextState: any,
- options?: AnimationOptions,
+ currentOptions?: AnimationOptions, nextOptions?: AnimationOptions,
subInstructions?: ElementInstructionMap): AnimationTransitionInstruction {
- const animationOptions = mergeAnimationOptions(this.ast.options || {}, options || {});
+ const errors: any[] = [];
+
+ const transitionAnimationParams = this.ast.options && this.ast.options.params || EMPTY_OBJECT;
+ const currentAnimationParams = currentOptions && currentOptions.params || EMPTY_OBJECT;
+ const currentStateStyles = this.buildStyles(currentState, currentAnimationParams, errors);
+ const nextAnimationParams = nextOptions && nextOptions.params || EMPTY_OBJECT;
+ const nextStateStyles = this.buildStyles(nextState, nextAnimationParams, errors);
- const backupStateStyles = this._stateStyles['*'] || {};
- const currentStateStyles = this._stateStyles[currentState] || backupStateStyles;
- const nextStateStyles = this._stateStyles[nextState] || backupStateStyles;
const queriedElements = new Set();
const preStyleMap = new Map();
const postStyleMap = new Map();
const isRemoval = nextState === 'void';
- const errors: any[] = [];
+ const animationOptions = {params: {...transitionAnimationParams, ...nextAnimationParams}};
+
const timelines = buildAnimationTimelines(
driver, element, this.ast.animation, currentStateStyles, nextStateStyles, animationOptions,
subInstructions, errors);
@@ -75,3 +88,31 @@ function oneOrMoreTransitionsMatch(
matchFns: TransitionMatcherFn[], currentState: any, nextState: any): boolean {
return matchFns.some(fn => fn(currentState, nextState));
}
+
+export class AnimationStateStyles {
+ constructor(private styles: StyleAst, private defaultParams: {[key: string]: any}) {}
+
+ buildStyles(params: {[key: string]: any}, errors: string[]): ɵStyleData {
+ const finalStyles: ɵStyleData = {};
+ const combinedParams = copyObj(this.defaultParams);
+ Object.keys(params).forEach(key => {
+ const value = params[key];
+ if (value != null) {
+ combinedParams[key] = value;
+ }
+ });
+ this.styles.styles.forEach(value => {
+ if (typeof value !== 'string') {
+ const styleObj = value as any;
+ Object.keys(styleObj).forEach(prop => {
+ let val = styleObj[prop];
+ if (val.length > 1) {
+ val = interpolateParams(val, combinedParams, errors);
+ }
+ finalStyles[prop] = val;
+ });
+ }
+ });
+ return finalStyles;
+ }
+}
diff --git a/packages/animations/browser/src/dsl/animation_trigger.ts b/packages/animations/browser/src/dsl/animation_trigger.ts
index 19cfdeaad5..9d820e25c2 100644
--- a/packages/animations/browser/src/dsl/animation_trigger.ts
+++ b/packages/animations/browser/src/dsl/animation_trigger.ts
@@ -7,10 +7,11 @@
*/
import {ɵStyleData} from '@angular/animations';
-import {copyStyles} from '../util';
+import {copyStyles, interpolateParams} from '../util';
+
+import {SequenceAst, StyleAst, TransitionAst, TriggerAst} from './animation_ast';
+import {AnimationStateStyles, AnimationTransitionFactory} from './animation_transition_factory';
-import {SequenceAst, TransitionAst, TriggerAst} from './animation_ast';
-import {AnimationTransitionFactory} from './animation_transition_factory';
/**
* @experimental Animation support is experimental.
@@ -25,16 +26,12 @@ export function buildTrigger(name: string, ast: TriggerAst): AnimationTrigger {
export class AnimationTrigger {
public transitionFactories: AnimationTransitionFactory[] = [];
public fallbackTransition: AnimationTransitionFactory;
- public states: {[stateName: string]: ɵStyleData} = {};
+ public states: {[stateName: string]: AnimationStateStyles} = {};
constructor(public name: string, public ast: TriggerAst) {
ast.states.forEach(ast => {
- const obj = this.states[ast.name] = {};
- ast.style.styles.forEach(styleTuple => {
- if (typeof styleTuple == 'object') {
- copyStyles(styleTuple as ɵStyleData, false, obj);
- }
- });
+ const defaultParams = (ast.options && ast.options.params) || {};
+ this.states[ast.name] = new AnimationStateStyles(ast.style, defaultParams);
});
balanceProperties(this.states, 'true', '1');
@@ -53,10 +50,15 @@ export class AnimationTrigger {
const entry = this.transitionFactories.find(f => f.match(currentState, nextState));
return entry || null;
}
+
+ matchStyles(currentState: any, params: {[key: string]: any}, errors: any[]): ɵStyleData {
+ return this.fallbackTransition.buildStyles(currentState, params, errors);
+ }
}
function createFallbackTransition(
- triggerName: string, states: {[stateName: string]: ɵStyleData}): AnimationTransitionFactory {
+ triggerName: string,
+ states: {[stateName: string]: AnimationStateStyles}): AnimationTransitionFactory {
const matchers = [(fromState: any, toState: any) => true];
const animation = new SequenceAst([]);
const transition = new TransitionAst(matchers, animation);
diff --git a/packages/animations/browser/src/render/animation_engine_next.ts b/packages/animations/browser/src/render/animation_engine_next.ts
index 4aa25fc0db..d7d598cbe1 100644
--- a/packages/animations/browser/src/render/animation_engine_next.ts
+++ b/packages/animations/browser/src/render/animation_engine_next.ts
@@ -29,8 +29,8 @@ export class AnimationEngine {
this._transitionEngine = new TransitionAnimationEngine(driver, normalizer);
this._timelineEngine = new TimelineAnimationEngine(driver, normalizer);
- this._transitionEngine.onRemovalComplete =
- (element: any, context: any) => { this.onRemovalComplete(element, context); }
+ this._transitionEngine.onRemovalComplete = (element: any, context: any) =>
+ this.onRemovalComplete(element, context);
}
registerTrigger(
diff --git a/packages/animations/browser/src/render/transition_animation_engine.ts b/packages/animations/browser/src/render/transition_animation_engine.ts
index 1f41927a84..721f1bd75b 100644
--- a/packages/animations/browser/src/render/transition_animation_engine.ts
+++ b/packages/animations/browser/src/render/transition_animation_engine.ts
@@ -66,6 +66,8 @@ export class StateValue {
public value: string;
public options: AnimationOptions;
+ get params(): {[key: string]: any} { return this.options.params as{[key: string]: any}; }
+
constructor(input: any) {
const isObj = input && input.hasOwnProperty('value');
const value = isObj ? input['value'] : input;
@@ -213,7 +215,24 @@ export class AnimationTransitionNamespace {
// The removal arc here is special cased because the same element is triggered
// twice in the event that it contains animations on the outer/inner portions
// of the host container
- if (!isRemoval && fromState.value === toState.value) return;
+ if (!isRemoval && fromState.value === toState.value) {
+ // this means that despite the value not changing, some inner params
+ // have changed which means that the animation final styles need to be applied
+ if (!objEquals(fromState.params, toState.params)) {
+ const errors: any[] = [];
+ const fromStyles = trigger.matchStyles(fromState.value, fromState.params, errors);
+ const toStyles = trigger.matchStyles(toState.value, toState.params, errors);
+ if (errors.length) {
+ this._engine.reportError(errors);
+ } else {
+ this._engine.afterFlush(() => {
+ eraseStyles(element, fromStyles);
+ setStyles(element, toStyles);
+ });
+ }
+ }
+ return;
+ }
const playersOnElement: TransitionAnimationPlayer[] =
getOrSetAsInMap(this._engine.playersByElement, element, []);
@@ -490,6 +509,7 @@ export class TransitionAnimationEngine {
// this method is designed to be overridden by the code that uses this engine
public onRemovalComplete = (element: any, context: any) => {};
+ /** @internal */
_onRemovalComplete(element: any, context: any) { this.onRemovalComplete(element, context); }
constructor(public driver: AnimationDriver, private _normalizer: AnimationStyleNormalizer) {}
@@ -663,7 +683,7 @@ export class TransitionAnimationEngine {
private _buildInstruction(entry: QueueInstruction, subTimelines: ElementInstructionMap) {
return entry.transition.build(
this.driver, entry.element, entry.fromState.value, entry.toState.value,
- entry.toState.options, subTimelines);
+ entry.fromState.options, entry.toState.options, subTimelines);
}
destroyInnerAnimations(containerElement: any) {
@@ -780,6 +800,11 @@ export class TransitionAnimationEngine {
}
}
+ reportError(errors: string[]) {
+ throw new Error(
+ `Unable to process animations due to the following failed trigger transitions\n ${errors.join("\n")}`);
+ }
+
private _flushAnimations(cleanupFns: Function[], microtaskId: number):
TransitionAnimationPlayer[] {
const subTimelines = new ElementInstructionMap();
@@ -900,14 +925,14 @@ export class TransitionAnimationEngine {
}
if (erroneousTransitions.length) {
- let msg = `Unable to process animations due to the following failed trigger transitions\n`;
+ const errors: string[] = [];
erroneousTransitions.forEach(instruction => {
- msg += `@${instruction.triggerName} has failed due to:\n`;
- instruction.errors !.forEach(error => { msg += `- ${error}\n`; });
+ errors.push(`@${instruction.triggerName} has failed due to:\n`);
+ instruction.errors !.forEach(error => errors.push(`- ${error}\n`));
});
allPlayers.forEach(player => player.destroy());
- throw new Error(msg);
+ this.reportError(errors);
}
// these can only be detected here since we have a map of all the elements
@@ -1168,8 +1193,17 @@ export class TransitionAnimationEngine {
if (details && details.removedBeforeQueried) return new NoopAnimationPlayer();
const isQueriedElement = element !== rootElement;
- const previousPlayers = flattenGroupPlayers(
- (allPreviousPlayersMap.get(element) || EMPTY_PLAYER_ARRAY).map(p => p.getRealPlayer()));
+ const previousPlayers =
+ flattenGroupPlayers((allPreviousPlayersMap.get(element) || EMPTY_PLAYER_ARRAY)
+ .map(p => p.getRealPlayer()))
+ .filter(p => {
+ // the `element` is not apart of the AnimationPlayer definition, but
+ // Mock/WebAnimations
+ // use the element within their implementation. This will be added in Angular5 to
+ // AnimationPlayer
+ const pp = p as any;
+ return pp.element ? pp.element === element : false;
+ });
const preStyles = preStylesMap.get(element);
const postStyles = postStylesMap.get(element);
@@ -1481,3 +1515,14 @@ function _flattenGroupPlayersRecur(players: AnimationPlayer[], finalPlayers: Ani
}
}
}
+
+function objEquals(a: {[key: string]: any}, b: {[key: string]: any}): boolean {
+ const k1 = Object.keys(a);
+ const k2 = Object.keys(b);
+ if (k1.length != k2.length) return false;
+ for (let i = 0; i < k1.length; i++) {
+ const prop = k1[i];
+ if (!b.hasOwnProperty(prop) || a[prop] !== b[prop]) return false;
+ }
+ return true;
+}
diff --git a/packages/animations/browser/src/util.ts b/packages/animations/browser/src/util.ts
index a6dbb75f56..dbdfa4f213 100644
--- a/packages/animations/browser/src/util.ts
+++ b/packages/animations/browser/src/util.ts
@@ -9,6 +9,8 @@ import {AnimateTimings, AnimationMetadata, AnimationOptions, sequence, ɵStyleDa
export const ONE_SECOND = 1000;
+export const SUBSTITUTION_EXPR_START = '{{';
+export const SUBSTITUTION_EXPR_END = '}}';
export const ENTER_CLASSNAME = 'ng-enter';
export const LEAVE_CLASSNAME = 'ng-leave';
export const ENTER_SELECTOR = '.ng-enter';
@@ -151,10 +153,8 @@ export function normalizeAnimationEntry(steps: AnimationMetadata | AnimationMeta
export function validateStyleParams(
value: string | number, options: AnimationOptions, errors: any[]) {
const params = options.params || {};
- if (typeof value !== 'string') return;
-
- const matches = value.toString().match(PARAM_REGEX);
- if (matches) {
+ const matches = extractStyleParams(value);
+ if (matches.length) {
matches.forEach(varName => {
if (!params.hasOwnProperty(varName)) {
errors.push(
@@ -164,7 +164,22 @@ export function validateStyleParams(
}
}
-const PARAM_REGEX = /\{\{\s*(.+?)\s*\}\}/g;
+const PARAM_REGEX =
+ new RegExp(`${SUBSTITUTION_EXPR_START}\\s*(.+?)\\s*${SUBSTITUTION_EXPR_END}`, 'g');
+export function extractStyleParams(value: string | number): string[] {
+ let params: string[] = [];
+ if (typeof value === 'string') {
+ const val = value.toString();
+
+ let match: any;
+ while (match = PARAM_REGEX.exec(val)) {
+ params.push(match[1] as string);
+ }
+ PARAM_REGEX.lastIndex = 0;
+ }
+ return params;
+}
+
export function interpolateParams(
value: string | number, params: {[name: string]: any}, errors: any[]): string|number {
const original = value.toString();
diff --git a/packages/animations/browser/test/dsl/animation_spec.ts b/packages/animations/browser/test/dsl/animation_spec.ts
index d022379e12..bc6da97a92 100644
--- a/packages/animations/browser/test/dsl/animation_spec.ts
+++ b/packages/animations/browser/test/dsl/animation_spec.ts
@@ -5,7 +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 {AUTO_STYLE, AnimationMetadata, AnimationMetadataType, animate, animation, group, keyframes, query, sequence, style, transition, trigger, useAnimation, ɵStyleData} from '@angular/animations';
+import {AUTO_STYLE, AnimationMetadata, AnimationMetadataType, animate, animation, group, keyframes, query, sequence, state, style, transition, trigger, useAnimation, ɵStyleData} from '@angular/animations';
import {AnimationOptions} from '@angular/core/src/animation/dsl';
import {Animation} from '../../src/dsl/animation';
@@ -174,6 +174,30 @@ export function main() {
validateAndThrowAnimationSequence(steps2);
}).toThrowError(/keyframes\(\) must be placed inside of a call to animate\(\)/);
});
+
+ it('should throw if dynamic style substitutions are used without defaults within state() definitions',
+ () => {
+ const steps = [state('final', style({
+ 'width': '{{ one }}px',
+ 'borderRadius': '{{ two }}px {{ three }}px',
+ }))];
+
+ expect(() => { validateAndThrowAnimationSequence(steps); })
+ .toThrowError(
+ /state\("final", ...\) must define default values for all the following style substitutions: one, two, three/);
+
+ const steps2 = [state(
+ 'panfinal', style({
+ 'color': '{{ greyColor }}',
+ 'borderColor': '1px solid {{ greyColor }}',
+ 'backgroundColor': '{{ redColor }}',
+ }),
+ {params: {redColor: 'maroon'}})];
+
+ expect(() => { validateAndThrowAnimationSequence(steps2); })
+ .toThrowError(
+ /state\("panfinal", ...\) must define default values for all the following style substitutions: greyColor/);
+ });
});
describe('keyframe building', () => {
@@ -427,17 +451,17 @@ export function main() {
it('should throw an error when an input variable is not provided when invoked and is not a default value',
() => {
- expect(() => {invokeAnimationSequence(rootElement, [style({color: '{{ color }}'})])})
+ expect(() => invokeAnimationSequence(rootElement, [style({color: '{{ color }}'})]))
.toThrowError(/Please provide a value for the animation param color/);
expect(
- () => {invokeAnimationSequence(
+ () => invokeAnimationSequence(
rootElement,
[
style({color: '{{ start }}'}),
animate('{{ time }}', style({color: '{{ end }}'})),
],
- buildParams({start: 'blue', end: 'red'}))})
+ buildParams({start: 'blue', end: 'red'})))
.toThrowError(/Please provide a value for the animation param time/);
});
});
diff --git a/packages/animations/browser/test/dsl/animation_trigger_spec.ts b/packages/animations/browser/test/dsl/animation_trigger_spec.ts
index eeb200005f..9db8e2404b 100644
--- a/packages/animations/browser/test/dsl/animation_trigger_spec.ts
+++ b/packages/animations/browser/test/dsl/animation_trigger_spec.ts
@@ -51,12 +51,14 @@ export function main() {
describe('trigger usage', () => {
it('should construct a trigger based on the states and transition data', () => {
const result = makeTrigger('name', [
- state('on', style({width: 0})), state('off', style({width: 100})),
- transition('on => off', animate(1000)), transition('off => on', animate(1000))
+ state('on', style({width: 0})),
+ state('off', style({width: 100})),
+ transition('on => off', animate(1000)),
+ transition('off => on', animate(1000)),
]);
- expect(result.states).toEqual({'on': {width: 0}, 'off': {width: 100}});
-
+ expect(result.states['on'].buildStyles({}, [])).toEqual({width: 0});
+ expect(result.states['off'].buildStyles({}, [])).toEqual({width: 100});
expect(result.transitionFactories.length).toEqual(2);
});
@@ -66,7 +68,9 @@ export function main() {
transition('off => on', animate(1000))
]);
- expect(result.states).toEqual({'on': {width: 50}, 'off': {width: 50}});
+
+ expect(result.states['on'].buildStyles({}, [])).toEqual({width: 50});
+ expect(result.states['off'].buildStyles({}, [])).toEqual({width: 50});
});
it('should find the first transition that matches', () => {
@@ -145,7 +149,7 @@ export function main() {
'a => b', [style({height: '{{ a }}'}), animate(1000, style({height: '{{ b }}'}))],
buildParams({a: '100px', b: '200px'}))]);
- const trans = buildTransition(result, element, 'a', 'b', buildParams({a: '300px'})) !;
+ const trans = buildTransition(result, element, 'a', 'b', {}, buildParams({a: '300px'})) !;
const keyframes = trans.timelines[0].keyframes;
expect(keyframes).toEqual([{height: '300px', offset: 0}, {height: '200px', offset: 1}]);
@@ -182,7 +186,7 @@ export function main() {
const trans = buildTransition(result, element, false, true) !;
expect(trans.timelines[0].keyframes).toEqual([
{offset: 0, color: 'red'}, {offset: 1, color: 'green'}
- ])
+ ]);
});
it('should match `1` and `0` state styles on a `true <=> false` boolean transition given boolean values',
@@ -195,7 +199,7 @@ export function main() {
const trans = buildTransition(result, element, false, true) !;
expect(trans.timelines[0].keyframes).toEqual([
{offset: 0, color: 'orange'}, {offset: 1, color: 'blue'}
- ])
+ ]);
});
describe('aliases', () => {
@@ -219,11 +223,12 @@ export function main() {
function buildTransition(
trigger: AnimationTrigger, element: any, fromState: any, toState: any,
- params?: AnimationOptions): AnimationTransitionInstruction|null {
+ fromOptions?: AnimationOptions, toOptions?: AnimationOptions): AnimationTransitionInstruction|
+ null {
const trans = trigger.matchTransition(fromState, toState) !;
if (trans) {
const driver = new MockAnimationDriver();
- return trans.build(driver, element, fromState, toState, params) !;
+ return trans.build(driver, element, fromState, toState, fromOptions, toOptions) !;
}
return null;
}
diff --git a/packages/animations/src/animation_metadata.ts b/packages/animations/src/animation_metadata.ts
index cabf6dd429..d0f907508b 100755
--- a/packages/animations/src/animation_metadata.ts
+++ b/packages/animations/src/animation_metadata.ts
@@ -105,6 +105,7 @@ export interface AnimationTriggerMetadata extends AnimationMetadata {
export interface AnimationStateMetadata extends AnimationMetadata {
name: string;
styles: AnimationStyleMetadata;
+ options?: {params: {[name: string]: any}};
}
/**
@@ -567,8 +568,10 @@ export function style(
*
* @experimental Animation support is experimental.
*/
-export function state(name: string, styles: AnimationStyleMetadata): AnimationStateMetadata {
- return {type: AnimationMetadataType.State, name, styles};
+export function state(
+ name: string, styles: AnimationStyleMetadata,
+ options?: {params: {[name: string]: any}}): AnimationStateMetadata {
+ return {type: AnimationMetadataType.State, name, styles, options};
}
/**
diff --git a/packages/benchpress/index.ts b/packages/benchpress/index.ts
index f91106e540..1b11272642 100644
--- a/packages/benchpress/index.ts
+++ b/packages/benchpress/index.ts
@@ -9,7 +9,7 @@
// Must be imported first, because Angular decorators throw on load.
import 'reflect-metadata';
-export {InjectionToken, Injector, Provider, ReflectiveInjector} from '@angular/core';
+export {InjectionToken, Injector, Provider, ReflectiveInjector, StaticProvider} from '@angular/core';
export {Options} from './src/common_options';
export {MeasureValues} from './src/measure_values';
export {Metric} from './src/metric';
diff --git a/packages/benchpress/src/metric/perflog_metric.ts b/packages/benchpress/src/metric/perflog_metric.ts
index 391a97ff90..f31fc6186d 100644
--- a/packages/benchpress/src/metric/perflog_metric.ts
+++ b/packages/benchpress/src/metric/perflog_metric.ts
@@ -20,7 +20,14 @@ import {PerfLogEvent, PerfLogFeatures, WebDriverExtension} from '../web_driver_e
export class PerflogMetric extends Metric {
static SET_TIMEOUT = new InjectionToken('PerflogMetric.setTimeout');
static PROVIDERS = [
- PerflogMetric, {
+ {
+ provide: PerflogMetric,
+ deps: [
+ WebDriverExtension, PerflogMetric.SET_TIMEOUT, Options.MICRO_METRICS, Options.FORCE_GC,
+ Options.CAPTURE_FRAMES, Options.RECEIVED_DATA, Options.REQUEST_COUNT
+ ]
+ },
+ {
provide: PerflogMetric.SET_TIMEOUT,
useValue: (fn: Function, millis: number) => setTimeout(fn, millis)
}
@@ -156,7 +163,7 @@ export class PerflogMetric extends Metric {
return result;
}
let resolve: (result: any) => void;
- const promise = new Promise(res => { resolve = res; });
+ const promise = new Promise<{[key: string]: number}>(res => { resolve = res; });
this._setTimeout(() => resolve(this._readUntilEndMark(markName, loopCount + 1)), 100);
return promise;
});
diff --git a/packages/benchpress/src/metric/user_metric.ts b/packages/benchpress/src/metric/user_metric.ts
index 19b8d57dfe..fe253d913d 100644
--- a/packages/benchpress/src/metric/user_metric.ts
+++ b/packages/benchpress/src/metric/user_metric.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {Inject, Injectable} from '@angular/core';
+import {Inject, Injectable, StaticProvider} from '@angular/core';
import {Options} from '../common_options';
import {Metric} from '../metric';
@@ -14,7 +14,8 @@ import {WebDriverAdapter} from '../web_driver_adapter';
@Injectable()
export class UserMetric extends Metric {
- static PROVIDERS = [UserMetric];
+ static PROVIDERS =
+ [{provide: UserMetric, deps: [Options.USER_METRICS, WebDriverAdapter]}];
constructor(
@Inject(Options.USER_METRICS) private _userMetrics: {[key: string]: string},
diff --git a/packages/benchpress/src/reporter/console_reporter.ts b/packages/benchpress/src/reporter/console_reporter.ts
index 72fd545175..1659640010 100644
--- a/packages/benchpress/src/reporter/console_reporter.ts
+++ b/packages/benchpress/src/reporter/console_reporter.ts
@@ -22,7 +22,11 @@ export class ConsoleReporter extends Reporter {
static PRINT = new InjectionToken('ConsoleReporter.print');
static COLUMN_WIDTH = new InjectionToken('ConsoleReporter.columnWidth');
static PROVIDERS = [
- ConsoleReporter, {provide: ConsoleReporter.COLUMN_WIDTH, useValue: 18}, {
+ {
+ provide: ConsoleReporter,
+ deps: [ConsoleReporter.COLUMN_WIDTH, SampleDescription, ConsoleReporter.PRINT]
+ },
+ {provide: ConsoleReporter.COLUMN_WIDTH, useValue: 18}, {
provide: ConsoleReporter.PRINT,
useValue: function(v: any) {
// tslint:disable-next-line:no-console
diff --git a/packages/benchpress/src/reporter/json_file_reporter.ts b/packages/benchpress/src/reporter/json_file_reporter.ts
index 2a8d0fb629..aaca71e916 100644
--- a/packages/benchpress/src/reporter/json_file_reporter.ts
+++ b/packages/benchpress/src/reporter/json_file_reporter.ts
@@ -22,7 +22,13 @@ import {formatStats, sortedProps} from './util';
@Injectable()
export class JsonFileReporter extends Reporter {
static PATH = new InjectionToken('JsonFileReporter.path');
- static PROVIDERS = [JsonFileReporter, {provide: JsonFileReporter.PATH, useValue: '.'}];
+ static PROVIDERS = [
+ {
+ provide: JsonFileReporter,
+ deps: [SampleDescription, JsonFileReporter.PATH, Options.WRITE_FILE, Options.NOW]
+ },
+ {provide: JsonFileReporter.PATH, useValue: '.'}
+ ];
constructor(
private _description: SampleDescription, @Inject(JsonFileReporter.PATH) private _path: string,
diff --git a/packages/benchpress/src/runner.ts b/packages/benchpress/src/runner.ts
index 97a188f4d5..0d9a201f8d 100644
--- a/packages/benchpress/src/runner.ts
+++ b/packages/benchpress/src/runner.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {Provider, ReflectiveInjector} from '@angular/core';
+import {Injector, StaticProvider} from '@angular/core';
import {Options} from './common_options';
import {Metric} from './metric';
@@ -34,17 +34,17 @@ import {IOsDriverExtension} from './webdriver/ios_driver_extension';
* It provides defaults, creates the injector and calls the sampler.
*/
export class Runner {
- constructor(private _defaultProviders: Provider[] = []) {}
+ constructor(private _defaultProviders: StaticProvider[] = []) {}
sample({id, execute, prepare, microMetrics, providers, userMetrics}: {
id: string,
execute?: Function,
prepare?: Function,
microMetrics?: {[key: string]: string},
- providers?: Provider[],
+ providers?: StaticProvider[],
userMetrics?: {[key: string]: string}
}): Promise {
- const sampleProviders: Provider[] = [
+ const sampleProviders: StaticProvider[] = [
_DEFAULT_PROVIDERS, this._defaultProviders, {provide: Options.SAMPLE_ID, useValue: id},
{provide: Options.EXECUTE, useValue: execute}
];
@@ -61,7 +61,7 @@ export class Runner {
sampleProviders.push(providers);
}
- const inj = ReflectiveInjector.resolveAndCreate(sampleProviders);
+ const inj = Injector.create(sampleProviders);
const adapter: WebDriverAdapter = inj.get(WebDriverAdapter);
return Promise
@@ -75,7 +75,7 @@ export class Runner {
// Only WebDriverAdapter is reused.
// TODO vsavkin consider changing it when toAsyncFactory is added back or when child
// injectors are handled better.
- const injector = ReflectiveInjector.resolveAndCreate([
+ const injector = Injector.create([
sampleProviders, {provide: Options.CAPABILITIES, useValue: capabilities},
{provide: Options.USER_AGENT, useValue: userAgent},
{provide: WebDriverAdapter, useValue: adapter}
diff --git a/packages/benchpress/src/sampler.ts b/packages/benchpress/src/sampler.ts
index beb190852c..bd9d0fc868 100644
--- a/packages/benchpress/src/sampler.ts
+++ b/packages/benchpress/src/sampler.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {Inject, Injectable} from '@angular/core';
+import {Inject, Injectable, StaticProvider} from '@angular/core';
import {Options} from './common_options';
import {MeasureValues} from './measure_values';
@@ -26,8 +26,12 @@ import {WebDriverAdapter} from './web_driver_adapter';
*/
@Injectable()
export class Sampler {
- static PROVIDERS = [Sampler];
-
+ static PROVIDERS = [{
+ provide: Sampler,
+ deps: [
+ WebDriverAdapter, Metric, Reporter, Validator, Options.PREPARE, Options.EXECUTE, Options.NOW
+ ]
+ }];
constructor(
private _driver: WebDriverAdapter, private _metric: Metric, private _reporter: Reporter,
private _validator: Validator, @Inject(Options.PREPARE) private _prepare: Function,
diff --git a/packages/benchpress/src/validator/regression_slope_validator.ts b/packages/benchpress/src/validator/regression_slope_validator.ts
index f0e2fa0980..8f590e2b24 100644
--- a/packages/benchpress/src/validator/regression_slope_validator.ts
+++ b/packages/benchpress/src/validator/regression_slope_validator.ts
@@ -21,7 +21,11 @@ export class RegressionSlopeValidator extends Validator {
static SAMPLE_SIZE = new InjectionToken('RegressionSlopeValidator.sampleSize');
static METRIC = new InjectionToken('RegressionSlopeValidator.metric');
static PROVIDERS = [
- RegressionSlopeValidator, {provide: RegressionSlopeValidator.SAMPLE_SIZE, useValue: 10},
+ {
+ provide: RegressionSlopeValidator,
+ deps: [RegressionSlopeValidator.SAMPLE_SIZE, RegressionSlopeValidator.METRIC]
+ },
+ {provide: RegressionSlopeValidator.SAMPLE_SIZE, useValue: 10},
{provide: RegressionSlopeValidator.METRIC, useValue: 'scriptTime'}
];
diff --git a/packages/benchpress/src/validator/size_validator.ts b/packages/benchpress/src/validator/size_validator.ts
index 711eb9edbb..7d546c186a 100644
--- a/packages/benchpress/src/validator/size_validator.ts
+++ b/packages/benchpress/src/validator/size_validator.ts
@@ -17,7 +17,10 @@ import {Validator} from '../validator';
@Injectable()
export class SizeValidator extends Validator {
static SAMPLE_SIZE = new InjectionToken('SizeValidator.sampleSize');
- static PROVIDERS = [SizeValidator, {provide: SizeValidator.SAMPLE_SIZE, useValue: 10}];
+ static PROVIDERS = [
+ {provide: SizeValidator, deps: [SizeValidator.SAMPLE_SIZE]},
+ {provide: SizeValidator.SAMPLE_SIZE, useValue: 10}
+ ];
constructor(@Inject(SizeValidator.SAMPLE_SIZE) private _sampleSize: number) { super(); }
diff --git a/packages/benchpress/src/webdriver/chrome_driver_extension.ts b/packages/benchpress/src/webdriver/chrome_driver_extension.ts
index 0cf41cb38d..d1ceba67b1 100644
--- a/packages/benchpress/src/webdriver/chrome_driver_extension.ts
+++ b/packages/benchpress/src/webdriver/chrome_driver_extension.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {Inject, Injectable} from '@angular/core';
+import {Inject, Injectable, StaticProvider} from '@angular/core';
import {Options} from '../common_options';
import {WebDriverAdapter} from '../web_driver_adapter';
@@ -21,7 +21,10 @@ import {PerfLogEvent, PerfLogFeatures, WebDriverExtension} from '../web_driver_e
*/
@Injectable()
export class ChromeDriverExtension extends WebDriverExtension {
- static PROVIDERS = [ChromeDriverExtension];
+ static PROVIDERS = [{
+ provide: ChromeDriverExtension,
+ deps: [WebDriverAdapter, Options.USER_AGENT]
+ }];
private _majorChromeVersion: number;
private _firstRun = true;
diff --git a/packages/benchpress/src/webdriver/firefox_driver_extension.ts b/packages/benchpress/src/webdriver/firefox_driver_extension.ts
index 0c3c360632..2d34742505 100644
--- a/packages/benchpress/src/webdriver/firefox_driver_extension.ts
+++ b/packages/benchpress/src/webdriver/firefox_driver_extension.ts
@@ -13,7 +13,7 @@ import {PerfLogEvent, PerfLogFeatures, WebDriverExtension} from '../web_driver_e
@Injectable()
export class FirefoxDriverExtension extends WebDriverExtension {
- static PROVIDERS = [FirefoxDriverExtension];
+ static PROVIDERS = [{provide: FirefoxDriverExtension, deps: [WebDriverAdapter]}];
private _profilerStarted: boolean;
@@ -40,7 +40,7 @@ export class FirefoxDriverExtension extends WebDriverExtension {
return this._driver.executeScript(script);
}
- readPerfLog(): Promise {
+ readPerfLog(): Promise {
return this._driver.executeAsyncScript('var cb = arguments[0]; window.getProfile(cb);');
}
diff --git a/packages/benchpress/src/webdriver/ios_driver_extension.ts b/packages/benchpress/src/webdriver/ios_driver_extension.ts
index 8ceb3e2eeb..fefef6314c 100644
--- a/packages/benchpress/src/webdriver/ios_driver_extension.ts
+++ b/packages/benchpress/src/webdriver/ios_driver_extension.ts
@@ -13,7 +13,7 @@ import {PerfLogEvent, PerfLogFeatures, WebDriverExtension} from '../web_driver_e
@Injectable()
export class IOsDriverExtension extends WebDriverExtension {
- static PROVIDERS = [IOsDriverExtension];
+ static PROVIDERS = [{provide: IOsDriverExtension, deps: [WebDriverAdapter]}];
constructor(private _driver: WebDriverAdapter) { super(); }
diff --git a/packages/benchpress/src/webdriver/selenium_webdriver_adapter.ts b/packages/benchpress/src/webdriver/selenium_webdriver_adapter.ts
index efb1d91f4b..4aa0ad27e0 100644
--- a/packages/benchpress/src/webdriver/selenium_webdriver_adapter.ts
+++ b/packages/benchpress/src/webdriver/selenium_webdriver_adapter.ts
@@ -6,15 +6,19 @@
* found in the LICENSE file at https://angular.io/license
*/
+import {StaticProvider} from '@angular/core';
+
import {WebDriverAdapter} from '../web_driver_adapter';
+
/**
* Adapter for the selenium-webdriver.
*/
export class SeleniumWebDriverAdapter extends WebDriverAdapter {
- static PROTRACTOR_PROVIDERS = [{
+ static PROTRACTOR_PROVIDERS = [{
provide: WebDriverAdapter,
- useFactory: () => new SeleniumWebDriverAdapter((global).browser)
+ useFactory: () => new SeleniumWebDriverAdapter((global).browser),
+ deps: []
}];
constructor(private _driver: any) { super(); }
diff --git a/packages/benchpress/test/metric/multi_metric_spec.ts b/packages/benchpress/test/metric/multi_metric_spec.ts
index 53f2892bbd..3d8ff95467 100644
--- a/packages/benchpress/test/metric/multi_metric_spec.ts
+++ b/packages/benchpress/test/metric/multi_metric_spec.ts
@@ -7,12 +7,13 @@
*/
import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/testing/src/testing_internal';
-import {Metric, MultiMetric, ReflectiveInjector} from '../../index';
+
+import {Injector, Metric, MultiMetric} from '../../index';
export function main() {
function createMetric(ids: any[]) {
- const m = ReflectiveInjector
- .resolveAndCreate([
+ const m = Injector
+ .create([
ids.map(id => ({provide: id, useValue: new MockMetric(id)})),
MultiMetric.provideWith(ids)
])
diff --git a/packages/benchpress/test/metric/perflog_metric_spec.ts b/packages/benchpress/test/metric/perflog_metric_spec.ts
index 66b81cc160..68822092f9 100644
--- a/packages/benchpress/test/metric/perflog_metric_spec.ts
+++ b/packages/benchpress/test/metric/perflog_metric_spec.ts
@@ -6,10 +6,10 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {Provider} from '@angular/core';
+import {StaticProvider} from '@angular/core';
import {AsyncTestCompleter, beforeEach, describe, expect, inject, it} from '@angular/core/testing/src/testing_internal';
-import {Metric, Options, PerfLogEvent, PerfLogFeatures, PerflogMetric, ReflectiveInjector, WebDriverExtension} from '../../index';
+import {Injector, Metric, Options, PerfLogEvent, PerfLogFeatures, PerflogMetric, WebDriverExtension} from '../../index';
import {TraceEventFactory} from '../trace_event_factory';
export function main() {
@@ -33,7 +33,7 @@ export function main() {
if (!microMetrics) {
microMetrics = {};
}
- const providers: Provider[] = [
+ const providers: StaticProvider[] = [
Options.DEFAULT_PROVIDERS, PerflogMetric.PROVIDERS,
{provide: Options.MICRO_METRICS, useValue: microMetrics}, {
provide: PerflogMetric.SET_TIMEOUT,
@@ -59,7 +59,7 @@ export function main() {
if (requestCount != null) {
providers.push({provide: Options.REQUEST_COUNT, useValue: requestCount});
}
- return ReflectiveInjector.resolveAndCreate(providers).get(PerflogMetric);
+ return Injector.create(providers).get(PerflogMetric);
}
describe('perflog metric', () => {
diff --git a/packages/benchpress/test/metric/user_metric_spec.ts b/packages/benchpress/test/metric/user_metric_spec.ts
index fe0c382efd..6884620b68 100644
--- a/packages/benchpress/test/metric/user_metric_spec.ts
+++ b/packages/benchpress/test/metric/user_metric_spec.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {Provider, ReflectiveInjector} from '@angular/core';
+import {Injector, StaticProvider} from '@angular/core';
import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/testing/src/testing_internal';
import {Options, PerfLogEvent, PerfLogFeatures, UserMetric, WebDriverAdapter} from '../../index';
@@ -25,12 +25,12 @@ export function main() {
userMetrics = {};
}
wdAdapter = new MockDriverAdapter();
- const providers: Provider[] = [
+ const providers: StaticProvider[] = [
Options.DEFAULT_PROVIDERS, UserMetric.PROVIDERS,
{provide: Options.USER_METRICS, useValue: userMetrics},
{provide: WebDriverAdapter, useValue: wdAdapter}
];
- return ReflectiveInjector.resolveAndCreate(providers).get(UserMetric);
+ return Injector.create(providers).get(UserMetric);
}
describe('user metric', () => {
diff --git a/packages/benchpress/test/reporter/console_reporter_spec.ts b/packages/benchpress/test/reporter/console_reporter_spec.ts
index bfe30e7547..7e3c15b6cb 100644
--- a/packages/benchpress/test/reporter/console_reporter_spec.ts
+++ b/packages/benchpress/test/reporter/console_reporter_spec.ts
@@ -6,10 +6,10 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {Provider} from '@angular/core';
+import {StaticProvider} from '@angular/core';
import {describe, expect, it} from '@angular/core/testing/src/testing_internal';
-import {ConsoleReporter, MeasureValues, ReflectiveInjector, SampleDescription} from '../../index';
+import {ConsoleReporter, Injector, MeasureValues, SampleDescription} from '../../index';
export function main() {
describe('console reporter', () => {
@@ -30,7 +30,7 @@ export function main() {
if (sampleId == null) {
sampleId = 'null';
}
- const providers: Provider[] = [
+ const providers: StaticProvider[] = [
ConsoleReporter.PROVIDERS, {
provide: SampleDescription,
useValue: new SampleDescription(sampleId, descriptions, metrics !)
@@ -40,7 +40,7 @@ export function main() {
if (columnWidth != null) {
providers.push({provide: ConsoleReporter.COLUMN_WIDTH, useValue: columnWidth});
}
- reporter = ReflectiveInjector.resolveAndCreate(providers).get(ConsoleReporter);
+ reporter = Injector.create(providers).get(ConsoleReporter);
}
it('should print the sample id, description and table header', () => {
diff --git a/packages/benchpress/test/reporter/json_file_reporter_spec.ts b/packages/benchpress/test/reporter/json_file_reporter_spec.ts
index 27c4b25584..cbdbf5d9d4 100644
--- a/packages/benchpress/test/reporter/json_file_reporter_spec.ts
+++ b/packages/benchpress/test/reporter/json_file_reporter_spec.ts
@@ -8,7 +8,7 @@
import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/testing/src/testing_internal';
-import {JsonFileReporter, MeasureValues, Options, ReflectiveInjector, SampleDescription} from '../../index';
+import {Injector, JsonFileReporter, MeasureValues, Options, SampleDescription} from '../../index';
export function main() {
describe('file reporter', () => {
@@ -34,7 +34,7 @@ export function main() {
}
}
];
- return ReflectiveInjector.resolveAndCreate(providers).get(JsonFileReporter);
+ return Injector.create(providers).get(JsonFileReporter);
}
it('should write all data into a file',
diff --git a/packages/benchpress/test/reporter/multi_reporter_spec.ts b/packages/benchpress/test/reporter/multi_reporter_spec.ts
index 1ee54f2cde..aa1502e36b 100644
--- a/packages/benchpress/test/reporter/multi_reporter_spec.ts
+++ b/packages/benchpress/test/reporter/multi_reporter_spec.ts
@@ -8,12 +8,12 @@
import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/testing/src/testing_internal';
-import {MeasureValues, MultiReporter, ReflectiveInjector, Reporter} from '../../index';
+import {Injector, MeasureValues, MultiReporter, Reporter} from '../../index';
export function main() {
function createReporters(ids: any[]) {
- const r = ReflectiveInjector
- .resolveAndCreate([
+ const r = Injector
+ .create([
ids.map(id => ({provide: id, useValue: new MockReporter(id)})),
MultiReporter.provideWith(ids)
])
diff --git a/packages/benchpress/test/runner_spec.ts b/packages/benchpress/test/runner_spec.ts
index 7c4ee88847..27683730c5 100644
--- a/packages/benchpress/test/runner_spec.ts
+++ b/packages/benchpress/test/runner_spec.ts
@@ -8,11 +8,11 @@
import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/testing/src/testing_internal';
-import {Injector, Metric, Options, ReflectiveInjector, Runner, SampleDescription, SampleState, Sampler, Validator, WebDriverAdapter} from '../index';
+import {Injector, Metric, Options, Runner, SampleDescription, SampleState, Sampler, Validator, WebDriverAdapter} from '../index';
export function main() {
describe('runner', () => {
- let injector: ReflectiveInjector;
+ let injector: Injector;
let runner: Runner;
function createRunner(defaultProviders?: any[]): Runner {
@@ -22,7 +22,7 @@ export function main() {
runner = new Runner([
defaultProviders, {
provide: Sampler,
- useFactory: (_injector: ReflectiveInjector) => {
+ useFactory: (_injector: Injector) => {
injector = _injector;
return new MockSampler();
},
diff --git a/packages/benchpress/test/sampler_spec.ts b/packages/benchpress/test/sampler_spec.ts
index e7e8339ee7..82ca5d6d64 100644
--- a/packages/benchpress/test/sampler_spec.ts
+++ b/packages/benchpress/test/sampler_spec.ts
@@ -8,7 +8,7 @@
import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/testing/src/testing_internal';
-import {MeasureValues, Metric, Options, ReflectiveInjector, Reporter, Sampler, Validator, WebDriverAdapter} from '../index';
+import {Injector, MeasureValues, Metric, Options, Reporter, Sampler, Validator, WebDriverAdapter} from '../index';
export function main() {
const EMPTY_EXECUTE = () => {};
@@ -44,7 +44,7 @@ export function main() {
providers.push({provide: Options.PREPARE, useValue: prepare});
}
- sampler = ReflectiveInjector.resolveAndCreate(providers).get(Sampler);
+ sampler = Injector.create(providers).get(Sampler);
}
it('should call the prepare and execute callbacks using WebDriverAdapter.waitFor',
diff --git a/packages/benchpress/test/validator/regression_slope_validator_spec.ts b/packages/benchpress/test/validator/regression_slope_validator_spec.ts
index 75be174bef..3b8c3e02ce 100644
--- a/packages/benchpress/test/validator/regression_slope_validator_spec.ts
+++ b/packages/benchpress/test/validator/regression_slope_validator_spec.ts
@@ -8,15 +8,15 @@
import {describe, expect, it} from '@angular/core/testing/src/testing_internal';
-import {MeasureValues, ReflectiveInjector, RegressionSlopeValidator} from '../../index';
+import {Injector, MeasureValues, RegressionSlopeValidator} from '../../index';
export function main() {
describe('regression slope validator', () => {
let validator: RegressionSlopeValidator;
function createValidator({size, metric}: {size: number, metric: string}) {
- validator = ReflectiveInjector
- .resolveAndCreate([
+ validator = Injector
+ .create([
RegressionSlopeValidator.PROVIDERS,
{provide: RegressionSlopeValidator.METRIC, useValue: metric},
{provide: RegressionSlopeValidator.SAMPLE_SIZE, useValue: size}
diff --git a/packages/benchpress/test/validator/size_validator_spec.ts b/packages/benchpress/test/validator/size_validator_spec.ts
index 99bc4b9951..da5a14ad57 100644
--- a/packages/benchpress/test/validator/size_validator_spec.ts
+++ b/packages/benchpress/test/validator/size_validator_spec.ts
@@ -8,7 +8,7 @@
import {describe, expect, it} from '@angular/core/testing/src/testing_internal';
-import {MeasureValues, ReflectiveInjector, SizeValidator} from '../../index';
+import {Injector, MeasureValues, SizeValidator} from '../../index';
export function main() {
describe('size validator', () => {
@@ -16,8 +16,8 @@ export function main() {
function createValidator(size: number) {
validator =
- ReflectiveInjector
- .resolveAndCreate(
+ Injector
+ .create(
[SizeValidator.PROVIDERS, {provide: SizeValidator.SAMPLE_SIZE, useValue: size}])
.get(SizeValidator);
}
diff --git a/packages/benchpress/test/web_driver_extension_spec.ts b/packages/benchpress/test/web_driver_extension_spec.ts
index 37fdab29ab..178f851262 100644
--- a/packages/benchpress/test/web_driver_extension_spec.ts
+++ b/packages/benchpress/test/web_driver_extension_spec.ts
@@ -8,14 +8,14 @@
import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/testing/src/testing_internal';
-import {Options, ReflectiveInjector, WebDriverExtension} from '../index';
+import {Injector, Options, WebDriverExtension} from '../index';
export function main() {
function createExtension(ids: any[], caps: any) {
return new Promise((res, rej) => {
try {
- res(ReflectiveInjector
- .resolveAndCreate([
+ res(Injector
+ .create([
ids.map((id) => ({provide: id, useValue: new MockExtension(id)})),
{provide: Options.CAPABILITIES, useValue: caps},
WebDriverExtension.provideFirstSupported(ids)
diff --git a/packages/benchpress/test/webdriver/chrome_driver_extension_spec.ts b/packages/benchpress/test/webdriver/chrome_driver_extension_spec.ts
index f12d8a8bd6..ebb17e3d9b 100644
--- a/packages/benchpress/test/webdriver/chrome_driver_extension_spec.ts
+++ b/packages/benchpress/test/webdriver/chrome_driver_extension_spec.ts
@@ -8,7 +8,7 @@
import {AsyncTestCompleter, describe, expect, iit, inject, it} from '@angular/core/testing/src/testing_internal';
-import {ChromeDriverExtension, Options, ReflectiveInjector, WebDriverAdapter, WebDriverExtension} from '../../index';
+import {ChromeDriverExtension, Injector, Options, WebDriverAdapter, WebDriverExtension} from '../../index';
import {TraceEventFactory} from '../trace_event_factory';
export function main() {
@@ -41,8 +41,8 @@ export function main() {
userAgent = CHROME45_USER_AGENT;
}
log = [];
- extension = ReflectiveInjector
- .resolveAndCreate([
+ extension = Injector
+ .create([
ChromeDriverExtension.PROVIDERS, {
provide: WebDriverAdapter,
useValue: new MockDriverAdapter(log, perfRecords, messageMethod)
diff --git a/packages/benchpress/test/webdriver/ios_driver_extension_spec.ts b/packages/benchpress/test/webdriver/ios_driver_extension_spec.ts
index a91fc2982d..7ff0cd6b13 100644
--- a/packages/benchpress/test/webdriver/ios_driver_extension_spec.ts
+++ b/packages/benchpress/test/webdriver/ios_driver_extension_spec.ts
@@ -8,7 +8,7 @@
import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/testing/src/testing_internal';
-import {IOsDriverExtension, ReflectiveInjector, WebDriverAdapter, WebDriverExtension} from '../../index';
+import {IOsDriverExtension, Injector, WebDriverAdapter, WebDriverExtension} from '../../index';
import {TraceEventFactory} from '../trace_event_factory';
export function main() {
@@ -24,8 +24,8 @@ export function main() {
}
log = [];
extension =
- ReflectiveInjector
- .resolveAndCreate([
+ Injector
+ .create([
IOsDriverExtension.PROVIDERS,
{provide: WebDriverAdapter, useValue: new MockDriverAdapter(log, perfRecords)}
])
diff --git a/packages/common/http/src/request.ts b/packages/common/http/src/request.ts
index 7673e71f0e..a77da15f7d 100644
--- a/packages/common/http/src/request.ts
+++ b/packages/common/http/src/request.ts
@@ -15,8 +15,11 @@ import {HttpParams} from './params';
* All values are optional and will override default values if provided.
*/
interface HttpRequestInit {
- headers?: HttpHeaders, reportProgress?: boolean, params?: HttpParams,
- responseType?: 'arraybuffer'|'blob'|'json'|'text', withCredentials?: boolean,
+ headers?: HttpHeaders;
+ reportProgress?: boolean;
+ params?: HttpParams;
+ responseType?: 'arraybuffer'|'blob'|'json'|'text';
+ withCredentials?: boolean;
}
/**
diff --git a/packages/common/http/src/response.ts b/packages/common/http/src/response.ts
index 0850505cc5..a56e4da14d 100644
--- a/packages/common/http/src/response.ts
+++ b/packages/common/http/src/response.ts
@@ -120,7 +120,10 @@ export interface HttpUserEvent { type: HttpEventType.User; }
*
* @experimental
*/
-export interface HttpJsonParseError { error: Error, text: string, }
+export interface HttpJsonParseError {
+ error: Error;
+ text: string;
+}
/**
* Union type for all possible events on the response stream.
@@ -233,7 +236,7 @@ export class HttpHeaderResponse extends HttpResponseBase {
status: update.status !== undefined ? update.status : this.status,
statusText: update.statusText || this.statusText,
url: update.url || this.url || undefined,
- })
+ });
}
}
diff --git a/packages/common/http/src/xhr.ts b/packages/common/http/src/xhr.ts
index 8f35a98ffe..88ad2e2658 100644
--- a/packages/common/http/src/xhr.ts
+++ b/packages/common/http/src/xhr.ts
@@ -268,27 +268,26 @@ export class HttpXhrBackend implements HttpBackend {
// The upload progress event handler, which is only registered if
// progress events are enabled.
- const onUpProgress =
- (event: ProgressEvent) => {
- // Upload progress events are simpler. Begin building the progress
- // event.
- let progress: HttpUploadProgressEvent = {
- type: HttpEventType.UploadProgress,
- loaded: event.loaded,
- };
+ const onUpProgress = (event: ProgressEvent) => {
+ // Upload progress events are simpler. Begin building the progress
+ // event.
+ let progress: HttpUploadProgressEvent = {
+ type: HttpEventType.UploadProgress,
+ loaded: event.loaded,
+ };
- // If the total number of bytes being uploaded is available, include
- // it.
- if (event.lengthComputable) {
- progress.total = event.total;
- }
+ // If the total number of bytes being uploaded is available, include
+ // it.
+ if (event.lengthComputable) {
+ progress.total = event.total;
+ }
- // Send the event.
- observer.next(progress);
- }
+ // Send the event.
+ observer.next(progress);
+ };
- // By default, register for load and error events.
- xhr.addEventListener('load', onLoad);
+ // By default, register for load and error events.
+ xhr.addEventListener('load', onLoad);
xhr.addEventListener('error', onError);
// Progress events are only enabled if requested.
diff --git a/packages/common/http/test/jsonp_spec.ts b/packages/common/http/test/jsonp_spec.ts
index 1a7bb49254..141cd544db 100644
--- a/packages/common/http/test/jsonp_spec.ts
+++ b/packages/common/http/test/jsonp_spec.ts
@@ -56,11 +56,11 @@ export function main() {
});
describe('throws an error', () => {
it('when request method is not JSONP',
- () => {expect(() => backend.handle(SAMPLE_REQ.clone({method: 'GET'})))
- .toThrowError(JSONP_ERR_WRONG_METHOD)});
+ () => expect(() => backend.handle(SAMPLE_REQ.clone({method: 'GET'})))
+ .toThrowError(JSONP_ERR_WRONG_METHOD));
it('when response type is not json',
- () => {expect(() => backend.handle(SAMPLE_REQ.clone({responseType: 'text'})))
- .toThrowError(JSONP_ERR_WRONG_RESPONSE_TYPE)});
+ () => expect(() => backend.handle(SAMPLE_REQ.clone({responseType: 'text'})))
+ .toThrowError(JSONP_ERR_WRONG_RESPONSE_TYPE));
it('when callback is never called', (done: DoneFn) => {
backend.handle(SAMPLE_REQ).subscribe(undefined, (err: HttpErrorResponse) => {
expect(err.status).toBe(0);
@@ -69,7 +69,7 @@ export function main() {
done();
});
document.mockLoad();
- })
+ });
});
});
}
diff --git a/packages/common/http/test/params_spec.ts b/packages/common/http/test/params_spec.ts
index 27c2d83d76..8bc167836b 100644
--- a/packages/common/http/test/params_spec.ts
+++ b/packages/common/http/test/params_spec.ts
@@ -13,7 +13,7 @@ export function main() {
describe('initialization', () => {
it('should be empty at construction', () => {
const body = new HttpParams();
- expect(body.toString()).toEqual('')
+ expect(body.toString()).toEqual('');
});
it('should parse an existing url', () => {
diff --git a/packages/common/http/test/xhr_spec.ts b/packages/common/http/test/xhr_spec.ts
index cdc3162a95..fc9dff6422 100644
--- a/packages/common/http/test/xhr_spec.ts
+++ b/packages/common/http/test/xhr_spec.ts
@@ -271,7 +271,7 @@ export function main() {
done();
});
factory.mock.mockFlush(200, 'OK', 'Test');
- })
+ });
});
describe('corrects for quirks', () => {
it('by normalizing 1223 status to 204', (done: DoneFn) => {
diff --git a/packages/common/http/test/xsrf_spec.ts b/packages/common/http/test/xsrf_spec.ts
index f59b7df84d..1c5c235432 100644
--- a/packages/common/http/test/xsrf_spec.ts
+++ b/packages/common/http/test/xsrf_spec.ts
@@ -64,7 +64,7 @@ export function main() {
});
describe('HttpXsrfCookieExtractor', () => {
let document: {[key: string]: string};
- let extractor: HttpXsrfCookieExtractor
+ let extractor: HttpXsrfCookieExtractor;
beforeEach(() => {
document = {
cookie: 'XSRF-TOKEN=test',
diff --git a/packages/common/http/testing/src/backend.ts b/packages/common/http/testing/src/backend.ts
index 1cb5e2b125..3de345b984 100644
--- a/packages/common/http/testing/src/backend.ts
+++ b/packages/common/http/testing/src/backend.ts
@@ -126,7 +126,7 @@ export class HttpClientTestingBackend implements HttpBackend, HttpTestingControl
const requests = open.map(testReq => {
const url = testReq.request.urlWithParams.split('?')[0];
const method = testReq.request.method;
- return `${method} ${url}`
+ return `${method} ${url}`;
})
.join(', ');
throw new Error(`Expected no open requests, found ${open.length}: ${requests}`);
diff --git a/packages/common/src/directives/ng_component_outlet.ts b/packages/common/src/directives/ng_component_outlet.ts
index 34e32c8e71..3c018a7803 100644
--- a/packages/common/src/directives/ng_component_outlet.ts
+++ b/packages/common/src/directives/ng_component_outlet.ts
@@ -6,7 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {ComponentFactoryResolver, ComponentRef, Directive, Injector, Input, NgModuleFactory, NgModuleRef, OnChanges, OnDestroy, Provider, SimpleChanges, Type, ViewContainerRef} from '@angular/core';
+import {ComponentFactoryResolver, ComponentRef, Directive, Injector, Input, NgModuleFactory, NgModuleRef, OnChanges, OnDestroy, SimpleChanges, StaticProvider, Type, ViewContainerRef} from '@angular/core';
+
/**
* Instantiates a single {@link Component} type and inserts its Host View into current View.
diff --git a/packages/common/src/directives/ng_template_outlet.ts b/packages/common/src/directives/ng_template_outlet.ts
index ccfa8ecee4..800eed23ec 100644
--- a/packages/common/src/directives/ng_template_outlet.ts
+++ b/packages/common/src/directives/ng_template_outlet.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {Directive, EmbeddedViewRef, Input, OnChanges, SimpleChanges, TemplateRef, ViewContainerRef} from '@angular/core';
+import {Directive, EmbeddedViewRef, Input, OnChanges, SimpleChange, SimpleChanges, TemplateRef, ViewContainerRef} from '@angular/core';
/**
* @ngModule CommonModule
@@ -49,13 +49,58 @@ export class NgTemplateOutlet implements OnChanges {
set ngOutletContext(context: Object) { this.ngTemplateOutletContext = context; }
ngOnChanges(changes: SimpleChanges) {
- if (this._viewRef) {
- this._viewContainerRef.remove(this._viewContainerRef.indexOf(this._viewRef));
- }
+ const recreateView = this._shouldRecreateView(changes);
- if (this.ngTemplateOutlet) {
- this._viewRef = this._viewContainerRef.createEmbeddedView(
- this.ngTemplateOutlet, this.ngTemplateOutletContext);
+ if (recreateView) {
+ if (this._viewRef) {
+ this._viewContainerRef.remove(this._viewContainerRef.indexOf(this._viewRef));
+ }
+
+ if (this.ngTemplateOutlet) {
+ this._viewRef = this._viewContainerRef.createEmbeddedView(
+ this.ngTemplateOutlet, this.ngTemplateOutletContext);
+ }
+ } else {
+ if (this._viewRef && this.ngTemplateOutletContext) {
+ this._updateExistingContext(this.ngTemplateOutletContext);
+ }
+ }
+ }
+
+ /**
+ * We need to re-create existing embedded view if:
+ * - templateRef has changed
+ * - context has changes
+ *
+ * To mark context object as changed when the corresponding object
+ * shape changes (new properties are added or existing properties are removed).
+ * In other words we consider context with the same properties as "the same" even
+ * if object reference changes (see https://github.com/angular/angular/issues/13407).
+ */
+ private _shouldRecreateView(changes: SimpleChanges): boolean {
+ const ctxChange = changes['ngTemplateOutletContext'];
+ return !!changes['ngTemplateOutlet'] || (ctxChange && this._hasContextShapeChanged(ctxChange));
+ }
+
+ private _hasContextShapeChanged(ctxChange: SimpleChange): boolean {
+ const prevCtxKeys = Object.keys(ctxChange.previousValue || {});
+ const currCtxKeys = Object.keys(ctxChange.currentValue || {});
+
+ if (prevCtxKeys.length === currCtxKeys.length) {
+ for (let propName of currCtxKeys) {
+ if (prevCtxKeys.indexOf(propName) === -1) {
+ return true;
+ }
+ }
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ private _updateExistingContext(ctx: Object): void {
+ for (let propName of Object.keys(ctx)) {
+ (this._viewRef.context)[propName] = (this.ngTemplateOutletContext)[propName];
}
}
}
diff --git a/packages/common/src/pipes/date_pipe.ts b/packages/common/src/pipes/date_pipe.ts
index 12a5aa7340..aec4cee835 100644
--- a/packages/common/src/pipes/date_pipe.ts
+++ b/packages/common/src/pipes/date_pipe.ts
@@ -43,8 +43,8 @@ const ISO8601_DATE_REGEX =
* | month | M | L (S) | MMM (Sep) | MMMM (September) | M (9) | MM (09) |
* | day | d | - | - | - | d (3) | dd (03) |
* | weekday | E | E (S) | EEE (Sun) | EEEE (Sunday) | - | - |
- * | hour | j | - | - | - | j (13) | jj (13) |
- * | hour12 | h | - | - | - | h (1 PM) | hh (01 PM)|
+ * | hour | j | - | - | - | j (1 PM) | jj (1 PM) |
+ * | hour12 | h | - | - | - | h (1) | hh (01) |
* | hour24 | H | - | - | - | H (13) | HH (13) |
* | minute | m | - | - | - | m (5) | mm (05) |
* | second | s | - | - | - | s (9) | ss (09) |
diff --git a/packages/common/test/directives/ng_component_outlet_spec.ts b/packages/common/test/directives/ng_component_outlet_spec.ts
index 0d897a97c1..45f2c5e8b3 100644
--- a/packages/common/test/directives/ng_component_outlet_spec.ts
+++ b/packages/common/test/directives/ng_component_outlet_spec.ts
@@ -8,7 +8,7 @@
import {CommonModule} from '@angular/common';
import {NgComponentOutlet} from '@angular/common/src/directives/ng_component_outlet';
-import {Compiler, Component, ComponentRef, Inject, InjectionToken, Injector, NO_ERRORS_SCHEMA, NgModule, NgModuleFactory, Optional, Provider, QueryList, ReflectiveInjector, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef} from '@angular/core';
+import {Compiler, Component, ComponentRef, Inject, InjectionToken, Injector, NO_ERRORS_SCHEMA, NgModule, NgModuleFactory, Optional, QueryList, StaticProvider, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef} from '@angular/core';
import {TestBed, async, fakeAsync} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/src/matchers';
@@ -96,7 +96,7 @@ export function main() {
const uniqueValue = {};
fixture.componentInstance.currentComponent = InjectedComponent;
- fixture.componentInstance.injector = ReflectiveInjector.resolveAndCreate(
+ fixture.componentInstance.injector = Injector.create(
[{provide: TEST_TOKEN, useValue: uniqueValue}], fixture.componentRef.injector);
fixture.detectChanges();
diff --git a/packages/common/test/directives/ng_template_outlet_spec.ts b/packages/common/test/directives/ng_template_outlet_spec.ts
index 8f1d25ac95..13135629cd 100644
--- a/packages/common/test/directives/ng_template_outlet_spec.ts
+++ b/packages/common/test/directives/ng_template_outlet_spec.ts
@@ -7,7 +7,7 @@
*/
import {CommonModule} from '@angular/common';
-import {Component, ContentChildren, Directive, NO_ERRORS_SCHEMA, QueryList, TemplateRef} from '@angular/core';
+import {Component, ContentChildren, Directive, Injectable, NO_ERRORS_SCHEMA, OnDestroy, QueryList, TemplateRef} from '@angular/core';
import {ComponentFixture, TestBed, async} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/src/matchers';
@@ -26,11 +26,9 @@ export function main() {
beforeEach(() => {
TestBed.configureTestingModule({
- declarations: [
- TestComponent,
- CaptureTplRefs,
- ],
+ declarations: [TestComponent, CaptureTplRefs, DestroyableCmpt],
imports: [CommonModule],
+ providers: [DestroyedSpyService]
});
});
@@ -125,9 +123,105 @@ export function main() {
fixture.componentInstance.context = {shawshank: 'was here'};
detectChangesAndExpectText('was here');
}));
+
+ it('should update but not destroy embedded view when context values change', () => {
+ const template =
+ `:{{foo}}` +
+ ``;
+
+ fixture = createTestComponent(template);
+ const spyService = fixture.debugElement.injector.get(DestroyedSpyService);
+
+ detectChangesAndExpectText('Content to destroy:bar');
+ expect(spyService.destroyed).toBeFalsy();
+
+ fixture.componentInstance.value = 'baz';
+ detectChangesAndExpectText('Content to destroy:baz');
+ expect(spyService.destroyed).toBeFalsy();
+ });
+
+ it('should recreate embedded view when context shape changes', () => {
+ const template =
+ `:{{foo}}` +
+ ``;
+
+ fixture = createTestComponent(template);
+ const spyService = fixture.debugElement.injector.get(DestroyedSpyService);
+
+ detectChangesAndExpectText('Content to destroy:bar');
+ expect(spyService.destroyed).toBeFalsy();
+
+ fixture.componentInstance.context = {foo: 'baz', other: true};
+ detectChangesAndExpectText('Content to destroy:baz');
+ expect(spyService.destroyed).toBeTruthy();
+ });
+
+ it('should destroy embedded view when context value changes and templateRef becomes undefined',
+ () => {
+ const template =
+ `:{{foo}}` +
+ ``;
+
+ fixture = createTestComponent(template);
+ const spyService = fixture.debugElement.injector.get(DestroyedSpyService);
+
+ detectChangesAndExpectText('Content to destroy:bar');
+ expect(spyService.destroyed).toBeFalsy();
+
+ fixture.componentInstance.value = 'baz';
+ detectChangesAndExpectText('');
+ expect(spyService.destroyed).toBeTruthy();
+ });
+
+ it('should not try to update null / undefined context when context changes but template stays the same',
+ () => {
+ const template = `{{foo}}` +
+ ``;
+
+ fixture = createTestComponent(template);
+ detectChangesAndExpectText('');
+
+ fixture.componentInstance.value = 'baz';
+ detectChangesAndExpectText('');
+ });
+
+ it('should not try to update null / undefined context when template changes', () => {
+ const template = `{{foo}}` +
+ `{{foo}}` +
+ ``;
+
+ fixture = createTestComponent(template);
+ detectChangesAndExpectText('');
+
+ fixture.componentInstance.value = 'baz';
+ detectChangesAndExpectText('');
+ });
+
+ it('should not try to update context on undefined view', () => {
+ const template = `{{foo}}` +
+ ``;
+
+ fixture = createTestComponent(template);
+ detectChangesAndExpectText('');
+
+ fixture.componentInstance.value = 'baz';
+ detectChangesAndExpectText('');
+ });
});
}
+@Injectable()
+class DestroyedSpyService {
+ destroyed = false;
+}
+
+@Component({selector: 'destroyable-cmpt', template: 'Content to destroy'})
+class DestroyableCmpt implements OnDestroy {
+ constructor(private _spyService: DestroyedSpyService) {}
+
+ ngOnDestroy(): void { this._spyService.destroyed = true; }
+}
+
@Directive({selector: 'tpl-refs', exportAs: 'tplRefs'})
class CaptureTplRefs {
@ContentChildren(TemplateRef) tplRefs: QueryList>;
@@ -137,6 +231,7 @@ class CaptureTplRefs {
class TestComponent {
currentTplRef: TemplateRef;
context: any = {foo: 'bar'};
+ value = 'bar';
}
function createTestComponent(template: string): ComponentFixture {
diff --git a/packages/common/test/pipes/date_pipe_spec.ts b/packages/common/test/pipes/date_pipe_spec.ts
index 07a009d0dc..a4fb977011 100644
--- a/packages/common/test/pipes/date_pipe_spec.ts
+++ b/packages/common/test/pipes/date_pipe_spec.ts
@@ -124,9 +124,12 @@ export function main() {
expectDateFormatAs(date, pattern, dateFixtures[pattern]);
});
- Object.keys(isoStringWithoutTimeFixtures).forEach((pattern: string) => {
- expectDateFormatAs(isoStringWithoutTime, pattern, isoStringWithoutTimeFixtures[pattern]);
- });
+ if (!browserDetection.isOldChrome) {
+ Object.keys(isoStringWithoutTimeFixtures).forEach((pattern: string) => {
+ expectDateFormatAs(
+ isoStringWithoutTime, pattern, isoStringWithoutTimeFixtures[pattern]);
+ });
+ }
expect(pipe.transform(date, 'Z')).toBeDefined();
});
diff --git a/packages/compiler-cli/index.ts b/packages/compiler-cli/index.ts
index f242a5b64b..482b8ecba8 100644
--- a/packages/compiler-cli/index.ts
+++ b/packages/compiler-cli/index.ts
@@ -20,8 +20,7 @@ export {BuiltinType, DeclarationKind, Definition, PipeInfo, Pipes, Signature, Sp
export * from './src/transformers/api';
export * from './src/transformers/entry_points';
-export {main as ngc} from './src/ngc';
-export {performCompilation} from './src/ngc';
+export {performCompilation} from './src/perform-compile';
// TODO(hansl): moving to Angular 4 need to update this API.
export {NgTools_InternalApi_NG_2 as __NGTOOLS_PRIVATE_API_2} from './src/ngtools_api';
diff --git a/packages/compiler-cli/package.json b/packages/compiler-cli/package.json
index ef72109112..ef1ce4b573 100644
--- a/packages/compiler-cli/package.json
+++ b/packages/compiler-cli/package.json
@@ -9,7 +9,7 @@
"ng-xi18n": "./src/extract_i18n.js"
},
"dependencies": {
- "@angular/tsc-wrapped": "5.0.0-beta.1",
+ "@angular/tsc-wrapped": "5.0.0-beta.2",
"reflect-metadata": "^0.1.2",
"minimist": "^1.2.0"
},
diff --git a/packages/compiler-cli/src/codegen.ts b/packages/compiler-cli/src/codegen.ts
index cf8ea0a07e..f861db7382 100644
--- a/packages/compiler-cli/src/codegen.ts
+++ b/packages/compiler-cli/src/codegen.ts
@@ -96,7 +96,7 @@ export class CodeGenerator {
}
}
if (!transContent) {
- missingTranslation = MissingTranslationStrategy.Ignore
+ missingTranslation = MissingTranslationStrategy.Ignore;
}
const {compiler: aotCompiler} = compiler.createAotCompiler(ngCompilerHost, {
translations: transContent,
diff --git a/packages/compiler-cli/src/compiler_host.ts b/packages/compiler-cli/src/compiler_host.ts
index 04d5083770..e7c5c03954 100644
--- a/packages/compiler-cli/src/compiler_host.ts
+++ b/packages/compiler-cli/src/compiler_host.ts
@@ -25,8 +25,9 @@ export interface CompilerHostContext extends ts.ModuleResolutionHost {
assumeFileExists(fileName: string): void;
}
+export interface MetadataProvider { getMetadata(source: ts.SourceFile): ModuleMetadata|undefined; }
+
export class CompilerHost implements AotCompilerHost {
- protected metadataCollector = new MetadataCollector();
private isGenDirChildOfRootDir: boolean;
protected basePath: string;
private genDir: string;
@@ -39,7 +40,8 @@ export class CompilerHost implements AotCompilerHost {
constructor(
protected program: ts.Program, protected options: AngularCompilerOptions,
- protected context: CompilerHostContext, collectorOptions?: CollectorOptions) {
+ protected context: CompilerHostContext, collectorOptions?: CollectorOptions,
+ protected metadataProvider: MetadataProvider = new MetadataCollector()) {
// normalize the path so that it never ends with '/'.
this.basePath = path.normalize(path.join(this.options.basePath !, '.')).replace(/\\/g, '/');
this.genDir = path.normalize(path.join(this.options.genDir !, '.')).replace(/\\/g, '/');
@@ -206,7 +208,7 @@ export class CompilerHost implements AotCompilerHost {
}
const sf = this.getSourceFile(filePath);
- const metadata = this.metadataCollector.getMetadata(sf);
+ const metadata = this.metadataProvider.getMetadata(sf);
return metadata ? [metadata] : [];
}
@@ -245,7 +247,7 @@ export class CompilerHost implements AotCompilerHost {
v3Metadata.metadata[prop] = v1Metadata.metadata[prop];
}
- const exports = this.metadataCollector.getMetadata(this.getSourceFile(dtsFilePath));
+ const exports = this.metadataProvider.getMetadata(this.getSourceFile(dtsFilePath));
if (exports) {
for (let prop in exports.metadata) {
if (!v3Metadata.metadata[prop]) {
diff --git a/packages/compiler-cli/src/diagnostics/check_types.ts b/packages/compiler-cli/src/diagnostics/check_types.ts
index 3a2030043d..0029f2fe60 100644
--- a/packages/compiler-cli/src/diagnostics/check_types.ts
+++ b/packages/compiler-cli/src/diagnostics/check_types.ts
@@ -203,11 +203,9 @@ class TypeCheckingHost implements ts.CompilerHost {
}
writeFile: ts.WriteFileCallback =
- () => { throw new Error('Unexpected write in diagnostic program'); }
+ () => { throw new Error('Unexpected write in diagnostic program'); };
- getCurrentDirectory(): string {
- return this.host.getCurrentDirectory();
- }
+ getCurrentDirectory(): string { return this.host.getCurrentDirectory(); }
getDirectories(path: string): string[] { return this.host.getDirectories(path); }
diff --git a/packages/compiler-cli/src/ngc.ts b/packages/compiler-cli/src/ngc.ts
index 6d21ff851d..658ed31d6e 100644
--- a/packages/compiler-cli/src/ngc.ts
+++ b/packages/compiler-cli/src/ngc.ts
@@ -10,185 +10,14 @@
import 'reflect-metadata';
import {isSyntaxError, syntaxError} from '@angular/compiler';
-import {MetadataBundler, createBundleIndexHost} from '@angular/tsc-wrapped';
import * as fs from 'fs';
import * as path from 'path';
-import * as ts from 'typescript';
-import * as api from './transformers/api';
-import * as ng from './transformers/entry_points';
-
-const TS_EXT = /\.ts$/;
-
-type Diagnostics = ts.Diagnostic[] | api.Diagnostic[];
-
-function isTsDiagnostics(diagnostics: any): diagnostics is ts.Diagnostic[] {
- return diagnostics && diagnostics[0] && (diagnostics[0].file || diagnostics[0].messageText);
-}
-
-function formatDiagnostics(cwd: string, diags: Diagnostics): string {
- if (diags && diags.length) {
- if (isTsDiagnostics(diags)) {
- return ts.formatDiagnostics(diags, {
- getCurrentDirectory: () => cwd,
- getCanonicalFileName: fileName => fileName,
- getNewLine: () => ts.sys.newLine
- });
- } else {
- return diags
- .map(d => {
- let res = api.DiagnosticCategory[d.category];
- if (d.span) {
- res +=
- ` at ${d.span.start.file.url}(${d.span.start.line + 1},${d.span.start.col + 1})`;
- }
- if (d.span && d.span.details) {
- res += `: ${d.span.details}, ${d.message}\n`;
- } else {
- res += `: ${d.message}\n`;
- }
- return res;
- })
- .join();
- }
- } else
- return '';
-}
-
-function check(cwd: string, ...args: Diagnostics[]) {
- if (args.some(diags => !!(diags && diags[0]))) {
- throw syntaxError(args.map(diags => {
- if (diags && diags[0]) {
- return formatDiagnostics(cwd, diags);
- }
- })
- .filter(message => !!message)
- .join(''));
- }
-}
-
-function syntheticError(message: string): ts.Diagnostic {
- return {
- file: null as any as ts.SourceFile,
- start: 0,
- length: 0,
- messageText: message,
- category: ts.DiagnosticCategory.Error,
- code: 0
- };
-}
-
-export function readConfiguration(
- project: string, basePath: string, checkFunc: (cwd: string, ...args: any[]) => void = check,
- existingOptions?: ts.CompilerOptions) {
- // Allow a directory containing tsconfig.json as the project value
- // Note, TS@next returns an empty array, while earlier versions throw
- const projectFile =
- fs.lstatSync(project).isDirectory() ? path.join(project, 'tsconfig.json') : project;
- let {config, error} = ts.readConfigFile(projectFile, ts.sys.readFile);
-
- if (error) checkFunc(basePath, [error]);
- const parseConfigHost = {
- useCaseSensitiveFileNames: true,
- fileExists: fs.existsSync,
- readDirectory: ts.sys.readDirectory,
- readFile: ts.sys.readFile
- };
- const parsed = ts.parseJsonConfigFileContent(config, parseConfigHost, basePath, existingOptions);
-
- checkFunc(basePath, parsed.errors);
-
- // Default codegen goes to the current directory
- // Parsed options are already converted to absolute paths
- const ngOptions = config.angularCompilerOptions || {};
- // Ignore the genDir option
- ngOptions.genDir = basePath;
-
- return {parsed, ngOptions};
-}
-
-function getProjectDirectory(project: string): string {
- let isFile: boolean;
- try {
- isFile = fs.lstatSync(project).isFile();
- } catch (e) {
- // Project doesn't exist. Assume it is a file has an extension. This case happens
- // when the project file is passed to set basePath but no tsconfig.json file exists.
- // It is used in tests to ensure that the options can be passed in without there being
- // an actual config file.
- isFile = path.extname(project) !== '';
- }
-
- // If project refers to a file, the project directory is the file's parent directory
- // otherwise project is the project directory.
- return isFile ? path.dirname(project) : project;
-}
-
-export function performCompilation(
- basePath: string, files: string[], options: ts.CompilerOptions, ngOptions: any,
- consoleError: (s: string) => void = console.error,
- checkFunc: (cwd: string, ...args: any[]) => void = check, tsCompilerHost?: ts.CompilerHost) {
- try {
- ngOptions.basePath = basePath;
- ngOptions.genDir = basePath;
-
- let host = tsCompilerHost || ts.createCompilerHost(options, true);
- host.realpath = p => p;
-
- const rootFileNames = files.map(f => path.normalize(f));
-
- const addGeneratedFileName =
- (fileName: string) => {
- if (fileName.startsWith(basePath) && TS_EXT.exec(fileName)) {
- rootFileNames.push(fileName);
- }
- }
-
- if (ngOptions.flatModuleOutFile && !ngOptions.skipMetadataEmit) {
- const {host: bundleHost, indexName, errors} =
- createBundleIndexHost(ngOptions, rootFileNames, host);
- if (errors) checkFunc(basePath, errors);
- if (indexName) addGeneratedFileName(indexName);
- host = bundleHost;
- }
-
- const ngHostOptions = {...options, ...ngOptions};
- const ngHost = ng.createHost({tsHost: host, options: ngHostOptions});
-
- const ngProgram =
- ng.createProgram({rootNames: rootFileNames, host: ngHost, options: ngHostOptions});
-
- // Check parameter diagnostics
- checkFunc(basePath, ngProgram.getTsOptionDiagnostics(), ngProgram.getNgOptionDiagnostics());
-
- // Check syntactic diagnostics
- checkFunc(basePath, ngProgram.getTsSyntacticDiagnostics());
-
- // Check TypeScript semantic and Angular structure diagnostics
- checkFunc(
- basePath, ngProgram.getTsSemanticDiagnostics(), ngProgram.getNgStructuralDiagnostics());
-
- // Check Angular semantic diagnostics
- checkFunc(basePath, ngProgram.getNgSemanticDiagnostics());
-
- ngProgram.emit({
- emitFlags: api.EmitFlags.Default |
- ((ngOptions.skipMetadataEmit || ngOptions.flatModuleOutFile) ? 0 : api.EmitFlags.Metadata)
- });
- } catch (e) {
- if (isSyntaxError(e)) {
- console.error(e.message);
- consoleError(e.message);
- return 1;
- }
- }
-
- return 0;
-}
+import {performCompilation, readConfiguration, throwOnDiagnostics} from './perform-compile';
export function main(
args: string[], consoleError: (s: string) => void = console.error,
- checkFunc: (cwd: string, ...args: any[]) => void = check): number {
+ checkFunc: (cwd: string, ...args: any[]) => void = throwOnDiagnostics): number {
try {
const parsedArgs = require('minimist')(args);
const project = parsedArgs.p || parsedArgs.project || '.';
diff --git a/packages/compiler-cli/src/path_mapped_compiler_host.ts b/packages/compiler-cli/src/path_mapped_compiler_host.ts
index 2463b5b4ee..f17b0a5800 100644
--- a/packages/compiler-cli/src/path_mapped_compiler_host.ts
+++ b/packages/compiler-cli/src/path_mapped_compiler_host.ts
@@ -132,7 +132,7 @@ export class PathMappedCompilerHost extends CompilerHost {
} else {
const sf = this.getSourceFile(rootedPath);
sf.fileName = sf.fileName;
- const metadata = this.metadataCollector.getMetadata(sf);
+ const metadata = this.metadataProvider.getMetadata(sf);
return metadata ? [metadata] : [];
}
}
diff --git a/packages/compiler-cli/src/perform-compile.ts b/packages/compiler-cli/src/perform-compile.ts
new file mode 100644
index 0000000000..55feddb347
--- /dev/null
+++ b/packages/compiler-cli/src/perform-compile.ts
@@ -0,0 +1,192 @@
+/**
+ * @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 {isSyntaxError, syntaxError} from '@angular/compiler';
+import {MetadataBundler, createBundleIndexHost} from '@angular/tsc-wrapped';
+import * as fs from 'fs';
+import * as path from 'path';
+import * as ts from 'typescript';
+import * as api from './transformers/api';
+import * as ng from './transformers/entry_points';
+
+const TS_EXT = /\.ts$/;
+
+export type Diagnostics = ts.Diagnostic[] | api.Diagnostic[];
+
+function isTsDiagnostics(diagnostics: any): diagnostics is ts.Diagnostic[] {
+ return diagnostics && diagnostics[0] && (diagnostics[0].file || diagnostics[0].messageText);
+}
+
+function formatDiagnostics(cwd: string, diags: Diagnostics): string {
+ if (diags && diags.length) {
+ if (isTsDiagnostics(diags)) {
+ return ts.formatDiagnostics(diags, {
+ getCurrentDirectory: () => cwd,
+ getCanonicalFileName: fileName => fileName,
+ getNewLine: () => ts.sys.newLine
+ });
+ } else {
+ return diags
+ .map(d => {
+ let res = api.DiagnosticCategory[d.category];
+ if (d.span) {
+ res +=
+ ` at ${d.span.start.file.url}(${d.span.start.line + 1},${d.span.start.col + 1})`;
+ }
+ if (d.span && d.span.details) {
+ res += `: ${d.span.details}, ${d.message}\n`;
+ } else {
+ res += `: ${d.message}\n`;
+ }
+ return res;
+ })
+ .join();
+ }
+ } else
+ return '';
+}
+
+/**
+ * Throw a syntax error exception with a message formatted for output
+ * if the args parameter contains diagnostics errors.
+ *
+ * @param cwd The directory to report error as relative to.
+ * @param args A list of potentially empty diagnostic errors.
+ */
+export function throwOnDiagnostics(cwd: string, ...args: Diagnostics[]) {
+ if (args.some(diags => !!(diags && diags[0]))) {
+ throw syntaxError(args.map(diags => {
+ if (diags && diags[0]) {
+ return formatDiagnostics(cwd, diags);
+ }
+ })
+ .filter(message => !!message)
+ .join(''));
+ }
+}
+
+function syntheticError(message: string): ts.Diagnostic {
+ return {
+ file: null as any as ts.SourceFile,
+ start: 0,
+ length: 0,
+ messageText: message,
+ category: ts.DiagnosticCategory.Error,
+ code: 0
+ };
+}
+
+export function readConfiguration(
+ project: string, basePath: string,
+ checkFunc: (cwd: string, ...args: any[]) => void = throwOnDiagnostics,
+ existingOptions?: ts.CompilerOptions) {
+ // Allow a directory containing tsconfig.json as the project value
+ // Note, TS@next returns an empty array, while earlier versions throw
+ const projectFile =
+ fs.lstatSync(project).isDirectory() ? path.join(project, 'tsconfig.json') : project;
+ let {config, error} = ts.readConfigFile(projectFile, ts.sys.readFile);
+
+ if (error) checkFunc(basePath, [error]);
+ const parseConfigHost = {
+ useCaseSensitiveFileNames: true,
+ fileExists: fs.existsSync,
+ readDirectory: ts.sys.readDirectory,
+ readFile: ts.sys.readFile
+ };
+ const parsed = ts.parseJsonConfigFileContent(config, parseConfigHost, basePath, existingOptions);
+
+ checkFunc(basePath, parsed.errors);
+
+ // Default codegen goes to the current directory
+ // Parsed options are already converted to absolute paths
+ const ngOptions = config.angularCompilerOptions || {};
+ // Ignore the genDir option
+ ngOptions.genDir = basePath;
+
+ return {parsed, ngOptions};
+}
+
+function getProjectDirectory(project: string): string {
+ let isFile: boolean;
+ try {
+ isFile = fs.lstatSync(project).isFile();
+ } catch (e) {
+ // Project doesn't exist. Assume it is a file has an extension. This case happens
+ // when the project file is passed to set basePath but no tsconfig.json file exists.
+ // It is used in tests to ensure that the options can be passed in without there being
+ // an actual config file.
+ isFile = path.extname(project) !== '';
+ }
+
+ // If project refers to a file, the project directory is the file's parent directory
+ // otherwise project is the project directory.
+ return isFile ? path.dirname(project) : project;
+}
+
+export function performCompilation(
+ basePath: string, files: string[], options: ts.CompilerOptions, ngOptions: any,
+ consoleError: (s: string) => void = console.error,
+ checkFunc: (cwd: string, ...args: any[]) => void = throwOnDiagnostics,
+ tsCompilerHost?: ts.CompilerHost) {
+ try {
+ ngOptions.basePath = basePath;
+ ngOptions.genDir = basePath;
+
+ let host = tsCompilerHost || ts.createCompilerHost(options, true);
+ host.realpath = p => p;
+
+ const rootFileNames = files.map(f => path.normalize(f));
+
+ const addGeneratedFileName = (fileName: string) => {
+ if (fileName.startsWith(basePath) && TS_EXT.exec(fileName)) {
+ rootFileNames.push(fileName);
+ }
+ };
+
+ if (ngOptions.flatModuleOutFile && !ngOptions.skipMetadataEmit) {
+ const {host: bundleHost, indexName, errors} =
+ createBundleIndexHost(ngOptions, rootFileNames, host);
+ if (errors) checkFunc(basePath, errors);
+ if (indexName) addGeneratedFileName(indexName);
+ host = bundleHost;
+ }
+
+ const ngHostOptions = {...options, ...ngOptions};
+ const ngHost = ng.createHost({tsHost: host, options: ngHostOptions});
+
+ const ngProgram =
+ ng.createProgram({rootNames: rootFileNames, host: ngHost, options: ngHostOptions});
+
+ // Check parameter diagnostics
+ checkFunc(basePath, ngProgram.getTsOptionDiagnostics(), ngProgram.getNgOptionDiagnostics());
+
+ // Check syntactic diagnostics
+ checkFunc(basePath, ngProgram.getTsSyntacticDiagnostics());
+
+ // Check TypeScript semantic and Angular structure diagnostics
+ checkFunc(
+ basePath, ngProgram.getTsSemanticDiagnostics(), ngProgram.getNgStructuralDiagnostics());
+
+ // Check Angular semantic diagnostics
+ checkFunc(basePath, ngProgram.getNgSemanticDiagnostics());
+
+ ngProgram.emit({
+ emitFlags: api.EmitFlags.Default |
+ ((ngOptions.skipMetadataEmit || ngOptions.flatModuleOutFile) ? 0 : api.EmitFlags.Metadata)
+ });
+ } catch (e) {
+ if (isSyntaxError(e)) {
+ console.error(e.message);
+ consoleError(e.message);
+ return 1;
+ }
+ throw e;
+ }
+
+ return 0;
+}
diff --git a/packages/compiler-cli/src/transformers/api.ts b/packages/compiler-cli/src/transformers/api.ts
index ea0919c3b5..de1ccc3b9d 100644
--- a/packages/compiler-cli/src/transformers/api.ts
+++ b/packages/compiler-cli/src/transformers/api.ts
@@ -91,6 +91,10 @@ export interface CompilerOptions extends ts.CompilerOptions {
// Whether to enable support for and the template attribute (true by default)
enableLegacyTemplate?: boolean;
+
+ // Whether to enable lowering expressions lambdas and expressions in a reference value
+ // position.
+ disableExpressionLowering?: boolean;
}
export interface ModuleFilenameResolver {
diff --git a/packages/compiler-cli/src/transformers/entry_points.ts b/packages/compiler-cli/src/transformers/entry_points.ts
index b55b4c81ad..4e4f5b1721 100644
--- a/packages/compiler-cli/src/transformers/entry_points.ts
+++ b/packages/compiler-cli/src/transformers/entry_points.ts
@@ -1,3 +1,11 @@
+/**
+ * @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 * as ts from 'typescript';
import {CompilerHost, CompilerOptions, Program} from './api';
diff --git a/packages/compiler-cli/src/transformers/lower_expressions.ts b/packages/compiler-cli/src/transformers/lower_expressions.ts
new file mode 100644
index 0000000000..286ce3b4fa
--- /dev/null
+++ b/packages/compiler-cli/src/transformers/lower_expressions.ts
@@ -0,0 +1,176 @@
+/**
+ * @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 {CollectorOptions, MetadataCollector, MetadataValue, ModuleMetadata} from '@angular/tsc-wrapped';
+import * as ts from 'typescript';
+
+export interface LoweringRequest {
+ kind: ts.SyntaxKind;
+ location: number;
+ end: number;
+ name: string;
+}
+
+export type RequestLocationMap = Map;
+
+interface Declaration {
+ name: string;
+ node: ts.Node;
+}
+
+interface DeclarationInsert {
+ declarations: Declaration[];
+ priorTo: ts.Node;
+}
+
+function toMap(items: T[], select: (item: T) => K): Map {
+ return new Map(items.map<[K, T]>(i => [select(i), i]));
+}
+
+function transformSourceFile(
+ sourceFile: ts.SourceFile, requests: RequestLocationMap,
+ context: ts.TransformationContext): ts.SourceFile {
+ const inserts: DeclarationInsert[] = [];
+
+ // Calculate the range of intersting locations. The transform will only visit nodes in this
+ // range to improve the performance on large files.
+ const locations = Array.from(requests.keys());
+ const min = Math.min(...locations);
+ const max = Math.max(...locations);
+
+ function visitSourceFile(sourceFile: ts.SourceFile): ts.SourceFile {
+ function topLevelStatement(node: ts.Node): ts.Node {
+ const declarations: Declaration[] = [];
+
+ function visitNode(node: ts.Node): ts.Node {
+ const nodeRequest = requests.get(node.pos);
+ if (nodeRequest && nodeRequest.kind == node.kind && nodeRequest.end == node.end) {
+ // This node is requested to be rewritten as a reference to the exported name.
+ // Record that the node needs to be moved to an exported variable with the given name
+ const name = nodeRequest.name;
+ declarations.push({name, node});
+ return ts.createIdentifier(name);
+ }
+ if (node.pos <= max && node.end >= min) return ts.visitEachChild(node, visitNode, context);
+ return node;
+ }
+
+ const result = ts.visitEachChild(node, visitNode, context);
+
+ if (declarations.length) {
+ inserts.push({priorTo: result, declarations});
+ }
+ return result;
+ }
+
+ const traversedSource = ts.visitEachChild(sourceFile, topLevelStatement, context);
+ if (inserts.length) {
+ // Insert the declarations before the rewritten statement that references them.
+ const insertMap = toMap(inserts, i => i.priorTo);
+ const newStatements: ts.Statement[] = [...traversedSource.statements];
+ for (let i = newStatements.length; i >= 0; i--) {
+ const statement = newStatements[i];
+ const insert = insertMap.get(statement);
+ if (insert) {
+ const declarations = insert.declarations.map(
+ i => ts.createVariableDeclaration(
+ i.name, /* type */ undefined, i.node as ts.Expression));
+ const statement = ts.createVariableStatement(
+ /* modifiers */ undefined,
+ ts.createVariableDeclarationList(declarations, ts.NodeFlags.Const));
+ newStatements.splice(i, 0, statement);
+ }
+ }
+
+ // Insert an exports clause to export the declarations
+ newStatements.push(ts.createExportDeclaration(
+ /* decorators */ undefined,
+ /* modifiers */ undefined,
+ ts.createNamedExports(
+ inserts
+ .reduce(
+ (accumulator, insert) => [...accumulator, ...insert.declarations],
+ [] as Declaration[])
+ .map(
+ declaration => ts.createExportSpecifier(
+ /* propertyName */ undefined, declaration.name)))));
+ return ts.updateSourceFileNode(traversedSource, newStatements);
+ }
+ return traversedSource;
+ }
+
+ return visitSourceFile(sourceFile);
+}
+
+export function getExpressionLoweringTransformFactory(requestsMap: RequestsMap):
+ (context: ts.TransformationContext) => (sourceFile: ts.SourceFile) => ts.SourceFile {
+ // Return the factory
+ return (context: ts.TransformationContext) => (sourceFile: ts.SourceFile): ts.SourceFile => {
+ const requests = requestsMap.getRequests(sourceFile);
+ if (requests && requests.size) {
+ return transformSourceFile(sourceFile, requests, context);
+ }
+ return sourceFile;
+ };
+}
+
+export interface RequestsMap { getRequests(sourceFile: ts.SourceFile): RequestLocationMap; }
+
+interface MetadataAndLoweringRequests {
+ metadata: ModuleMetadata|undefined;
+ requests: RequestLocationMap;
+}
+
+export class LowerMetadataCache implements RequestsMap {
+ private collector: MetadataCollector;
+ private metadataCache = new Map();
+
+ constructor(options: CollectorOptions, private strict?: boolean) {
+ this.collector = new MetadataCollector(options);
+ }
+
+ getMetadata(sourceFile: ts.SourceFile): ModuleMetadata|undefined {
+ return this.ensureMetadataAndRequests(sourceFile).metadata;
+ }
+
+ getRequests(sourceFile: ts.SourceFile): RequestLocationMap {
+ return this.ensureMetadataAndRequests(sourceFile).requests;
+ }
+
+ private ensureMetadataAndRequests(sourceFile: ts.SourceFile): MetadataAndLoweringRequests {
+ let result = this.metadataCache.get(sourceFile.fileName);
+ if (!result) {
+ result = this.getMetadataAndRequests(sourceFile);
+ this.metadataCache.set(sourceFile.fileName, result);
+ }
+ return result;
+ }
+
+ private getMetadataAndRequests(sourceFile: ts.SourceFile): MetadataAndLoweringRequests {
+ let identNumber = 0;
+ const freshIdent = () => '\u0275' + identNumber++;
+ const requests = new Map();
+ const replaceNode = (node: ts.Node) => {
+ const name = freshIdent();
+ requests.set(node.pos, {name, kind: node.kind, location: node.pos, end: node.end});
+ return {__symbolic: 'reference', name};
+ };
+
+ const substituteExpression = (value: MetadataValue, node: ts.Node): MetadataValue => {
+ if (node.kind === ts.SyntaxKind.ArrowFunction ||
+ node.kind === ts.SyntaxKind.FunctionExpression) {
+ return replaceNode(node);
+ }
+ return value;
+ };
+
+ const metadata = this.collector.getMetadata(sourceFile, this.strict, substituteExpression);
+
+ return {metadata, requests};
+ }
+}
\ No newline at end of file
diff --git a/packages/compiler-cli/src/transformers/module_filename_resolver.ts b/packages/compiler-cli/src/transformers/module_filename_resolver.ts
index 9c224d85de..d0a55da56a 100644
--- a/packages/compiler-cli/src/transformers/module_filename_resolver.ts
+++ b/packages/compiler-cli/src/transformers/module_filename_resolver.ts
@@ -1,8 +1,15 @@
+/**
+ * @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 * as path from 'path';
import * as ts from 'typescript';
-import {ModuleFilenameResolver} from './api';
-import {CompilerOptions} from './api';
+import {CompilerOptions, ModuleFilenameResolver} from './api';
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
const DTS = /\.d\.ts$/;
diff --git a/packages/compiler-cli/src/transformers/node_emitter.ts b/packages/compiler-cli/src/transformers/node_emitter.ts
index f5b81ec6c0..8adc7e8f64 100644
--- a/packages/compiler-cli/src/transformers/node_emitter.ts
+++ b/packages/compiler-cli/src/transformers/node_emitter.ts
@@ -32,7 +32,7 @@ export class TypeScriptNodeEmitter {
}
statements[0] = ts.setSyntheticLeadingComments(
statements[0],
- [{kind: ts.SyntaxKind.MultiLineCommentTrivia, text: preamble, pos: -1, end: -1}])
+ [{kind: ts.SyntaxKind.MultiLineCommentTrivia, text: preamble, pos: -1, end: -1}]);
}
return [newSourceFile, converter.getNodeMap()];
}
@@ -279,7 +279,7 @@ class _NodeEmitterVisitor implements StatementVisitor, ExpressionVisitor {
return this.record(
expr, ts.createCall(
expr.fn.visitExpression(this, null), /* typeArguments */ undefined,
- expr.args.map(arg => arg.visitExpression(this, null))))
+ expr.args.map(arg => arg.visitExpression(this, null))));
}
visitInstantiateExpr(expr: InstantiateExpr): RecordedNode {
diff --git a/packages/compiler-cli/src/transformers/program.ts b/packages/compiler-cli/src/transformers/program.ts
index 17b5d34024..b2753555fa 100644
--- a/packages/compiler-cli/src/transformers/program.ts
+++ b/packages/compiler-cli/src/transformers/program.ts
@@ -15,8 +15,8 @@ import * as ts from 'typescript';
import {CompilerHost as AotCompilerHost, CompilerHostContext} from '../compiler_host';
import {TypeChecker} from '../diagnostics/check_types';
-import {CompilerHost, CompilerOptions, DiagnosticCategory} from './api';
-import {Diagnostic, EmitFlags, Program} from './api';
+import {CompilerHost, CompilerOptions, Diagnostic, DiagnosticCategory, EmitFlags, Program} from './api';
+import {LowerMetadataCache, getExpressionLoweringTransformFactory} from './lower_expressions';
import {getAngularEmitterTransformFactory} from './node_emitter_transform';
const GENERATED_FILES = /\.ngfactory\.js$|\.ngstyle\.js$|\.ngsummary\.js$/;
@@ -35,7 +35,7 @@ class AngularCompilerProgram implements Program {
private aotCompilerHost: AotCompilerHost;
private compiler: AotCompiler;
private srcNames: string[];
- private collector: MetadataCollector;
+ private metadataCache: LowerMetadataCache;
// Lazily initialized fields
private _analyzedModules: NgAnalyzedModules|undefined;
private _structuralDiagnostics: Diagnostic[] = [];
@@ -55,13 +55,14 @@ class AngularCompilerProgram implements Program {
this.tsProgram = ts.createProgram(rootNames, options, host, this.oldTsProgram);
this.srcNames = this.tsProgram.getSourceFiles().map(sf => sf.fileName);
- this.aotCompilerHost = new AotCompilerHost(this.tsProgram, options, host);
+ this.metadataCache = new LowerMetadataCache({quotedNames: true}, !!options.strictMetadataEmit);
+ this.aotCompilerHost = new AotCompilerHost(
+ this.tsProgram, options, host, /* collectorOptions */ undefined, this.metadataCache);
if (host.readResource) {
this.aotCompilerHost.loadResource = host.readResource.bind(host);
}
const {compiler} = createAotCompiler(this.aotCompilerHost, options);
this.compiler = compiler;
- this.collector = new MetadataCollector({quotedNames: true});
}
// Program implementation
@@ -118,11 +119,9 @@ class AngularCompilerProgram implements Program {
const emitMap = new Map();
const result = this.programWithStubs.emit(
/* targetSourceFile */ undefined,
- createWriteFileCallback(emitFlags, this.host, this.collector, this.options, emitMap),
- cancellationToken, (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS, {
- after: this.options.skipTemplateCodegen ? [] : [getAngularEmitterTransformFactory(
- this.generatedFiles)]
- });
+ createWriteFileCallback(emitFlags, this.host, this.metadataCache, emitMap),
+ cancellationToken, (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS,
+ this.calculateTransforms());
this.generatedFiles.forEach(file => {
if (file.source && file.source.length && SUMMARY_JSON_FILES.test(file.genFileUrl)) {
@@ -144,7 +143,7 @@ class AngularCompilerProgram implements Program {
}
private get structuralDiagnostics(): Diagnostic[] {
- return this.analyzedModules && this._structuralDiagnostics
+ return this.analyzedModules && this._structuralDiagnostics;
}
private get stubs(): GeneratedFile[] {
@@ -171,7 +170,7 @@ class AngularCompilerProgram implements Program {
}
private get generatedFiles(): GeneratedFile[] {
- return this._generatedFiles || (this._generatedFiles = this.generateFiles())
+ return this._generatedFiles || (this._generatedFiles = this.generateFiles());
}
private get typeChecker(): TypeChecker {
@@ -184,6 +183,22 @@ class AngularCompilerProgram implements Program {
return this.generatedFiles && this._generatedFileDiagnostics !;
}
+ private calculateTransforms(): ts.CustomTransformers {
+ const before: ts.TransformerFactory[] = [];
+ const after: ts.TransformerFactory[] = [];
+ if (!this.options.disableExpressionLowering) {
+ // TODO(chuckj): fix and re-enable + tests - see https://github.com/angular/angular/pull/18388
+ // before.push(getExpressionLoweringTransformFactory(this.metadataCache));
+ }
+ if (!this.options.skipTemplateCodegen) {
+ after.push(getAngularEmitterTransformFactory(this.generatedFiles));
+ }
+ const result: ts.CustomTransformers = {};
+ if (before.length) result.before = before;
+ if (after.length) result.after = after;
+ return result;
+ }
+
private catchAnalysisError(e: any): NgAnalyzedModules {
if (isSyntaxError(e)) {
const parserErrors = getParseErrors(e);
@@ -257,8 +272,7 @@ export function createProgram(
}
function writeMetadata(
- emitFilePath: string, sourceFile: ts.SourceFile, collector: MetadataCollector,
- ngOptions: CompilerOptions) {
+ emitFilePath: string, sourceFile: ts.SourceFile, metadataCache: LowerMetadataCache) {
if (/\.js$/.test(emitFilePath)) {
const path = emitFilePath.replace(/\.js$/, '.metadata.json');
@@ -271,7 +285,7 @@ function writeMetadata(
collectableFile = (collectableFile as any).original;
}
- const metadata = collector.getMetadata(collectableFile, !!ngOptions.strictMetadataEmit);
+ const metadata = metadataCache.getMetadata(collectableFile);
if (metadata) {
const metadataText = JSON.stringify([metadata]);
writeFileSync(path, metadataText, {encoding: 'utf-8'});
@@ -280,8 +294,8 @@ function writeMetadata(
}
function createWriteFileCallback(
- emitFlags: EmitFlags, host: ts.CompilerHost, collector: MetadataCollector,
- ngOptions: CompilerOptions, emitMap: Map) {
+ emitFlags: EmitFlags, host: ts.CompilerHost, metadataCache: LowerMetadataCache,
+ emitMap: Map) {
const withMetadata =
(fileName: string, data: string, writeByteOrderMark: boolean,
onError?: (message: string) => void, sourceFiles?: ts.SourceFile[]) => {
@@ -291,7 +305,7 @@ function createWriteFileCallback(
}
if (!generatedFile && sourceFiles && sourceFiles.length == 1) {
emitMap.set(sourceFiles[0].fileName, fileName);
- writeMetadata(fileName, sourceFiles[0], collector, ngOptions);
+ writeMetadata(fileName, sourceFiles[0], metadataCache);
}
};
const withoutMetadata =
@@ -329,7 +343,7 @@ function createProgramWithStubsHost(
generatedFiles: GeneratedFile[], originalProgram: ts.Program,
originalHost: ts.CompilerHost): ts.CompilerHost {
interface FileData {
- g: GeneratedFile
+ g: GeneratedFile;
s?: ts.SourceFile;
}
return new class implements ts.CompilerHost {
diff --git a/packages/compiler-cli/test/diagnostics/mocks.ts b/packages/compiler-cli/test/diagnostics/mocks.ts
index 6cd06d8616..c87acd9f96 100644
--- a/packages/compiler-cli/test/diagnostics/mocks.ts
+++ b/packages/compiler-cli/test/diagnostics/mocks.ts
@@ -110,12 +110,14 @@ const summaryResolver = new AotSummaryResolver(
staticSymbolCache);
export class DiagnosticContext {
+ // tslint:disable
_analyzedModules: NgAnalyzedModules;
_staticSymbolResolver: StaticSymbolResolver|undefined;
_reflector: StaticReflector|undefined;
_errors: {e: any, path?: string}[] = [];
_resolver: CompileMetadataResolver|undefined;
_refletor: StaticReflector;
+ // tslint:enable
constructor(
public service: ts.LanguageService, public program: ts.Program,
diff --git a/packages/compiler-cli/test/diagnostics/symbol_query_spec.ts b/packages/compiler-cli/test/diagnostics/symbol_query_spec.ts
index cf7d7697e6..317b26ed3e 100644
--- a/packages/compiler-cli/test/diagnostics/symbol_query_spec.ts
+++ b/packages/compiler-cli/test/diagnostics/symbol_query_spec.ts
@@ -45,7 +45,7 @@ describe('symbol query', () => {
options.basePath = '/quickstart';
const aotHost = new CompilerHost(program, options, host, {verboseInvalidExpression: true});
context = new DiagnosticContext(service, program, checker, aotHost);
- query = getSymbolQuery(program, checker, sourceFile, emptyPipes)
+ query = getSymbolQuery(program, checker, sourceFile, emptyPipes);
});
it('should be able to get undefined for an unknown symbol', () => {
diff --git a/packages/compiler-cli/test/ngc_spec.ts b/packages/compiler-cli/test/ngc_spec.ts
index 2c272d0c96..0a994cf6e8 100644
--- a/packages/compiler-cli/test/ngc_spec.ts
+++ b/packages/compiler-cli/test/ngc_spec.ts
@@ -11,7 +11,8 @@ import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
-import {main, performCompilation} from '../src/ngc';
+import {main} from '../src/ngc';
+import {performCompilation} from '../src/perform-compile';
function getNgRootDir() {
const moduleFilename = module.filename.replace(/\\/g, '/');
@@ -308,7 +309,6 @@ describe('ngc command-line', () => {
const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')]);
expect(exitCode).toEqual(0);
-
expect(fs.existsSync(path.resolve(outDir, 'mymodule.ngfactory.js'))).toBe(true);
expect(fs.existsSync(path.resolve(
outDir, 'node_modules', '@angular', 'core', 'src',
@@ -316,17 +316,133 @@ describe('ngc command-line', () => {
.toBe(true);
});
+ xdescribe('expression lowering', () => {
+ beforeEach(() => {
+ writeConfig(`{
+ "extends": "./tsconfig-base.json",
+ "files": ["mymodule.ts"]
+ }`);
+ });
+
+ function compile(): number {
+ const errors: string[] = [];
+ const result = main(['-p', path.join(basePath, 'tsconfig.json')], s => errors.push(s));
+ expect(errors).toEqual([]);
+ return result;
+ }
+
+ it('should be able to lower a lambda expression in a provider', () => {
+ write('mymodule.ts', `
+ import {CommonModule} from '@angular/common';
+ import {NgModule} from '@angular/core';
+
+ class Foo {}
+
+ @NgModule({
+ imports: [CommonModule],
+ providers: [{provide: 'someToken', useFactory: () => new Foo()}]
+ })
+ export class MyModule {}
+ `);
+ expect(compile()).toEqual(0);
+
+ const mymodulejs = path.resolve(outDir, 'mymodule.js');
+ const mymoduleSource = fs.readFileSync(mymodulejs, 'utf8');
+ expect(mymoduleSource).toContain('var ɵ0 = function () { return new Foo(); }');
+ expect(mymoduleSource).toContain('export { ɵ0');
+
+ const mymodulefactory = path.resolve(outDir, 'mymodule.ngfactory.js');
+ const mymodulefactorySource = fs.readFileSync(mymodulefactory, 'utf8');
+ expect(mymodulefactorySource).toContain('"someToken", i1.ɵ0');
+ });
+
+ it('should be able to lower a function expression in a provider', () => {
+ write('mymodule.ts', `
+ import {CommonModule} from '@angular/common';
+ import {NgModule} from '@angular/core';
+
+ class Foo {}
+
+ @NgModule({
+ imports: [CommonModule],
+ providers: [{provide: 'someToken', useFactory: function() {return new Foo();}}]
+ })
+ export class MyModule {}
+ `);
+ expect(compile()).toEqual(0);
+
+ const mymodulejs = path.resolve(outDir, 'mymodule.js');
+ const mymoduleSource = fs.readFileSync(mymodulejs, 'utf8');
+ expect(mymoduleSource).toContain('var ɵ0 = function () { return new Foo(); }');
+ expect(mymoduleSource).toContain('export { ɵ0');
+
+ const mymodulefactory = path.resolve(outDir, 'mymodule.ngfactory.js');
+ const mymodulefactorySource = fs.readFileSync(mymodulefactory, 'utf8');
+ expect(mymodulefactorySource).toContain('"someToken", i1.ɵ0');
+ });
+
+ it('should able to lower multiple expressions', () => {
+ write('mymodule.ts', `
+ import {CommonModule} from '@angular/common';
+ import {NgModule} from '@angular/core';
+
+ class Foo {}
+
+ @NgModule({
+ imports: [CommonModule],
+ providers: [
+ {provide: 'someToken', useFactory: () => new Foo()},
+ {provide: 'someToken', useFactory: () => new Foo()},
+ {provide: 'someToken', useFactory: () => new Foo()},
+ {provide: 'someToken', useFactory: () => new Foo()}
+ ]
+ })
+ export class MyModule {}
+ `);
+ expect(compile()).toEqual(0);
+ const mymodulejs = path.resolve(outDir, 'mymodule.js');
+ const mymoduleSource = fs.readFileSync(mymodulejs, 'utf8');
+ expect(mymoduleSource).toContain('ɵ0 = function () { return new Foo(); }');
+ expect(mymoduleSource).toContain('ɵ1 = function () { return new Foo(); }');
+ expect(mymoduleSource).toContain('ɵ2 = function () { return new Foo(); }');
+ expect(mymoduleSource).toContain('ɵ3 = function () { return new Foo(); }');
+ expect(mymoduleSource).toContain('export { ɵ0, ɵ1, ɵ2, ɵ3');
+ });
+
+ it('should be able to lower an indirect expression', () => {
+ write('mymodule.ts', `
+ import {CommonModule} from '@angular/common';
+ import {NgModule} from '@angular/core';
+
+ class Foo {}
+
+ const factory = () => new Foo();
+
+ @NgModule({
+ imports: [CommonModule],
+ providers: [{provide: 'someToken', useFactory: factory}]
+ })
+ export class MyModule {}
+ `);
+ expect(compile()).toEqual(0);
+
+ const mymodulejs = path.resolve(outDir, 'mymodule.js');
+ const mymoduleSource = fs.readFileSync(mymodulejs, 'utf8');
+ expect(mymoduleSource).toContain('var ɵ0 = function () { return new Foo(); }');
+ expect(mymoduleSource).toContain('export { ɵ0');
+ });
+ });
+
const shouldExist = (fileName: string) => {
if (!fs.existsSync(path.resolve(outDir, fileName))) {
throw new Error(`Expected ${fileName} to be emitted (outDir: ${outDir})`);
}
};
- const shouldNotExist =
- (fileName: string) => {
- if (fs.existsSync(path.resolve(outDir, fileName))) {
- throw new Error(`Did not expect ${fileName} to be emitted (outDir: ${outDir})`);
- }
- }
+ const shouldNotExist = (fileName: string) => {
+ if (fs.existsSync(path.resolve(outDir, fileName))) {
+ throw new Error(`Did not expect ${fileName} to be emitted (outDir: ${outDir})`);
+ }
+ };
it('should be able to generate a flat module library', () => {
writeConfig(`
diff --git a/packages/compiler-cli/test/transformers/lower_expressions_spec.ts b/packages/compiler-cli/test/transformers/lower_expressions_spec.ts
new file mode 100644
index 0000000000..7588982b03
--- /dev/null
+++ b/packages/compiler-cli/test/transformers/lower_expressions_spec.ts
@@ -0,0 +1,107 @@
+/**
+ * @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 * as ts from 'typescript';
+
+import {LoweringRequest, RequestLocationMap, getExpressionLoweringTransformFactory} from '../../src/transformers/lower_expressions';
+import {Directory, MockAotContext, MockCompilerHost} from '../mocks';
+
+describe('Expression lowering', () => {
+ it('should be able to lower a simple expression', () => {
+ expect(convert('const a = 1 +◊b: 2◊;')).toBe('const b = 2; const a = 1 + b; export { b };');
+ });
+
+ it('should be able to lower an expression in a decorator', () => {
+ expect(convert(`
+ import {Component} from '@angular/core';
+
+ @Component({
+ provider: [{provide: 'someToken', useFactory:◊l: () => null◊}]
+ })
+ class MyClass {}
+ `)).toContain('const l = () => null; exports.l = l;');
+ });
+});
+
+function convert(annotatedSource: string) {
+ const annotations: {start: number, length: number, name: string}[] = [];
+ let adjustment = 0;
+ const unannotatedSource = annotatedSource.replace(
+ /◊([a-zA-Z]+):(.*)◊/g,
+ (text: string, name: string, source: string, index: number): string => {
+ annotations.push({start: index + adjustment, length: source.length, name});
+ adjustment -= text.length - source.length;
+ return source;
+ });
+
+ const baseFileName = 'someFile';
+ const moduleName = '/' + baseFileName;
+ const fileName = moduleName + '.ts';
+ const context = new MockAotContext('/', {[baseFileName + '.ts']: unannotatedSource});
+ const host = new MockCompilerHost(context);
+
+ const sourceFile = ts.createSourceFile(
+ fileName, unannotatedSource, ts.ScriptTarget.Latest, /* setParentNodes */ true);
+ const requests = new Map();
+
+ for (const annotation of annotations) {
+ const node = findNode(sourceFile, annotation.start, annotation.length);
+ expect(node).toBeDefined();
+ if (node) {
+ const location = node.pos;
+ requests.set(location, {name: annotation.name, kind: node.kind, location, end: node.end});
+ }
+ }
+
+ const program = ts.createProgram(
+ [fileName], {module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2017}, host);
+ const moduleSourceFile = program.getSourceFile(fileName);
+ const transformers: ts.CustomTransformers = {
+ before: [getExpressionLoweringTransformFactory({
+ getRequests(sourceFile: ts.SourceFile): RequestLocationMap{
+ if (sourceFile.fileName == moduleSourceFile.fileName) {
+ return requests;
+ } else {return new Map();}
+ }
+ })]
+ };
+ let result: string = '';
+ const emitResult = program.emit(
+ moduleSourceFile, (emittedFileName, data, writeByteOrderMark, onError, sourceFiles) => {
+ if (fileName.startsWith(moduleName)) {
+ result = data;
+ }
+ }, undefined, undefined, transformers);
+ return normalizeResult(result);
+};
+
+function findNode(node: ts.Node, start: number, length: number): ts.Node|undefined {
+ function find(node: ts.Node): ts.Node|undefined {
+ if (node.getFullStart() == start && node.getEnd() == start + length) {
+ return node;
+ }
+ if (node.getFullStart() <= start && node.getEnd() >= start + length) {
+ return ts.forEachChild(node, find);
+ }
+ }
+ return ts.forEachChild(node, find);
+}
+
+function normalizeResult(result: string): string {
+ // Remove TypeScript prefixes
+ // Remove new lines
+ // Squish adjacent spaces
+ // Remove prefix and postfix spaces
+ return result.replace('"use strict";', ' ')
+ .replace('exports.__esModule = true;', ' ')
+ .replace('Object.defineProperty(exports, "__esModule", { value: true });', ' ')
+ .replace(/\n/g, ' ')
+ .replace(/ +/g, ' ')
+ .replace(/^ /g, '')
+ .replace(/ $/g, '');
+}
diff --git a/packages/compiler/src/aot/compiler_options.ts b/packages/compiler/src/aot/compiler_options.ts
index 772ce3ca12..ed260b3a6a 100644
--- a/packages/compiler/src/aot/compiler_options.ts
+++ b/packages/compiler/src/aot/compiler_options.ts
@@ -14,5 +14,5 @@ export interface AotCompilerOptions {
translations?: string;
missingTranslation?: MissingTranslationStrategy;
enableLegacyTemplate?: boolean;
- enableSummariesForJit?: boolean
+ enableSummariesForJit?: boolean;
}
diff --git a/packages/compiler/src/aot/static_reflector.ts b/packages/compiler/src/aot/static_reflector.ts
index 5a9a74ed2b..fb76750878 100644
--- a/packages/compiler/src/aot/static_reflector.ts
+++ b/packages/compiler/src/aot/static_reflector.ts
@@ -376,7 +376,6 @@ export class StaticReflector implements CompileReflector {
if (calling.get(functionSymbol)) {
throw new Error('Recursion not supported');
}
- calling.set(functionSymbol, true);
try {
const value = targetFunction['value'];
if (value && (depth != 0 || value.__symbolic != 'error')) {
@@ -387,6 +386,7 @@ export class StaticReflector implements CompileReflector {
if (defaults && defaults.length > args.length) {
args.push(...defaults.slice(args.length).map((value: any) => simplify(value)));
}
+ calling.set(functionSymbol, true);
const functionScope = BindingScope.build();
for (let i = 0; i < parameters.length; i++) {
functionScope.define(parameters[i], args[i]);
@@ -621,7 +621,7 @@ export class StaticReflector implements CompileReflector {
}
return simplifyInContext(context, value, depth, references + 1);
}
- return simplify(value)
+ return simplify(value);
});
}
return IGNORE;
diff --git a/packages/compiler/src/aot/summary_serializer.ts b/packages/compiler/src/aot/summary_serializer.ts
index f5d9432374..11e420a2d1 100644
--- a/packages/compiler/src/aot/summary_serializer.ts
+++ b/packages/compiler/src/aot/summary_serializer.ts
@@ -345,4 +345,4 @@ class FromJsonDeserializer extends ValueTransformer {
return super.visitStringMap(map, context);
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/compiler/src/jit/compiler_factory.ts b/packages/compiler/src/jit/compiler_factory.ts
index fc2458ea0d..1e51157f0d 100644
--- a/packages/compiler/src/jit/compiler_factory.ts
+++ b/packages/compiler/src/jit/compiler_factory.ts
@@ -6,8 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {COMPILER_OPTIONS, Compiler, CompilerFactory, CompilerOptions, Inject, InjectionToken, MissingTranslationStrategy, Optional, PlatformRef, Provider, ReflectiveInjector, TRANSLATIONS, TRANSLATIONS_FORMAT, Type, ViewEncapsulation, createPlatformFactory, isDevMode, platformCore, ɵConsole as Console} from '@angular/core';
+import {COMPILER_OPTIONS, Compiler, CompilerFactory, CompilerOptions, Inject, InjectionToken, Injector, MissingTranslationStrategy, Optional, PACKAGE_ROOT_URL, PlatformRef, StaticProvider, TRANSLATIONS, TRANSLATIONS_FORMAT, Type, ViewEncapsulation, createPlatformFactory, isDevMode, platformCore, ɵConsole as Console} from '@angular/core';
+import {StaticSymbolCache} from '../aot/static_symbol';
import {CompileReflector} from '../compile_reflector';
import {CompilerConfig} from '../config';
import {DirectiveNormalizer} from '../directive_normalizer';
@@ -16,7 +17,7 @@ import {Lexer} from '../expression_parser/lexer';
import {Parser} from '../expression_parser/parser';
import * as i18n from '../i18n/index';
import {CompilerInjectable} from '../injectable';
-import {CompileMetadataResolver} from '../metadata_resolver';
+import {CompileMetadataResolver, ERROR_COLLECTOR_TOKEN} from '../metadata_resolver';
import {HtmlParser} from '../ml_parser/html_parser';
import {NgModuleCompiler} from '../ng_module_compiler';
import {NgModuleResolver} from '../ng_module_resolver';
@@ -26,7 +27,7 @@ import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry';
import {ElementSchemaRegistry} from '../schema/element_schema_registry';
import {StyleCompiler} from '../style_compiler';
import {JitSummaryResolver, SummaryResolver} from '../summary_resolver';
-import {TemplateParser} from '../template_parser/template_parser';
+import {TEMPLATE_TRANSFORMS, TemplateParser} from '../template_parser/template_parser';
import {DEFAULT_PACKAGE_URL_PROVIDER, UrlResolver} from '../url_resolver';
import {ViewCompiler} from '../view_compiler/view_compiler';
@@ -45,17 +46,18 @@ const baseHtmlParser = new InjectionToken('HtmlParser');
* A set of providers that provide `JitCompiler` and its dependencies to use for
* template compilation.
*/
-export const COMPILER_PROVIDERS: Array|{[k: string]: any}|any[]> = [
+export const COMPILER_PROVIDERS = [
{provide: CompileReflector, useValue: new JitReflector()},
{provide: ResourceLoader, useValue: _NO_RESOURCE_LOADER},
- JitSummaryResolver,
+ {provide: JitSummaryResolver, deps: []},
{provide: SummaryResolver, useExisting: JitSummaryResolver},
- Console,
- Lexer,
- Parser,
+ {provide: Console, deps: []},
+ {provide: Lexer, deps: []},
+ {provide: Parser, deps: [Lexer]},
{
provide: baseHtmlParser,
useClass: HtmlParser,
+ deps: [],
},
{
provide: i18n.I18NHtmlParser,
@@ -78,22 +80,37 @@ export const COMPILER_PROVIDERS: Array|{[k: string]: any}|any[]> =
provide: HtmlParser,
useExisting: i18n.I18NHtmlParser,
},
- TemplateParser,
- DirectiveNormalizer,
- CompileMetadataResolver,
+ {
+ provide: TemplateParser, deps: [CompilerConfig, CompileReflector,
+ Parser, ElementSchemaRegistry,
+ i18n.I18NHtmlParser, Console, [Optional, TEMPLATE_TRANSFORMS]]
+ },
+ { provide: DirectiveNormalizer, deps: [ResourceLoader, UrlResolver, HtmlParser, CompilerConfig]},
+ { provide: CompileMetadataResolver, deps: [CompilerConfig, NgModuleResolver,
+ DirectiveResolver, PipeResolver,
+ SummaryResolver,
+ ElementSchemaRegistry,
+ DirectiveNormalizer, Console,
+ [Optional, StaticSymbolCache],
+ CompileReflector,
+ [Optional, ERROR_COLLECTOR_TOKEN]]},
DEFAULT_PACKAGE_URL_PROVIDER,
- StyleCompiler,
- ViewCompiler,
- NgModuleCompiler,
- {provide: CompilerConfig, useValue: new CompilerConfig()},
- JitCompiler,
- {provide: Compiler, useExisting: JitCompiler},
- DomElementSchemaRegistry,
- {provide: ElementSchemaRegistry, useExisting: DomElementSchemaRegistry},
- UrlResolver,
- DirectiveResolver,
- PipeResolver,
- NgModuleResolver,
+ { provide: StyleCompiler, deps: [UrlResolver]},
+ { provide: ViewCompiler, deps: [CompilerConfig, CompileReflector, ElementSchemaRegistry]},
+ { provide: NgModuleCompiler, deps: [CompileReflector] },
+ { provide: CompilerConfig, useValue: new CompilerConfig()},
+ { provide: JitCompiler, deps: [Injector, CompileMetadataResolver,
+ TemplateParser, StyleCompiler,
+ ViewCompiler, NgModuleCompiler,
+ SummaryResolver, CompilerConfig,
+ Console]},
+ { provide: Compiler, useExisting: JitCompiler},
+ { provide: DomElementSchemaRegistry, deps: []},
+ { provide: ElementSchemaRegistry, useExisting: DomElementSchemaRegistry},
+ { provide: UrlResolver, deps: [PACKAGE_ROOT_URL]},
+ { provide: DirectiveResolver, deps: [CompileReflector]},
+ { provide: PipeResolver, deps: [CompileReflector]},
+ { provide: NgModuleResolver, deps: [CompileReflector]},
];
@CompilerInjectable()
@@ -112,7 +129,7 @@ export class JitCompilerFactory implements CompilerFactory {
}
createCompiler(options: CompilerOptions[] = []): Compiler {
const opts = _mergeOptions(this._defaultOptions.concat(options));
- const injector = ReflectiveInjector.resolveAndCreate([
+ const injector = Injector.create([
COMPILER_PROVIDERS, {
provide: CompilerConfig,
useFactory: () => {
@@ -142,7 +159,7 @@ export class JitCompilerFactory implements CompilerFactory {
*/
export const platformCoreDynamic = createPlatformFactory(platformCore, 'coreDynamic', [
{provide: COMPILER_OPTIONS, useValue: {}, multi: true},
- {provide: CompilerFactory, useClass: JitCompilerFactory},
+ {provide: CompilerFactory, useClass: JitCompilerFactory, deps: [COMPILER_OPTIONS]},
]);
function _mergeOptions(optionsArr: CompilerOptions[]): CompilerOptions {
diff --git a/packages/compiler/src/ml_parser/ast.ts b/packages/compiler/src/ml_parser/ast.ts
index 0a5b0e6449..3bd8742fed 100644
--- a/packages/compiler/src/ml_parser/ast.ts
+++ b/packages/compiler/src/ml_parser/ast.ts
@@ -142,7 +142,7 @@ export function findNode(nodes: Node[], position: number): HtmlAstPath {
return true;
}
}
- }
+ };
visitAll(visitor, nodes);
diff --git a/packages/compiler/src/ml_parser/parser.ts b/packages/compiler/src/ml_parser/parser.ts
index e617156536..ab6a7c29f7 100644
--- a/packages/compiler/src/ml_parser/parser.ts
+++ b/packages/compiler/src/ml_parser/parser.ts
@@ -230,12 +230,9 @@ class _TreeBuilder {
}
private _closeVoidElement(): void {
- if (this._elementStack.length > 0) {
- const el = this._elementStack[this._elementStack.length - 1];
-
- if (this.getTagDefinition(el.name).isVoid) {
- this._elementStack.pop();
- }
+ const el = this._getParentElement();
+ if (el && this.getTagDefinition(el.name).isVoid) {
+ this._elementStack.pop();
}
}
@@ -274,11 +271,10 @@ class _TreeBuilder {
}
private _pushElement(el: html.Element) {
- if (this._elementStack.length > 0) {
- const parentEl = this._elementStack[this._elementStack.length - 1];
- if (this.getTagDefinition(parentEl.name).isClosedByChild(el.name)) {
- this._elementStack.pop();
- }
+ const parentEl = this._getParentElement();
+
+ if (parentEl && this.getTagDefinition(parentEl.name).isClosedByChild(el.name)) {
+ this._elementStack.pop();
}
const tagDef = this.getTagDefinition(el.name);
@@ -353,7 +349,7 @@ class _TreeBuilder {
* `` elements are skipped as they are not rendered as DOM element.
*/
private _getParentElementSkippingContainers():
- {parent: html.Element, container: html.Element|null} {
+ {parent: html.Element | null, container: html.Element|null} {
let container: html.Element|null = null;
for (let i = this._elementStack.length - 1; i >= 0; i--) {
@@ -363,7 +359,7 @@ class _TreeBuilder {
container = this._elementStack[i];
}
- return {parent: this._elementStack[this._elementStack.length - 1], container};
+ return {parent: null, container};
}
private _addToParent(node: html.Node) {
diff --git a/packages/compiler/src/ml_parser/tags.ts b/packages/compiler/src/ml_parser/tags.ts
index 0a882b6c51..5f06c3fd25 100644
--- a/packages/compiler/src/ml_parser/tags.ts
+++ b/packages/compiler/src/ml_parser/tags.ts
@@ -56,274 +56,272 @@ export function isNgTemplate(tagName: string): boolean {
return splitNsName(tagName)[1] === 'ng-template';
}
-export function getNsPrefix(fullName: string): string
+export function getNsPrefix(fullName: string): string;
export function getNsPrefix(fullName: null): null;
- export function getNsPrefix(fullName: string | null): string |
- null {
- return fullName === null ? null : splitNsName(fullName)[0];
- }
+export function getNsPrefix(fullName: string | null): string|null {
+ return fullName === null ? null : splitNsName(fullName)[0];
+}
- export function mergeNsAndName(prefix: string, localName: string):
- string {
- return prefix ? `:${prefix}:${localName}` : localName;
- }
+export function mergeNsAndName(prefix: string, localName: string): string {
+ return prefix ? `:${prefix}:${localName}` : localName;
+}
- // see http://www.w3.org/TR/html51/syntax.html#named-character-references
- // see https://html.spec.whatwg.org/multipage/entities.json
- // This list is not exhaustive to keep the compiler footprint low.
- // The `{` / `ƫ` syntax should be used when the named character reference does not
- // exist.
- export const NAMED_ENTITIES: {[k: string]: string} = {
- 'Aacute': '\u00C1',
- 'aacute': '\u00E1',
- 'Acirc': '\u00C2',
- 'acirc': '\u00E2',
- 'acute': '\u00B4',
- 'AElig': '\u00C6',
- 'aelig': '\u00E6',
- 'Agrave': '\u00C0',
- 'agrave': '\u00E0',
- 'alefsym': '\u2135',
- 'Alpha': '\u0391',
- 'alpha': '\u03B1',
- 'amp': '&',
- 'and': '\u2227',
- 'ang': '\u2220',
- 'apos': '\u0027',
- 'Aring': '\u00C5',
- 'aring': '\u00E5',
- 'asymp': '\u2248',
- 'Atilde': '\u00C3',
- 'atilde': '\u00E3',
- 'Auml': '\u00C4',
- 'auml': '\u00E4',
- 'bdquo': '\u201E',
- 'Beta': '\u0392',
- 'beta': '\u03B2',
- 'brvbar': '\u00A6',
- 'bull': '\u2022',
- 'cap': '\u2229',
- 'Ccedil': '\u00C7',
- 'ccedil': '\u00E7',
- 'cedil': '\u00B8',
- 'cent': '\u00A2',
- 'Chi': '\u03A7',
- 'chi': '\u03C7',
- 'circ': '\u02C6',
- 'clubs': '\u2663',
- 'cong': '\u2245',
- 'copy': '\u00A9',
- 'crarr': '\u21B5',
- 'cup': '\u222A',
- 'curren': '\u00A4',
- 'dagger': '\u2020',
- 'Dagger': '\u2021',
- 'darr': '\u2193',
- 'dArr': '\u21D3',
- 'deg': '\u00B0',
- 'Delta': '\u0394',
- 'delta': '\u03B4',
- 'diams': '\u2666',
- 'divide': '\u00F7',
- 'Eacute': '\u00C9',
- 'eacute': '\u00E9',
- 'Ecirc': '\u00CA',
- 'ecirc': '\u00EA',
- 'Egrave': '\u00C8',
- 'egrave': '\u00E8',
- 'empty': '\u2205',
- 'emsp': '\u2003',
- 'ensp': '\u2002',
- 'Epsilon': '\u0395',
- 'epsilon': '\u03B5',
- 'equiv': '\u2261',
- 'Eta': '\u0397',
- 'eta': '\u03B7',
- 'ETH': '\u00D0',
- 'eth': '\u00F0',
- 'Euml': '\u00CB',
- 'euml': '\u00EB',
- 'euro': '\u20AC',
- 'exist': '\u2203',
- 'fnof': '\u0192',
- 'forall': '\u2200',
- 'frac12': '\u00BD',
- 'frac14': '\u00BC',
- 'frac34': '\u00BE',
- 'frasl': '\u2044',
- 'Gamma': '\u0393',
- 'gamma': '\u03B3',
- 'ge': '\u2265',
- 'gt': '>',
- 'harr': '\u2194',
- 'hArr': '\u21D4',
- 'hearts': '\u2665',
- 'hellip': '\u2026',
- 'Iacute': '\u00CD',
- 'iacute': '\u00ED',
- 'Icirc': '\u00CE',
- 'icirc': '\u00EE',
- 'iexcl': '\u00A1',
- 'Igrave': '\u00CC',
- 'igrave': '\u00EC',
- 'image': '\u2111',
- 'infin': '\u221E',
- 'int': '\u222B',
- 'Iota': '\u0399',
- 'iota': '\u03B9',
- 'iquest': '\u00BF',
- 'isin': '\u2208',
- 'Iuml': '\u00CF',
- 'iuml': '\u00EF',
- 'Kappa': '\u039A',
- 'kappa': '\u03BA',
- 'Lambda': '\u039B',
- 'lambda': '\u03BB',
- 'lang': '\u27E8',
- 'laquo': '\u00AB',
- 'larr': '\u2190',
- 'lArr': '\u21D0',
- 'lceil': '\u2308',
- 'ldquo': '\u201C',
- 'le': '\u2264',
- 'lfloor': '\u230A',
- 'lowast': '\u2217',
- 'loz': '\u25CA',
- 'lrm': '\u200E',
- 'lsaquo': '\u2039',
- 'lsquo': '\u2018',
- 'lt': '<',
- 'macr': '\u00AF',
- 'mdash': '\u2014',
- 'micro': '\u00B5',
- 'middot': '\u00B7',
- 'minus': '\u2212',
- 'Mu': '\u039C',
- 'mu': '\u03BC',
- 'nabla': '\u2207',
- 'nbsp': '\u00A0',
- 'ndash': '\u2013',
- 'ne': '\u2260',
- 'ni': '\u220B',
- 'not': '\u00AC',
- 'notin': '\u2209',
- 'nsub': '\u2284',
- 'Ntilde': '\u00D1',
- 'ntilde': '\u00F1',
- 'Nu': '\u039D',
- 'nu': '\u03BD',
- 'Oacute': '\u00D3',
- 'oacute': '\u00F3',
- 'Ocirc': '\u00D4',
- 'ocirc': '\u00F4',
- 'OElig': '\u0152',
- 'oelig': '\u0153',
- 'Ograve': '\u00D2',
- 'ograve': '\u00F2',
- 'oline': '\u203E',
- 'Omega': '\u03A9',
- 'omega': '\u03C9',
- 'Omicron': '\u039F',
- 'omicron': '\u03BF',
- 'oplus': '\u2295',
- 'or': '\u2228',
- 'ordf': '\u00AA',
- 'ordm': '\u00BA',
- 'Oslash': '\u00D8',
- 'oslash': '\u00F8',
- 'Otilde': '\u00D5',
- 'otilde': '\u00F5',
- 'otimes': '\u2297',
- 'Ouml': '\u00D6',
- 'ouml': '\u00F6',
- 'para': '\u00B6',
- 'permil': '\u2030',
- 'perp': '\u22A5',
- 'Phi': '\u03A6',
- 'phi': '\u03C6',
- 'Pi': '\u03A0',
- 'pi': '\u03C0',
- 'piv': '\u03D6',
- 'plusmn': '\u00B1',
- 'pound': '\u00A3',
- 'prime': '\u2032',
- 'Prime': '\u2033',
- 'prod': '\u220F',
- 'prop': '\u221D',
- 'Psi': '\u03A8',
- 'psi': '\u03C8',
- 'quot': '\u0022',
- 'radic': '\u221A',
- 'rang': '\u27E9',
- 'raquo': '\u00BB',
- 'rarr': '\u2192',
- 'rArr': '\u21D2',
- 'rceil': '\u2309',
- 'rdquo': '\u201D',
- 'real': '\u211C',
- 'reg': '\u00AE',
- 'rfloor': '\u230B',
- 'Rho': '\u03A1',
- 'rho': '\u03C1',
- 'rlm': '\u200F',
- 'rsaquo': '\u203A',
- 'rsquo': '\u2019',
- 'sbquo': '\u201A',
- 'Scaron': '\u0160',
- 'scaron': '\u0161',
- 'sdot': '\u22C5',
- 'sect': '\u00A7',
- 'shy': '\u00AD',
- 'Sigma': '\u03A3',
- 'sigma': '\u03C3',
- 'sigmaf': '\u03C2',
- 'sim': '\u223C',
- 'spades': '\u2660',
- 'sub': '\u2282',
- 'sube': '\u2286',
- 'sum': '\u2211',
- 'sup': '\u2283',
- 'sup1': '\u00B9',
- 'sup2': '\u00B2',
- 'sup3': '\u00B3',
- 'supe': '\u2287',
- 'szlig': '\u00DF',
- 'Tau': '\u03A4',
- 'tau': '\u03C4',
- 'there4': '\u2234',
- 'Theta': '\u0398',
- 'theta': '\u03B8',
- 'thetasym': '\u03D1',
- 'thinsp': '\u2009',
- 'THORN': '\u00DE',
- 'thorn': '\u00FE',
- 'tilde': '\u02DC',
- 'times': '\u00D7',
- 'trade': '\u2122',
- 'Uacute': '\u00DA',
- 'uacute': '\u00FA',
- 'uarr': '\u2191',
- 'uArr': '\u21D1',
- 'Ucirc': '\u00DB',
- 'ucirc': '\u00FB',
- 'Ugrave': '\u00D9',
- 'ugrave': '\u00F9',
- 'uml': '\u00A8',
- 'upsih': '\u03D2',
- 'Upsilon': '\u03A5',
- 'upsilon': '\u03C5',
- 'Uuml': '\u00DC',
- 'uuml': '\u00FC',
- 'weierp': '\u2118',
- 'Xi': '\u039E',
- 'xi': '\u03BE',
- 'Yacute': '\u00DD',
- 'yacute': '\u00FD',
- 'yen': '\u00A5',
- 'yuml': '\u00FF',
- 'Yuml': '\u0178',
- 'Zeta': '\u0396',
- 'zeta': '\u03B6',
- 'zwj': '\u200D',
- 'zwnj': '\u200C',
- };
+// see http://www.w3.org/TR/html51/syntax.html#named-character-references
+// see https://html.spec.whatwg.org/multipage/entities.json
+// This list is not exhaustive to keep the compiler footprint low.
+// The `{` / `ƫ` syntax should be used when the named character reference does not
+// exist.
+export const NAMED_ENTITIES: {[k: string]: string} = {
+ 'Aacute': '\u00C1',
+ 'aacute': '\u00E1',
+ 'Acirc': '\u00C2',
+ 'acirc': '\u00E2',
+ 'acute': '\u00B4',
+ 'AElig': '\u00C6',
+ 'aelig': '\u00E6',
+ 'Agrave': '\u00C0',
+ 'agrave': '\u00E0',
+ 'alefsym': '\u2135',
+ 'Alpha': '\u0391',
+ 'alpha': '\u03B1',
+ 'amp': '&',
+ 'and': '\u2227',
+ 'ang': '\u2220',
+ 'apos': '\u0027',
+ 'Aring': '\u00C5',
+ 'aring': '\u00E5',
+ 'asymp': '\u2248',
+ 'Atilde': '\u00C3',
+ 'atilde': '\u00E3',
+ 'Auml': '\u00C4',
+ 'auml': '\u00E4',
+ 'bdquo': '\u201E',
+ 'Beta': '\u0392',
+ 'beta': '\u03B2',
+ 'brvbar': '\u00A6',
+ 'bull': '\u2022',
+ 'cap': '\u2229',
+ 'Ccedil': '\u00C7',
+ 'ccedil': '\u00E7',
+ 'cedil': '\u00B8',
+ 'cent': '\u00A2',
+ 'Chi': '\u03A7',
+ 'chi': '\u03C7',
+ 'circ': '\u02C6',
+ 'clubs': '\u2663',
+ 'cong': '\u2245',
+ 'copy': '\u00A9',
+ 'crarr': '\u21B5',
+ 'cup': '\u222A',
+ 'curren': '\u00A4',
+ 'dagger': '\u2020',
+ 'Dagger': '\u2021',
+ 'darr': '\u2193',
+ 'dArr': '\u21D3',
+ 'deg': '\u00B0',
+ 'Delta': '\u0394',
+ 'delta': '\u03B4',
+ 'diams': '\u2666',
+ 'divide': '\u00F7',
+ 'Eacute': '\u00C9',
+ 'eacute': '\u00E9',
+ 'Ecirc': '\u00CA',
+ 'ecirc': '\u00EA',
+ 'Egrave': '\u00C8',
+ 'egrave': '\u00E8',
+ 'empty': '\u2205',
+ 'emsp': '\u2003',
+ 'ensp': '\u2002',
+ 'Epsilon': '\u0395',
+ 'epsilon': '\u03B5',
+ 'equiv': '\u2261',
+ 'Eta': '\u0397',
+ 'eta': '\u03B7',
+ 'ETH': '\u00D0',
+ 'eth': '\u00F0',
+ 'Euml': '\u00CB',
+ 'euml': '\u00EB',
+ 'euro': '\u20AC',
+ 'exist': '\u2203',
+ 'fnof': '\u0192',
+ 'forall': '\u2200',
+ 'frac12': '\u00BD',
+ 'frac14': '\u00BC',
+ 'frac34': '\u00BE',
+ 'frasl': '\u2044',
+ 'Gamma': '\u0393',
+ 'gamma': '\u03B3',
+ 'ge': '\u2265',
+ 'gt': '>',
+ 'harr': '\u2194',
+ 'hArr': '\u21D4',
+ 'hearts': '\u2665',
+ 'hellip': '\u2026',
+ 'Iacute': '\u00CD',
+ 'iacute': '\u00ED',
+ 'Icirc': '\u00CE',
+ 'icirc': '\u00EE',
+ 'iexcl': '\u00A1',
+ 'Igrave': '\u00CC',
+ 'igrave': '\u00EC',
+ 'image': '\u2111',
+ 'infin': '\u221E',
+ 'int': '\u222B',
+ 'Iota': '\u0399',
+ 'iota': '\u03B9',
+ 'iquest': '\u00BF',
+ 'isin': '\u2208',
+ 'Iuml': '\u00CF',
+ 'iuml': '\u00EF',
+ 'Kappa': '\u039A',
+ 'kappa': '\u03BA',
+ 'Lambda': '\u039B',
+ 'lambda': '\u03BB',
+ 'lang': '\u27E8',
+ 'laquo': '\u00AB',
+ 'larr': '\u2190',
+ 'lArr': '\u21D0',
+ 'lceil': '\u2308',
+ 'ldquo': '\u201C',
+ 'le': '\u2264',
+ 'lfloor': '\u230A',
+ 'lowast': '\u2217',
+ 'loz': '\u25CA',
+ 'lrm': '\u200E',
+ 'lsaquo': '\u2039',
+ 'lsquo': '\u2018',
+ 'lt': '<',
+ 'macr': '\u00AF',
+ 'mdash': '\u2014',
+ 'micro': '\u00B5',
+ 'middot': '\u00B7',
+ 'minus': '\u2212',
+ 'Mu': '\u039C',
+ 'mu': '\u03BC',
+ 'nabla': '\u2207',
+ 'nbsp': '\u00A0',
+ 'ndash': '\u2013',
+ 'ne': '\u2260',
+ 'ni': '\u220B',
+ 'not': '\u00AC',
+ 'notin': '\u2209',
+ 'nsub': '\u2284',
+ 'Ntilde': '\u00D1',
+ 'ntilde': '\u00F1',
+ 'Nu': '\u039D',
+ 'nu': '\u03BD',
+ 'Oacute': '\u00D3',
+ 'oacute': '\u00F3',
+ 'Ocirc': '\u00D4',
+ 'ocirc': '\u00F4',
+ 'OElig': '\u0152',
+ 'oelig': '\u0153',
+ 'Ograve': '\u00D2',
+ 'ograve': '\u00F2',
+ 'oline': '\u203E',
+ 'Omega': '\u03A9',
+ 'omega': '\u03C9',
+ 'Omicron': '\u039F',
+ 'omicron': '\u03BF',
+ 'oplus': '\u2295',
+ 'or': '\u2228',
+ 'ordf': '\u00AA',
+ 'ordm': '\u00BA',
+ 'Oslash': '\u00D8',
+ 'oslash': '\u00F8',
+ 'Otilde': '\u00D5',
+ 'otilde': '\u00F5',
+ 'otimes': '\u2297',
+ 'Ouml': '\u00D6',
+ 'ouml': '\u00F6',
+ 'para': '\u00B6',
+ 'permil': '\u2030',
+ 'perp': '\u22A5',
+ 'Phi': '\u03A6',
+ 'phi': '\u03C6',
+ 'Pi': '\u03A0',
+ 'pi': '\u03C0',
+ 'piv': '\u03D6',
+ 'plusmn': '\u00B1',
+ 'pound': '\u00A3',
+ 'prime': '\u2032',
+ 'Prime': '\u2033',
+ 'prod': '\u220F',
+ 'prop': '\u221D',
+ 'Psi': '\u03A8',
+ 'psi': '\u03C8',
+ 'quot': '\u0022',
+ 'radic': '\u221A',
+ 'rang': '\u27E9',
+ 'raquo': '\u00BB',
+ 'rarr': '\u2192',
+ 'rArr': '\u21D2',
+ 'rceil': '\u2309',
+ 'rdquo': '\u201D',
+ 'real': '\u211C',
+ 'reg': '\u00AE',
+ 'rfloor': '\u230B',
+ 'Rho': '\u03A1',
+ 'rho': '\u03C1',
+ 'rlm': '\u200F',
+ 'rsaquo': '\u203A',
+ 'rsquo': '\u2019',
+ 'sbquo': '\u201A',
+ 'Scaron': '\u0160',
+ 'scaron': '\u0161',
+ 'sdot': '\u22C5',
+ 'sect': '\u00A7',
+ 'shy': '\u00AD',
+ 'Sigma': '\u03A3',
+ 'sigma': '\u03C3',
+ 'sigmaf': '\u03C2',
+ 'sim': '\u223C',
+ 'spades': '\u2660',
+ 'sub': '\u2282',
+ 'sube': '\u2286',
+ 'sum': '\u2211',
+ 'sup': '\u2283',
+ 'sup1': '\u00B9',
+ 'sup2': '\u00B2',
+ 'sup3': '\u00B3',
+ 'supe': '\u2287',
+ 'szlig': '\u00DF',
+ 'Tau': '\u03A4',
+ 'tau': '\u03C4',
+ 'there4': '\u2234',
+ 'Theta': '\u0398',
+ 'theta': '\u03B8',
+ 'thetasym': '\u03D1',
+ 'thinsp': '\u2009',
+ 'THORN': '\u00DE',
+ 'thorn': '\u00FE',
+ 'tilde': '\u02DC',
+ 'times': '\u00D7',
+ 'trade': '\u2122',
+ 'Uacute': '\u00DA',
+ 'uacute': '\u00FA',
+ 'uarr': '\u2191',
+ 'uArr': '\u21D1',
+ 'Ucirc': '\u00DB',
+ 'ucirc': '\u00FB',
+ 'Ugrave': '\u00D9',
+ 'ugrave': '\u00F9',
+ 'uml': '\u00A8',
+ 'upsih': '\u03D2',
+ 'Upsilon': '\u03A5',
+ 'upsilon': '\u03C5',
+ 'Uuml': '\u00DC',
+ 'uuml': '\u00FC',
+ 'weierp': '\u2118',
+ 'Xi': '\u039E',
+ 'xi': '\u03BE',
+ 'Yacute': '\u00DD',
+ 'yacute': '\u00FD',
+ 'yen': '\u00A5',
+ 'yuml': '\u00FF',
+ 'Yuml': '\u0178',
+ 'Zeta': '\u0396',
+ 'zeta': '\u03B6',
+ 'zwj': '\u200D',
+ 'zwnj': '\u200C',
+};
diff --git a/packages/compiler/src/output/output_ast.ts b/packages/compiler/src/output/output_ast.ts
index 8cd09a93e1..d98f67f4d1 100644
--- a/packages/compiler/src/output/output_ast.ts
+++ b/packages/compiler/src/output/output_ast.ts
@@ -228,7 +228,7 @@ export class ReadVarExpr extends Expression {
set(value: Expression): WriteVarExpr {
if (!this.name) {
- throw new Error(`Built in variable ${this.builtin} can not be assigned to.`)
+ throw new Error(`Built in variable ${this.builtin} can not be assigned to.`);
}
return new WriteVarExpr(this.name, value, null, this.sourceSpan);
}
diff --git a/packages/compiler/src/style_url_resolver.ts b/packages/compiler/src/style_url_resolver.ts
index 1f2bbb5dd6..1a45ff0ddc 100644
--- a/packages/compiler/src/style_url_resolver.ts
+++ b/packages/compiler/src/style_url_resolver.ts
@@ -43,5 +43,5 @@ export function extractStyleUrls(
}
const CSS_IMPORT_REGEXP = /@import\s+(?:url\()?\s*(?:(?:['"]([^'"]*))|([^;\)\s]*))[^;]*;?/g;
-const CSS_COMMENT_REGEXP = /\/\*.+?\*\//g;
+const CSS_COMMENT_REGEXP = /\/\*[\s\S]+?\*\//g;
const URL_WITH_SCHEMA_REGEXP = /^([^:/?#]+):/;
diff --git a/packages/compiler/src/view_compiler/view_compiler.ts b/packages/compiler/src/view_compiler/view_compiler.ts
index 6398c06199..f0dca508b5 100644
--- a/packages/compiler/src/view_compiler/view_compiler.ts
+++ b/packages/compiler/src/view_compiler/view_compiler.ts
@@ -1071,4 +1071,4 @@ function calcStaticDynamicQueryFlags(
flags |= NodeFlags.DynamicQuery;
}
return flags;
-}
\ No newline at end of file
+}
diff --git a/packages/compiler/test/aot/compiler_spec.ts b/packages/compiler/test/aot/compiler_spec.ts
index faf041d9a8..a515126e51 100644
--- a/packages/compiler/test/aot/compiler_spec.ts
+++ b/packages/compiler/test/aot/compiler_spec.ts
@@ -28,7 +28,7 @@ describe('compiler (unbundled Angular)', () => {
describe('aot source mapping', () => {
const componentPath = '/app/app.component.ts';
- const ngComponentPath = 'ng:///app/app.component.ts'
+ const ngComponentPath = 'ng:///app/app.component.ts';
let rootDir: MockDirectory;
let appDir: MockDirectory;
diff --git a/packages/compiler/test/aot/static_reflector_spec.ts b/packages/compiler/test/aot/static_reflector_spec.ts
index 8d72f75166..c594c15a30 100644
--- a/packages/compiler/test/aot/static_reflector_spec.ts
+++ b/packages/compiler/test/aot/static_reflector_spec.ts
@@ -462,6 +462,20 @@ describe('StaticReflector', () => {
expect(annotations[0].providers[0].useValue.members[0]).toEqual('staticMethod');
});
+ it('should be able to get metadata for a class calling a macro function', () => {
+ const annotations = reflector.annotations(
+ reflector.getStaticSymbol('/tmp/src/call-macro-function.ts', 'MyComponent'));
+ expect(annotations.length).toBe(1);
+ expect(annotations[0].providers.useValue).toBe(100);
+ });
+
+ it('should be able to get metadata for a class calling a nested macro function', () => {
+ const annotations = reflector.annotations(
+ reflector.getStaticSymbol('/tmp/src/call-macro-function.ts', 'MyComponentNested'));
+ expect(annotations.length).toBe(1);
+ expect(annotations[0].providers.useValue.useValue).toBe(100);
+ });
+
// #13605
it('should not throw on unknown decorators', () => {
const data = Object.create(DEFAULT_TEST_DATA);
@@ -1392,6 +1406,25 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
static VALUE = 'Some string';
}
`,
+ '/tmp/src/macro-function.ts': `
+ export function v(value: any) {
+ return { provide: 'a', useValue: value };
+ }
+ `,
+ '/tmp/src/call-macro-function.ts': `
+ import {Component} from '@angular/core';
+ import {v} from './macro-function';
+
+ @Component({
+ providers: v(100)
+ })
+ export class MyComponent { }
+
+ @Component({
+ providers: v(v(100))
+ })
+ export class MyComponentNested { }
+ `,
'/tmp/src/static-field-reference.ts': `
import {Component} from '@angular/core';
import {MyModule} from './static-field';
diff --git a/packages/compiler/test/aot/static_symbol_resolver_spec.ts b/packages/compiler/test/aot/static_symbol_resolver_spec.ts
index eba341aac7..4ee26ff77c 100644
--- a/packages/compiler/test/aot/static_symbol_resolver_spec.ts
+++ b/packages/compiler/test/aot/static_symbol_resolver_spec.ts
@@ -459,7 +459,7 @@ export class MockStaticSymbolResolverHost implements StaticSymbolResolverHost {
const errors =
diagnostics
.map(d => {
- const {line, character} = ts.getLineAndCharacterOfPosition(d.file, d.start);
+ const {line, character} = ts.getLineAndCharacterOfPosition(d.file !, d.start !);
return `(${line}:${character}): ${d.messageText}`;
})
.join('\n');
diff --git a/packages/compiler/test/aot/test_util.ts b/packages/compiler/test/aot/test_util.ts
index 93b3c7b25b..7ea8633c78 100644
--- a/packages/compiler/test/aot/test_util.ts
+++ b/packages/compiler/test/aot/test_util.ts
@@ -559,8 +559,8 @@ export function expectNoDiagnostics(program: ts.Program) {
function lineInfo(diagnostic: ts.Diagnostic): string {
if (diagnostic.file) {
- const start = diagnostic.start;
- let end = diagnostic.start + diagnostic.length;
+ const start = diagnostic.start !;
+ let end = diagnostic.start ! + diagnostic.length !;
const source = diagnostic.file.text;
let lineStart = start;
let lineEnd = end;
diff --git a/packages/compiler/test/directive_normalizer_spec.ts b/packages/compiler/test/directive_normalizer_spec.ts
index 114f2672a0..bd68115eb1 100644
--- a/packages/compiler/test/directive_normalizer_spec.ts
+++ b/packages/compiler/test/directive_normalizer_spec.ts
@@ -328,10 +328,7 @@ export function main() {
describe('normalizeExternalStylesheets', () => {
- beforeEach(() => {
- TestBed.configureCompiler(
- {providers: [{provide: ResourceLoader, useClass: SpyResourceLoader}]});
- });
+ beforeEach(() => { TestBed.configureCompiler({providers: [SpyResourceLoader.PROVIDE]}); });
it('should load an external stylesheet',
inject(
diff --git a/packages/compiler/test/i18n/extractor_merger_spec.ts b/packages/compiler/test/i18n/extractor_merger_spec.ts
index 641b769c3f..0618e0408f 100644
--- a/packages/compiler/test/i18n/extractor_merger_spec.ts
+++ b/packages/compiler/test/i18n/extractor_merger_spec.ts
@@ -490,7 +490,7 @@ export function main() {
.toEqual(
'foo');
});
- })
+ });
});
}
diff --git a/packages/compiler/test/i18n/integration_common.ts b/packages/compiler/test/i18n/integration_common.ts
index b2abdb8d0d..4d7e37d620 100644
--- a/packages/compiler/test/i18n/integration_common.ts
+++ b/packages/compiler/test/i18n/integration_common.ts
@@ -26,6 +26,7 @@ export class I18nComponent {
}
export class FrLocalization extends NgLocalization {
+ public static PROVIDE = {provide: NgLocalization, useClass: FrLocalization, deps: []};
getPluralCategory(value: number): string {
switch (value) {
case 0:
diff --git a/packages/compiler/test/i18n/integration_xliff2_spec.ts b/packages/compiler/test/i18n/integration_xliff2_spec.ts
index 3594db225a..add927a8c1 100644
--- a/packages/compiler/test/i18n/integration_xliff2_spec.ts
+++ b/packages/compiler/test/i18n/integration_xliff2_spec.ts
@@ -26,8 +26,8 @@ export function main() {
beforeEach(async(() => {
TestBed.configureCompiler({
providers: [
- {provide: ResourceLoader, useClass: SpyResourceLoader},
- {provide: NgLocalization, useClass: FrLocalization},
+ SpyResourceLoader.PROVIDE,
+ FrLocalization.PROVIDE,
{provide: TRANSLATIONS, useValue: XLIFF2_TOMERGE},
{provide: TRANSLATIONS_FORMAT, useValue: 'xlf2'},
]
diff --git a/packages/compiler/test/i18n/integration_xliff_spec.ts b/packages/compiler/test/i18n/integration_xliff_spec.ts
index 1bcc2f0cc6..7355fff485 100644
--- a/packages/compiler/test/i18n/integration_xliff_spec.ts
+++ b/packages/compiler/test/i18n/integration_xliff_spec.ts
@@ -26,8 +26,8 @@ export function main() {
beforeEach(async(() => {
TestBed.configureCompiler({
providers: [
- {provide: ResourceLoader, useClass: SpyResourceLoader},
- {provide: NgLocalization, useClass: FrLocalization},
+ SpyResourceLoader.PROVIDE,
+ FrLocalization.PROVIDE,
{provide: TRANSLATIONS, useValue: XLIFF_TOMERGE},
{provide: TRANSLATIONS_FORMAT, useValue: 'xliff'},
]
diff --git a/packages/compiler/test/i18n/integration_xmb_xtb_spec.ts b/packages/compiler/test/i18n/integration_xmb_xtb_spec.ts
index ebd7c324f7..88f249a346 100644
--- a/packages/compiler/test/i18n/integration_xmb_xtb_spec.ts
+++ b/packages/compiler/test/i18n/integration_xmb_xtb_spec.ts
@@ -26,8 +26,8 @@ export function main() {
beforeEach(async(() => {
TestBed.configureCompiler({
providers: [
- {provide: ResourceLoader, useClass: SpyResourceLoader},
- {provide: NgLocalization, useClass: FrLocalization},
+ SpyResourceLoader.PROVIDE,
+ FrLocalization.PROVIDE,
{provide: TRANSLATIONS, useValue: XTB},
{provide: TRANSLATIONS_FORMAT, useValue: 'xtb'},
]
diff --git a/packages/compiler/test/ml_parser/html_parser_spec.ts b/packages/compiler/test/ml_parser/html_parser_spec.ts
index 1a8b2bd630..837ec0ebb0 100644
--- a/packages/compiler/test/ml_parser/html_parser_spec.ts
+++ b/packages/compiler/test/ml_parser/html_parser_spec.ts
@@ -152,6 +152,16 @@ export function main() {
]);
});
+ it('should append the required parent considering top level ng-container', () => {
+ expect(humanizeDom(
+ parser.parse('
', 'TestComp')))
+ .toEqual([
+ [html.Element, 'ng-container', 0],
+ [html.Element, 'tr', 1],
+ [html.Element, 'p', 0],
+ ]);
+ });
+
it('should special case ng-container when adding a required parent', () => {
expect(humanizeDom(parser.parse(
'',
diff --git a/packages/compiler/test/output/output_jit_spec.ts b/packages/compiler/test/output/output_jit_spec.ts
index 017ddf8cb6..b9aad8dadf 100644
--- a/packages/compiler/test/output/output_jit_spec.ts
+++ b/packages/compiler/test/output/output_jit_spec.ts
@@ -31,5 +31,5 @@ export function main() {
expect(Object.keys(args).length).toBe(20);
});
});
- })
+ });
}
\ No newline at end of file
diff --git a/packages/compiler/test/parse_util_spec.ts b/packages/compiler/test/parse_util_spec.ts
index 65521cf451..9c9ce43f0b 100644
--- a/packages/compiler/test/parse_util_spec.ts
+++ b/packages/compiler/test/parse_util_spec.ts
@@ -6,23 +6,21 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {ParseError, ParseErrorLevel, ParseLocation, ParseSourceFile, ParseSourceSpan} from '../src/parse_util'
+import {ParseError, ParseErrorLevel, ParseLocation, ParseSourceFile, ParseSourceSpan} from '../src/parse_util';
export function main() {
- describe(
- 'ParseError',
- () => {
- it('should reflect the level in the message', () => {
- const file = new ParseSourceFile(`foo\nbar\nfoo`, 'url');
- const start = new ParseLocation(file, 4, 1, 0);
- const end = new ParseLocation(file, 6, 1, 2);
- const span = new ParseSourceSpan(start, end);
+ describe('ParseError', () => {
+ it('should reflect the level in the message', () => {
+ const file = new ParseSourceFile(`foo\nbar\nfoo`, 'url');
+ const start = new ParseLocation(file, 4, 1, 0);
+ const end = new ParseLocation(file, 6, 1, 2);
+ const span = new ParseSourceSpan(start, end);
- const fatal = new ParseError(span, 'fatal', ParseErrorLevel.ERROR);
- expect(fatal.toString()).toEqual('fatal ("foo\n[ERROR ->]bar\nfoo"): url@1:0');
+ const fatal = new ParseError(span, 'fatal', ParseErrorLevel.ERROR);
+ expect(fatal.toString()).toEqual('fatal ("foo\n[ERROR ->]bar\nfoo"): url@1:0');
- const warning = new ParseError(span, 'warning', ParseErrorLevel.WARNING);
- expect(warning.toString()).toEqual('warning ("foo\n[WARNING ->]bar\nfoo"): url@1:0');
- });
- });
+ const warning = new ParseError(span, 'warning', ParseErrorLevel.WARNING);
+ expect(warning.toString()).toEqual('warning ("foo\n[WARNING ->]bar\nfoo"): url@1:0');
+ });
+ });
}
\ No newline at end of file
diff --git a/packages/compiler/test/runtime_compiler_spec.ts b/packages/compiler/test/runtime_compiler_spec.ts
index 318ca06a1b..199e9e2827 100644
--- a/packages/compiler/test/runtime_compiler_spec.ts
+++ b/packages/compiler/test/runtime_compiler_spec.ts
@@ -36,7 +36,7 @@ export function main() {
beforeEach(() => {
TestBed.configureCompiler(
- {providers: [{provide: ResourceLoader, useClass: StubResourceLoader}]});
+ {providers: [{provide: ResourceLoader, useClass: StubResourceLoader, deps: []}]});
});
it('should throw when using a templateUrl that has not been compiled before', async(() => {
@@ -68,7 +68,7 @@ export function main() {
beforeEach(() => {
TestBed.configureCompiler(
- {providers: [{provide: ResourceLoader, useClass: StubResourceLoader}]});
+ {providers: [{provide: ResourceLoader, useClass: StubResourceLoader, deps: []}]});
});
it('should allow to use templateUrl components that have been loaded before', async(() => {
@@ -88,10 +88,7 @@ export function main() {
let dirResolver: MockDirectiveResolver;
let injector: Injector;
- beforeEach(() => {
- TestBed.configureCompiler(
- {providers: [{provide: ResourceLoader, useClass: SpyResourceLoader}]});
- });
+ beforeEach(() => { TestBed.configureCompiler({providers: [SpyResourceLoader.PROVIDE]}); });
beforeEach(fakeAsync(inject(
[Compiler, ResourceLoader, DirectiveResolver, Injector],
diff --git a/packages/compiler/test/spies.ts b/packages/compiler/test/spies.ts
index b86939489a..54c2263c4a 100644
--- a/packages/compiler/test/spies.ts
+++ b/packages/compiler/test/spies.ts
@@ -11,5 +11,6 @@ import {ResourceLoader} from '@angular/compiler/src/resource_loader';
import {SpyObject} from '@angular/core/testing/src/testing_internal';
export class SpyResourceLoader extends SpyObject {
+ public static PROVIDE = {provide: ResourceLoader, useClass: SpyResourceLoader, deps: []};
constructor() { super(ResourceLoader); }
}
diff --git a/packages/compiler/test/style_url_resolver_spec.ts b/packages/compiler/test/style_url_resolver_spec.ts
index 5b7b548f1e..3b135e9925 100644
--- a/packages/compiler/test/style_url_resolver_spec.ts
+++ b/packages/compiler/test/style_url_resolver_spec.ts
@@ -40,11 +40,15 @@ export function main() {
const css = `
@import '1.css';
/*@import '2.css';*/
+ /*
+ @import '3.css';
+ */
`;
const styleWithImports = extractStyleUrls(urlResolver, 'http://ng.io', css);
expect(styleWithImports.style.trim()).toEqual('');
expect(styleWithImports.styleUrls).toContain('http://ng.io/1.css');
expect(styleWithImports.styleUrls).not.toContain('http://ng.io/2.css');
+ expect(styleWithImports.styleUrls).not.toContain('http://ng.io/3.css');
});
it('should extract "@import url()" urls', () => {
diff --git a/packages/compiler/test/template_parser/template_parser_spec.ts b/packages/compiler/test/template_parser/template_parser_spec.ts
index c0a2fe8335..8204b2f67c 100644
--- a/packages/compiler/test/template_parser/template_parser_spec.ts
+++ b/packages/compiler/test/template_parser/template_parser_spec.ts
@@ -308,7 +308,7 @@ export function main() {
TestBed.configureCompiler({
providers: [
TEST_COMPILER_PROVIDERS,
- {provide: ElementSchemaRegistry, useClass: DomElementSchemaRegistry}
+ {provide: ElementSchemaRegistry, useClass: DomElementSchemaRegistry, deps: []}
]
});
});
diff --git a/packages/compiler/testing/src/test_bindings.ts b/packages/compiler/testing/src/test_bindings.ts
index 8812ac8ecb..4956bf64f4 100644
--- a/packages/compiler/testing/src/test_bindings.ts
+++ b/packages/compiler/testing/src/test_bindings.ts
@@ -20,6 +20,6 @@ export function createUrlResolverWithoutPackagePrefix(): UrlResolver {
// TODO: get rid of it or move to a separate @angular/internal_testing package
export const TEST_COMPILER_PROVIDERS: Provider[] = [
{provide: ElementSchemaRegistry, useValue: new MockSchemaRegistry({}, {}, {}, [], [])},
- {provide: ResourceLoader, useClass: MockResourceLoader},
- {provide: UrlResolver, useFactory: createUrlResolverWithoutPackagePrefix}
+ {provide: ResourceLoader, useClass: MockResourceLoader, deps: []},
+ {provide: UrlResolver, useFactory: createUrlResolverWithoutPackagePrefix, deps: []}
];
diff --git a/packages/compiler/testing/src/testing.ts b/packages/compiler/testing/src/testing.ts
index c77be42fd8..0d259fa709 100644
--- a/packages/compiler/testing/src/testing.ts
+++ b/packages/compiler/testing/src/testing.ts
@@ -28,7 +28,7 @@ export * from './pipe_resolver_mock';
import {createPlatformFactory, ModuleWithComponentFactories, Injectable, CompilerOptions, COMPILER_OPTIONS, CompilerFactory, ComponentFactory, NgModuleFactory, Injector, NgModule, Component, Directive, Pipe, Type, PlatformRef, ɵstringify} from '@angular/core';
import {MetadataOverride, ɵTestingCompilerFactory as TestingCompilerFactory, ɵTestingCompiler as TestingCompiler} from '@angular/core/testing';
-import {platformCoreDynamic, JitCompiler, DirectiveResolver, NgModuleResolver, PipeResolver, CompileMetadataResolver} from '@angular/compiler';
+import {platformCoreDynamic, JitCompiler, DirectiveResolver, NgModuleResolver, PipeResolver, CompileMetadataResolver, CompileReflector} from '@angular/compiler';
import {MockDirectiveResolver} from './directive_resolver_mock';
import {MockNgModuleResolver} from './ng_module_resolver_mock';
import {MockPipeResolver} from './pipe_resolver_mock';
@@ -124,15 +124,19 @@ export const platformCoreDynamicTesting: (extraProviders?: any[]) => PlatformRef
provide: COMPILER_OPTIONS,
useValue: {
providers: [
- MockPipeResolver,
+ {provide: MockPipeResolver, deps: [Injector, CompileReflector]},
{provide: PipeResolver, useExisting: MockPipeResolver},
- MockDirectiveResolver,
+ {provide: MockDirectiveResolver, deps: [Injector, CompileReflector]},
{provide: DirectiveResolver, useExisting: MockDirectiveResolver},
- MockNgModuleResolver,
+ {provide: MockNgModuleResolver, deps: [Injector, CompileReflector]},
{provide: NgModuleResolver, useExisting: MockNgModuleResolver},
]
},
multi: true
},
- {provide: TestingCompilerFactory, useClass: TestingCompilerFactoryImpl}
+ {
+ provide: TestingCompilerFactory,
+ useClass: TestingCompilerFactoryImpl,
+ deps: [CompilerFactory]
+ }
]);
diff --git a/packages/core/src/application_init.ts b/packages/core/src/application_init.ts
index fdc1966a00..b801ab26b4 100644
--- a/packages/core/src/application_init.ts
+++ b/packages/core/src/application_init.ts
@@ -45,11 +45,10 @@ export class ApplicationInitStatus {
const asyncInitPromises: Promise[] = [];
- const complete =
- () => {
- this._done = true;
- this.resolve();
- }
+ const complete = () => {
+ this._done = true;
+ this.resolve();
+ };
if (this.appInits) {
for (let i = 0; i < this.appInits.length; i++) {
diff --git a/packages/core/src/application_ref.ts b/packages/core/src/application_ref.ts
index ebb6a28b2d..a3dd39d563 100644
--- a/packages/core/src/application_ref.ts
+++ b/packages/core/src/application_ref.ts
@@ -19,7 +19,7 @@ import {isPromise} from '../src/util/lang';
import {ApplicationInitStatus} from './application_init';
import {APP_BOOTSTRAP_LISTENER, PLATFORM_INITIALIZER} from './application_tokens';
import {Console} from './console';
-import {Injectable, InjectionToken, Injector, Provider, ReflectiveInjector} from './di';
+import {Injectable, InjectionToken, Injector, StaticProvider} from './di';
import {CompilerFactory, CompilerOptions} from './linker/compiler';
import {ComponentFactory, ComponentRef} from './linker/component_factory';
import {ComponentFactoryBoundToModule, ComponentFactoryResolver} from './linker/component_factory_resolver';
@@ -99,17 +99,18 @@ export function createPlatform(injector: Injector): PlatformRef {
* @experimental APIs related to application bootstrap are currently under review.
*/
export function createPlatformFactory(
- parentPlatformFactory: ((extraProviders?: Provider[]) => PlatformRef) | null, name: string,
- providers: Provider[] = []): (extraProviders?: Provider[]) => PlatformRef {
+ parentPlatformFactory: ((extraProviders?: StaticProvider[]) => PlatformRef) | null,
+ name: string, providers: StaticProvider[] = []): (extraProviders?: StaticProvider[]) =>
+ PlatformRef {
const marker = new InjectionToken(`Platform: ${name}`);
- return (extraProviders: Provider[] = []) => {
+ return (extraProviders: StaticProvider[] = []) => {
let platform = getPlatform();
if (!platform || platform.injector.get(ALLOW_MULTIPLE_PLATFORMS, false)) {
if (parentPlatformFactory) {
parentPlatformFactory(
providers.concat(extraProviders).concat({provide: marker, useValue: true}));
} else {
- createPlatform(ReflectiveInjector.resolveAndCreate(
+ createPlatform(Injector.create(
providers.concat(extraProviders).concat({provide: marker, useValue: true})));
}
}
@@ -292,8 +293,7 @@ export class PlatformRef_ extends PlatformRef {
// Attention: Don't use ApplicationRef.run here,
// as we want to be sure that all possible constructor calls are inside `ngZone.run`!
return ngZone.run(() => {
- const ngZoneInjector =
- ReflectiveInjector.resolveAndCreate([{provide: NgZone, useValue: ngZone}], this.injector);
+ const ngZoneInjector = Injector.create([{provide: NgZone, useValue: ngZone}], this.injector);
const moduleRef = >moduleFactory.create(ngZoneInjector);
const exceptionHandler: ErrorHandler = moduleRef.injector.get(ErrorHandler, null);
if (!exceptionHandler) {
diff --git a/packages/core/src/change_detection/differs/default_iterable_differ.ts b/packages/core/src/change_detection/differs/default_iterable_differ.ts
index 6f5686b165..a73a597904 100644
--- a/packages/core/src/change_detection/differs/default_iterable_differ.ts
+++ b/packages/core/src/change_detection/differs/default_iterable_differ.ts
@@ -52,11 +52,9 @@ export class DefaultIterableDiffer implements IterableDiffer, IterableChan
// Keeps track of records where custom track by is the same, but item identity has changed
private _identityChangesHead: IterableChangeRecord_|null = null;
private _identityChangesTail: IterableChangeRecord_|null = null;
- private _trackByFn: TrackByFunction
+ private _trackByFn: TrackByFunction;
- constructor(trackByFn?: TrackByFunction) {
- this._trackByFn = trackByFn || trackByIdentity;
- }
+ constructor(trackByFn?: TrackByFunction) { this._trackByFn = trackByFn || trackByIdentity; }
get collection() { return this._collection; }
diff --git a/packages/core/src/change_detection/differs/iterable_differs.ts b/packages/core/src/change_detection/differs/iterable_differs.ts
index 72bad7296b..d1daadc3b5 100644
--- a/packages/core/src/change_detection/differs/iterable_differs.ts
+++ b/packages/core/src/change_detection/differs/iterable_differs.ts
@@ -6,9 +6,10 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {Optional, Provider, SkipSelf} from '../../di';
+import {Optional, SkipSelf, StaticProvider} from '../../di';
import {ChangeDetectorRef} from '../change_detector_ref';
+
/**
* A type describing supported iterable types.
*
@@ -181,7 +182,7 @@ export class IterableDiffers {
* })
* ```
*/
- static extend(factories: IterableDifferFactory[]): Provider {
+ static extend(factories: IterableDifferFactory[]): StaticProvider {
return {
provide: IterableDiffers,
useFactory: (parent: IterableDiffers) => {
diff --git a/packages/core/src/change_detection/differs/keyvalue_differs.ts b/packages/core/src/change_detection/differs/keyvalue_differs.ts
index f401ab8389..c6637ecf9d 100644
--- a/packages/core/src/change_detection/differs/keyvalue_differs.ts
+++ b/packages/core/src/change_detection/differs/keyvalue_differs.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {Optional, Provider, SkipSelf} from '../../di';
+import {Optional, SkipSelf, StaticProvider} from '../../di';
import {ChangeDetectorRef} from '../change_detector_ref';
@@ -156,7 +156,7 @@ export class KeyValueDiffers {
* })
* ```
*/
- static extend(factories: KeyValueDifferFactory[]): Provider {
+ static extend(factories: KeyValueDifferFactory[]): StaticProvider {
return {
provide: KeyValueDiffers,
useFactory: (parent: KeyValueDiffers) => {
diff --git a/packages/core/src/di.ts b/packages/core/src/di.ts
index cbfcbaf62d..976fd3f5e4 100644
--- a/packages/core/src/di.ts
+++ b/packages/core/src/di.ts
@@ -18,7 +18,7 @@ export {forwardRef, resolveForwardRef, ForwardRefFn} from './di/forward_ref';
export {Injector} from './di/injector';
export {ReflectiveInjector} from './di/reflective_injector';
-export {Provider, TypeProvider, ValueProvider, ClassProvider, ExistingProvider, FactoryProvider} from './di/provider';
+export {StaticProvider, ValueProvider, ExistingProvider, FactoryProvider, Provider, TypeProvider, ClassProvider} from './di/provider';
export {ResolvedReflectiveFactory, ResolvedReflectiveProvider} from './di/reflective_provider';
export {ReflectiveKey} from './di/reflective_key';
export {InjectionToken, OpaqueToken} from './di/injection_token';
diff --git a/packages/core/src/di/injector.ts b/packages/core/src/di/injector.ts
index d640c81a08..a0a380a414 100644
--- a/packages/core/src/di/injector.ts
+++ b/packages/core/src/di/injector.ts
@@ -9,7 +9,10 @@
import {Type} from '../type';
import {stringify} from '../util';
+import {resolveForwardRef} from './forward_ref';
import {InjectionToken} from './injection_token';
+import {Inject, Optional, Self, SkipSelf} from './metadata';
+import {ConstructorProvider, ExistingProvider, FactoryProvider, StaticClassProvider, StaticProvider, ValueProvider} from './provider';
const _THROW_IF_NOT_FOUND = new Object();
export const THROW_IF_NOT_FOUND = _THROW_IF_NOT_FOUND;
@@ -17,7 +20,7 @@ export const THROW_IF_NOT_FOUND = _THROW_IF_NOT_FOUND;
class _NullInjector implements Injector {
get(token: any, notFoundValue: any = _THROW_IF_NOT_FOUND): any {
if (notFoundValue === _THROW_IF_NOT_FOUND) {
- throw new Error(`No provider for ${stringify(token)}!`);
+ throw new Error(`NullInjectorError: No provider for ${stringify(token)}!`);
}
return notFoundValue;
}
@@ -60,4 +63,307 @@ export abstract class Injector {
* @suppress {duplicate}
*/
abstract get(token: any, notFoundValue?: any): any;
+
+ /**
+ * Create a new Injector which is configure using `StaticProvider`s.
+ *
+ * ### Example
+ *
+ * {@example core/di/ts/provider_spec.ts region='ConstructorProvider'}
+ */
+ static create(providers: StaticProvider[], parent?: Injector): Injector {
+ return new StaticInjector(providers, parent);
+ }
+}
+
+
+
+const IDENT = function(value: T): T {
+ return value;
+};
+const EMPTY = [];
+const CIRCULAR = IDENT;
+const MULTI_PROVIDER_FN = function(): any[] {
+ return Array.prototype.slice.call(arguments);
+};
+const GET_PROPERTY_NAME = {} as any;
+const USE_VALUE =
+ getClosureSafeProperty({provide: String, useValue: GET_PROPERTY_NAME});
+const NG_TOKEN_PATH = 'ngTokenPath';
+const NG_TEMP_TOKEN_PATH = 'ngTempTokenPath';
+const enum OptionFlags {
+ Optional = 1 << 0,
+ CheckSelf = 1 << 1,
+ CheckParent = 1 << 2,
+ Default = CheckSelf | CheckParent
+}
+const NULL_INJECTOR = Injector.NULL;
+const NEW_LINE = /\n/gm;
+const NO_NEW_LINE = 'ɵ';
+
+export class StaticInjector implements Injector {
+ readonly parent: Injector;
+
+ private _records: Map;
+
+ constructor(providers: StaticProvider[], parent: Injector = NULL_INJECTOR) {
+ this.parent = parent;
+ const records = this._records = new Map();
+ records.set(
+ Injector, {token: Injector, fn: IDENT, deps: EMPTY, value: this, useNew: false});
+ recursivelyProcessProviders(records, providers);
+ }
+
+ get(token: Type|InjectionToken, notFoundValue?: T): T;
+ get(token: any, notFoundValue?: any): any;
+ get(token: any, notFoundValue?: any): any {
+ const record = this._records.get(token);
+ try {
+ return tryResolveToken(token, record, this._records, this.parent, notFoundValue);
+ } catch (e) {
+ const tokenPath: any[] = e[NG_TEMP_TOKEN_PATH];
+ e.message = formatError('\n' + e.message, tokenPath);
+ e[NG_TOKEN_PATH] = tokenPath;
+ e[NG_TEMP_TOKEN_PATH] = null;
+ throw e;
+ }
+ }
+
+ toString() {
+ const tokens = [], records = this._records;
+ records.forEach((v, token) => tokens.push(stringify(token)));
+ return `StaticInjector[${tokens.join(', ')}]`;
+ }
+}
+
+type SupportedProvider =
+ ValueProvider | ExistingProvider | StaticClassProvider | ConstructorProvider | FactoryProvider;
+
+interface Record {
+ fn: Function;
+ useNew: boolean;
+ deps: DependencyRecord[];
+ value: any;
+}
+
+interface DependencyRecord {
+ token: any;
+ options: number;
+}
+
+type TokenPath = Array;
+
+function resolveProvider(provider: SupportedProvider): Record {
+ const deps = computeDeps(provider);
+ let fn: Function = IDENT;
+ let value: any = EMPTY;
+ let useNew: boolean = false;
+ let provide = resolveForwardRef(provider.provide);
+ if (USE_VALUE in provider) {
+ // We need to use USE_VALUE in provider since provider.useValue could be defined as undefined.
+ value = (provider as ValueProvider).useValue;
+ } else if ((provider as FactoryProvider).useFactory) {
+ fn = (provider as FactoryProvider).useFactory;
+ } else if ((provider as ExistingProvider).useExisting) {
+ // Just use IDENT
+ } else if ((provider as StaticClassProvider).useClass) {
+ useNew = true;
+ fn = resolveForwardRef((provider as StaticClassProvider).useClass);
+ } else if (typeof provide == 'function') {
+ useNew = true;
+ fn = provide;
+ } else {
+ throw staticError(
+ 'StaticProvider does not have [useValue|useFactory|useExisting|useClass] or [provide] is not newable',
+ provider);
+ }
+ return {deps, fn, useNew, value};
+}
+
+function multiProviderMixError(token: any) {
+ return staticError('Cannot mix multi providers and regular providers', token);
+}
+
+function recursivelyProcessProviders(records: Map, provider: StaticProvider) {
+ if (provider) {
+ provider = resolveForwardRef(provider);
+ if (provider instanceof Array) {
+ // if we have an array recurse into the array
+ for (let i = 0; i < provider.length; i++) {
+ recursivelyProcessProviders(records, provider[i]);
+ }
+ } else if (typeof provider === 'function') {
+ // Functions were supported in ReflectiveInjector, but are not here. For safety give useful
+ // error messages
+ throw staticError('Function/Class not supported', provider);
+ } else if (provider && typeof provider === 'object' && provider.provide) {
+ // At this point we have what looks like a provider: {provide: ?, ....}
+ let token = resolveForwardRef(provider.provide);
+ const resolvedProvider = resolveProvider(provider);
+ if (provider.multi === true) {
+ // This is a multi provider.
+ let multiProvider: Record|undefined = records.get(token);
+ if (multiProvider) {
+ if (multiProvider.fn !== MULTI_PROVIDER_FN) {
+ throw multiProviderMixError(token);
+ }
+ } else {
+ // Create a placeholder factory which will look up the constituents of the multi provider.
+ records.set(token, multiProvider = {
+ token: provider.provide,
+ deps: [],
+ useNew: false,
+ fn: MULTI_PROVIDER_FN,
+ value: EMPTY
+ });
+ }
+ // Treat the provider as the token.
+ token = provider;
+ multiProvider.deps.push({token, options: OptionFlags.Default});
+ }
+ const record = records.get(token);
+ if (record && record.fn == MULTI_PROVIDER_FN) {
+ throw multiProviderMixError(token);
+ }
+ records.set(token, resolvedProvider);
+ } else {
+ throw staticError('Unexpected provider', provider);
+ }
+ }
+}
+
+function tryResolveToken(
+ token: any, record: Record | undefined, records: Map, parent: Injector,
+ notFoundValue: any): any {
+ try {
+ return resolveToken(token, record, records, parent, notFoundValue);
+ } catch (e) {
+ // ensure that 'e' is of type Error.
+ if (!(e instanceof Error)) {
+ e = new Error(e);
+ }
+ const path: any[] = e[NG_TEMP_TOKEN_PATH] = e[NG_TEMP_TOKEN_PATH] || [];
+ path.unshift(token);
+ if (record && record.value == CIRCULAR) {
+ // Reset the Circular flag.
+ record.value = EMPTY;
+ }
+ throw e;
+ }
+}
+
+function resolveToken(
+ token: any, record: Record | undefined, records: Map, parent: Injector,
+ notFoundValue: any): any {
+ let value;
+ if (record) {
+ // If we don't have a record, this implies that we don't own the provider hence don't know how
+ // to resolve it.
+ value = record.value;
+ if (value == CIRCULAR) {
+ throw Error(NO_NEW_LINE + 'Circular dependency');
+ } else if (value === EMPTY) {
+ record.value = CIRCULAR;
+ let obj = undefined;
+ let useNew = record.useNew;
+ let fn = record.fn;
+ let depRecords = record.deps;
+ let deps = EMPTY;
+ if (depRecords.length) {
+ deps = [];
+ for (let i = 0; i < depRecords.length; i++) {
+ const depRecord: DependencyRecord = depRecords[i];
+ const options = depRecord.options;
+ const childRecord =
+ options & OptionFlags.CheckSelf ? records.get(depRecord.token) : undefined;
+ deps.push(tryResolveToken(
+ // Current Token to resolve
+ depRecord.token,
+ // A record which describes how to resolve the token.
+ // If undefined, this means we don't have such a record
+ childRecord,
+ // Other records we know about.
+ records,
+ // If we don't know how to resolve dependency and we should not check parent for it,
+ // than pass in Null injector.
+ !childRecord && !(options & OptionFlags.CheckParent) ? NULL_INJECTOR : parent,
+ options & OptionFlags.Optional ? null : Injector.THROW_IF_NOT_FOUND));
+ }
+ }
+ record.value = value = useNew ? new (fn as any)(...deps) : fn.apply(obj, deps);
+ }
+ } else {
+ value = parent.get(token, notFoundValue);
+ }
+ return value;
+}
+
+
+function computeDeps(provider: StaticProvider): DependencyRecord[] {
+ let deps: DependencyRecord[] = EMPTY;
+ const providerDeps: any[] =
+ (provider as ExistingProvider & StaticClassProvider & ConstructorProvider).deps;
+ if (providerDeps && providerDeps.length) {
+ deps = [];
+ for (let i = 0; i < providerDeps.length; i++) {
+ let options = OptionFlags.Default;
+ let token = resolveForwardRef(providerDeps[i]);
+ if (token instanceof Array) {
+ for (let j = 0, annotations = token; j < annotations.length; j++) {
+ const annotation = annotations[j];
+ if (annotation instanceof Optional || annotation == Optional) {
+ options = options | OptionFlags.Optional;
+ } else if (annotation instanceof SkipSelf || annotation == SkipSelf) {
+ options = options & ~OptionFlags.CheckSelf;
+ } else if (annotation instanceof Self || annotation == Self) {
+ options = options & ~OptionFlags.CheckParent;
+ } else if (annotation instanceof Inject) {
+ token = (annotation as Inject).token;
+ } else {
+ token = resolveForwardRef(annotation);
+ }
+ }
+ }
+ deps.push({token, options});
+ }
+ } else if ((provider as ExistingProvider).useExisting) {
+ const token = resolveForwardRef((provider as ExistingProvider).useExisting);
+ deps = [{token, options: OptionFlags.Default}];
+ } else if (!providerDeps && !(USE_VALUE in provider)) {
+ // useValue & useExisting are the only ones which are exempt from deps all others need it.
+ throw staticError('\'deps\' required', provider);
+ }
+ return deps;
+}
+
+function formatError(text: string, obj: any): string {
+ text = text && text.charAt(0) === '\n' && text.charAt(1) == NO_NEW_LINE ? text.substr(2) : text;
+ let context = stringify(obj);
+ if (obj instanceof Array) {
+ context = obj.map(stringify).join(' -> ');
+ } else if (typeof obj === 'object') {
+ let parts = [];
+ for (let key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ let value = obj[key];
+ parts.push(
+ key + ':' + (typeof value === 'string' ? JSON.stringify(value) : stringify(value)));
+ }
+ }
+ context = `{${parts.join(', ')}}`;
+ }
+ return `StaticInjectorError[${context}]: ${text.replace(NEW_LINE, '\n ')}`;
+}
+
+function staticError(text: string, obj: any): Error {
+ return new Error(formatError(text, obj));
+}
+
+function getClosureSafeProperty(objWithPropertyToExtract: T): string {
+ for (let key in objWithPropertyToExtract) {
+ if (objWithPropertyToExtract[key] === GET_PROPERTY_NAME) {
+ return key;
+ }
+ }
+ throw Error('!prop');
}
diff --git a/packages/core/src/di/provider.ts b/packages/core/src/di/provider.ts
index acb8f23cb3..9ad449e3ff 100644
--- a/packages/core/src/di/provider.ts
+++ b/packages/core/src/di/provider.ts
@@ -8,32 +8,6 @@
import {Type} from '../type';
-/**
- * @whatItDoes Configures the {@link Injector} to return an instance of `Type` when `Type' is used
- * as token.
- * @howToUse
- * ```
- * @Injectable()
- * class MyService {}
- *
- * const provider: TypeProvider = MyService;
- * ```
- *
- * @description
- *
- * Create an instance by invoking the `new` operator and supplying additional arguments.
- * This form is a short form of `TypeProvider`;
- *
- * For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
- *
- * ### Example
- *
- * {@example core/di/ts/provider_spec.ts region='TypeProvider'}
- *
- * @stable
- */
-export interface TypeProvider extends Type {}
-
/**
* @whatItDoes Configures the {@link Injector} to return a value for a token.
* @howToUse
@@ -79,7 +53,7 @@ export interface ValueProvider {
* @Injectable()
* class MyService {}
*
- * const provider: ClassProvider = {provide: 'someToken', useClass: MyService};
+ * const provider: ClassProvider = {provide: 'someToken', useClass: MyService, deps: []};
* ```
*
* @description
@@ -87,24 +61,74 @@ export interface ValueProvider {
*
* ### Example
*
- * {@example core/di/ts/provider_spec.ts region='ClassProvider'}
+ * {@example core/di/ts/provider_spec.ts region='StaticClassProvider'}
*
* Note that following two providers are not equal:
- * {@example core/di/ts/provider_spec.ts region='ClassProviderDifference'}
+ * {@example core/di/ts/provider_spec.ts region='StaticClassProviderDifference'}
*
* @stable
*/
-export interface ClassProvider {
+export interface StaticClassProvider {
/**
* An injection token. (Typically an instance of `Type` or `InjectionToken`, but can be `any`).
*/
provide: any;
/**
- * Class to instantiate for the `token`.
+ * An optional class to instantiate for the `token`. (If not provided `provide` is assumed to be a
+ * class to
+ * instantiate)
*/
useClass: Type;
+ /**
+ * A list of `token`s which need to be resolved by the injector. The list of values is then
+ * used as arguments to the `useClass` constructor.
+ */
+ deps: any[];
+
+ /**
+ * If true, then injector returns an array of instances. This is useful to allow multiple
+ * providers spread across many files to provide configuration information to a common token.
+ *
+ * ### Example
+ *
+ * {@example core/di/ts/provider_spec.ts region='MultiProviderAspect'}
+ */
+ multi?: boolean;
+}
+
+/**
+ * @whatItDoes Configures the {@link Injector} to return an instance of a token.
+ * @howToUse
+ * ```
+ * @Injectable()
+ * class MyService {}
+ *
+ * const provider: ClassProvider = {provide: MyClass, deps: []};
+ * ```
+ *
+ * @description
+ * For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
+ *
+ * ### Example
+ *
+ * {@example core/di/ts/provider_spec.ts region='ConstructorProvider'}
+ *
+ * @stable
+ */
+export interface ConstructorProvider {
+ /**
+ * An injection token. (Typically an instance of `Type` or `InjectionToken`, but can be `any`).
+ */
+ provide: Type;
+
+ /**
+ * A list of `token`s which need to be resolved by the injector. The list of values is then
+ * used as arguments to the `useClass` constructor.
+ */
+ deps: any[];
+
/**
* If true, then injector returns an array of instances. This is useful to allow multiple
* providers spread across many files to provide configuration information to a common token.
@@ -205,11 +229,95 @@ export interface FactoryProvider {
multi?: boolean;
}
+/**
+ * @whatItDoes Describes how the {@link Injector} should be configured in a static way (Without
+ * reflection).
+ * @howToUse
+ * See {@link ValueProvider}, {@link ExistingProvider}, {@link FactoryProvider}.
+ *
+ * @description
+ * For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
+ *
+ * @stable
+ */
+export type StaticProvider = ValueProvider | ExistingProvider | StaticClassProvider |
+ ConstructorProvider | FactoryProvider | any[];
+
+
+/**
+ * @whatItDoes Configures the {@link Injector} to return an instance of `Type` when `Type' is used
+ * as token.
+ * @howToUse
+ * ```
+ * @Injectable()
+ * class MyService {}
+ *
+ * const provider: TypeProvider = MyService;
+ * ```
+ *
+ * @description
+ *
+ * Create an instance by invoking the `new` operator and supplying additional arguments.
+ * This form is a short form of `TypeProvider`;
+ *
+ * For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
+ *
+ * ### Example
+ *
+ * {@example core/di/ts/provider_spec.ts region='TypeProvider'}
+ *
+ * @stable
+ */
+export interface TypeProvider extends Type {}
+
+/**
+ * @whatItDoes Configures the {@link Injector} to return an instance of `useClass` for a token.
+ * @howToUse
+ * ```
+ * @Injectable()
+ * class MyService {}
+ *
+ * const provider: ClassProvider = {provide: 'someToken', useClass: MyService};
+ * ```
+ *
+ * @description
+ * For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
+ *
+ * ### Example
+ *
+ * {@example core/di/ts/provider_spec.ts region='ClassProvider'}
+ *
+ * Note that following two providers are not equal:
+ * {@example core/di/ts/provider_spec.ts region='ClassProviderDifference'}
+ *
+ * @stable
+ */
+export interface ClassProvider {
+ /**
+ * An injection token. (Typically an instance of `Type` or `InjectionToken`, but can be `any`).
+ */
+ provide: any;
+
+ /**
+ * Class to instantiate for the `token`.
+ */
+ useClass: Type;
+
+ /**
+ * If true, then injector returns an array of instances. This is useful to allow multiple
+ * providers spread across many files to provide configuration information to a common token.
+ *
+ * ### Example
+ *
+ * {@example core/di/ts/provider_spec.ts region='MultiProviderAspect'}
+ */
+ multi?: boolean;
+}
+
/**
* @whatItDoes Describes how the {@link Injector} should be configured.
* @howToUse
- * See {@link TypeProvider}, {@link ValueProvider}, {@link ClassProvider}, {@link ExistingProvider},
- * {@link FactoryProvider}.
+ * See {@link TypeProvider}, {@link ClassProvider}, {@link StaticProvider}.
*
* @description
* For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
diff --git a/packages/core/src/di/reflective_injector.ts b/packages/core/src/di/reflective_injector.ts
index a0e939dddd..2dd381b657 100644
--- a/packages/core/src/di/reflective_injector.ts
+++ b/packages/core/src/di/reflective_injector.ts
@@ -13,6 +13,8 @@ import {cyclicDependencyError, instantiationError, noProviderError, outOfBoundsE
import {ReflectiveKey} from './reflective_key';
import {ReflectiveDependency, ResolvedReflectiveFactory, ResolvedReflectiveProvider, resolveReflectiveProviders} from './reflective_provider';
+
+
// Threshold for the dynamic version
const UNDEFINED = new Object();
diff --git a/packages/core/src/di/reflective_key.ts b/packages/core/src/di/reflective_key.ts
index e23df7ad0a..765f3fded6 100644
--- a/packages/core/src/di/reflective_key.ts
+++ b/packages/core/src/di/reflective_key.ts
@@ -24,7 +24,7 @@ import {resolveForwardRef} from './forward_ref';
* `Key` should not be created directly. {@link ReflectiveInjector} creates keys automatically when
* resolving
* providers.
- * @experimental
+ * @deprecated No replacement
*/
export class ReflectiveKey {
/**
diff --git a/packages/core/src/linker/compiler.ts b/packages/core/src/linker/compiler.ts
index 28320877b8..f92bfc1f87 100644
--- a/packages/core/src/linker/compiler.ts
+++ b/packages/core/src/linker/compiler.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {Injectable, InjectionToken} from '../di';
+import {Injectable, InjectionToken, StaticProvider} from '../di';
import {MissingTranslationStrategy} from '../i18n/tokens';
import {ViewEncapsulation} from '../metadata';
import {Type} from '../type';
@@ -14,6 +14,7 @@ import {Type} from '../type';
import {ComponentFactory} from './component_factory';
import {NgModuleFactory} from './ng_module_factory';
+
/**
* Combination of NgModuleFactory and ComponentFactorys.
*
@@ -101,7 +102,7 @@ export type CompilerOptions = {
useDebug?: boolean,
useJit?: boolean,
defaultEncapsulation?: ViewEncapsulation,
- providers?: any[],
+ providers?: StaticProvider[],
missingTranslation?: MissingTranslationStrategy,
// Whether to support the `` tag and the `template` attribute to define angular
// templates. They have been deprecated in 4.x, `` should be used instead.
diff --git a/packages/core/src/platform_core_providers.ts b/packages/core/src/platform_core_providers.ts
index 7b7cd5f693..6ff1967188 100644
--- a/packages/core/src/platform_core_providers.ts
+++ b/packages/core/src/platform_core_providers.ts
@@ -9,22 +9,16 @@
import {PlatformRef, PlatformRef_, createPlatformFactory} from './application_ref';
import {PLATFORM_ID} from './application_tokens';
import {Console} from './console';
-import {Provider} from './di';
-import {Reflector, reflector} from './reflection/reflection';
+import {Injector, StaticProvider} from './di';
import {TestabilityRegistry} from './testability/testability';
-function _reflector(): Reflector {
- return reflector;
-}
-
-const _CORE_PLATFORM_PROVIDERS: Provider[] = [
+const _CORE_PLATFORM_PROVIDERS: StaticProvider[] = [
// Set a default platform name for platforms that don't set it explicitly.
{provide: PLATFORM_ID, useValue: 'unknown'},
- PlatformRef_,
+ {provide: PlatformRef_, deps: [Injector]},
{provide: PlatformRef, useExisting: PlatformRef_},
- {provide: Reflector, useFactory: _reflector, deps: []},
- TestabilityRegistry,
- Console,
+ {provide: TestabilityRegistry, deps: []},
+ {provide: Console, deps: []},
];
/**
diff --git a/packages/core/src/util.ts b/packages/core/src/util.ts
index 3e256b4797..393cf40488 100644
--- a/packages/core/src/util.ts
+++ b/packages/core/src/util.ts
@@ -60,6 +60,10 @@ export function stringify(token: any): string {
return token;
}
+ if (token instanceof Array) {
+ return '[' + token.map(stringify).join(', ') + ']';
+ }
+
if (token == null) {
return '' + token;
}
diff --git a/packages/core/src/view/query.ts b/packages/core/src/view/query.ts
index c93b624a2e..842216207e 100644
--- a/packages/core/src/view/query.ts
+++ b/packages/core/src/view/query.ts
@@ -190,4 +190,4 @@ export function getQueryValue(
}
return value;
}
-}
\ No newline at end of file
+}
diff --git a/packages/core/src/view/refs.ts b/packages/core/src/view/refs.ts
index 2318ca6f45..9db6f7c44d 100644
--- a/packages/core/src/view/refs.ts
+++ b/packages/core/src/view/refs.ts
@@ -466,7 +466,8 @@ export function createNgModuleRef(
class NgModuleRef_ implements NgModuleData, InternalNgModuleRef {
private _destroyListeners: (() => void)[] = [];
private _destroyed: boolean = false;
- public _providers: any[];
+ /** @internal */
+ _providers: any[];
constructor(
private _moduleType: Type, public _parent: Injector,
diff --git a/packages/core/src/view/view.ts b/packages/core/src/view/view.ts
index 18d07766c3..7572791207 100644
--- a/packages/core/src/view/view.ts
+++ b/packages/core/src/view/view.ts
@@ -161,7 +161,7 @@ function validateNode(parent: NodeDef | null, node: NodeDef, nodeCount: number)
const parentFlags = parent ? parent.flags : 0;
if ((parentFlags & NodeFlags.TypeElement) === 0) {
throw new Error(
- `Illegal State: Provider/Directive nodes need to be children of elements or anchors, at index ${node.index}!`);
+ `Illegal State: StaticProvider/Directive nodes need to be children of elements or anchors, at index ${node.index}!`);
}
}
if (node.query) {
diff --git a/packages/core/test/animation/animation_integration_spec.ts b/packages/core/test/animation/animation_integration_spec.ts
index ee5d8ec6cd..e7e5ee5186 100644
--- a/packages/core/test/animation/animation_integration_spec.ts
+++ b/packages/core/test/animation/animation_integration_spec.ts
@@ -868,6 +868,87 @@ export function main() {
expect(pp[2].currentSnapshot).toEqual({opacity: AUTO_STYLE});
});
+ it('should provide the styling of previous players that are grouped and queried and make sure match the players with the correct elements',
+ () => {
+ @Component({
+ selector: 'ani-cmp',
+ template: `
+
+ `,
+ animations: [
+ trigger(
+ 'myAnimation',
+ [
+ transition(
+ '1 => 2',
+ [
+ style({fontSize: '10px'}),
+ query(
+ '.inner',
+ [
+ style({fontSize: '20px'}),
+ ]),
+ animate('1s', style({fontSize: '100px'})),
+ query(
+ '.inner',
+ [
+ animate('1s', style({fontSize: '200px'})),
+ ]),
+ ]),
+ transition(
+ '2 => 3',
+ [
+ animate('1s', style({fontSize: '0px'})),
+ query(
+ '.inner',
+ [
+ animate('1s', style({fontSize: '0px'})),
+ ]),
+ ]),
+ ]),
+ ],
+ })
+ class Cmp {
+ exp: any = false;
+ }
+
+ TestBed.configureTestingModule({declarations: [Cmp]});
+
+ const engine = TestBed.get(ɵAnimationEngine);
+ const fixture = TestBed.createComponent(Cmp);
+ const cmp = fixture.componentInstance;
+
+ fixture.detectChanges();
+
+ cmp.exp = '1';
+ fixture.detectChanges();
+ resetLog();
+
+ cmp.exp = '2';
+ fixture.detectChanges();
+ resetLog();
+
+ cmp.exp = '3';
+ fixture.detectChanges();
+ const players = getLog();
+ expect(players.length).toEqual(2);
+ const [p1, p2] = players as MockAnimationPlayer[];
+
+ const pp1 = p1.previousPlayers as MockAnimationPlayer[];
+ expect(p1.element.classList.contains('container')).toBeTruthy();
+ for (let i = 0; i < pp1.length; i++) {
+ expect(pp1[i].element).toEqual(p1.element);
+ }
+
+ const pp2 = p2.previousPlayers as MockAnimationPlayer[];
+ expect(p2.element.classList.contains('inner')).toBeTruthy();
+ for (let i = 0; i < pp2.length; i++) {
+ expect(pp2[i].element).toEqual(p2.element);
+ }
+ });
+
it('should properly balance styles between states even if there are no destination state styles',
() => {
@Component({
@@ -1445,6 +1526,60 @@ export function main() {
expect(players.length).toEqual(0);
});
+ it('should update the final state styles when params update even if the expression hasn\'t changed',
+ fakeAsync(() => {
+ @Component({
+ selector: 'ani-cmp',
+ template: `
+
+ `,
+ animations: [
+ trigger(
+ 'myAnimation',
+ [
+ state('*', style({color: '{{ color }}'}), {params: {color: 'black'}}),
+ transition('* => 1', animate(500))
+ ]),
+ ]
+ })
+ class Cmp {
+ public exp: any;
+ public color: string|null;
+ }
+
+ TestBed.configureTestingModule({declarations: [Cmp]});
+
+ const engine = TestBed.get(ɵAnimationEngine);
+ const fixture = TestBed.createComponent(Cmp);
+ const cmp = fixture.componentInstance;
+
+ cmp.exp = '1';
+ cmp.color = 'red';
+ fixture.detectChanges();
+ const player = getLog()[0] !;
+ const element = player.element;
+ player.finish();
+
+ flushMicrotasks();
+ expect(getDOM().hasStyle(element, 'color', 'red')).toBeTruthy();
+
+ cmp.exp = '1';
+ cmp.color = 'blue';
+ fixture.detectChanges();
+ resetLog();
+
+ flushMicrotasks();
+ expect(getDOM().hasStyle(element, 'color', 'blue')).toBeTruthy();
+
+ cmp.exp = '1';
+ cmp.color = null;
+ fixture.detectChanges();
+ resetLog();
+
+ flushMicrotasks();
+ expect(getDOM().hasStyle(element, 'color', 'black')).toBeTruthy();
+ }));
+
it('should substitute in values if the provided state match is an object with values', () => {
@Component({
selector: 'ani-cmp',
@@ -1482,6 +1617,138 @@ export function main() {
]);
});
+ it('should retain substituted styles on the element once the animation is complete if referenced in the final state',
+ fakeAsync(() => {
+ @Component({
+ selector: 'ani-cmp',
+ template: `
+
+ `,
+ animations: [
+ trigger(
+ 'myAnimation',
+ [
+ state(
+ 'start', style({
+ color: '{{ color }}',
+ fontSize: '{{ fontSize }}px',
+ width: '{{ width }}'
+ }),
+ {params: {color: 'red', fontSize: '200', width: '10px'}}),
+
+ state(
+ 'final',
+ style(
+ {color: '{{ color }}', fontSize: '{{ fontSize }}px', width: '888px'}),
+ {params: {color: 'green', fontSize: '50', width: '100px'}}),
+
+ transition('start => final', animate(500)),
+ ]),
+ ]
+ })
+ class Cmp {
+ public exp: any;
+ public color: any;
+ }
+
+ TestBed.configureTestingModule({declarations: [Cmp]});
+
+ const engine = TestBed.get(ɵAnimationEngine);
+ const fixture = TestBed.createComponent(Cmp);
+ const cmp = fixture.componentInstance;
+
+ cmp.exp = 'start';
+ cmp.color = 'red';
+ fixture.detectChanges();
+ resetLog();
+
+ cmp.exp = 'final';
+ cmp.color = 'blue';
+ fixture.detectChanges();
+
+ const players = getLog();
+ expect(players.length).toEqual(1);
+ const [p1] = players;
+
+ expect(p1.keyframes).toEqual([
+ {color: 'red', fontSize: '200px', width: '10px', offset: 0},
+ {color: 'blue', fontSize: '50px', width: '888px', offset: 1}
+ ]);
+
+ const element = p1.element;
+ p1.finish();
+ flushMicrotasks();
+
+ expect(getDOM().hasStyle(element, 'color', 'blue')).toBeTruthy();
+ expect(getDOM().hasStyle(element, 'fontSize', '50px')).toBeTruthy();
+ expect(getDOM().hasStyle(element, 'width', '888px')).toBeTruthy();
+ }));
+
+ it('should only evaluate final state param substitutions from the expression and state values and not from the transition options ',
+ fakeAsync(() => {
+ @Component({
+ selector: 'ani-cmp',
+ template: `
+
+ `,
+ animations: [
+ trigger(
+ 'myAnimation',
+ [
+ state(
+ 'start', style({
+ width: '{{ width }}',
+ height: '{{ height }}',
+ }),
+ {params: {width: '0px', height: '0px'}}),
+
+ state(
+ 'final', style({
+ width: '{{ width }}',
+ height: '{{ height }}',
+ }),
+ {params: {width: '100px', height: '100px'}}),
+
+ transition(
+ 'start => final', [animate(500)],
+ {params: {width: '333px', height: '666px'}}),
+ ]),
+ ]
+ })
+ class Cmp {
+ public exp: any;
+ }
+
+ TestBed.configureTestingModule({declarations: [Cmp]});
+
+ const engine = TestBed.get(ɵAnimationEngine);
+ const fixture = TestBed.createComponent(Cmp);
+ const cmp = fixture.componentInstance;
+
+ cmp.exp = 'start';
+ fixture.detectChanges();
+ resetLog();
+
+ cmp.exp = 'final';
+ fixture.detectChanges();
+
+ const players = getLog();
+ expect(players.length).toEqual(1);
+ const [p1] = players;
+
+ expect(p1.keyframes).toEqual([
+ {width: '0px', height: '0px', offset: 0},
+ {width: '100px', height: '100px', offset: 1},
+ ]);
+
+ const element = p1.element;
+ p1.finish();
+ flushMicrotasks();
+
+ expect(getDOM().hasStyle(element, 'width', '100px')).toBeTruthy();
+ expect(getDOM().hasStyle(element, 'height', '100px')).toBeTruthy();
+ }));
+
it('should not flush animations twice when an inner component runs change detection', () => {
@Component({
selector: 'outer-cmp',
@@ -1882,7 +2149,7 @@ export function main() {
class Cmp {
exp: string;
log: any[] = [];
- callback = (event: any) => { this.log.push(`${event.phaseName} => ${event.toState}`); }
+ callback = (event: any) => this.log.push(`${event.phaseName} => ${event.toState}`);
}
TestBed.configureTestingModule({
diff --git a/packages/core/test/application_init_spec.ts b/packages/core/test/application_init_spec.ts
index ae0bd543c4..693d538876 100644
--- a/packages/core/test/application_init_spec.ts
+++ b/packages/core/test/application_init_spec.ts
@@ -35,7 +35,7 @@ export function main() {
return () => {
const initStatus = injector.get(ApplicationInitStatus);
initStatus.donePromise.then(() => { expect(completerResolver).toBe(true); });
- }
+ };
};
promise = new Promise((res) => { resolve = res; });
TestBed.configureTestingModule({
diff --git a/packages/core/test/change_detection/differs/iterable_differs_spec.ts b/packages/core/test/change_detection/differs/iterable_differs_spec.ts
index 8a94bb1799..7d4b99be47 100644
--- a/packages/core/test/change_detection/differs/iterable_differs_spec.ts
+++ b/packages/core/test/change_detection/differs/iterable_differs_spec.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {ReflectiveInjector} from '@angular/core';
+import {Injector} from '@angular/core';
import {IterableDiffers} from '@angular/core/src/change_detection/differs/iterable_differs';
import {SpyIterableDifferFactory} from '../../spies';
@@ -50,7 +50,7 @@ export function main() {
describe('.extend()', () => {
it('should throw if calling extend when creating root injector', () => {
- const injector = ReflectiveInjector.resolveAndCreate([IterableDiffers.extend([])]);
+ const injector = Injector.create([IterableDiffers.extend([])]);
expect(() => injector.get(IterableDiffers))
.toThrowError(/Cannot extend IterableDiffers without a parent injector/);
@@ -58,9 +58,8 @@ export function main() {
it('should extend di-inherited differs', () => {
const parent = new IterableDiffers([factory1]);
- const injector =
- ReflectiveInjector.resolveAndCreate([{provide: IterableDiffers, useValue: parent}]);
- const childInjector = injector.resolveAndCreateChild([IterableDiffers.extend([factory2])]);
+ const injector = Injector.create([{provide: IterableDiffers, useValue: parent}]);
+ const childInjector = Injector.create([IterableDiffers.extend([factory2])], injector);
expect(injector.get(IterableDiffers).factories).toEqual([factory1]);
expect(childInjector.get(IterableDiffers).factories).toEqual([factory2, factory1]);
diff --git a/packages/core/test/di/injector_spec.ts b/packages/core/test/di/injector_spec.ts
index 753741fe25..820d5069da 100644
--- a/packages/core/test/di/injector_spec.ts
+++ b/packages/core/test/di/injector_spec.ts
@@ -13,12 +13,13 @@ import {describe, expect, it} from '@angular/core/testing/src/testing_internal';
export function main() {
describe('Injector.NULL', () => {
it('should throw if no arg is given', () => {
- expect(() => Injector.NULL.get('someToken')).toThrowError('No provider for someToken!');
+ expect(() => Injector.NULL.get('someToken'))
+ .toThrowError('NullInjectorError: No provider for someToken!');
});
it('should throw if THROW_IF_NOT_FOUND is given', () => {
expect(() => Injector.NULL.get('someToken', Injector.THROW_IF_NOT_FOUND))
- .toThrowError('No provider for someToken!');
+ .toThrowError('NullInjectorError: No provider for someToken!');
});
it('should return the default value',
diff --git a/packages/core/test/di/static_injector_spec.ts b/packages/core/test/di/static_injector_spec.ts
new file mode 100644
index 0000000000..f2b8df2f54
--- /dev/null
+++ b/packages/core/test/di/static_injector_spec.ts
@@ -0,0 +1,479 @@
+/**
+ * @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 {Inject, InjectionToken, Injector, Optional, ReflectiveKey, Self, SkipSelf, forwardRef} from '@angular/core';
+import {getOriginalError} from '@angular/core/src/errors';
+import {expect} from '@angular/platform-browser/testing/src/matchers';
+
+import {stringify} from '../../src/util';
+
+class Engine {
+ static PROVIDER = {provide: Engine, useClass: Engine, deps: []};
+}
+
+class BrokenEngine {
+ static PROVIDER = {provide: Engine, useClass: BrokenEngine, deps: []};
+ constructor() { throw new Error('Broken Engine'); }
+}
+
+class DashboardSoftware {
+ static PROVIDER = {provide: DashboardSoftware, useClass: DashboardSoftware, deps: []};
+}
+
+class Dashboard {
+ static PROVIDER = {provide: Dashboard, useClass: Dashboard, deps: [DashboardSoftware]};
+ constructor(software: DashboardSoftware) {}
+}
+
+class TurboEngine extends Engine {
+ static PROVIDER = {provide: Engine, useClass: TurboEngine, deps: []};
+}
+
+class Car {
+ static PROVIDER = {provide: Car, useClass: Car, deps: [Engine]};
+ constructor(public engine: Engine) {}
+}
+
+class CarWithOptionalEngine {
+ static PROVIDER = {
+ provide: CarWithOptionalEngine,
+ useClass: CarWithOptionalEngine,
+ deps: [[new Optional(), Engine]]
+ };
+ constructor(public engine: Engine) {}
+}
+
+class CarWithDashboard {
+ static PROVIDER = {
+ provide: CarWithDashboard,
+ useClass: CarWithDashboard,
+ deps: [Engine, Dashboard]
+ };
+ engine: Engine;
+ dashboard: Dashboard;
+ constructor(engine: Engine, dashboard: Dashboard) {
+ this.engine = engine;
+ this.dashboard = dashboard;
+ }
+}
+
+class SportsCar extends Car {
+ static PROVIDER = {provide: Car, useClass: SportsCar, deps: [Engine]};
+}
+
+class CyclicEngine {
+ static PROVIDER = {provide: Engine, useClass: CyclicEngine, deps: [Car]};
+ constructor(car: Car) {}
+}
+
+class NoAnnotations {
+ constructor(secretDependency: any) {}
+}
+
+function factoryFn(a: any) {}
+
+export function main() {
+ const dynamicProviders = [
+ {provide: 'provider0', useValue: 1}, {provide: 'provider1', useValue: 1},
+ {provide: 'provider2', useValue: 1}, {provide: 'provider3', useValue: 1},
+ {provide: 'provider4', useValue: 1}, {provide: 'provider5', useValue: 1},
+ {provide: 'provider6', useValue: 1}, {provide: 'provider7', useValue: 1},
+ {provide: 'provider8', useValue: 1}, {provide: 'provider9', useValue: 1},
+ {provide: 'provider10', useValue: 1}
+ ];
+
+ describe(`StaticInjector`, () => {
+
+ it('should instantiate a class without dependencies', () => {
+ const injector = Injector.create([Engine.PROVIDER]);
+ const engine = injector.get(Engine);
+
+ expect(engine).toBeAnInstanceOf(Engine);
+ });
+
+ it('should resolve dependencies based on type information', () => {
+ const injector = Injector.create([Engine.PROVIDER, Car.PROVIDER]);
+ const car = injector.get(Car);
+
+ expect(car).toBeAnInstanceOf(Car);
+ expect(car.engine).toBeAnInstanceOf(Engine);
+ });
+
+ it('should cache instances', () => {
+ const injector = Injector.create([Engine.PROVIDER]);
+
+ const e1 = injector.get(Engine);
+ const e2 = injector.get(Engine);
+
+ expect(e1).toBe(e2);
+ });
+
+ it('should provide to a value', () => {
+ const injector = Injector.create([{provide: Engine, useValue: 'fake engine'}]);
+
+ const engine = injector.get(Engine);
+ expect(engine).toEqual('fake engine');
+ });
+
+ it('should inject dependencies instance of InjectionToken', () => {
+ const TOKEN = new InjectionToken('token');
+
+ const injector = Injector.create([
+ {provide: TOKEN, useValue: 'by token'},
+ {provide: Engine, useFactory: (v: string) => v, deps: [[TOKEN]]},
+ ]);
+
+ const engine = injector.get(Engine);
+ expect(engine).toEqual('by token');
+ });
+
+ it('should provide to a factory', () => {
+ function sportsCarFactory(e: any) { return new SportsCar(e); }
+
+ const injector = Injector.create(
+ [Engine.PROVIDER, {provide: Car, useFactory: sportsCarFactory, deps: [Engine]}]);
+
+ const car = injector.get(Car);
+ expect(car).toBeAnInstanceOf(SportsCar);
+ expect(car.engine).toBeAnInstanceOf(Engine);
+ });
+
+ it('should supporting provider to null', () => {
+ const injector = Injector.create([{provide: Engine, useValue: null}]);
+ const engine = injector.get(Engine);
+ expect(engine).toBeNull();
+ });
+
+ it('should provide to an alias', () => {
+ const injector = Injector.create([
+ Engine.PROVIDER, {provide: SportsCar, useClass: SportsCar, deps: [Engine]},
+ {provide: Car, useExisting: SportsCar}
+ ]);
+
+ const car = injector.get(Car);
+ const sportsCar = injector.get(SportsCar);
+ expect(car).toBeAnInstanceOf(SportsCar);
+ expect(car).toBe(sportsCar);
+ });
+
+ it('should support multiProviders', () => {
+ const injector = Injector.create([
+ Engine.PROVIDER, {provide: Car, useClass: SportsCar, deps: [Engine], multi: true},
+ {provide: Car, useClass: CarWithOptionalEngine, deps: [Engine], multi: true}
+ ]);
+
+ const cars = injector.get(Car) as any as Car[];
+ expect(cars.length).toEqual(2);
+ expect(cars[0]).toBeAnInstanceOf(SportsCar);
+ expect(cars[1]).toBeAnInstanceOf(CarWithOptionalEngine);
+ });
+
+ it('should support multiProviders that are created using useExisting', () => {
+ const injector = Injector.create([
+ Engine.PROVIDER, {provide: SportsCar, useClass: SportsCar, deps: [Engine]},
+ {provide: Car, useExisting: SportsCar, multi: true}
+ ]);
+
+ const cars = injector.get(Car) as any as Car[];
+ expect(cars.length).toEqual(1);
+ expect(cars[0]).toBe(injector.get(SportsCar));
+ });
+
+ it('should throw when the aliased provider does not exist', () => {
+ const injector = Injector.create([{provide: 'car', useExisting: SportsCar}]);
+ const e =
+ `StaticInjectorError[car -> ${stringify(SportsCar)}]: \n NullInjectorError: No provider for ${stringify(SportsCar)}!`;
+ expect(() => injector.get('car')).toThrowError(e);
+ });
+
+ it('should handle forwardRef in useExisting', () => {
+ const injector = Injector.create([
+ {provide: 'originalEngine', useClass: forwardRef(() => Engine), deps: []}, {
+ provide: 'aliasedEngine',
+ useExisting: forwardRef(() => 'originalEngine'),
+ deps: []
+ }
+ ]);
+ expect(injector.get('aliasedEngine')).toBeAnInstanceOf(Engine);
+ });
+
+ it('should support overriding factory dependencies', () => {
+ const injector = Injector.create([
+ Engine.PROVIDER,
+ {provide: Car, useFactory: (e: Engine) => new SportsCar(e), deps: [Engine]}
+ ]);
+
+ const car = injector.get(Car);
+ expect(car).toBeAnInstanceOf(SportsCar);
+ expect(car.engine).toBeAnInstanceOf(Engine);
+ });
+
+ it('should support optional dependencies', () => {
+ const injector = Injector.create([CarWithOptionalEngine.PROVIDER]);
+
+ const car = injector.get(CarWithOptionalEngine);
+ expect(car.engine).toEqual(null);
+ });
+
+ it('should flatten passed-in providers', () => {
+ const injector = Injector.create([[[Engine.PROVIDER, Car.PROVIDER]]]);
+
+ const car = injector.get(Car);
+ expect(car).toBeAnInstanceOf(Car);
+ });
+
+ it('should use the last provider when there are multiple providers for same token', () => {
+ const injector = Injector.create([
+ {provide: Engine, useClass: Engine, deps: []},
+ {provide: Engine, useClass: TurboEngine, deps: []}
+ ]);
+
+ expect(injector.get(Engine)).toBeAnInstanceOf(TurboEngine);
+ });
+
+ it('should use non-type tokens', () => {
+ const injector = Injector.create([{provide: 'token', useValue: 'value'}]);
+
+ expect(injector.get('token')).toEqual('value');
+ });
+
+ it('should throw when given invalid providers', () => {
+ expect(() => Injector.create(['blah']))
+ .toThrowError('StaticInjectorError[blah]: Unexpected provider');
+ });
+
+ it('should throw when missing deps', () => {
+ expect(() => Injector.create([{provide: Engine, useClass: Engine}]))
+ .toThrowError(
+ 'StaticInjectorError[{provide:Engine, useClass:Engine}]: \'deps\' required');
+ });
+
+ it('should throw when using reflective API', () => {
+ expect(() => Injector.create([Engine]))
+ .toThrowError('StaticInjectorError[Engine]: Function/Class not supported');
+ });
+
+ it('should throw when unknown provider shape API', () => {
+ expect(() => Injector.create([{provide: 'abc', deps: [Engine]}]))
+ .toThrowError(
+ 'StaticInjectorError[{provide:"abc", deps:[Engine]}]: StaticProvider does not have [useValue|useFactory|useExisting|useClass] or [provide] is not newable');
+ });
+
+ it('should throw when given invalid providers and serialize the provider', () => {
+ expect(() => Injector.create([{foo: 'bar', bar: Car}]))
+ .toThrowError('StaticInjectorError[{foo:"bar", bar:Car}]: Unexpected provider');
+ });
+
+ it('should provide itself', () => {
+ const parent = Injector.create([]);
+ const child = Injector.create([], parent);
+
+ expect(child.get(Injector)).toBe(child);
+ });
+
+ it('should throw when no provider defined', () => {
+ const injector = Injector.create([]);
+ expect(() => injector.get('NonExisting'))
+ .toThrowError(
+ 'StaticInjectorError[NonExisting]: \n NullInjectorError: No provider for NonExisting!');
+ });
+
+ it('should show the full path when no provider', () => {
+ const injector =
+ Injector.create([CarWithDashboard.PROVIDER, Engine.PROVIDER, Dashboard.PROVIDER]);
+ expect(() => injector.get(CarWithDashboard))
+ .toThrowError(
+ `StaticInjectorError[${stringify(CarWithDashboard)} -> ${stringify(Dashboard)} -> DashboardSoftware]:
+ NullInjectorError: No provider for DashboardSoftware!`);
+ });
+
+ it('should throw when trying to instantiate a cyclic dependency', () => {
+ const injector = Injector.create([Car.PROVIDER, CyclicEngine.PROVIDER]);
+
+ expect(() => injector.get(Car))
+ .toThrowError(
+ `StaticInjectorError[${stringify(Car)} -> ${stringify(Engine)} -> ${stringify(Car)}]: Circular dependency`);
+ });
+
+ it('should show the full path when error happens in a constructor', () => {
+ const error = new Error('MyError');
+ const injector = Injector.create(
+ [Car.PROVIDER, {provide: Engine, useFactory: () => { throw error; }, deps: []}]);
+
+ try {
+ injector.get(Car);
+ throw 'Must throw';
+ } catch (e) {
+ expect(e).toBe(error);
+ expect(e.message).toContain(
+ `StaticInjectorError[${stringify(Car)} -> Engine]: \n MyError`);
+ expect(e.ngTokenPath[0]).toEqual(Car);
+ expect(e.ngTokenPath[1]).toEqual(Engine);
+ }
+ });
+
+ it('should instantiate an object after a failed attempt', () => {
+ let isBroken = true;
+
+ const injector = Injector.create([
+ Car.PROVIDER, {
+ provide: Engine,
+ useFactory: (() => isBroken ? new BrokenEngine() : new Engine()),
+ deps: []
+ }
+ ]);
+
+ expect(() => injector.get(Car))
+ .toThrowError('StaticInjectorError[Car -> Engine]: \n Broken Engine');
+
+ isBroken = false;
+
+ expect(injector.get(Car)).toBeAnInstanceOf(Car);
+ });
+
+ it('should support null/undefined values', () => {
+ const injector = Injector.create([
+ {provide: 'null', useValue: null},
+ {provide: 'undefined', useValue: undefined},
+ ]);
+ expect(injector.get('null')).toBe(null);
+ expect(injector.get('undefined')).toBe(undefined);
+ });
+
+ });
+
+
+ describe('child', () => {
+ it('should load instances from parent injector', () => {
+ const parent = Injector.create([Engine.PROVIDER]);
+ const child = Injector.create([], parent);
+
+ const engineFromParent = parent.get(Engine);
+ const engineFromChild = child.get(Engine);
+
+ expect(engineFromChild).toBe(engineFromParent);
+ });
+
+ it('should not use the child providers when resolving the dependencies of a parent provider',
+ () => {
+ const parent = Injector.create([Car.PROVIDER, Engine.PROVIDER]);
+ const child = Injector.create([TurboEngine.PROVIDER], parent);
+
+ const carFromChild = child.get(Car);
+ expect(carFromChild.engine).toBeAnInstanceOf(Engine);
+ });
+
+ it('should create new instance in a child injector', () => {
+ const parent = Injector.create([Engine.PROVIDER]);
+ const child = Injector.create([TurboEngine.PROVIDER], parent);
+
+ const engineFromParent = parent.get(Engine);
+ const engineFromChild = child.get(Engine);
+
+ expect(engineFromParent).not.toBe(engineFromChild);
+ expect(engineFromChild).toBeAnInstanceOf(TurboEngine);
+ });
+
+ it('should give access to parent', () => {
+ const parent = Injector.create([]);
+ const child = Injector.create([], parent);
+ expect((child as any).parent).toBe(parent);
+ });
+ });
+
+
+ describe('instantiate', () => {
+ it('should instantiate an object in the context of the injector', () => {
+ const inj = Injector.create([Engine.PROVIDER]);
+ const childInj = Injector.create([Car.PROVIDER], inj);
+ const car = childInj.get(Car);
+ expect(car).toBeAnInstanceOf(Car);
+ expect(car.engine).toBe(inj.get(Engine));
+ });
+ });
+
+ describe('depedency resolution', () => {
+ describe('@Self()', () => {
+ it('should return a dependency from self', () => {
+ const inj = Injector.create([
+ Engine.PROVIDER,
+ {provide: Car, useFactory: (e: Engine) => new Car(e), deps: [[Engine, new Self()]]}
+ ]);
+
+ expect(inj.get(Car)).toBeAnInstanceOf(Car);
+ });
+
+ it('should throw when not requested provider on self', () => {
+ const parent = Injector.create([Engine.PROVIDER]);
+ const child = Injector.create(
+ [{provide: Car, useFactory: (e: Engine) => new Car(e), deps: [[Engine, new Self()]]}],
+ parent);
+
+ expect(() => child.get(Car))
+ .toThrowError(`StaticInjectorError[${stringify(Car)} -> ${stringify(Engine)}]:
+ NullInjectorError: No provider for Engine!`);
+ });
+ });
+
+ describe('default', () => {
+ it('should skip self', () => {
+ const parent = Injector.create([Engine.PROVIDER]);
+ const child = Injector.create(
+ [
+ TurboEngine.PROVIDER,
+ {provide: Car, useFactory: (e: Engine) => new Car(e), deps: [[SkipSelf, Engine]]}
+ ],
+ parent);
+
+ expect(child.get(Car).engine).toBeAnInstanceOf(Engine);
+ });
+ });
+ });
+
+ describe('resolve', () => {
+ it('should throw when mixing multi providers with regular providers', () => {
+ expect(() => {
+ Injector.create(
+ [{provide: Engine, useClass: BrokenEngine, deps: [], multi: true}, Engine.PROVIDER]);
+ }).toThrowError(/Cannot mix multi providers and regular providers/);
+
+ expect(() => {
+ Injector.create(
+ [Engine.PROVIDER, {provide: Engine, useClass: BrokenEngine, deps: [], multi: true}]);
+ }).toThrowError(/Cannot mix multi providers and regular providers/);
+ });
+
+ it('should resolve forward references', () => {
+ const injector = Injector.create([
+ [{provide: forwardRef(() => BrokenEngine), useClass: forwardRef(() => Engine), deps: []}], {
+ provide: forwardRef(() => String),
+ useFactory: (e: any) => e,
+ deps: [forwardRef(() => BrokenEngine)]
+ }
+ ]);
+ expect(injector.get(String)).toBeAnInstanceOf(Engine);
+ expect(injector.get(BrokenEngine)).toBeAnInstanceOf(Engine);
+ });
+
+ it('should support overriding factory dependencies with dependency annotations', () => {
+ const injector = Injector.create([
+ Engine.PROVIDER,
+ {provide: 'token', useFactory: (e: any) => e, deps: [[new Inject(Engine)]]}
+ ]);
+
+ expect(injector.get('token')).toBeAnInstanceOf(Engine);
+ });
+ });
+
+ describe('displayName', () => {
+ it('should work', () => {
+ expect(Injector.create([Engine.PROVIDER, {provide: BrokenEngine, useValue: null}]).toString())
+ .toEqual('StaticInjector[Injector, Engine, BrokenEngine]');
+ });
+ });
+}
diff --git a/packages/core/test/linker/change_detection_integration_spec.ts b/packages/core/test/linker/change_detection_integration_spec.ts
index 8db439d1a4..e5d7b3f508 100644
--- a/packages/core/test/linker/change_detection_integration_spec.ts
+++ b/packages/core/test/linker/change_detection_integration_spec.ts
@@ -1353,7 +1353,7 @@ export function main() {
tpl: TemplateRef;
@Input()
- outerTpl: TemplateRef
+ outerTpl: TemplateRef;
constructor(public cdRef: ChangeDetectorRef) {}
log(id: string) { log.push(`inner-${id}`); }
diff --git a/packages/core/test/linker/integration_spec.ts b/packages/core/test/linker/integration_spec.ts
index 40eb1e4380..ce9ef724f8 100644
--- a/packages/core/test/linker/integration_spec.ts
+++ b/packages/core/test/linker/integration_spec.ts
@@ -7,7 +7,7 @@
*/
import {CommonModule} from '@angular/common';
-import {Compiler, ComponentFactory, ErrorHandler, EventEmitter, Host, Inject, Injectable, InjectionToken, Injector, NO_ERRORS_SCHEMA, NgModule, NgModuleRef, OnDestroy, ReflectiveInjector, SkipSelf} from '@angular/core';
+import {Compiler, ComponentFactory, ErrorHandler, EventEmitter, Host, Inject, Injectable, InjectionToken, Injector, NO_ERRORS_SCHEMA, NgModule, NgModuleRef, OnDestroy, SkipSelf} from '@angular/core';
import {ChangeDetectionStrategy, ChangeDetectorRef, PipeTransform} from '@angular/core/src/change_detection/change_detection';
import {getDebugContext} from '@angular/core/src/errors';
import {ComponentFactoryResolver} from '@angular/core/src/linker/component_factory_resolver';
@@ -1850,8 +1850,7 @@ class DynamicViewport {
const myService = new MyService();
myService.greeting = 'dynamic greet';
- this.injector = ReflectiveInjector.resolveAndCreate(
- [{provide: MyService, useValue: myService}], vc.injector);
+ this.injector = Injector.create([{provide: MyService, useValue: myService}], vc.injector);
this.componentFactory =
componentFactoryResolver.resolveComponentFactory(ChildCompUsingService) !;
}
diff --git a/packages/core/test/linker/ng_module_integration_spec.ts b/packages/core/test/linker/ng_module_integration_spec.ts
index 0991efe7dc..56660ed27e 100644
--- a/packages/core/test/linker/ng_module_integration_spec.ts
+++ b/packages/core/test/linker/ng_module_integration_spec.ts
@@ -720,7 +720,7 @@ function declareTests({useJit}: {useJit: boolean}) {
it('should throw when the aliased provider does not exist', () => {
const injector = createInjector([{provide: 'car', useExisting: SportsCar}]);
- const e = `No provider for ${stringify(SportsCar)}!`;
+ const e = `NullInjectorError: No provider for ${stringify(SportsCar)}!`;
expect(() => injector.get('car')).toThrowError(e);
});
@@ -830,7 +830,8 @@ function declareTests({useJit}: {useJit: boolean}) {
it('should throw when no provider defined', () => {
const injector = createInjector([]);
- expect(() => injector.get('NonExisting')).toThrowError('No provider for NonExisting!');
+ expect(() => injector.get('NonExisting'))
+ .toThrowError('NullInjectorError: No provider for NonExisting!');
});
it('should throw when trying to instantiate a cyclic dependency', () => {
diff --git a/packages/core/test/view/provider_spec.ts b/packages/core/test/view/provider_spec.ts
index 98f15f1ea2..7732af73ac 100644
--- a/packages/core/test/view/provider_spec.ts
+++ b/packages/core/test/view/provider_spec.ts
@@ -163,13 +163,19 @@ export function main() {
// root elements
expect(() => createAndGetRootNodes(compViewDef(nodes)))
- .toThrowError('No provider for Dep!');
+ .toThrowError(
+ 'StaticInjectorError[Dep]: \n' +
+ ' StaticInjectorError[Dep]: \n' +
+ ' NullInjectorError: No provider for Dep!');
// non root elements
expect(
() => createAndGetRootNodes(compViewDef(
[elementDef(NodeFlags.None, null !, null !, 4, 'span')].concat(nodes))))
- .toThrowError('No provider for Dep!');
+ .toThrowError(
+ 'StaticInjectorError[Dep]: \n' +
+ ' StaticInjectorError[Dep]: \n' +
+ ' NullInjectorError: No provider for Dep!');
});
it('should inject from a parent element in a parent view', () => {
@@ -191,7 +197,10 @@ export function main() {
elementDef(NodeFlags.None, null !, null !, 1, 'span'),
directiveDef(NodeFlags.None, null !, 0, SomeService, ['nonExistingDep'])
])))
- .toThrowError('No provider for nonExistingDep!');
+ .toThrowError(
+ 'StaticInjectorError[nonExistingDep]: \n' +
+ ' StaticInjectorError[nonExistingDep]: \n' +
+ ' NullInjectorError: No provider for nonExistingDep!');
});
it('should use null for optional missing dependencies', () => {
diff --git a/packages/core/testing/src/test_bed.ts b/packages/core/testing/src/test_bed.ts
index 962dd11126..ff169116ca 100644
--- a/packages/core/testing/src/test_bed.ts
+++ b/packages/core/testing/src/test_bed.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {ApplicationInitStatus, CompilerOptions, Component, Directive, InjectionToken, Injector, ModuleWithComponentFactories, NgModule, NgModuleFactory, NgModuleRef, NgZone, Optional, Pipe, PlatformRef, Provider, ReflectiveInjector, SchemaMetadata, SkipSelf, Type, ɵDepFlags as DepFlags, ɵERROR_COMPONENT_TYPE, ɵNodeFlags as NodeFlags, ɵclearProviderOverrides as clearProviderOverrides, ɵoverrideProvider as overrideProvider, ɵstringify as stringify} from '@angular/core';
+import {ApplicationInitStatus, CompilerOptions, Component, Directive, InjectionToken, Injector, ModuleWithComponentFactories, NgModule, NgModuleFactory, NgModuleRef, NgZone, Optional, Pipe, PlatformRef, Provider, SchemaMetadata, SkipSelf, Type, ɵDepFlags as DepFlags, ɵERROR_COMPONENT_TYPE, ɵNodeFlags as NodeFlags, ɵclearProviderOverrides as clearProviderOverrides, ɵoverrideProvider as overrideProvider, ɵstringify as stringify} from '@angular/core';
import {AsyncTestCompleter} from './async_test_completer';
import {ComponentFixture} from './component_fixture';
@@ -308,8 +308,8 @@ export class TestBed implements Injector {
}
}
const ngZone = new NgZone({enableLongStackTrace: true});
- const ngZoneInjector = ReflectiveInjector.resolveAndCreate(
- [{provide: NgZone, useValue: ngZone}], this.platform.injector);
+ const ngZoneInjector =
+ Injector.create([{provide: NgZone, useValue: ngZone}], this.platform.injector);
this._moduleRef = this._moduleFactory.create(ngZoneInjector);
// ApplicationInitStatus.runInitializers() is marked @internal to core. So casting to any
// before accessing it.
diff --git a/packages/examples/core/di/ts/provider_spec.ts b/packages/examples/core/di/ts/provider_spec.ts
index fce21ac041..4a273df1dc 100644
--- a/packages/examples/core/di/ts/provider_spec.ts
+++ b/packages/examples/core/di/ts/provider_spec.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {Inject, Injectable, InjectionToken, Optional, ReflectiveInjector} from '@angular/core';
+import {Injectable, InjectionToken, Injector, Optional, ReflectiveInjector} from '@angular/core';
export function main() {
describe('Provider examples', () => {
@@ -30,8 +30,7 @@ export function main() {
describe('ValueProvider', () => {
it('works', () => {
// #docregion ValueProvider
- const injector =
- ReflectiveInjector.resolveAndCreate([{provide: String, useValue: 'Hello'}]);
+ const injector = Injector.create([{provide: String, useValue: 'Hello'}]);
expect(injector.get(String)).toEqual('Hello');
// #enddocregion
@@ -41,12 +40,13 @@ export function main() {
describe('MultiProviderAspect', () => {
it('works', () => {
// #docregion MultiProviderAspect
- const injector = ReflectiveInjector.resolveAndCreate([
- {provide: 'local', multi: true, useValue: 'en'},
- {provide: 'local', multi: true, useValue: 'sk'},
+ const locale = new InjectionToken('locale');
+ const injector = Injector.create([
+ {provide: locale, multi: true, useValue: 'en'},
+ {provide: locale, multi: true, useValue: 'sk'},
]);
- const locales: string[] = injector.get('local');
+ const locales: string[] = injector.get(locale);
expect(locales).toEqual(['en', 'sk']);
// #enddocregion
});
@@ -89,6 +89,61 @@ export function main() {
});
});
+ describe('StaticClassProvider', () => {
+ it('works', () => {
+ // #docregion StaticClassProvider
+ abstract class Shape { name: string; }
+
+ class Square extends Shape {
+ name = 'square';
+ }
+
+ const injector = Injector.create([{provide: Shape, useClass: Square, deps: []}]);
+
+ const shape: Shape = injector.get(Shape);
+ expect(shape.name).toEqual('square');
+ expect(shape instanceof Square).toBe(true);
+ // #enddocregion
+ });
+
+ it('is different then useExisting', () => {
+ // #docregion StaticClassProviderDifference
+ class Greeting {
+ salutation = 'Hello';
+ }
+
+ class FormalGreeting extends Greeting {
+ salutation = 'Greetings';
+ }
+
+ const injector = Injector.create([
+ {provide: FormalGreeting, useClass: FormalGreeting, deps: []},
+ {provide: Greeting, useClass: FormalGreeting, deps: []}
+ ]);
+
+ // The injector returns different instances.
+ // See: {provide: ?, useExisting: ?} if you want the same instance.
+ expect(injector.get(FormalGreeting)).not.toBe(injector.get(Greeting));
+ // #enddocregion
+ });
+ });
+
+ describe('ConstructorProvider', () => {
+ it('works', () => {
+ // #docregion ConstructorProvider
+ class Square {
+ name = 'square';
+ }
+
+ const injector = Injector.create([{provide: Square, deps: []}]);
+
+ const shape: Square = injector.get(Square);
+ expect(shape.name).toEqual('square');
+ expect(shape instanceof Square).toBe(true);
+ // #enddocregion
+ });
+ });
+
describe('ExistingProvider', () => {
it('works', () => {
// #docregion ExistingProvider
@@ -100,8 +155,9 @@ export function main() {
salutation = 'Greetings';
}
- const injector = ReflectiveInjector.resolveAndCreate(
- [FormalGreeting, {provide: Greeting, useExisting: FormalGreeting}]);
+ const injector = Injector.create([
+ {provide: FormalGreeting, deps: []}, {provide: Greeting, useExisting: FormalGreeting}
+ ]);
expect(injector.get(Greeting).salutation).toEqual('Greetings');
expect(injector.get(FormalGreeting).salutation).toEqual('Greetings');
@@ -116,7 +172,7 @@ export function main() {
const Location = new InjectionToken('location');
const Hash = new InjectionToken('hash');
- const injector = ReflectiveInjector.resolveAndCreate([
+ const injector = Injector.create([
{provide: Location, useValue: 'http://angular.io/#someLocation'}, {
provide: Hash,
useFactory: (location: string) => location.split('#')[1],
@@ -133,7 +189,7 @@ export function main() {
const Location = new InjectionToken('location');
const Hash = new InjectionToken('hash');
- const injector = ReflectiveInjector.resolveAndCreate([{
+ const injector = Injector.create([{
provide: Hash,
useFactory: (location: string) => `Hash for: ${location}`,
// use a nested array to define metadata for dependencies.
diff --git a/packages/forms/src/directives/default_value_accessor.ts b/packages/forms/src/directives/default_value_accessor.ts
index f550dc6213..6208323338 100644
--- a/packages/forms/src/directives/default_value_accessor.ts
+++ b/packages/forms/src/directives/default_value_accessor.ts
@@ -83,14 +83,17 @@ export class DefaultValueAccessor implements ControlValueAccessor {
this._renderer.setProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
}
+ /** @internal */
_handleInput(value: any): void {
if (!this._compositionMode || (this._compositionMode && !this._composing)) {
this.onChange(value);
}
}
+ /** @internal */
_compositionStart(): void { this._composing = true; }
+ /** @internal */
_compositionEnd(value: any): void {
this._composing = false;
this._compositionMode && this.onChange(value);
diff --git a/packages/forms/src/directives/ng_control_status.ts b/packages/forms/src/directives/ng_control_status.ts
index e1cd164c4a..51c91d3f4e 100644
--- a/packages/forms/src/directives/ng_control_status.ts
+++ b/packages/forms/src/directives/ng_control_status.ts
@@ -38,7 +38,16 @@ export const ngControlStatusHost = {
/**
* Directive automatically applied to Angular form controls that sets CSS classes
- * based on control status (valid/invalid/dirty/etc).
+ * based on control status. The following classes are applied as the properties
+ * become true:
+ *
+ * * ng-valid
+ * * ng-invalid
+ * * ng-pending
+ * * ng-pristine
+ * * ng-dirty
+ * * ng-untouched
+ * * ng-touched
*
* @stable
*/
diff --git a/packages/forms/src/directives/range_value_accessor.ts b/packages/forms/src/directives/range_value_accessor.ts
index 65cf42abb6..e695fde116 100644
--- a/packages/forms/src/directives/range_value_accessor.ts
+++ b/packages/forms/src/directives/range_value_accessor.ts
@@ -6,11 +6,11 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {Directive, ElementRef, Provider, Renderer2, forwardRef} from '@angular/core';
+import {Directive, ElementRef, Renderer2, StaticProvider, forwardRef} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
-export const RANGE_VALUE_ACCESSOR: Provider = {
+export const RANGE_VALUE_ACCESSOR: StaticProvider = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => RangeValueAccessor),
multi: true
diff --git a/packages/forms/src/directives/reactive_directives/form_group_directive.ts b/packages/forms/src/directives/reactive_directives/form_group_directive.ts
index 01fe3bf2af..15d08eeab3 100644
--- a/packages/forms/src/directives/reactive_directives/form_group_directive.ts
+++ b/packages/forms/src/directives/reactive_directives/form_group_directive.ts
@@ -134,6 +134,7 @@ export class FormGroupDirective extends ControlContainer implements Form,
onSubmit($event: Event): boolean {
this._submitted = true;
+ this._syncPendingControls();
this.ngSubmit.emit($event);
return false;
}
@@ -145,6 +146,16 @@ export class FormGroupDirective extends ControlContainer implements Form,
this._submitted = false;
}
+ /** @internal */
+ _syncPendingControls() {
+ this.form._syncPendingControls();
+ this.directives.forEach(dir => {
+ if (dir.control._updateOn === 'submit') {
+ dir.viewToModelUpdate(dir.control._pendingValue);
+ }
+ });
+ }
+
/** @internal */
_updateDomValue() {
this.directives.forEach(dir => {
diff --git a/packages/forms/src/directives/select_control_value_accessor.ts b/packages/forms/src/directives/select_control_value_accessor.ts
index aec3b02f7a..43e6322ae3 100644
--- a/packages/forms/src/directives/select_control_value_accessor.ts
+++ b/packages/forms/src/directives/select_control_value_accessor.ts
@@ -6,10 +6,11 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {Directive, ElementRef, Host, Input, OnDestroy, Optional, Provider, Renderer2, forwardRef, ɵlooseIdentical as looseIdentical} from '@angular/core';
+import {Directive, ElementRef, Host, Input, OnDestroy, Optional, Renderer2, StaticProvider, forwardRef, ɵlooseIdentical as looseIdentical} from '@angular/core';
+
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
-export const SELECT_VALUE_ACCESSOR: Provider = {
+export const SELECT_VALUE_ACCESSOR: StaticProvider = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => SelectControlValueAccessor),
multi: true
diff --git a/packages/forms/src/directives/select_multiple_control_value_accessor.ts b/packages/forms/src/directives/select_multiple_control_value_accessor.ts
index 7db6202ead..c8e5b692e1 100644
--- a/packages/forms/src/directives/select_multiple_control_value_accessor.ts
+++ b/packages/forms/src/directives/select_multiple_control_value_accessor.ts
@@ -6,10 +6,11 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {Directive, ElementRef, Host, Input, OnDestroy, Optional, Provider, Renderer2, forwardRef, ɵlooseIdentical as looseIdentical} from '@angular/core';
+import {Directive, ElementRef, Host, Input, OnDestroy, Optional, Renderer2, StaticProvider, forwardRef, ɵlooseIdentical as looseIdentical} from '@angular/core';
+
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
-export const SELECT_MULTIPLE_VALUE_ACCESSOR: Provider = {
+export const SELECT_MULTIPLE_VALUE_ACCESSOR: StaticProvider = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => SelectMultipleControlValueAccessor),
multi: true
diff --git a/packages/forms/src/directives/shared.ts b/packages/forms/src/directives/shared.ts
index 8823b62818..428f502743 100644
--- a/packages/forms/src/directives/shared.ts
+++ b/packages/forms/src/directives/shared.ts
@@ -38,23 +38,10 @@ export function setUpControl(control: FormControl, dir: NgControl): void {
control.asyncValidator = Validators.composeAsync([control.asyncValidator !, dir.asyncValidator]);
dir.valueAccessor !.writeValue(control.value);
- // view -> model
- dir.valueAccessor !.registerOnChange((newValue: any) => {
- dir.viewToModelUpdate(newValue);
- control.markAsDirty();
- control.setValue(newValue, {emitModelToViewChange: false});
- });
+ setUpViewChangePipeline(control, dir);
+ setUpModelChangePipeline(control, dir);
- // touched
- dir.valueAccessor !.registerOnTouched(() => control.markAsTouched());
-
- control.registerOnChange((newValue: any, emitModelEvent: boolean) => {
- // control -> view
- dir.valueAccessor !.writeValue(newValue);
-
- // control -> ngModel
- if (emitModelEvent) dir.viewToModelUpdate(newValue);
- });
+ setUpBlurPipeline(control, dir);
if (dir.valueAccessor !.setDisabledState) {
control.registerOnDisabledChange(
@@ -92,6 +79,40 @@ export function cleanUpControl(control: FormControl, dir: NgControl) {
if (control) control._clearChangeFns();
}
+function setUpViewChangePipeline(control: FormControl, dir: NgControl): void {
+ dir.valueAccessor !.registerOnChange((newValue: any) => {
+ control._pendingValue = newValue;
+ control._pendingDirty = true;
+
+ if (control._updateOn === 'change') updateControl(control, dir);
+ });
+}
+
+function setUpBlurPipeline(control: FormControl, dir: NgControl): void {
+ dir.valueAccessor !.registerOnTouched(() => {
+ control._pendingTouched = true;
+
+ if (control._updateOn === 'blur') updateControl(control, dir);
+ if (control._updateOn !== 'submit') control.markAsTouched();
+ });
+}
+
+function updateControl(control: FormControl, dir: NgControl): void {
+ dir.viewToModelUpdate(control._pendingValue);
+ if (control._pendingDirty) control.markAsDirty();
+ control.setValue(control._pendingValue, {emitModelToViewChange: false});
+}
+
+function setUpModelChangePipeline(control: FormControl, dir: NgControl): void {
+ control.registerOnChange((newValue: any, emitModelEvent: boolean) => {
+ // control -> view
+ dir.valueAccessor !.writeValue(newValue);
+
+ // control -> ngModel
+ if (emitModelEvent) dir.viewToModelUpdate(newValue);
+ });
+}
+
export function setUpFormContainer(
control: FormGroup | FormArray, dir: AbstractFormGroupDirective | FormArrayName) {
if (control == null) _throwError(dir, 'Cannot find control with');
diff --git a/packages/forms/src/directives/validators.ts b/packages/forms/src/directives/validators.ts
index 7ec13a6208..00882f016a 100644
--- a/packages/forms/src/directives/validators.ts
+++ b/packages/forms/src/directives/validators.ts
@@ -6,11 +6,13 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {Directive, Input, OnChanges, Provider, SimpleChanges, forwardRef} from '@angular/core';
+import {Directive, Input, OnChanges, SimpleChanges, StaticProvider, forwardRef} from '@angular/core';
import {Observable} from 'rxjs/Observable';
+
import {AbstractControl} from '../model';
import {NG_VALIDATORS, Validators} from '../validators';
+
/** @experimental */
export type ValidationErrors = {
[key: string]: any
@@ -45,13 +47,13 @@ export interface AsyncValidator extends Validator {
validate(c: AbstractControl): Promise|Observable;
}
-export const REQUIRED_VALIDATOR: Provider = {
+export const REQUIRED_VALIDATOR: StaticProvider = {
provide: NG_VALIDATORS,
useExisting: forwardRef(() => RequiredValidator),
multi: true
};
-export const CHECKBOX_REQUIRED_VALIDATOR: Provider = {
+export const CHECKBOX_REQUIRED_VALIDATOR: StaticProvider = {
provide: NG_VALIDATORS,
useExisting: forwardRef(() => CheckboxRequiredValidator),
multi: true
diff --git a/packages/forms/src/model.ts b/packages/forms/src/model.ts
index 0f215ffbbb..b0d1e64058 100644
--- a/packages/forms/src/model.ts
+++ b/packages/forms/src/model.ts
@@ -55,16 +55,45 @@ function _find(control: AbstractControl, path: Array| string, del
}, control);
}
-function coerceToValidator(validator?: ValidatorFn | ValidatorFn[] | null): ValidatorFn|null {
+function coerceToValidator(
+ validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null): ValidatorFn|
+ null {
+ const validator =
+ (isOptionsObj(validatorOrOpts) ? (validatorOrOpts as AbstractControlOptions).validators :
+ validatorOrOpts) as ValidatorFn |
+ ValidatorFn[] | null;
+
return Array.isArray(validator) ? composeValidators(validator) : validator || null;
}
-function coerceToAsyncValidator(asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null):
- AsyncValidatorFn|null {
- return Array.isArray(asyncValidator) ? composeAsyncValidators(asyncValidator) :
- asyncValidator || null;
+function coerceToAsyncValidator(
+ asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null, validatorOrOpts?: ValidatorFn |
+ ValidatorFn[] | AbstractControlOptions | null): AsyncValidatorFn|null {
+ const origAsyncValidator =
+ (isOptionsObj(validatorOrOpts) ? (validatorOrOpts as AbstractControlOptions).asyncValidators :
+ asyncValidator) as AsyncValidatorFn |
+ AsyncValidatorFn | null;
+
+ return Array.isArray(origAsyncValidator) ? composeAsyncValidators(origAsyncValidator) :
+ origAsyncValidator || null;
}
+export type FormHooks = 'change' | 'blur' | 'submit';
+
+export interface AbstractControlOptions {
+ validators?: ValidatorFn|ValidatorFn[]|null;
+ asyncValidators?: AsyncValidatorFn|AsyncValidatorFn[]|null;
+ updateOn?: FormHooks;
+}
+
+
+function isOptionsObj(
+ validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null): boolean {
+ return validatorOrOpts != null && !Array.isArray(validatorOrOpts) &&
+ typeof validatorOrOpts === 'object';
+}
+
+
/**
* @whatItDoes This is the base class for {@link FormControl}, {@link FormGroup}, and
* {@link FormArray}.
@@ -79,6 +108,13 @@ function coerceToAsyncValidator(asyncValidator?: AsyncValidatorFn | AsyncValidat
export abstract class AbstractControl {
/** @internal */
_value: any;
+
+ /** @internal */
+ _pendingDirty: boolean;
+
+ /** @internal */
+ _pendingTouched: boolean;
+
/** @internal */
_onCollectionChange = () => {};
@@ -255,6 +291,7 @@ export abstract class AbstractControl {
*/
markAsUntouched(opts: {onlySelf?: boolean} = {}): void {
this._touched = false;
+ this._pendingTouched = false;
this._forEachChild(
(control: AbstractControl) => { control.markAsUntouched({onlySelf: true}); });
@@ -287,6 +324,7 @@ export abstract class AbstractControl {
*/
markAsPristine(opts: {onlySelf?: boolean} = {}): void {
this._pristine = true;
+ this._pendingDirty = false;
this._forEachChild((control: AbstractControl) => { control.markAsPristine({onlySelf: true}); });
@@ -539,6 +577,9 @@ export abstract class AbstractControl {
/** @internal */
abstract _allControlsDisabled(): boolean;
+ /** @internal */
+ abstract _syncPendingControls(): boolean;
+
/** @internal */
_anyControlsHaveStatus(status: string): boolean {
return this._anyControls((control: AbstractControl) => control.status === status);
@@ -612,9 +653,12 @@ export abstract class AbstractControl {
* console.log(ctrl.status); // 'DISABLED'
* ```
*
- * To include a sync validator (or an array of sync validators) with the control,
- * pass it in as the second argument. Async validators are also supported, but
- * have to be passed in separately as the third arg.
+ * The second {@link FormControl} argument can accept one of three things:
+ * * a sync validator function
+ * * an array of sync validator functions
+ * * an options object containing validator and/or async validator functions
+ *
+ * Example of a single sync validator function:
*
* ```ts
* const ctrl = new FormControl('', Validators.required);
@@ -622,6 +666,27 @@ export abstract class AbstractControl {
* console.log(ctrl.status); // 'INVALID'
* ```
*
+ * Example using options object:
+ *
+ * ```ts
+ * const ctrl = new FormControl('', {
+ * validators: Validators.required,
+ * asyncValidators: myAsyncValidator
+ * });
+ * ```
+ *
+ * The options object can also be used to define when the control should update.
+ * By default, the value and validity of a control updates whenever the value
+ * changes. You can configure it to update on the blur event instead by setting
+ * the `updateOn` option to `'blur'`.
+ *
+ * ```ts
+ * const c = new FormControl('', { updateOn: 'blur' });
+ * ```
+ *
+ * You can also set `updateOn` to `'submit'`, which will delay value and validity
+ * updates until the parent form of the control fires a submit event.
+ *
* See its superclass, {@link AbstractControl}, for more properties and methods.
*
* * **npm package**: `@angular/forms`
@@ -632,11 +697,21 @@ export class FormControl extends AbstractControl {
/** @internal */
_onChange: Function[] = [];
+ /** @internal */
+ _updateOn: FormHooks = 'change';
+
+ /** @internal */
+ _pendingValue: any;
+
constructor(
- formState: any = null, validator?: ValidatorFn|ValidatorFn[]|null,
+ formState: any = null,
+ validatorOrOpts?: ValidatorFn|ValidatorFn[]|AbstractControlOptions|null,
asyncValidator?: AsyncValidatorFn|AsyncValidatorFn[]|null) {
- super(coerceToValidator(validator), coerceToAsyncValidator(asyncValidator));
+ super(
+ coerceToValidator(validatorOrOpts),
+ coerceToAsyncValidator(asyncValidator, validatorOrOpts));
this._applyFormState(formState);
+ this._setUpdateStrategy(validatorOrOpts);
this.updateValueAndValidity({onlySelf: true, emitEvent: false});
this._initObservables();
}
@@ -664,7 +739,7 @@ export class FormControl extends AbstractControl {
emitModelToViewChange?: boolean,
emitViewToModelChange?: boolean
} = {}): void {
- this._value = value;
+ this._value = this._pendingValue = value;
if (this._onChange.length && options.emitModelToViewChange !== false) {
this._onChange.forEach(
(changeFn) => changeFn(this._value, options.emitViewToModelChange !== false));
@@ -764,13 +839,30 @@ export class FormControl extends AbstractControl {
*/
_forEachChild(cb: Function): void {}
+ /** @internal */
+ _syncPendingControls(): boolean {
+ if (this._updateOn === 'submit') {
+ this.setValue(this._pendingValue, {onlySelf: true, emitModelToViewChange: false});
+ if (this._pendingDirty) this.markAsDirty();
+ if (this._pendingTouched) this.markAsTouched();
+ return true;
+ }
+ return false;
+ }
+
private _applyFormState(formState: any) {
if (this._isBoxedValue(formState)) {
- this._value = formState.value;
+ this._value = this._pendingValue = formState.value;
formState.disabled ? this.disable({onlySelf: true, emitEvent: false}) :
this.enable({onlySelf: true, emitEvent: false});
} else {
- this._value = formState;
+ this._value = this._pendingValue = formState;
+ }
+ }
+
+ private _setUpdateStrategy(opts?: ValidatorFn|ValidatorFn[]|AbstractControlOptions|null): void {
+ if (isOptionsObj(opts) && (opts as AbstractControlOptions).updateOn != null) {
+ this._updateOn = (opts as AbstractControlOptions).updateOn !;
}
}
}
@@ -823,15 +915,28 @@ export class FormControl extends AbstractControl {
* }
* ```
*
+ * Like {@link FormControl} instances, you can alternatively choose to pass in
+ * validators and async validators as part of an options object.
+ *
+ * ```
+ * const form = new FormGroup({
+ * password: new FormControl('')
+ * passwordConfirm: new FormControl('')
+ * }, {validators: passwordMatchValidator, asyncValidators: otherValidator});
+ * ```
+ *
* * **npm package**: `@angular/forms`
*
* @stable
*/
export class FormGroup extends AbstractControl {
constructor(
- public controls: {[key: string]: AbstractControl}, validator?: ValidatorFn|null,
- asyncValidator?: AsyncValidatorFn|null) {
- super(validator || null, asyncValidator || null);
+ public controls: {[key: string]: AbstractControl},
+ validatorOrOpts?: ValidatorFn|ValidatorFn[]|AbstractControlOptions|null,
+ asyncValidator?: AsyncValidatorFn|AsyncValidatorFn[]|null) {
+ super(
+ coerceToValidator(validatorOrOpts),
+ coerceToAsyncValidator(asyncValidator, validatorOrOpts));
this._initObservables();
this._setUpControls();
this.updateValueAndValidity({onlySelf: true, emitEvent: false});
@@ -1009,6 +1114,15 @@ export class FormGroup extends AbstractControl {
});
}
+ /** @internal */
+ _syncPendingControls(): boolean {
+ let subtreeUpdated = this._reduceChildren(false, (updated: boolean, child: AbstractControl) => {
+ return child._syncPendingControls() ? true : updated;
+ });
+ if (subtreeUpdated) this.updateValueAndValidity({onlySelf: true});
+ return subtreeUpdated;
+ }
+
/** @internal */
_throwIfControlMissing(name: string): void {
if (!Object.keys(this.controls).length) {
@@ -1114,9 +1228,19 @@ export class FormGroup extends AbstractControl {
* console.log(arr.status); // 'VALID'
* ```
*
- * You can also include array-level validators as the second arg, or array-level async
- * validators as the third arg. These come in handy when you want to perform validation
- * that considers the value of more than one child control.
+ * You can also include array-level validators and async validators. These come in handy
+ * when you want to perform validation that considers the value of more than one child
+ * control.
+ *
+ * The two types of validators can be passed in separately as the second and third arg
+ * respectively, or together as part of an options object.
+ *
+ * ```
+ * const arr = new FormArray([
+ * new FormControl('Nancy'),
+ * new FormControl('Drew')
+ * ], {validators: myValidator, asyncValidators: myAsyncValidator});
+ * ```
*
* ### Adding or removing controls
*
@@ -1132,9 +1256,12 @@ export class FormGroup extends AbstractControl {
*/
export class FormArray extends AbstractControl {
constructor(
- public controls: AbstractControl[], validator?: ValidatorFn|null,
- asyncValidator?: AsyncValidatorFn|null) {
- super(validator || null, asyncValidator || null);
+ public controls: AbstractControl[],
+ validatorOrOpts?: ValidatorFn|ValidatorFn[]|AbstractControlOptions|null,
+ asyncValidator?: AsyncValidatorFn|AsyncValidatorFn[]|null) {
+ super(
+ coerceToValidator(validatorOrOpts),
+ coerceToAsyncValidator(asyncValidator, validatorOrOpts));
this._initObservables();
this._setUpControls();
this.updateValueAndValidity({onlySelf: true, emitEvent: false});
@@ -1308,6 +1435,15 @@ export class FormArray extends AbstractControl {
});
}
+ /** @internal */
+ _syncPendingControls(): boolean {
+ let subtreeUpdated = this.controls.reduce((updated: boolean, child: AbstractControl) => {
+ return child._syncPendingControls() ? true : updated;
+ }, false);
+ if (subtreeUpdated) this.updateValueAndValidity({onlySelf: true});
+ return subtreeUpdated;
+ }
+
/** @internal */
_throwIfControlMissing(index: number): void {
if (!this.controls.length) {
diff --git a/packages/forms/test/form_array_spec.ts b/packages/forms/test/form_array_spec.ts
index 2b94b80ec7..11b0605ae3 100644
--- a/packages/forms/test/form_array_spec.ts
+++ b/packages/forms/test/form_array_spec.ts
@@ -8,8 +8,8 @@
import {fakeAsync, tick} from '@angular/core/testing';
import {AsyncTestCompleter, beforeEach, describe, inject, it} from '@angular/core/testing/src/testing_internal';
-import {AbstractControl, FormArray, FormControl, FormGroup} from '@angular/forms';
-
+import {AbstractControl, FormArray, FormControl, FormGroup, ValidationErrors} from '@angular/forms';
+import {of } from 'rxjs/observable/of';
import {Validators} from '../src/validators';
export function main() {
@@ -725,18 +725,113 @@ export function main() {
});
});
+ describe('validator', () => {
+ function simpleValidator(c: AbstractControl): ValidationErrors|null {
+ return c.get([0]) !.value === 'correct' ? null : {'broken': true};
+ }
+
+ function arrayRequiredValidator(c: AbstractControl): ValidationErrors|null {
+ return Validators.required(c.get([0]) as AbstractControl);
+ }
+
+ it('should set a single validator', () => {
+ const a = new FormArray([new FormControl()], simpleValidator);
+ expect(a.valid).toBe(false);
+ expect(a.errors).toEqual({'broken': true});
+
+ a.setValue(['correct']);
+ expect(a.valid).toBe(true);
+ });
+
+ it('should set a single validator from options obj', () => {
+ const a = new FormArray([new FormControl()], {validators: simpleValidator});
+ expect(a.valid).toBe(false);
+ expect(a.errors).toEqual({'broken': true});
+
+ a.setValue(['correct']);
+ expect(a.valid).toBe(true);
+ });
+
+ it('should set multiple validators from an array', () => {
+ const a = new FormArray([new FormControl()], [simpleValidator, arrayRequiredValidator]);
+ expect(a.valid).toBe(false);
+ expect(a.errors).toEqual({'required': true, 'broken': true});
+
+ a.setValue(['c']);
+ expect(a.valid).toBe(false);
+ expect(a.errors).toEqual({'broken': true});
+
+ a.setValue(['correct']);
+ expect(a.valid).toBe(true);
+ });
+
+ it('should set multiple validators from options obj', () => {
+ const a = new FormArray(
+ [new FormControl()], {validators: [simpleValidator, arrayRequiredValidator]});
+ expect(a.valid).toBe(false);
+ expect(a.errors).toEqual({'required': true, 'broken': true});
+
+ a.setValue(['c']);
+ expect(a.valid).toBe(false);
+ expect(a.errors).toEqual({'broken': true});
+
+ a.setValue(['correct']);
+ expect(a.valid).toBe(true);
+ });
+ });
+
describe('asyncValidator', () => {
+ function otherObservableValidator() { return of ({'other': true}); }
+
it('should run the async validator', fakeAsync(() => {
const c = new FormControl('value');
const g = new FormArray([c], null !, asyncValidator('expected'));
expect(g.pending).toEqual(true);
- tick(1);
+ tick();
expect(g.errors).toEqual({'async': true});
expect(g.pending).toEqual(false);
}));
+
+ it('should set a single async validator from options obj', fakeAsync(() => {
+ const g = new FormArray(
+ [new FormControl('value')], {asyncValidators: asyncValidator('expected')});
+
+ expect(g.pending).toEqual(true);
+
+ tick();
+
+ expect(g.errors).toEqual({'async': true});
+ expect(g.pending).toEqual(false);
+ }));
+
+ it('should set multiple async validators from an array', fakeAsync(() => {
+ const g = new FormArray(
+ [new FormControl('value')], null !,
+ [asyncValidator('expected'), otherObservableValidator]);
+
+ expect(g.pending).toEqual(true);
+
+ tick();
+
+ expect(g.errors).toEqual({'async': true, 'other': true});
+ expect(g.pending).toEqual(false);
+ }));
+
+ it('should set multiple async validators from options obj', fakeAsync(() => {
+ const g = new FormArray(
+ [new FormControl('value')],
+ {asyncValidators: [asyncValidator('expected'), otherObservableValidator]});
+
+ expect(g.pending).toEqual(true);
+
+ tick();
+
+ expect(g.errors).toEqual({'async': true, 'other': true});
+ expect(g.pending).toEqual(false);
+ }));
});
describe('disable() & enable()', () => {
diff --git a/packages/forms/test/form_control_spec.ts b/packages/forms/test/form_control_spec.ts
index 4fa4d9f3a2..a424a5619a 100644
--- a/packages/forms/test/form_control_spec.ts
+++ b/packages/forms/test/form_control_spec.ts
@@ -76,7 +76,27 @@ export function main() {
});
+ describe('updateOn', () => {
+
+ it('should default to on change', () => {
+ const c = new FormControl('');
+ expect(c._updateOn).toEqual('change');
+ });
+
+ it('should default to on change with an options obj', () => {
+ const c = new FormControl('', {validators: Validators.required});
+ expect(c._updateOn).toEqual('change');
+ });
+
+ it('should set updateOn when updating on blur', () => {
+ const c = new FormControl('', {updateOn: 'blur'});
+ expect(c._updateOn).toEqual('blur');
+ });
+
+ });
+
describe('validator', () => {
+
it('should run validator with the initial value', () => {
const c = new FormControl('value', Validators.required);
expect(c.valid).toEqual(true);
@@ -97,6 +117,39 @@ export function main() {
expect(c.valid).toEqual(true);
});
+ it('should support single validator from options obj', () => {
+ const c = new FormControl(null, {validators: Validators.required});
+ expect(c.valid).toEqual(false);
+ expect(c.errors).toEqual({required: true});
+
+ c.setValue('value');
+ expect(c.valid).toEqual(true);
+ });
+
+ it('should support multiple validators from options obj', () => {
+ const c =
+ new FormControl(null, {validators: [Validators.required, Validators.minLength(3)]});
+ expect(c.valid).toEqual(false);
+ expect(c.errors).toEqual({required: true});
+
+ c.setValue('aa');
+ expect(c.valid).toEqual(false);
+ expect(c.errors).toEqual({minlength: {requiredLength: 3, actualLength: 2}});
+
+ c.setValue('aaa');
+ expect(c.valid).toEqual(true);
+ });
+
+ it('should support a null validators value', () => {
+ const c = new FormControl(null, {validators: null});
+ expect(c.valid).toEqual(true);
+ });
+
+ it('should support an empty options obj', () => {
+ const c = new FormControl(null, {});
+ expect(c.valid).toEqual(true);
+ });
+
it('should return errors', () => {
const c = new FormControl(null, Validators.required);
expect(c.errors).toEqual({'required': true});
@@ -222,6 +275,40 @@ export function main() {
expect(c.errors).toEqual({'async': true, 'other': true});
}));
+
+ it('should support a single async validator from options obj', fakeAsync(() => {
+ const c = new FormControl('value', {asyncValidators: asyncValidator('expected')});
+ expect(c.pending).toEqual(true);
+ tick();
+
+ expect(c.valid).toEqual(false);
+ expect(c.errors).toEqual({'async': true});
+ }));
+
+ it('should support multiple async validators from options obj', fakeAsync(() => {
+ const c = new FormControl(
+ 'value', {asyncValidators: [asyncValidator('expected'), otherAsyncValidator]});
+ expect(c.pending).toEqual(true);
+ tick();
+
+ expect(c.valid).toEqual(false);
+ expect(c.errors).toEqual({'async': true, 'other': true});
+ }));
+
+ it('should support a mix of validators from options obj', fakeAsync(() => {
+ const c = new FormControl(
+ '', {validators: Validators.required, asyncValidators: asyncValidator('expected')});
+ tick();
+ expect(c.errors).toEqual({required: true});
+
+ c.setValue('value');
+ expect(c.pending).toBe(true);
+
+ tick();
+ expect(c.valid).toEqual(false);
+ expect(c.errors).toEqual({'async': true});
+ }));
+
it('should add single async validator', fakeAsync(() => {
const c = new FormControl('value', null !);
diff --git a/packages/forms/test/form_group_spec.ts b/packages/forms/test/form_group_spec.ts
index aa62b78c9c..feb20cdf0b 100644
--- a/packages/forms/test/form_group_spec.ts
+++ b/packages/forms/test/form_group_spec.ts
@@ -9,10 +9,15 @@
import {EventEmitter} from '@angular/core';
import {async, fakeAsync, tick} from '@angular/core/testing';
import {AsyncTestCompleter, beforeEach, describe, inject, it} from '@angular/core/testing/src/testing_internal';
-import {AbstractControl, FormArray, FormControl, FormGroup, Validators} from '@angular/forms';
+import {AbstractControl, FormArray, FormControl, FormGroup, ValidationErrors, Validators} from '@angular/forms';
+import {of } from 'rxjs/observable/of';
export function main() {
+ function simpleValidator(c: AbstractControl): ValidationErrors|null {
+ return c.get('one') !.value === 'correct' ? null : {'broken': true};
+ }
+
function asyncValidator(expected: string, timeouts = {}) {
return (c: AbstractControl) => {
let resolve: (result: any) => void = undefined !;
@@ -36,6 +41,8 @@ export function main() {
return e;
}
+ function otherObservableValidator() { return of ({'other': true}); }
+
describe('FormGroup', () => {
describe('value', () => {
it('should be the reduced value of the child controls', () => {
@@ -104,26 +111,6 @@ export function main() {
});
});
- describe('errors', () => {
- it('should run the validator when the value changes', () => {
- const simpleValidator = (c: FormGroup) =>
- c.controls['one'].value != 'correct' ? {'broken': true} : null;
-
- const c = new FormControl(null);
- const g = new FormGroup({'one': c}, simpleValidator);
-
- c.setValue('correct');
-
- expect(g.valid).toEqual(true);
- expect(g.errors).toEqual(null);
-
- c.setValue('incorrect');
-
- expect(g.valid).toEqual(false);
- expect(g.errors).toEqual({'broken': true});
- });
- });
-
describe('dirty', () => {
let c: FormControl, g: FormGroup;
@@ -629,7 +616,7 @@ export function main() {
it('should return true when the component is enabled', () => {
expect(group.contains('required')).toEqual(true);
- group.enable('optional');
+ group.enable();
expect(group.contains('optional')).toEqual(true);
});
@@ -687,6 +674,66 @@ export function main() {
});
});
+ describe('validator', () => {
+
+ function containsValidator(c: AbstractControl): ValidationErrors|null {
+ return c.get('one') !.value && c.get('one') !.value.indexOf('c') !== -1 ? null :
+ {'missing': true};
+ }
+
+ it('should run a single validator when the value changes', () => {
+ const c = new FormControl(null);
+ const g = new FormGroup({'one': c}, simpleValidator);
+
+ c.setValue('correct');
+
+ expect(g.valid).toEqual(true);
+ expect(g.errors).toEqual(null);
+
+ c.setValue('incorrect');
+
+ expect(g.valid).toEqual(false);
+ expect(g.errors).toEqual({'broken': true});
+ });
+
+ it('should support multiple validators from array', () => {
+ const g = new FormGroup({one: new FormControl()}, [simpleValidator, containsValidator]);
+ expect(g.valid).toEqual(false);
+ expect(g.errors).toEqual({missing: true, broken: true});
+
+ g.setValue({one: 'c'});
+ expect(g.valid).toEqual(false);
+ expect(g.errors).toEqual({broken: true});
+
+ g.setValue({one: 'correct'});
+ expect(g.valid).toEqual(true);
+ });
+
+ it('should set single validator from options obj', () => {
+ const g = new FormGroup({one: new FormControl()}, {validators: simpleValidator});
+ expect(g.valid).toEqual(false);
+ expect(g.errors).toEqual({broken: true});
+
+ g.setValue({one: 'correct'});
+ expect(g.valid).toEqual(true);
+ });
+
+ it('should set multiple validators from options obj', () => {
+ const g = new FormGroup(
+ {one: new FormControl()}, {validators: [simpleValidator, containsValidator]});
+ expect(g.valid).toEqual(false);
+ expect(g.errors).toEqual({missing: true, broken: true});
+
+ g.setValue({one: 'c'});
+ expect(g.valid).toEqual(false);
+ expect(g.errors).toEqual({broken: true});
+
+ g.setValue({one: 'correct'});
+ expect(g.valid).toEqual(true);
+ });
+
+ });
+
describe('asyncValidator', () => {
it('should run the async validator', fakeAsync(() => {
const c = new FormControl('value');
@@ -700,6 +747,38 @@ export function main() {
expect(g.pending).toEqual(false);
}));
+ it('should set multiple async validators from array', fakeAsync(() => {
+ const g = new FormGroup(
+ {'one': new FormControl('value')}, null !,
+ [asyncValidator('expected'), otherObservableValidator]);
+ expect(g.pending).toEqual(true);
+
+ tick();
+ expect(g.errors).toEqual({'async': true, 'other': true});
+ expect(g.pending).toEqual(false);
+ }));
+
+ it('should set single async validator from options obj', fakeAsync(() => {
+ const g = new FormGroup(
+ {'one': new FormControl('value')}, {asyncValidators: asyncValidator('expected')});
+ expect(g.pending).toEqual(true);
+
+ tick();
+ expect(g.errors).toEqual({'async': true});
+ expect(g.pending).toEqual(false);
+ }));
+
+ it('should set multiple async validators from options obj', fakeAsync(() => {
+ const g = new FormGroup(
+ {'one': new FormControl('value')},
+ {asyncValidators: [asyncValidator('expected'), otherObservableValidator]});
+ expect(g.pending).toEqual(true);
+
+ tick();
+ expect(g.errors).toEqual({'async': true, 'other': true});
+ expect(g.pending).toEqual(false);
+ }));
+
it('should set the parent group\'s status to pending', fakeAsync(() => {
const c = new FormControl('value', null !, asyncValidator('expected'));
const g = new FormGroup({'one': c});
diff --git a/packages/forms/test/reactive_integration_spec.ts b/packages/forms/test/reactive_integration_spec.ts
index 3e758b0aed..4db317ca6e 100644
--- a/packages/forms/test/reactive_integration_spec.ts
+++ b/packages/forms/test/reactive_integration_spec.ts
@@ -12,8 +12,10 @@ import {AbstractControl, AsyncValidator, AsyncValidatorFn, COMPOSITION_BUFFER_MO
import {By} from '@angular/platform-browser/src/dom/debug/by';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util';
+import {merge} from 'rxjs/observable/merge';
import {timer} from 'rxjs/observable/timer';
import {_do} from 'rxjs/operator/do';
+
import {MyInput, MyInputForm} from './value_accessor_integration_spec';
export function main() {
@@ -731,6 +733,493 @@ export function main() {
});
+ describe('updateOn options', () => {
+
+ describe('on blur', () => {
+
+ it('should not update value or validity based on user input until blur', () => {
+ const fixture = initTest(FormControlComp);
+ const control = new FormControl('', {validators: Validators.required, updateOn: 'blur'});
+ fixture.componentInstance.control = control;
+ fixture.detectChanges();
+
+ const input = fixture.debugElement.query(By.css('input')).nativeElement;
+ input.value = 'Nancy';
+ dispatchEvent(input, 'input');
+ fixture.detectChanges();
+
+ expect(control.value).toEqual('', 'Expected value to remain unchanged until blur.');
+ expect(control.valid).toBe(false, 'Expected no validation to occur until blur.');
+
+ dispatchEvent(input, 'blur');
+ fixture.detectChanges();
+
+ expect(control.value)
+ .toEqual('Nancy', 'Expected value to change once control is blurred.');
+ expect(control.valid).toBe(true, 'Expected validation to run once control is blurred.');
+ });
+
+ it('should not update parent group value/validity from child until blur', () => {
+ const fixture = initTest(FormGroupComp);
+ const form = new FormGroup(
+ {login: new FormControl('', {validators: Validators.required, updateOn: 'blur'})});
+ fixture.componentInstance.form = form;
+ fixture.detectChanges();
+
+ const input = fixture.debugElement.query(By.css('input')).nativeElement;
+ input.value = 'Nancy';
+ dispatchEvent(input, 'input');
+ fixture.detectChanges();
+
+ expect(form.value)
+ .toEqual({login: ''}, 'Expected group value to remain unchanged until blur.');
+ expect(form.valid).toBe(false, 'Expected no validation to occur on group until blur.');
+
+ dispatchEvent(input, 'blur');
+ fixture.detectChanges();
+
+ expect(form.value)
+ .toEqual({login: 'Nancy'}, 'Expected group value to change once input blurred.');
+ expect(form.valid).toBe(true, 'Expected validation to run once input blurred.');
+ });
+
+ it('should not wait for blur event to update if value is set programmatically', () => {
+ const fixture = initTest(FormControlComp);
+ const control = new FormControl('', {validators: Validators.required, updateOn: 'blur'});
+ fixture.componentInstance.control = control;
+ fixture.detectChanges();
+
+ control.setValue('Nancy');
+ fixture.detectChanges();
+
+ const input = fixture.debugElement.query(By.css('input')).nativeElement;
+ expect(input.value).toEqual('Nancy', 'Expected value to propagate to view immediately.');
+ expect(control.value).toEqual('Nancy', 'Expected model value to update immediately.');
+ expect(control.valid).toBe(true, 'Expected validation to run immediately.');
+ });
+
+ it('should not update dirty state until control is blurred', () => {
+ const fixture = initTest(FormControlComp);
+ const control = new FormControl('', {updateOn: 'blur'});
+ fixture.componentInstance.control = control;
+ fixture.detectChanges();
+
+ expect(control.dirty).toBe(false, 'Expected control to start out pristine.');
+
+ const input = fixture.debugElement.query(By.css('input')).nativeElement;
+ input.value = 'Nancy';
+ dispatchEvent(input, 'input');
+ fixture.detectChanges();
+
+ expect(control.dirty).toBe(false, 'Expected control to stay pristine until blurred.');
+
+ dispatchEvent(input, 'blur');
+ fixture.detectChanges();
+
+ expect(control.dirty).toBe(true, 'Expected control to update dirty state when blurred.');
+ });
+
+ it('should continue waiting for blur to update if previously blurred', () => {
+ const fixture = initTest(FormControlComp);
+ const control =
+ new FormControl('Nancy', {validators: Validators.required, updateOn: 'blur'});
+ fixture.componentInstance.control = control;
+ fixture.detectChanges();
+
+ const input = fixture.debugElement.query(By.css('input')).nativeElement;
+ dispatchEvent(input, 'blur');
+ fixture.detectChanges();
+
+ dispatchEvent(input, 'focus');
+ input.value = '';
+ dispatchEvent(input, 'input');
+ fixture.detectChanges();
+
+ expect(control.value)
+ .toEqual('Nancy', 'Expected value to remain unchanged until second blur.');
+ expect(control.valid).toBe(true, 'Expected validation not to run until second blur.');
+
+ dispatchEvent(input, 'blur');
+ fixture.detectChanges();
+
+ expect(control.value).toEqual('', 'Expected value to update when blur occurs again.');
+ expect(control.valid).toBe(false, 'Expected validation to run when blur occurs again.');
+ });
+
+ it('should not use stale pending value if value set programmatically', () => {
+ const fixture = initTest(FormControlComp);
+ const control = new FormControl('', {validators: Validators.required, updateOn: 'blur'});
+ fixture.componentInstance.control = control;
+ fixture.detectChanges();
+
+ const input = fixture.debugElement.query(By.css('input')).nativeElement;
+ input.value = 'aa';
+ dispatchEvent(input, 'input');
+ fixture.detectChanges();
+
+ control.setValue('Nancy');
+ fixture.detectChanges();
+
+ dispatchEvent(input, 'blur');
+ fixture.detectChanges();
+
+ expect(input.value).toEqual('Nancy', 'Expected programmatic value to stick after blur.');
+ });
+
+ it('should set initial value and validity on init', () => {
+ const fixture = initTest(FormControlComp);
+ const control =
+ new FormControl('Nancy', {validators: Validators.maxLength(3), updateOn: 'blur'});
+ fixture.componentInstance.control = control;
+ fixture.detectChanges();
+
+ const input = fixture.debugElement.query(By.css('input')).nativeElement;
+
+ expect(input.value).toEqual('Nancy', 'Expected value to be set in the view.');
+ expect(control.value).toEqual('Nancy', 'Expected initial model value to be set.');
+ expect(control.valid).toBe(false, 'Expected validation to run on initial value.');
+ });
+
+ it('should reset properly', () => {
+ const fixture = initTest(FormControlComp);
+ const control = new FormControl('', {validators: Validators.required, updateOn: 'blur'});
+ fixture.componentInstance.control = control;
+ fixture.detectChanges();
+
+ const input = fixture.debugElement.query(By.css('input')).nativeElement;
+ input.value = 'aa';
+ dispatchEvent(input, 'input');
+ fixture.detectChanges();
+
+ dispatchEvent(input, 'blur');
+ fixture.detectChanges();
+ expect(control.dirty).toBe(true, 'Expected control to be dirty on blur.');
+
+ control.reset();
+
+ dispatchEvent(input, 'blur');
+ fixture.detectChanges();
+
+ expect(input.value).toEqual('', 'Expected view value to reset');
+ expect(control.value).toBe(null, 'Expected pending value to reset.');
+ expect(control.dirty).toBe(false, 'Expected pending dirty value to reset.');
+ });
+
+ it('should not emit valueChanges or statusChanges until blur', () => {
+ const fixture = initTest(FormControlComp);
+ const control = new FormControl('', {validators: Validators.required, updateOn: 'blur'});
+ fixture.componentInstance.control = control;
+ fixture.detectChanges();
+ const values: string[] = [];
+
+ const sub =
+ merge(control.valueChanges, control.statusChanges).subscribe(val => values.push(val));
+
+ const input = fixture.debugElement.query(By.css('input')).nativeElement;
+ input.value = 'Nancy';
+ dispatchEvent(input, 'input');
+ fixture.detectChanges();
+
+ expect(values).toEqual([], 'Expected no valueChanges or statusChanges on input.');
+
+ dispatchEvent(input, 'blur');
+ fixture.detectChanges();
+
+ expect(values).toEqual(
+ ['Nancy', 'VALID'], 'Expected valueChanges and statusChanges on blur.');
+
+ sub.unsubscribe();
+ });
+
+
+ it('should mark as pristine properly if pending dirty', () => {
+ const fixture = initTest(FormControlComp);
+ const control = new FormControl('', {updateOn: 'blur'});
+ fixture.componentInstance.control = control;
+ fixture.detectChanges();
+
+ const input = fixture.debugElement.query(By.css('input')).nativeElement;
+ input.value = 'aa';
+ dispatchEvent(input, 'input');
+ fixture.detectChanges();
+
+ dispatchEvent(input, 'blur');
+ fixture.detectChanges();
+
+ control.markAsPristine();
+ expect(control.dirty).toBe(false, 'Expected control to become pristine.');
+
+ dispatchEvent(input, 'blur');
+ fixture.detectChanges();
+
+ expect(control.dirty).toBe(false, 'Expected pending dirty value to reset.');
+ });
+
+ });
+
+ describe('on submit', () => {
+
+ it('should set initial value and validity on init', () => {
+ const fixture = initTest(FormGroupComp);
+ const form = new FormGroup({
+ login:
+ new FormControl('Nancy', {validators: Validators.required, updateOn: 'submit'})
+ });
+ fixture.componentInstance.form = form;
+ fixture.detectChanges();
+
+ const input = fixture.debugElement.query(By.css('input')).nativeElement;
+ expect(input.value).toEqual('Nancy', 'Expected initial value to propagate to view.');
+ expect(form.value).toEqual({login: 'Nancy'}, 'Expected initial value to be set.');
+ expect(form.valid).toBe(true, 'Expected form to run validation on initial value.');
+ });
+
+ it('should not update value or validity until submit', () => {
+ const fixture = initTest(FormGroupComp);
+ const formGroup = new FormGroup(
+ {login: new FormControl('', {validators: Validators.required, updateOn: 'submit'})});
+ fixture.componentInstance.form = formGroup;
+ fixture.detectChanges();
+
+ const input = fixture.debugElement.query(By.css('input')).nativeElement;
+ input.value = 'Nancy';
+ dispatchEvent(input, 'input');
+ fixture.detectChanges();
+
+ expect(formGroup.value)
+ .toEqual({login: ''}, 'Expected form value to remain unchanged on input.');
+ expect(formGroup.valid).toBe(false, 'Expected form validation not to run on input.');
+
+ dispatchEvent(input, 'blur');
+ fixture.detectChanges();
+
+ expect(formGroup.value)
+ .toEqual({login: ''}, 'Expected form value to remain unchanged on blur.');
+ expect(formGroup.valid).toBe(false, 'Expected form validation not to run on blur.');
+
+ const form = fixture.debugElement.query(By.css('form')).nativeElement;
+ dispatchEvent(form, 'submit');
+ fixture.detectChanges();
+
+ expect(formGroup.value)
+ .toEqual({login: 'Nancy'}, 'Expected form value to update on submit.');
+ expect(formGroup.valid).toBe(true, 'Expected form validation to run on submit.');
+ });
+
+ it('should not update after submit until a second submit', () => {
+ const fixture = initTest(FormGroupComp);
+ const formGroup = new FormGroup(
+ {login: new FormControl('', {validators: Validators.required, updateOn: 'submit'})});
+ fixture.componentInstance.form = formGroup;
+ fixture.detectChanges();
+
+ const input = fixture.debugElement.query(By.css('input')).nativeElement;
+ input.value = 'Nancy';
+ dispatchEvent(input, 'input');
+ fixture.detectChanges();
+
+ const form = fixture.debugElement.query(By.css('form')).nativeElement;
+ dispatchEvent(form, 'submit');
+ fixture.detectChanges();
+
+ input.value = '';
+ dispatchEvent(input, 'input');
+ fixture.detectChanges();
+
+ expect(formGroup.value)
+ .toEqual({login: 'Nancy'}, 'Expected value not to change until a second submit.');
+ expect(formGroup.valid)
+ .toBe(true, 'Expected validation not to run until a second submit.');
+
+ dispatchEvent(form, 'submit');
+ fixture.detectChanges();
+
+ expect(formGroup.value)
+ .toEqual({login: ''}, 'Expected value to update on the second submit.');
+ expect(formGroup.valid).toBe(false, 'Expected validation to run on a second submit.');
+ });
+
+ it('should not wait for submit to set value programmatically', () => {
+ const fixture = initTest(FormGroupComp);
+ const formGroup = new FormGroup(
+ {login: new FormControl('', {validators: Validators.required, updateOn: 'submit'})});
+ fixture.componentInstance.form = formGroup;
+ fixture.detectChanges();
+
+ formGroup.setValue({login: 'Nancy'});
+ fixture.detectChanges();
+
+ const input = fixture.debugElement.query(By.css('input')).nativeElement;
+ expect(input.value).toEqual('Nancy', 'Expected view value to update immediately.');
+ expect(formGroup.value)
+ .toEqual({login: 'Nancy'}, 'Expected form value to update immediately.');
+ expect(formGroup.valid).toBe(true, 'Expected form validation to run immediately.');
+ });
+
+ it('should not update dirty until submit', () => {
+ const fixture = initTest(FormGroupComp);
+ const formGroup = new FormGroup({login: new FormControl('', {updateOn: 'submit'})});
+ fixture.componentInstance.form = formGroup;
+ fixture.detectChanges();
+
+ const input = fixture.debugElement.query(By.css('input')).nativeElement;
+ dispatchEvent(input, 'input');
+ fixture.detectChanges();
+
+ expect(formGroup.dirty).toBe(false, 'Expected dirty not to change on input.');
+
+ dispatchEvent(input, 'blur');
+ fixture.detectChanges();
+
+ expect(formGroup.dirty).toBe(false, 'Expected dirty not to change on blur.');
+
+ const form = fixture.debugElement.query(By.css('form')).nativeElement;
+ dispatchEvent(form, 'submit');
+ fixture.detectChanges();
+
+ expect(formGroup.dirty).toBe(true, 'Expected dirty to update on submit.');
+ });
+
+ it('should not update touched until submit', () => {
+ const fixture = initTest(FormGroupComp);
+ const formGroup = new FormGroup({login: new FormControl('', {updateOn: 'submit'})});
+ fixture.componentInstance.form = formGroup;
+ fixture.detectChanges();
+
+ const input = fixture.debugElement.query(By.css('input')).nativeElement;
+ dispatchEvent(input, 'blur');
+ fixture.detectChanges();
+
+ expect(formGroup.touched).toBe(false, 'Expected touched not to change until submit.');
+
+ const form = fixture.debugElement.query(By.css('form')).nativeElement;
+ dispatchEvent(form, 'submit');
+ fixture.detectChanges();
+
+ expect(formGroup.touched).toBe(true, 'Expected touched to update on submit.');
+ });
+
+ it('should reset properly', () => {
+ const fixture = initTest(FormGroupComp);
+ const formGroup = new FormGroup(
+ {login: new FormControl('', {validators: Validators.required, updateOn: 'submit'})});
+ fixture.componentInstance.form = formGroup;
+ fixture.detectChanges();
+
+ const input = fixture.debugElement.query(By.css('input')).nativeElement;
+ input.value = 'Nancy';
+ dispatchEvent(input, 'input');
+ fixture.detectChanges();
+
+ dispatchEvent(input, 'blur');
+ fixture.detectChanges();
+
+ formGroup.reset();
+ fixture.detectChanges();
+
+ expect(input.value).toEqual('', 'Expected view value to reset.');
+ expect(formGroup.value).toEqual({login: null}, 'Expected form value to reset');
+ expect(formGroup.dirty).toBe(false, 'Expected dirty to stay false on reset.');
+ expect(formGroup.touched).toBe(false, 'Expected touched to stay false on reset.');
+
+ const form = fixture.debugElement.query(By.css('form')).nativeElement;
+ dispatchEvent(form, 'submit');
+ fixture.detectChanges();
+
+ expect(formGroup.value)
+ .toEqual({login: null}, 'Expected form value to stay empty on submit');
+ expect(formGroup.dirty).toBe(false, 'Expected dirty to stay false on submit.');
+ expect(formGroup.touched).toBe(false, 'Expected touched to stay false on submit.');
+ });
+
+ it('should not emit valueChanges or statusChanges until submit', () => {
+ const fixture = initTest(FormGroupComp);
+ const control =
+ new FormControl('', {validators: Validators.required, updateOn: 'submit'});
+ const formGroup = new FormGroup({login: control});
+ fixture.componentInstance.form = formGroup;
+ fixture.detectChanges();
+
+ const values: string[] = [];
+ const streams = merge(
+ control.valueChanges, control.statusChanges, formGroup.valueChanges,
+ formGroup.statusChanges);
+ const sub = streams.subscribe(val => values.push(val));
+
+ const input = fixture.debugElement.query(By.css('input')).nativeElement;
+ input.value = 'Nancy';
+ dispatchEvent(input, 'input');
+ fixture.detectChanges();
+
+ expect(values).toEqual([], 'Expected no valueChanges or statusChanges on input');
+
+ dispatchEvent(input, 'blur');
+ fixture.detectChanges();
+
+ expect(values).toEqual([], 'Expected no valueChanges or statusChanges on blur');
+
+ const form = fixture.debugElement.query(By.css('form')).nativeElement;
+ dispatchEvent(form, 'submit');
+ fixture.detectChanges();
+
+ expect(values).toEqual(
+ ['Nancy', 'VALID', {login: 'Nancy'}, 'VALID'],
+ 'Expected valueChanges and statusChanges to update on submit.');
+
+ sub.unsubscribe();
+ });
+
+ it('should not run validation for onChange controls on submit', () => {
+ const validatorSpy = jasmine.createSpy('validator');
+ const groupValidatorSpy = jasmine.createSpy('groupValidatorSpy');
+
+ const fixture = initTest(NestedFormGroupComp);
+ const formGroup = new FormGroup({
+ signin: new FormGroup({login: new FormControl(), password: new FormControl()}),
+ email: new FormControl('', {updateOn: 'submit'})
+ });
+ fixture.componentInstance.form = formGroup;
+ fixture.detectChanges();
+
+ formGroup.get('signin.login') !.setValidators(validatorSpy);
+ formGroup.get('signin') !.setValidators(groupValidatorSpy);
+
+ const form = fixture.debugElement.query(By.css('form')).nativeElement;
+ dispatchEvent(form, 'submit');
+ fixture.detectChanges();
+
+ expect(validatorSpy).not.toHaveBeenCalled();
+ expect(groupValidatorSpy).not.toHaveBeenCalled();
+
+ });
+
+
+ it('should mark as untouched properly if pending touched', () => {
+ const fixture = initTest(FormGroupComp);
+ const formGroup = new FormGroup({login: new FormControl('', {updateOn: 'submit'})});
+ fixture.componentInstance.form = formGroup;
+ fixture.detectChanges();
+
+ const input = fixture.debugElement.query(By.css('input')).nativeElement;
+ dispatchEvent(input, 'blur');
+ fixture.detectChanges();
+
+ formGroup.markAsUntouched();
+ fixture.detectChanges();
+
+ expect(formGroup.touched).toBe(false, 'Expected group to become untouched.');
+
+ const form = fixture.debugElement.query(By.css('form')).nativeElement;
+ dispatchEvent(form, 'submit');
+ fixture.detectChanges();
+
+ expect(formGroup.touched).toBe(false, 'Expected touched to stay false on submit.');
+ });
+
+ });
+
+ });
+
describe('ngModel interactions', () => {
it('should support ngModel for complex forms', fakeAsync(() => {
@@ -785,6 +1274,33 @@ export function main() {
expect(input.selectionStart).toEqual(1);
}));
+ it('should work with updateOn submit', fakeAsync(() => {
+ const fixture = initTest(FormGroupNgModel);
+ const formGroup = new FormGroup({login: new FormControl('', {updateOn: 'submit'})});
+ fixture.componentInstance.form = formGroup;
+ fixture.componentInstance.login = 'initial';
+ fixture.detectChanges();
+ tick();
+
+ const input = fixture.debugElement.query(By.css('input')).nativeElement;
+ input.value = 'Nancy';
+ dispatchEvent(input, 'input');
+ fixture.detectChanges();
+ tick();
+
+ expect(fixture.componentInstance.login)
+ .toEqual('initial', 'Expected ngModel value to remain unchanged on input.');
+
+ const form = fixture.debugElement.query(By.css('form')).nativeElement;
+ dispatchEvent(form, 'submit');
+ fixture.detectChanges();
+ tick();
+
+ expect(fixture.componentInstance.login)
+ .toEqual('Nancy', 'Expected ngModel value to update on submit.');
+
+ }));
+
});
describe('validations', () => {
@@ -1116,7 +1632,7 @@ export function main() {
tick(100);
expect(resultArr.length)
- .toEqual(2, `Expected original observable to be canceled on the next value change.`)
+ .toEqual(2, `Expected original observable to be canceled on the next value change.`);
}));
@@ -1238,14 +1754,14 @@ export function main() {
TestBed.overrideComponent(FormGroupComp, {
set: {
template: `
-
+
`
}
});
const fixture = initTest(FormGroupComp);
- fixture.componentInstance.myGroup = new FormGroup({});
+ fixture.componentInstance.form = new FormGroup({});
expect(() => fixture.detectChanges())
.toThrowError(new RegExp(
@@ -1256,14 +1772,14 @@ export function main() {
TestBed.overrideComponent(FormGroupComp, {
set: {
template: `
-
+
`
}
});
const fixture = initTest(FormGroupComp);
- fixture.componentInstance.myGroup = new FormGroup({});
+ fixture.componentInstance.form = new FormGroup({});
expect(() => fixture.detectChanges()).not.toThrowError();
});
@@ -1272,7 +1788,7 @@ export function main() {
TestBed.overrideComponent(FormGroupComp, {
set: {
template: `
-
+
@@ -1281,8 +1797,7 @@ export function main() {
}
});
const fixture = initTest(FormGroupComp);
- const myGroup = new FormGroup({person: new FormGroup({})});
- fixture.componentInstance.myGroup = new FormGroup({person: new FormGroup({})});
+ fixture.componentInstance.form = new FormGroup({person: new FormGroup({})});
expect(() => fixture.detectChanges())
.toThrowError(new RegExp(
@@ -1293,7 +1808,7 @@ export function main() {
TestBed.overrideComponent(FormGroupComp, {
set: {
template: `
-
+
@@ -1302,7 +1817,7 @@ export function main() {
}
});
const fixture = initTest(FormGroupComp);
- fixture.componentInstance.myGroup = new FormGroup({});
+ fixture.componentInstance.form = new FormGroup({});
expect(() => fixture.detectChanges())
.toThrowError(
@@ -1406,7 +1921,9 @@ export function main() {
// formControl should update normally
expect(fixture.componentInstance.control.value).toEqual('updatedValue');
});
+
});
+
});
}
@@ -1470,7 +1987,6 @@ class FormControlComp {
class FormGroupComp {
control: FormControl;
form: FormGroup;
- myGroup: FormGroup;
event: Event;
}
@@ -1522,13 +2038,12 @@ class FormArrayNestedGroup {
cityArray: FormArray;
}
-
@Component({
selector: 'form-group-ng-model',
template: `
-
+
`
+ `
})
class FormGroupNgModel {
form: FormGroup;
diff --git a/packages/forms/test/value_accessor_integration_spec.ts b/packages/forms/test/value_accessor_integration_spec.ts
index e8c2c5cad6..b708121882 100644
--- a/packages/forms/test/value_accessor_integration_spec.ts
+++ b/packages/forms/test/value_accessor_integration_spec.ts
@@ -1,3 +1,11 @@
+/**
+ * @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, EventEmitter, Input, Output, Type} from '@angular/core';
import {ComponentFixture, TestBed, async, fakeAsync, tick} from '@angular/core/testing';
import {AbstractControl, ControlValueAccessor, FormControl, FormGroup, FormsModule, NG_VALIDATORS, NG_VALUE_ACCESSOR, NgControl, NgForm, ReactiveFormsModule, Validators} from '@angular/forms';
diff --git a/packages/http/test/backends/jsonp_backend_spec.ts b/packages/http/test/backends/jsonp_backend_spec.ts
index ecef2c529e..b750a3e680 100644
--- a/packages/http/test/backends/jsonp_backend_spec.ts
+++ b/packages/http/test/backends/jsonp_backend_spec.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {ReflectiveInjector} from '@angular/core';
+import {Injector} from '@angular/core';
import {AsyncTestCompleter, SpyObject, afterEach, beforeEach, describe, inject, it} from '@angular/core/testing/src/testing_internal';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {BrowserJsonp} from '../../src/backends/browser_jsonp';
@@ -52,10 +52,10 @@ export function main() {
let sampleRequest: Request;
beforeEach(() => {
- const injector = ReflectiveInjector.resolveAndCreate([
- {provide: ResponseOptions, useClass: BaseResponseOptions},
- {provide: BrowserJsonp, useClass: MockBrowserJsonp},
- {provide: JSONPBackend, useClass: JSONPBackend_}
+ const injector = Injector.create([
+ {provide: ResponseOptions, useClass: BaseResponseOptions, deps: []},
+ {provide: BrowserJsonp, useClass: MockBrowserJsonp, deps: []},
+ {provide: JSONPBackend, useClass: JSONPBackend_, deps: [BrowserJsonp, ResponseOptions]}
]);
backend = injector.get(JSONPBackend);
const base = new BaseRequestOptions();
diff --git a/packages/http/test/backends/mock_backend_spec.ts b/packages/http/test/backends/mock_backend_spec.ts
index 3b4640decc..73a2d9df29 100644
--- a/packages/http/test/backends/mock_backend_spec.ts
+++ b/packages/http/test/backends/mock_backend_spec.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {ReflectiveInjector} from '@angular/core';
+import {Injector} from '@angular/core';
import {AsyncTestCompleter, beforeEach, describe, inject, it, xit} from '@angular/core/testing/src/testing_internal';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {ReplaySubject} from 'rxjs/ReplaySubject';
@@ -27,8 +27,10 @@ export function main() {
let sampleResponse2: Response;
beforeEach(() => {
- const injector = ReflectiveInjector.resolveAndCreate(
- [{provide: ResponseOptions, useClass: BaseResponseOptions}, MockBackend]);
+ const injector = Injector.create([
+ {provide: ResponseOptions, useClass: BaseResponseOptions, deps: []},
+ {provide: MockBackend, deps: []}
+ ]);
backend = injector.get(MockBackend);
const base = new BaseRequestOptions();
sampleRequest1 =
diff --git a/packages/http/test/http_spec.ts b/packages/http/test/http_spec.ts
index 3f0fb7b5c0..7b0a751fd7 100644
--- a/packages/http/test/http_spec.ts
+++ b/packages/http/test/http_spec.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {Injector, ReflectiveInjector} from '@angular/core';
+import {Injector} from '@angular/core';
import {TestBed, getTestBed} from '@angular/core/testing';
import {AsyncTestCompleter, afterEach, beforeEach, describe, inject, it} from '@angular/core/testing/src/testing_internal';
import {expect} from '@angular/platform-browser/testing/src/matchers';
@@ -79,8 +79,8 @@ export function main() {
let jsonp: Jsonp;
beforeEach(() => {
- injector = ReflectiveInjector.resolveAndCreate([
- BaseRequestOptions, MockBackend, {
+ injector = Injector.create([
+ {provide: BaseRequestOptions, deps: []}, {provide: MockBackend, deps: []}, {
provide: Http,
useFactory: function(backend: ConnectionBackend, defaultOptions: BaseRequestOptions) {
return new Http(backend, defaultOptions);
diff --git a/packages/http/testing/src/mock_backend.ts b/packages/http/testing/src/mock_backend.ts
index 4f3128dad9..dbaa2877f0 100644
--- a/packages/http/testing/src/mock_backend.ts
+++ b/packages/http/testing/src/mock_backend.ts
@@ -114,7 +114,7 @@ export class MockConnection implements Connection {
* ### Example
*
* ```
- * import {Injectable, ReflectiveInjector} from '@angular/core';
+ * import {Injectable, Injector} from '@angular/core';
* import {async, fakeAsync, tick} from '@angular/core/testing';
* import {BaseRequestOptions, ConnectionBackend, Http, RequestOptions} from '@angular/http';
* import {Response, ResponseOptions} from '@angular/http';
@@ -142,7 +142,7 @@ export class MockConnection implements Connection {
*
* describe('MockBackend HeroService Example', () => {
* beforeEach(() => {
- * this.injector = ReflectiveInjector.resolveAndCreate([
+ * this.injector = Injector.create([
* {provide: ConnectionBackend, useClass: MockBackend},
* {provide: RequestOptions, useClass: BaseRequestOptions},
* Http,
@@ -202,7 +202,7 @@ export class MockBackend implements ConnectionBackend {
* ### Example
*
* ```
- * import {ReflectiveInjector} from '@angular/core';
+ * import {Injector} from '@angular/core';
* import {fakeAsync, tick} from '@angular/core/testing';
* import {BaseRequestOptions, ConnectionBackend, Http, RequestOptions} from '@angular/http';
* import {Response, ResponseOptions} from '@angular/http';
@@ -213,7 +213,7 @@ export class MockBackend implements ConnectionBackend {
* MockConnection; // this will be set when a new connection is emitted from the
* // backend.
* let text: string; // this will be set from mock response
- * let injector = ReflectiveInjector.resolveAndCreate([
+ * let injector = Injector.create([
* {provide: ConnectionBackend, useClass: MockBackend},
* {provide: RequestOptions, useClass: BaseRequestOptions},
* Http,
diff --git a/packages/platform-browser-dynamic/src/platform-browser-dynamic.ts b/packages/platform-browser-dynamic/src/platform-browser-dynamic.ts
index d66060f889..9b65cf4e95 100644
--- a/packages/platform-browser-dynamic/src/platform-browser-dynamic.ts
+++ b/packages/platform-browser-dynamic/src/platform-browser-dynamic.ts
@@ -7,7 +7,7 @@
*/
import {ResourceLoader, platformCoreDynamic} from '@angular/compiler';
-import {PlatformRef, Provider, createPlatformFactory} from '@angular/core';
+import {PlatformRef, Provider, StaticProvider, createPlatformFactory} from '@angular/core';
import {INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS} from './platform_providers';
import {CachedResourceLoader} from './resource_loader/resource_loader_cache';
diff --git a/packages/platform-browser-dynamic/src/platform_providers.ts b/packages/platform-browser-dynamic/src/platform_providers.ts
index 88e937643a..f477ee77b7 100644
--- a/packages/platform-browser-dynamic/src/platform_providers.ts
+++ b/packages/platform-browser-dynamic/src/platform_providers.ts
@@ -8,17 +8,17 @@
import {ɵPLATFORM_BROWSER_ID as PLATFORM_BROWSER_ID} from '@angular/common';
import {ResourceLoader} from '@angular/compiler';
-import {COMPILER_OPTIONS, PLATFORM_ID, Provider} from '@angular/core';
+import {COMPILER_OPTIONS, PLATFORM_ID, StaticProvider} from '@angular/core';
import {ɵINTERNAL_BROWSER_PLATFORM_PROVIDERS as INTERNAL_BROWSER_PLATFORM_PROVIDERS} from '@angular/platform-browser';
import {ResourceLoaderImpl} from './resource_loader/resource_loader_impl';
-export const INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS: Provider[] = [
+export const INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS: StaticProvider[] = [
INTERNAL_BROWSER_PLATFORM_PROVIDERS,
{
provide: COMPILER_OPTIONS,
- useValue: {providers: [{provide: ResourceLoader, useClass: ResourceLoaderImpl}]},
+ useValue: {providers: [{provide: ResourceLoader, useClass: ResourceLoaderImpl, deps: []}]},
multi: true
},
{provide: PLATFORM_ID, useValue: PLATFORM_BROWSER_ID},
diff --git a/packages/platform-browser-dynamic/src/resource_loader/resource_loader_impl.ts b/packages/platform-browser-dynamic/src/resource_loader/resource_loader_impl.ts
index b047058b69..4e5dfd6e5e 100644
--- a/packages/platform-browser-dynamic/src/resource_loader/resource_loader_impl.ts
+++ b/packages/platform-browser-dynamic/src/resource_loader/resource_loader_impl.ts
@@ -14,7 +14,7 @@ export class ResourceLoaderImpl extends ResourceLoader {
get(url: string): Promise
{
let resolve: (result: any) => void;
let reject: (error: any) => void;
- const promise = new Promise((res, rej) => {
+ const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
diff --git a/packages/platform-browser-dynamic/test/resource_loader/resource_loader_cache_spec.ts b/packages/platform-browser-dynamic/test/resource_loader/resource_loader_cache_spec.ts
index 28a9e8aacc..0e878e5db6 100644
--- a/packages/platform-browser-dynamic/test/resource_loader/resource_loader_cache_spec.ts
+++ b/packages/platform-browser-dynamic/test/resource_loader/resource_loader_cache_spec.ts
@@ -26,8 +26,8 @@ export function main() {
beforeEach(fakeAsync(() => {
TestBed.configureCompiler({
providers: [
- {provide: UrlResolver, useClass: TestUrlResolver},
- {provide: ResourceLoader, useFactory: createCachedResourceLoader}
+ {provide: UrlResolver, useClass: TestUrlResolver, deps: []},
+ {provide: ResourceLoader, useFactory: createCachedResourceLoader, deps: []}
]
});
diff --git a/packages/platform-browser-dynamic/testing/src/testing.ts b/packages/platform-browser-dynamic/testing/src/testing.ts
index fea035e9e4..f0b6dc26da 100644
--- a/packages/platform-browser-dynamic/testing/src/testing.ts
+++ b/packages/platform-browser-dynamic/testing/src/testing.ts
@@ -7,7 +7,7 @@
*/
import {platformCoreDynamicTesting} from '@angular/compiler/testing';
-import {NgModule, PlatformRef, Provider, createPlatformFactory} from '@angular/core';
+import {NgModule, PlatformRef, StaticProvider, createPlatformFactory} from '@angular/core';
import {TestComponentRenderer} from '@angular/core/testing';
import {ɵINTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS as INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS} from '@angular/platform-browser-dynamic';
import {BrowserTestingModule} from '@angular/platform-browser/testing';
diff --git a/packages/platform-browser/src/browser.ts b/packages/platform-browser/src/browser.ts
index 070b9ae1f1..7043cdb8b5 100644
--- a/packages/platform-browser/src/browser.ts
+++ b/packages/platform-browser/src/browser.ts
@@ -7,7 +7,7 @@
*/
import {CommonModule, PlatformLocation, ɵPLATFORM_BROWSER_ID as PLATFORM_BROWSER_ID} from '@angular/common';
-import {APP_ID, ApplicationModule, ErrorHandler, ModuleWithProviders, NgModule, Optional, PLATFORM_ID, PLATFORM_INITIALIZER, PlatformRef, Provider, RendererFactory2, RootRenderer, Sanitizer, SkipSelf, Testability, createPlatformFactory, platformCore} from '@angular/core';
+import {APP_ID, ApplicationModule, ErrorHandler, ModuleWithProviders, NgModule, Optional, PLATFORM_ID, PLATFORM_INITIALIZER, PlatformRef, RendererFactory2, RootRenderer, Sanitizer, SkipSelf, StaticProvider, Testability, createPlatformFactory, platformCore} from '@angular/core';
import {BrowserDomAdapter} from './browser/browser_adapter';
import {BrowserPlatformLocation} from './browser/location/browser_platform_location';
@@ -26,10 +26,10 @@ import {KeyEventsPlugin} from './dom/events/key_events';
import {DomSharedStylesHost, SharedStylesHost} from './dom/shared_styles_host';
import {DomSanitizer, DomSanitizerImpl} from './security/dom_sanitization_service';
-export const INTERNAL_BROWSER_PLATFORM_PROVIDERS: Provider[] = [
+export const INTERNAL_BROWSER_PLATFORM_PROVIDERS: StaticProvider[] = [
{provide: PLATFORM_ID, useValue: PLATFORM_BROWSER_ID},
{provide: PLATFORM_INITIALIZER, useValue: initDomAdapter, multi: true},
- {provide: PlatformLocation, useClass: BrowserPlatformLocation},
+ {provide: PlatformLocation, useClass: BrowserPlatformLocation, deps: [DOCUMENT]},
{provide: DOCUMENT, useFactory: _document, deps: []},
];
@@ -39,15 +39,15 @@ export const INTERNAL_BROWSER_PLATFORM_PROVIDERS: Provider[] = [
* application to XSS risks. For more detail, see the [Security Guide](http://g.co/ng/security).
* @experimental
*/
-export const BROWSER_SANITIZATION_PROVIDERS: Array = [
+export const BROWSER_SANITIZATION_PROVIDERS: StaticProvider[] = [
{provide: Sanitizer, useExisting: DomSanitizer},
- {provide: DomSanitizer, useClass: DomSanitizerImpl},
+ {provide: DomSanitizer, useClass: DomSanitizerImpl, deps: [DOCUMENT]},
];
/**
* @stable
*/
-export const platformBrowser: (extraProviders?: Provider[]) => PlatformRef =
+export const platformBrowser: (extraProviders?: StaticProvider[]) => PlatformRef =
createPlatformFactory(platformCore, 'browser', INTERNAL_BROWSER_PLATFORM_PROVIDERS);
export function initDomAdapter() {
diff --git a/packages/platform-browser/src/browser/server-transition.ts b/packages/platform-browser/src/browser/server-transition.ts
index 2bfb9de470..fde6a705f7 100644
--- a/packages/platform-browser/src/browser/server-transition.ts
+++ b/packages/platform-browser/src/browser/server-transition.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {APP_INITIALIZER, ApplicationInitStatus, Inject, InjectionToken, Injector, Provider} from '@angular/core';
+import {APP_INITIALIZER, ApplicationInitStatus, Inject, InjectionToken, Injector, StaticProvider} from '@angular/core';
import {getDOM} from '../dom/dom_adapter';
import {DOCUMENT} from '../dom/dom_tokens';
@@ -31,7 +31,7 @@ export function appInitializerFactory(transitionId: string, document: any, injec
};
}
-export const SERVER_TRANSITION_PROVIDERS: Provider[] = [
+export const SERVER_TRANSITION_PROVIDERS: StaticProvider[] = [
{
provide: APP_INITIALIZER,
useFactory: appInitializerFactory,
diff --git a/packages/platform-browser/test/browser/bootstrap_spec.ts b/packages/platform-browser/test/browser/bootstrap_spec.ts
index ad2851092c..f1b0cf2926 100644
--- a/packages/platform-browser/test/browser/bootstrap_spec.ts
+++ b/packages/platform-browser/test/browser/bootstrap_spec.ts
@@ -7,7 +7,7 @@
*/
import {isPlatformBrowser} from '@angular/common';
-import {APP_INITIALIZER, CUSTOM_ELEMENTS_SCHEMA, Compiler, Component, Directive, ErrorHandler, Inject, Input, LOCALE_ID, NgModule, OnDestroy, PLATFORM_ID, PLATFORM_INITIALIZER, Pipe, Provider, VERSION, createPlatformFactory, ɵstringify as stringify} from '@angular/core';
+import {APP_INITIALIZER, CUSTOM_ELEMENTS_SCHEMA, Compiler, Component, Directive, ErrorHandler, Inject, Input, LOCALE_ID, NgModule, OnDestroy, PLATFORM_ID, PLATFORM_INITIALIZER, Pipe, Provider, StaticProvider, VERSION, createPlatformFactory, ɵstringify as stringify} from '@angular/core';
import {ApplicationRef, destroyPlatform} from '@angular/core/src/application_ref';
import {Console} from '@angular/core/src/console';
import {ComponentRef} from '@angular/core/src/linker/component_factory';
@@ -112,8 +112,8 @@ class DummyConsole implements Console {
class TestModule {}
-function bootstrap(
- cmpType: any, providers: Provider[] = [], platformProviders: Provider[] = []): Promise {
+function bootstrap(cmpType: any, providers: Provider[] = [], platformProviders: StaticProvider[] = [
+]): Promise {
@NgModule({
imports: [BrowserModule],
declarations: [cmpType],
diff --git a/packages/platform-browser/test/browser/tools/spies.ts b/packages/platform-browser/test/browser/tools/spies.ts
index 11d27ad1bd..f59a76d970 100644
--- a/packages/platform-browser/test/browser/tools/spies.ts
+++ b/packages/platform-browser/test/browser/tools/spies.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {ReflectiveInjector, ɵglobal as global} from '@angular/core';
+import {Injector, ɵglobal as global} from '@angular/core';
import {ApplicationRef, ApplicationRef_} from '@angular/core/src/application_ref';
import {SpyObject} from '@angular/core/testing/src/testing_internal';
@@ -18,8 +18,8 @@ export class SpyComponentRef extends SpyObject {
injector: any /** TODO #9100 */;
constructor() {
super();
- this.injector = ReflectiveInjector.resolveAndCreate(
- [{provide: ApplicationRef, useClass: SpyApplicationRef}]);
+ this.injector =
+ Injector.create([{provide: ApplicationRef, useClass: SpyApplicationRef, deps: []}]);
}
}
diff --git a/packages/platform-browser/test/dom/events/event_manager_spec.ts b/packages/platform-browser/test/dom/events/event_manager_spec.ts
index 8d924ba190..638a70d918 100644
--- a/packages/platform-browser/test/dom/events/event_manager_spec.ts
+++ b/packages/platform-browser/test/dom/events/event_manager_spec.ts
@@ -109,7 +109,6 @@ class FakeEventManagerPlugin extends EventManagerPlugin {
class FakeNgZone extends NgZone {
constructor() { super({enableLongStackTrace: false}); }
- run(fn: Function) { fn(); }
-
+ run(fn: (...args: any[]) => T, applyThis?: any, applyArgs?: any[]): T { return fn(); }
runOutsideAngular(fn: Function) { return fn(); }
}
diff --git a/packages/platform-browser/test/security/html_sanitizer_spec.ts b/packages/platform-browser/test/security/html_sanitizer_spec.ts
index 144a052168..51afd6d159 100644
--- a/packages/platform-browser/test/security/html_sanitizer_spec.ts
+++ b/packages/platform-browser/test/security/html_sanitizer_spec.ts
@@ -123,7 +123,7 @@ export function main() {
// depending on the browser, we might ge an exception
}
try {
- sanitizeHtml(defaultDoc, '')
+ sanitizeHtml(defaultDoc, '');
} catch (e) {
// depending on the browser, we might ge an exception
}
diff --git a/packages/platform-browser/testing/src/browser.ts b/packages/platform-browser/testing/src/browser.ts
index e698f67726..b9020df987 100644
--- a/packages/platform-browser/testing/src/browser.ts
+++ b/packages/platform-browser/testing/src/browser.ts
@@ -5,7 +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 {APP_ID, NgModule, NgZone, PLATFORM_INITIALIZER, PlatformRef, Provider, createPlatformFactory, platformCore} from '@angular/core';
+import {APP_ID, NgModule, NgZone, PLATFORM_INITIALIZER, PlatformRef, StaticProvider, createPlatformFactory, platformCore} from '@angular/core';
import {BrowserModule, ɵBrowserDomAdapter as BrowserDomAdapter, ɵELEMENT_PROBE_PROVIDERS as ELEMENT_PROBE_PROVIDERS} from '@angular/platform-browser';
import {BrowserDetection, createNgZone} from './browser_util';
@@ -14,7 +14,7 @@ function initBrowserTests() {
BrowserDetection.setup();
}
-const _TEST_BROWSER_PLATFORM_PROVIDERS: Provider[] =
+const _TEST_BROWSER_PLATFORM_PROVIDERS: StaticProvider[] =
[{provide: PLATFORM_INITIALIZER, useValue: initBrowserTests, multi: true}];
/**
diff --git a/packages/platform-server/src/server.ts b/packages/platform-server/src/server.ts
index facb7b3a39..b109411804 100644
--- a/packages/platform-server/src/server.ts
+++ b/packages/platform-server/src/server.ts
@@ -10,9 +10,9 @@ import {ɵAnimationEngine} from '@angular/animations/browser';
import {PlatformLocation, ɵPLATFORM_SERVER_ID as PLATFORM_SERVER_ID} from '@angular/common';
import {HttpClientModule} from '@angular/common/http';
import {platformCoreDynamic} from '@angular/compiler';
-import {Injectable, InjectionToken, Injector, NgModule, NgZone, PLATFORM_ID, PLATFORM_INITIALIZER, PlatformRef, Provider, RendererFactory2, RootRenderer, Testability, createPlatformFactory, isDevMode, platformCore, ɵALLOW_MULTIPLE_PLATFORMS as ALLOW_MULTIPLE_PLATFORMS} from '@angular/core';
+import {Injectable, InjectionToken, Injector, NgModule, NgZone, Optional, PLATFORM_ID, PLATFORM_INITIALIZER, PlatformRef, Provider, RendererFactory2, RootRenderer, StaticProvider, Testability, createPlatformFactory, isDevMode, platformCore, ɵALLOW_MULTIPLE_PLATFORMS as ALLOW_MULTIPLE_PLATFORMS} from '@angular/core';
import {HttpModule} from '@angular/http';
-import {BrowserModule, DOCUMENT, ɵSharedStylesHost as SharedStylesHost, ɵgetDOM as getDOM} from '@angular/platform-browser';
+import {BrowserModule, DOCUMENT, ɵSharedStylesHost as SharedStylesHost, ɵTRANSITION_ID, ɵgetDOM as getDOM} from '@angular/platform-browser';
import {NoopAnimationsModule, ɵAnimationRendererFactory} from '@angular/platform-browser/animations';
import {SERVER_HTTP_PROVIDERS} from './http';
@@ -27,11 +27,15 @@ function notSupported(feature: string): Error {
throw new Error(`platform-server does not support '${feature}'.`);
}
-export const INTERNAL_SERVER_PLATFORM_PROVIDERS: Array = [
+export const INTERNAL_SERVER_PLATFORM_PROVIDERS: StaticProvider[] = [
{provide: DOCUMENT, useFactory: _document, deps: [Injector]},
{provide: PLATFORM_ID, useValue: PLATFORM_SERVER_ID},
- {provide: PLATFORM_INITIALIZER, useFactory: initParse5Adapter, multi: true, deps: [Injector]},
- {provide: PlatformLocation, useClass: ServerPlatformLocation}, PlatformState,
+ {provide: PLATFORM_INITIALIZER, useFactory: initParse5Adapter, multi: true, deps: [Injector]}, {
+ provide: PlatformLocation,
+ useClass: ServerPlatformLocation,
+ deps: [DOCUMENT, [Optional, INITIAL_CONFIG]]
+ },
+ {provide: PlatformState, deps: [DOCUMENT]},
// Add special provider that allows multiple instances of platformServer* to be created.
{provide: ALLOW_MULTIPLE_PLATFORMS, useValue: true}
];
diff --git a/packages/platform-server/src/utils.ts b/packages/platform-server/src/utils.ts
index f768a24709..3239cc82ba 100644
--- a/packages/platform-server/src/utils.ts
+++ b/packages/platform-server/src/utils.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {ApplicationRef, NgModuleFactory, NgModuleRef, PlatformRef, Provider, Type} from '@angular/core';
+import {ApplicationRef, NgModuleFactory, NgModuleRef, PlatformRef, StaticProvider, Type} from '@angular/core';
import {ɵTRANSITION_ID} from '@angular/platform-browser';
import {filter} from 'rxjs/operator/filter';
import {first} from 'rxjs/operator/first';
@@ -21,11 +21,11 @@ const parse5 = require('parse5');
interface PlatformOptions {
document?: string;
url?: string;
- extraProviders?: Provider[];
+ extraProviders?: StaticProvider[];
}
function _getPlatform(
- platformFactory: (extraProviders: Provider[]) => PlatformRef,
+ platformFactory: (extraProviders: StaticProvider[]) => PlatformRef,
options: PlatformOptions): PlatformRef {
const extraProviders = options.extraProviders ? options.extraProviders : [];
return platformFactory([
@@ -67,8 +67,8 @@ the server-rendered app can be properly bootstrapped into a client app.`);
* @experimental
*/
export function renderModule(
- module: Type,
- options: {document?: string, url?: string, extraProviders?: Provider[]}): Promise {
+ module: Type, options: {document?: string, url?: string, extraProviders?: StaticProvider[]}):
+ Promise {
const platform = _getPlatform(platformDynamicServer, options);
return _render(platform, platform.bootstrapModule(module));
}
@@ -84,7 +84,8 @@ export function renderModule(
*/
export function renderModuleFactory(
moduleFactory: NgModuleFactory,
- options: {document?: string, url?: string, extraProviders?: Provider[]}): Promise {
+ options: {document?: string, url?: string, extraProviders?: StaticProvider[]}):
+ Promise {
const platform = _getPlatform(platformServer, options);
return _render(platform, platform.bootstrapModuleFactory(moduleFactory));
}
diff --git a/packages/platform-server/testing/src/server.ts b/packages/platform-server/testing/src/server.ts
index 85272e89d1..6ea98413b6 100644
--- a/packages/platform-server/testing/src/server.ts
+++ b/packages/platform-server/testing/src/server.ts
@@ -7,7 +7,7 @@
*/
import {platformCoreDynamicTesting} from '@angular/compiler/testing';
-import {NgModule, PlatformRef, Provider, createPlatformFactory} from '@angular/core';
+import {NgModule, PlatformRef, StaticProvider, createPlatformFactory} from '@angular/core';
import {BrowserDynamicTestingModule} from '@angular/platform-browser-dynamic/testing';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
import {ɵINTERNAL_SERVER_PLATFORM_PROVIDERS as INTERNAL_SERVER_PLATFORM_PROVIDERS, ɵSERVER_RENDER_PROVIDERS as SERVER_RENDER_PROVIDERS} from '@angular/platform-server';
diff --git a/packages/platform-webworker-dynamic/src/platform-webworker-dynamic.ts b/packages/platform-webworker-dynamic/src/platform-webworker-dynamic.ts
index 72aeb121f4..57c2a2bedb 100644
--- a/packages/platform-webworker-dynamic/src/platform-webworker-dynamic.ts
+++ b/packages/platform-webworker-dynamic/src/platform-webworker-dynamic.ts
@@ -8,7 +8,7 @@
import {ɵPLATFORM_WORKER_UI_ID as PLATFORM_WORKER_UI_ID} from '@angular/common';
import {ResourceLoader, platformCoreDynamic} from '@angular/compiler';
-import {COMPILER_OPTIONS, PLATFORM_ID, PlatformRef, Provider, createPlatformFactory} from '@angular/core';
+import {COMPILER_OPTIONS, PLATFORM_ID, PlatformRef, StaticProvider, createPlatformFactory} from '@angular/core';
import {ɵResourceLoaderImpl as ResourceLoaderImpl} from '@angular/platform-browser-dynamic';
export {VERSION} from './version';
@@ -19,7 +19,7 @@ export const platformWorkerAppDynamic =
createPlatformFactory(platformCoreDynamic, 'workerAppDynamic', [
{
provide: COMPILER_OPTIONS,
- useValue: {providers: [{provide: ResourceLoader, useClass: ResourceLoaderImpl}]},
+ useValue: {providers: [{provide: ResourceLoader, useClass: ResourceLoaderImpl, deps: []}]},
multi: true
},
{provide: PLATFORM_ID, useValue: PLATFORM_WORKER_UI_ID}
diff --git a/packages/platform-webworker/src/platform-webworker.ts b/packages/platform-webworker/src/platform-webworker.ts
index fa193e8db7..8ecc2a4b57 100644
--- a/packages/platform-webworker/src/platform-webworker.ts
+++ b/packages/platform-webworker/src/platform-webworker.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {PlatformRef, Provider} from '@angular/core';
+import {PlatformRef, StaticProvider} from '@angular/core';
import {WORKER_SCRIPT, platformWorkerUi} from './worker_render';
@@ -26,7 +26,7 @@ export {platformWorkerUi} from './worker_render';
* @experimental
*/
export function bootstrapWorkerUi(
- workerScriptUri: string, customProviders: Provider[] = []): Promise {
+ workerScriptUri: string, customProviders: StaticProvider[] = []): Promise {
// For now, just creates the worker ui platform...
const platform = platformWorkerUi([
{provide: WORKER_SCRIPT, useValue: workerScriptUri},
diff --git a/packages/platform-webworker/src/web_workers/ui/location_providers.ts b/packages/platform-webworker/src/web_workers/ui/location_providers.ts
index 71192b3547..f399cba7a8 100644
--- a/packages/platform-webworker/src/web_workers/ui/location_providers.ts
+++ b/packages/platform-webworker/src/web_workers/ui/location_providers.ts
@@ -6,9 +6,14 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {Injector, NgZone, PLATFORM_INITIALIZER, Provider} from '@angular/core';
-
+import {DOCUMENT} from '@angular/common';
+import {Injector, NgZone, PLATFORM_INITIALIZER, StaticProvider} from '@angular/core';
import {ɵBrowserPlatformLocation as BrowserPlatformLocation} from '@angular/platform-browser';
+
+import {MessageBus} from '../shared/message_bus';
+import {Serializer} from '../shared/serializer';
+import {ServiceMessageBrokerFactory} from '../shared/service_message_broker';
+
import {MessageBasedPlatformLocation} from './platform_location';
@@ -18,8 +23,10 @@ import {MessageBasedPlatformLocation} from './platform_location';
* include these providers when setting up the render thread.
* @experimental
*/
-export const WORKER_UI_LOCATION_PROVIDERS: Provider[] = [
- MessageBasedPlatformLocation, BrowserPlatformLocation,
+export const WORKER_UI_LOCATION_PROVIDERS = [
+ {provide: MessageBasedPlatformLocation, deps: [ServiceMessageBrokerFactory,
+ BrowserPlatformLocation, MessageBus, Serializer]},
+ {provide: BrowserPlatformLocation, deps: [DOCUMENT]},
{provide: PLATFORM_INITIALIZER, useFactory: initUiLocation, multi: true, deps: [Injector]}
];
diff --git a/packages/platform-webworker/src/worker_app.ts b/packages/platform-webworker/src/worker_app.ts
index 9f992b5924..2daf468a10 100644
--- a/packages/platform-webworker/src/worker_app.ts
+++ b/packages/platform-webworker/src/worker_app.ts
@@ -7,8 +7,9 @@
*/
import {CommonModule, ɵPLATFORM_WORKER_APP_ID as PLATFORM_WORKER_APP_ID} from '@angular/common';
-import {APP_INITIALIZER, ApplicationModule, ErrorHandler, NgModule, NgZone, PLATFORM_ID, PlatformRef, Provider, RendererFactory2, RootRenderer, createPlatformFactory, platformCore} from '@angular/core';
+import {APP_INITIALIZER, ApplicationModule, ErrorHandler, NgModule, NgZone, PLATFORM_ID, PlatformRef, RendererFactory2, RootRenderer, StaticProvider, createPlatformFactory, platformCore} from '@angular/core';
import {DOCUMENT, ɵBROWSER_SANITIZATION_PROVIDERS as BROWSER_SANITIZATION_PROVIDERS} from '@angular/platform-browser';
+
import {ON_WEB_WORKER} from './web_workers/shared/api';
import {ClientMessageBrokerFactory, ClientMessageBrokerFactory_} from './web_workers/shared/client_message_broker';
import {MessageBus} from './web_workers/shared/message_bus';
diff --git a/packages/platform-webworker/src/worker_render.ts b/packages/platform-webworker/src/worker_render.ts
index d3db75261c..71a6d16cc2 100644
--- a/packages/platform-webworker/src/worker_render.ts
+++ b/packages/platform-webworker/src/worker_render.ts
@@ -7,7 +7,7 @@
*/
import {CommonModule, ɵPLATFORM_WORKER_UI_ID as PLATFORM_WORKER_UI_ID} from '@angular/common';
-import {ErrorHandler, Injectable, InjectionToken, Injector, NgZone, PLATFORM_ID, PLATFORM_INITIALIZER, PlatformRef, Provider, RendererFactory2, RootRenderer, Testability, createPlatformFactory, isDevMode, platformCore, ɵAPP_ID_RANDOM_PROVIDER as APP_ID_RANDOM_PROVIDER} from '@angular/core';
+import {ErrorHandler, Injectable, InjectionToken, Injector, NgZone, PLATFORM_ID, PLATFORM_INITIALIZER, PlatformRef, RendererFactory2, RootRenderer, StaticProvider, Testability, createPlatformFactory, isDevMode, platformCore, ɵAPP_ID_RANDOM_PROVIDER as APP_ID_RANDOM_PROVIDER} from '@angular/core';
import {DOCUMENT, EVENT_MANAGER_PLUGINS, EventManager, HAMMER_GESTURE_CONFIG, HammerGestureConfig, ɵBROWSER_SANITIZATION_PROVIDERS as BROWSER_SANITIZATION_PROVIDERS, ɵBrowserDomAdapter as BrowserDomAdapter, ɵBrowserGetTestability as BrowserGetTestability, ɵDomEventsPlugin as DomEventsPlugin, ɵDomRendererFactory2 as DomRendererFactory2, ɵDomSharedStylesHost as DomSharedStylesHost, ɵHammerGesturesPlugin as HammerGesturesPlugin, ɵKeyEventsPlugin as KeyEventsPlugin, ɵSharedStylesHost as SharedStylesHost, ɵgetDOM as getDOM} from '@angular/platform-browser';
import {ON_WEB_WORKER} from './web_workers/shared/api';
@@ -20,6 +20,7 @@ import {ServiceMessageBrokerFactory, ServiceMessageBrokerFactory_} from './web_w
import {MessageBasedRenderer2} from './web_workers/ui/renderer';
+
/**
* Wrapper class that exposes the Worker
* and underlying {@link MessageBus} for lower level message passing.
@@ -52,32 +53,53 @@ export const WORKER_SCRIPT = new InjectionToken('WebWorkerScript');
export const WORKER_UI_STARTABLE_MESSAGING_SERVICE =
new InjectionToken<({start: () => void})[]>('WorkerRenderStartableMsgService');
-export const _WORKER_UI_PLATFORM_PROVIDERS: Provider[] = [
+export const _WORKER_UI_PLATFORM_PROVIDERS: StaticProvider[] = [
{provide: NgZone, useFactory: createNgZone, deps: []},
- MessageBasedRenderer2,
+ {
+ provide: MessageBasedRenderer2,
+ deps: [ServiceMessageBrokerFactory, MessageBus, Serializer, RenderStore, RendererFactory2]
+ },
{provide: WORKER_UI_STARTABLE_MESSAGING_SERVICE, useExisting: MessageBasedRenderer2, multi: true},
BROWSER_SANITIZATION_PROVIDERS,
{provide: ErrorHandler, useFactory: _exceptionHandler, deps: []},
{provide: DOCUMENT, useFactory: _document, deps: []},
// TODO(jteplitz602): Investigate if we definitely need EVENT_MANAGER on the render thread
// #5298
- {provide: EVENT_MANAGER_PLUGINS, useClass: DomEventsPlugin, multi: true},
- {provide: EVENT_MANAGER_PLUGINS, useClass: KeyEventsPlugin, multi: true},
- {provide: EVENT_MANAGER_PLUGINS, useClass: HammerGesturesPlugin, multi: true},
- {provide: HAMMER_GESTURE_CONFIG, useClass: HammerGestureConfig},
+ {
+ provide: EVENT_MANAGER_PLUGINS,
+ useClass: DomEventsPlugin,
+ deps: [DOCUMENT, NgZone],
+ multi: true
+ },
+ {provide: EVENT_MANAGER_PLUGINS, useClass: KeyEventsPlugin, deps: [DOCUMENT], multi: true},
+ {
+ provide: EVENT_MANAGER_PLUGINS,
+ useClass: HammerGesturesPlugin,
+ deps: [DOCUMENT, HAMMER_GESTURE_CONFIG],
+ multi: true
+ },
+ {provide: HAMMER_GESTURE_CONFIG, useClass: HammerGestureConfig, deps: []},
APP_ID_RANDOM_PROVIDER,
- DomRendererFactory2,
+ {provide: DomRendererFactory2, deps: [EventManager, DomSharedStylesHost]},
{provide: RendererFactory2, useExisting: DomRendererFactory2},
{provide: SharedStylesHost, useExisting: DomSharedStylesHost},
- {provide: ServiceMessageBrokerFactory, useClass: ServiceMessageBrokerFactory_},
- {provide: ClientMessageBrokerFactory, useClass: ClientMessageBrokerFactory_},
- Serializer,
+ {
+ provide: ServiceMessageBrokerFactory,
+ useClass: ServiceMessageBrokerFactory_,
+ deps: [MessageBus, Serializer]
+ },
+ {
+ provide: ClientMessageBrokerFactory,
+ useClass: ClientMessageBrokerFactory_,
+ deps: [MessageBus, Serializer]
+ },
+ {provide: Serializer, deps: [RenderStore]},
{provide: ON_WEB_WORKER, useValue: false},
- RenderStore,
- DomSharedStylesHost,
- Testability,
- EventManager,
- WebWorkerInstance,
+ {provide: RenderStore, deps: []},
+ {provide: DomSharedStylesHost, deps: [DOCUMENT]},
+ {provide: Testability, deps: [NgZone]},
+ {provide: EventManager, deps: [EVENT_MANAGER_PLUGINS, NgZone]},
+ {provide: WebWorkerInstance, deps: []},
{
provide: PLATFORM_INITIALIZER,
useFactory: initWebWorkerRenderPlatform,
diff --git a/packages/router/src/apply_redirects.ts b/packages/router/src/apply_redirects.ts
index 8000df3368..0d8c386938 100644
--- a/packages/router/src/apply_redirects.ts
+++ b/packages/router/src/apply_redirects.ts
@@ -444,8 +444,11 @@ function match(segmentGroup: UrlSegmentGroup, route: Route, segments: UrlSegment
if (!res) {
return {
- matched: false, consumedSegments: [], lastChild: 0, positionalParamSegments: {},
- }
+ matched: false,
+ consumedSegments: [],
+ lastChild: 0,
+ positionalParamSegments: {},
+ };
}
return {
diff --git a/packages/router/src/directives/router_link.ts b/packages/router/src/directives/router_link.ts
index 554fc2157c..87b805b305 100644
--- a/packages/router/src/directives/router_link.ts
+++ b/packages/router/src/directives/router_link.ts
@@ -70,7 +70,7 @@ import {UrlTree} from '../url_tree';
*
* You can tell the directive to how to handle queryParams, available options are:
* - 'merge' merge the queryParams into the current queryParams
- * - 'preserve' prserve the current queryParams
+ * - 'preserve' preserve the current queryParams
* - default / '' use the queryParams only
* same options for {@link NavigationExtras#queryParamsHandling}
*
diff --git a/packages/router/src/events.ts b/packages/router/src/events.ts
index 80564147d3..b1cc203dff 100644
--- a/packages/router/src/events.ts
+++ b/packages/router/src/events.ts
@@ -10,17 +10,66 @@ import {Route} from './config';
import {RouterStateSnapshot} from './router_state';
/**
- * @whatItDoes Represents an event triggered when a navigation starts.
+ * @whatItDoes Base for events the Router goes through, as opposed to events tied to a specific
+ * Route. `RouterEvent`s will only be fired one time for any given navigation.
*
- * @stable
+ * Example:
+ *
+ * ```
+ * class MyService {
+ * constructor(public router: Router, logger: Logger) {
+ * router.events.filter(e => e instanceof RouterEvent).subscribe(e => {
+ * logger.log(e.id, e.url);
+ * });
+ * }
+ * }
+ * ```
+ *
+ * @experimental
*/
-export class NavigationStart {
+export class RouterEvent {
constructor(
/** @docsNotRequired */
public id: number,
/** @docsNotRequired */
public url: string) {}
+}
+/**
+ * @whatItDoes Base for events tied to a specific `Route`, as opposed to events for the Router
+ * lifecycle. `RouteEvent`s may be fired multiple times during a single navigation and will
+ * always receive the `Route` they pertain to.
+ *
+ * Example:
+ *
+ * ```
+ * class MyService {
+ * constructor(public router: Router, spinner: Spinner) {
+ * router.events.filter(e => e instanceof RouteEvent).subscribe(e => {
+ * if (e instanceof ChildActivationStart) {
+ * spinner.start(e.route);
+ * } else if (e instanceof ChildActivationEnd) {
+ * spinner.end(e.route);
+ * }
+ * });
+ * }
+ * }
+ * ```
+ *
+ * @experimental
+ */
+export class RouteEvent {
+ constructor(
+ /** @docsNotRequired */
+ public route: Route) {}
+}
+
+/**
+ * @whatItDoes Represents an event triggered when a navigation starts.
+ *
+ * @stable
+ */
+export class NavigationStart extends RouterEvent {
/** @docsNotRequired */
toString(): string { return `NavigationStart(id: ${this.id}, url: '${this.url}')`; }
}
@@ -30,14 +79,16 @@ export class NavigationStart {
*
* @stable
*/
-export class NavigationEnd {
+export class NavigationEnd extends RouterEvent {
constructor(
/** @docsNotRequired */
- public id: number,
+ id: number,
/** @docsNotRequired */
- public url: string,
+ url: string,
/** @docsNotRequired */
- public urlAfterRedirects: string) {}
+ public urlAfterRedirects: string) {
+ super(id, url);
+ }
/** @docsNotRequired */
toString(): string {
@@ -50,14 +101,16 @@ export class NavigationEnd {
*
* @stable
*/
-export class NavigationCancel {
+export class NavigationCancel extends RouterEvent {
constructor(
/** @docsNotRequired */
- public id: number,
+ id: number,
/** @docsNotRequired */
- public url: string,
+ url: string,
/** @docsNotRequired */
- public reason: string) {}
+ public reason: string) {
+ super(id, url);
+ }
/** @docsNotRequired */
toString(): string { return `NavigationCancel(id: ${this.id}, url: '${this.url}')`; }
@@ -68,14 +121,16 @@ export class NavigationCancel {
*
* @stable
*/
-export class NavigationError {
+export class NavigationError extends RouterEvent {
constructor(
/** @docsNotRequired */
- public id: number,
+ id: number,
/** @docsNotRequired */
- public url: string,
+ url: string,
/** @docsNotRequired */
- public error: any) {}
+ public error: any) {
+ super(id, url);
+ }
/** @docsNotRequired */
toString(): string {
@@ -88,16 +143,18 @@ export class NavigationError {
*
* @stable
*/
-export class RoutesRecognized {
+export class RoutesRecognized extends RouterEvent {
constructor(
/** @docsNotRequired */
- public id: number,
+ id: number,
/** @docsNotRequired */
- public url: string,
+ url: string,
/** @docsNotRequired */
public urlAfterRedirects: string,
/** @docsNotRequired */
- public state: RouterStateSnapshot) {}
+ public state: RouterStateSnapshot) {
+ super(id, url);
+ }
/** @docsNotRequired */
toString(): string {
@@ -105,43 +162,23 @@ export class RoutesRecognized {
}
}
-/**
- * @whatItDoes Represents an event triggered before lazy loading a route config.
- *
- * @experimental
- */
-export class RouteConfigLoadStart {
- constructor(public route: Route) {}
-
- toString(): string { return `RouteConfigLoadStart(path: ${this.route.path})`; }
-}
-
-/**
- * @whatItDoes Represents an event triggered when a route has been lazy loaded.
- *
- * @experimental
- */
-export class RouteConfigLoadEnd {
- constructor(public route: Route) {}
-
- toString(): string { return `RouteConfigLoadEnd(path: ${this.route.path})`; }
-}
-
/**
* @whatItDoes Represents the start of the Guard phase of routing.
*
* @experimental
*/
-export class GuardsCheckStart {
+export class GuardsCheckStart extends RouterEvent {
constructor(
/** @docsNotRequired */
- public id: number,
+ id: number,
/** @docsNotRequired */
- public url: string,
+ url: string,
/** @docsNotRequired */
public urlAfterRedirects: string,
/** @docsNotRequired */
- public state: RouterStateSnapshot) {}
+ public state: RouterStateSnapshot) {
+ super(id, url);
+ }
toString(): string {
return `GuardsCheckStart(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`;
@@ -153,18 +190,20 @@ export class GuardsCheckStart {
*
* @experimental
*/
-export class GuardsCheckEnd {
+export class GuardsCheckEnd extends RouterEvent {
constructor(
/** @docsNotRequired */
- public id: number,
+ id: number,
/** @docsNotRequired */
- public url: string,
+ url: string,
/** @docsNotRequired */
public urlAfterRedirects: string,
/** @docsNotRequired */
public state: RouterStateSnapshot,
/** @docsNotRequired */
- public shouldActivate: boolean) {}
+ public shouldActivate: boolean) {
+ super(id, url);
+ }
toString(): string {
return `GuardsCheckEnd(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state}, shouldActivate: ${this.shouldActivate})`;
@@ -179,16 +218,18 @@ export class GuardsCheckEnd {
*
* @experimental
*/
-export class ResolveStart {
+export class ResolveStart extends RouterEvent {
constructor(
/** @docsNotRequired */
- public id: number,
+ id: number,
/** @docsNotRequired */
- public url: string,
+ url: string,
/** @docsNotRequired */
public urlAfterRedirects: string,
/** @docsNotRequired */
- public state: RouterStateSnapshot) {}
+ public state: RouterStateSnapshot) {
+ super(id, url);
+ }
toString(): string {
return `ResolveStart(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`;
@@ -201,22 +242,62 @@ export class ResolveStart {
*
* @experimental
*/
-export class ResolveEnd {
+export class ResolveEnd extends RouterEvent {
constructor(
/** @docsNotRequired */
- public id: number,
+ id: number,
/** @docsNotRequired */
- public url: string,
+ url: string,
/** @docsNotRequired */
public urlAfterRedirects: string,
/** @docsNotRequired */
- public state: RouterStateSnapshot) {}
+ public state: RouterStateSnapshot) {
+ super(id, url);
+ }
toString(): string {
return `ResolveEnd(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`;
}
}
+/**
+ * @whatItDoes Represents an event triggered before lazy loading a route config.
+ *
+ * @experimental
+ */
+export class RouteConfigLoadStart extends RouteEvent {
+ toString(): string { return `RouteConfigLoadStart(path: ${this.route.path})`; }
+}
+
+/**
+ * @whatItDoes Represents an event triggered when a route has been lazy loaded.
+ *
+ * @experimental
+ */
+export class RouteConfigLoadEnd extends RouteEvent {
+ toString(): string { return `RouteConfigLoadEnd(path: ${this.route.path})`; }
+}
+
+/**
+ * @whatItDoes Represents the start of end of the Resolve phase of routing. See note on
+ * {@link ChildActivationEnd} for use of this experimental API.
+ *
+ * @experimental
+ */
+export class ChildActivationStart extends RouteEvent {
+ toString(): string { return `ChildActivationStart(path: '${this.route.path}')`; }
+}
+
+/**
+ * @whatItDoes Represents the start of end of the Resolve phase of routing. See note on
+ * {@link ChildActivationStart} for use of this experimental API.
+ *
+ * @experimental
+ */
+export class ChildActivationEnd extends RouteEvent {
+ toString(): string { return `ChildActivationEnd(path: '${this.route.path}')`; }
+}
+
/**
* @whatItDoes Represents a router event, allowing you to track the lifecycle of the router.
*
@@ -227,15 +308,15 @@ export class ResolveEnd {
* - {@link RouteConfigLoadEnd},
* - {@link RoutesRecognized},
* - {@link GuardsCheckStart},
+ * - {@link ChildActivationStart},
* - {@link GuardsCheckEnd},
* - {@link ResolveStart},
* - {@link ResolveEnd},
+ * - {@link ChildActivationEnd}
* - {@link NavigationEnd},
* - {@link NavigationCancel},
* - {@link NavigationError}
*
* @stable
*/
-export type Event = NavigationStart | NavigationEnd | NavigationCancel | NavigationError |
- RoutesRecognized | RouteConfigLoadStart | RouteConfigLoadEnd | GuardsCheckStart |
- GuardsCheckEnd | ResolveStart | ResolveEnd;
+export type Event = RouterEvent | RouteEvent;
diff --git a/packages/router/src/index.ts b/packages/router/src/index.ts
index 6d202fbc3d..75abe1211a 100644
--- a/packages/router/src/index.ts
+++ b/packages/router/src/index.ts
@@ -11,7 +11,7 @@ export {Data, LoadChildren, LoadChildrenCallback, ResolveData, Route, Routes, Ru
export {RouterLink, RouterLinkWithHref} from './directives/router_link';
export {RouterLinkActive} from './directives/router_link_active';
export {RouterOutlet} from './directives/router_outlet';
-export {Event, GuardsCheckEnd, GuardsCheckStart, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, RoutesRecognized} from './events';
+export {ChildActivationEnd, ChildActivationStart, Event, GuardsCheckEnd, GuardsCheckStart, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, RouteEvent, RoutesRecognized} from './events';
export {CanActivate, CanActivateChild, CanDeactivate, CanLoad, Resolve} from './interfaces';
export {DetachedRouteHandle, RouteReuseStrategy} from './route_reuse_strategy';
export {NavigationExtras, Router} from './router';
diff --git a/packages/router/src/pre_activation.ts b/packages/router/src/pre_activation.ts
new file mode 100644
index 0000000000..e7d29466db
--- /dev/null
+++ b/packages/router/src/pre_activation.ts
@@ -0,0 +1,347 @@
+/**
+ * @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 {Injector} from '@angular/core';
+import {Observable} from 'rxjs/Observable';
+import {from} from 'rxjs/observable/from';
+import {of } from 'rxjs/observable/of';
+import {concatMap} from 'rxjs/operator/concatMap';
+import {every} from 'rxjs/operator/every';
+import {first} from 'rxjs/operator/first';
+import {last} from 'rxjs/operator/last';
+import {map} from 'rxjs/operator/map';
+import {mergeMap} from 'rxjs/operator/mergeMap';
+import {reduce} from 'rxjs/operator/reduce';
+
+import {LoadedRouterConfig, ResolveData, RunGuardsAndResolvers} from './config';
+import {ChildActivationStart, RouteEvent} from './events';
+import {ChildrenOutletContexts, OutletContext} from './router_outlet_context';
+import {ActivatedRouteSnapshot, RouterStateSnapshot, equalParamsAndUrlSegments, inheritedParamsDataResolve} from './router_state';
+import {andObservables, forEach, shallowEqual, wrapIntoObservable} from './utils/collection';
+import {TreeNode, nodeChildrenAsMap} from './utils/tree';
+
+class CanActivate {
+ constructor(public path: ActivatedRouteSnapshot[]) {}
+ get route(): ActivatedRouteSnapshot { return this.path[this.path.length - 1]; }
+}
+
+class CanDeactivate {
+ constructor(public component: Object|null, public route: ActivatedRouteSnapshot) {}
+}
+
+/**
+ * This class bundles the actions involved in preactivation of a route.
+ */
+export class PreActivation {
+ private canActivateChecks: CanActivate[] = [];
+ private canDeactivateChecks: CanDeactivate[] = [];
+
+ constructor(
+ private future: RouterStateSnapshot, private curr: RouterStateSnapshot,
+ private moduleInjector: Injector, private forwardEvent?: (evt: RouteEvent) => void) {}
+
+ initalize(parentContexts: ChildrenOutletContexts): void {
+ const futureRoot = this.future._root;
+ const currRoot = this.curr ? this.curr._root : null;
+ this.setupChildRouteGuards(futureRoot, currRoot, parentContexts, [futureRoot.value]);
+ }
+
+ checkGuards(): Observable {
+ if (!this.isDeactivating() && !this.isActivating()) {
+ return of (true);
+ }
+ const canDeactivate$ = this.runCanDeactivateChecks();
+ return mergeMap.call(
+ canDeactivate$,
+ (canDeactivate: boolean) => canDeactivate ? this.runCanActivateChecks() : of (false));
+ }
+
+ resolveData(): Observable {
+ if (!this.isActivating()) return of (null);
+ const checks$ = from(this.canActivateChecks);
+ const runningChecks$ =
+ concatMap.call(checks$, (check: CanActivate) => this.runResolve(check.route));
+ return reduce.call(runningChecks$, (_: any, __: any) => _);
+ }
+
+ isDeactivating(): boolean { return this.canDeactivateChecks.length !== 0; }
+
+ isActivating(): boolean { return this.canActivateChecks.length !== 0; }
+
+
+ /**
+ * Iterates over child routes and calls recursive `setupRouteGuards` to get `this` instance in
+ * proper state to run `checkGuards()` method.
+ */
+ private setupChildRouteGuards(
+ futureNode: TreeNode, currNode: TreeNode|null,
+ contexts: ChildrenOutletContexts|null, futurePath: ActivatedRouteSnapshot[]): void {
+ const prevChildren = nodeChildrenAsMap(currNode);
+
+ // Process the children of the future route
+ futureNode.children.forEach(c => {
+ this.setupRouteGuards(
+ c, prevChildren[c.value.outlet], contexts, futurePath.concat([c.value]));
+ delete prevChildren[c.value.outlet];
+ });
+
+ // Process any children left from the current route (not active for the future route)
+ forEach(
+ prevChildren, (v: TreeNode, k: string) =>
+ this.deactivateRouteAndItsChildren(v, contexts !.getContext(k)));
+ }
+
+ /**
+ * Iterates over child routes and calls recursive `setupRouteGuards` to get `this` instance in
+ * proper state to run `checkGuards()` method.
+ */
+ private setupRouteGuards(
+ futureNode: TreeNode, currNode: TreeNode,
+ parentContexts: ChildrenOutletContexts|null, futurePath: ActivatedRouteSnapshot[]): void {
+ const future = futureNode.value;
+ const curr = currNode ? currNode.value : null;
+ const context = parentContexts ? parentContexts.getContext(futureNode.value.outlet) : null;
+
+ // reusing the node
+ if (curr && future._routeConfig === curr._routeConfig) {
+ const shouldRunGuardsAndResolvers = this.shouldRunGuardsAndResolvers(
+ curr, future, future._routeConfig !.runGuardsAndResolvers);
+ if (shouldRunGuardsAndResolvers) {
+ this.canActivateChecks.push(new CanActivate(futurePath));
+ } else {
+ // we need to set the data
+ future.data = curr.data;
+ future._resolvedData = curr._resolvedData;
+ }
+
+ // If we have a component, we need to go through an outlet.
+ if (future.component) {
+ this.setupChildRouteGuards(
+ futureNode, currNode, context ? context.children : null, futurePath);
+
+ // if we have a componentless route, we recurse but keep the same outlet map.
+ } else {
+ this.setupChildRouteGuards(futureNode, currNode, parentContexts, futurePath);
+ }
+
+ if (shouldRunGuardsAndResolvers) {
+ const outlet = context !.outlet !;
+ this.canDeactivateChecks.push(new CanDeactivate(outlet.component, curr));
+ }
+ } else {
+ if (curr) {
+ this.deactivateRouteAndItsChildren(currNode, context);
+ }
+
+ this.canActivateChecks.push(new CanActivate(futurePath));
+ // If we have a component, we need to go through an outlet.
+ if (future.component) {
+ this.setupChildRouteGuards(futureNode, null, context ? context.children : null, futurePath);
+
+ // if we have a componentless route, we recurse but keep the same outlet map.
+ } else {
+ this.setupChildRouteGuards(futureNode, null, parentContexts, futurePath);
+ }
+ }
+ }
+
+ private shouldRunGuardsAndResolvers(
+ curr: ActivatedRouteSnapshot, future: ActivatedRouteSnapshot,
+ mode: RunGuardsAndResolvers|undefined): boolean {
+ switch (mode) {
+ case 'always':
+ return true;
+
+ case 'paramsOrQueryParamsChange':
+ return !equalParamsAndUrlSegments(curr, future) ||
+ !shallowEqual(curr.queryParams, future.queryParams);
+
+ case 'paramsChange':
+ default:
+ return !equalParamsAndUrlSegments(curr, future);
+ }
+ }
+
+ private deactivateRouteAndItsChildren(
+ route: TreeNode, context: OutletContext|null): void {
+ const children = nodeChildrenAsMap(route);
+ const r = route.value;
+
+ forEach(children, (node: TreeNode, childName: string) => {
+ if (!r.component) {
+ this.deactivateRouteAndItsChildren(node, context);
+ } else if (context) {
+ this.deactivateRouteAndItsChildren(node, context.children.getContext(childName));
+ } else {
+ this.deactivateRouteAndItsChildren(node, null);
+ }
+ });
+
+ if (!r.component) {
+ this.canDeactivateChecks.push(new CanDeactivate(null, r));
+ } else if (context && context.outlet && context.outlet.isActivated) {
+ this.canDeactivateChecks.push(new CanDeactivate(context.outlet.component, r));
+ } else {
+ this.canDeactivateChecks.push(new CanDeactivate(null, r));
+ }
+ }
+
+ private runCanDeactivateChecks(): Observable {
+ const checks$ = from(this.canDeactivateChecks);
+ const runningChecks$ = mergeMap.call(
+ checks$, (check: CanDeactivate) => this.runCanDeactivate(check.component, check.route));
+ return every.call(runningChecks$, (result: boolean) => result === true);
+ }
+
+ private runCanActivateChecks(): Observable {
+ const checks$ = from(this.canActivateChecks);
+ const runningChecks$ = concatMap.call(
+ checks$, (check: CanActivate) => andObservables(from([
+ this.fireChildActivationStart(check.path), this.runCanActivateChild(check.path),
+ this.runCanActivate(check.route)
+ ])));
+ return every.call(runningChecks$, (result: boolean) => result === true);
+ // this.fireChildActivationStart(check.path),
+ }
+
+ /**
+ * This should fire off `ChildActivationStart` events for each route being activated at this
+ * level.
+ * In other words, if you're activating `a` and `b` below, `path` will contain the
+ * `ActivatedRouteSnapshot`s for both and we will fire `ChildActivationStart` for both. Always
+ * return
+ * `true` so checks continue to run.
+ */
+ private fireChildActivationStart(path: ActivatedRouteSnapshot[]): Observable {
+ if (!this.forwardEvent) return of (true);
+ const childActivations = path.slice(0, path.length - 1).reverse().filter(_ => _ !== null);
+
+ return andObservables(map.call(from(childActivations), (snapshot: ActivatedRouteSnapshot) => {
+ if (this.forwardEvent && snapshot._routeConfig) {
+ this.forwardEvent(new ChildActivationStart(snapshot._routeConfig));
+ }
+ return of (true);
+ }));
+ }
+ private runCanActivate(future: ActivatedRouteSnapshot): Observable {
+ const canActivate = future._routeConfig ? future._routeConfig.canActivate : null;
+ if (!canActivate || canActivate.length === 0) return of (true);
+ const obs = map.call(from(canActivate), (c: any) => {
+ const guard = this.getToken(c, future);
+ let observable: Observable;
+ if (guard.canActivate) {
+ observable = wrapIntoObservable(guard.canActivate(future, this.future));
+ } else {
+ observable = wrapIntoObservable(guard(future, this.future));
+ }
+ return first.call(observable);
+ });
+ return andObservables(obs);
+ }
+
+ private runCanActivateChild(path: ActivatedRouteSnapshot[]): Observable {
+ const future = path[path.length - 1];
+
+ const canActivateChildGuards = path.slice(0, path.length - 1)
+ .reverse()
+ .map(p => this.extractCanActivateChild(p))
+ .filter(_ => _ !== null);
+
+ return andObservables(map.call(from(canActivateChildGuards), (d: any) => {
+ const obs = map.call(from(d.guards), (c: any) => {
+ const guard = this.getToken(c, d.node);
+ let observable: Observable;
+ if (guard.canActivateChild) {
+ observable = wrapIntoObservable(guard.canActivateChild(future, this.future));
+ } else {
+ observable = wrapIntoObservable(guard(future, this.future));
+ }
+ return first.call(observable);
+ });
+ return andObservables(obs);
+ }));
+ }
+
+ private extractCanActivateChild(p: ActivatedRouteSnapshot):
+ {node: ActivatedRouteSnapshot, guards: any[]}|null {
+ const canActivateChild = p._routeConfig ? p._routeConfig.canActivateChild : null;
+ if (!canActivateChild || canActivateChild.length === 0) return null;
+ return {node: p, guards: canActivateChild};
+ }
+
+ private runCanDeactivate(component: Object|null, curr: ActivatedRouteSnapshot):
+ Observable {
+ const canDeactivate = curr && curr._routeConfig ? curr._routeConfig.canDeactivate : null;
+ if (!canDeactivate || canDeactivate.length === 0) return of (true);
+ const canDeactivate$ = mergeMap.call(from(canDeactivate), (c: any) => {
+ const guard = this.getToken(c, curr);
+ let observable: Observable;
+ if (guard.canDeactivate) {
+ observable =
+ wrapIntoObservable(guard.canDeactivate(component, curr, this.curr, this.future));
+ } else {
+ observable = wrapIntoObservable(guard(component, curr, this.curr, this.future));
+ }
+ return first.call(observable);
+ });
+ return every.call(canDeactivate$, (result: any) => result === true);
+ }
+
+ private runResolve(future: ActivatedRouteSnapshot): Observable {
+ const resolve = future._resolve;
+ return map.call(this.resolveNode(resolve, future), (resolvedData: any): any => {
+ future._resolvedData = resolvedData;
+ future.data = {...future.data, ...inheritedParamsDataResolve(future).resolve};
+ return null;
+ });
+ }
+
+ private resolveNode(resolve: ResolveData, future: ActivatedRouteSnapshot): Observable {
+ const keys = Object.keys(resolve);
+ if (keys.length === 0) {
+ return of ({});
+ }
+ if (keys.length === 1) {
+ const key = keys[0];
+ return map.call(
+ this.getResolver(resolve[key], future), (value: any) => { return {[key]: value}; });
+ }
+ const data: {[k: string]: any} = {};
+ const runningResolvers$ = mergeMap.call(from(keys), (key: string) => {
+ return map.call(this.getResolver(resolve[key], future), (value: any) => {
+ data[key] = value;
+ return value;
+ });
+ });
+ return map.call(last.call(runningResolvers$), () => data);
+ }
+
+ private getResolver(injectionToken: any, future: ActivatedRouteSnapshot): Observable {
+ const resolver = this.getToken(injectionToken, future);
+ return resolver.resolve ? wrapIntoObservable(resolver.resolve(future, this.future)) :
+ wrapIntoObservable(resolver(future, this.future));
+ }
+
+ private getToken(token: any, snapshot: ActivatedRouteSnapshot): any {
+ const config = closestLoadedConfig(snapshot);
+ const injector = config ? config.module.injector : this.moduleInjector;
+ return injector.get(token);
+ }
+}
+
+
+function closestLoadedConfig(snapshot: ActivatedRouteSnapshot): LoadedRouterConfig|null {
+ if (!snapshot) return null;
+
+ for (let s = snapshot.parent; s; s = s.parent) {
+ const route = s._routeConfig;
+ if (route && route._loadedConfig) return route._loadedConfig;
+ }
+
+ return null;
+}
diff --git a/packages/router/src/router.ts b/packages/router/src/router.ts
index a5970a15a6..f9635c2e5b 100644
--- a/packages/router/src/router.ts
+++ b/packages/router/src/router.ts
@@ -12,31 +12,27 @@ import {BehaviorSubject} from 'rxjs/BehaviorSubject';
import {Observable} from 'rxjs/Observable';
import {Subject} from 'rxjs/Subject';
import {Subscription} from 'rxjs/Subscription';
-import {from} from 'rxjs/observable/from';
import {of } from 'rxjs/observable/of';
import {concatMap} from 'rxjs/operator/concatMap';
-import {every} from 'rxjs/operator/every';
-import {first} from 'rxjs/operator/first';
-import {last} from 'rxjs/operator/last';
import {map} from 'rxjs/operator/map';
import {mergeMap} from 'rxjs/operator/mergeMap';
-import {reduce} from 'rxjs/operator/reduce';
import {applyRedirects} from './apply_redirects';
-import {LoadedRouterConfig, QueryParamsHandling, ResolveData, Route, Routes, RunGuardsAndResolvers, validateConfig} from './config';
+import {LoadedRouterConfig, QueryParamsHandling, Route, Routes, validateConfig} from './config';
import {createRouterState} from './create_router_state';
import {createUrlTree} from './create_url_tree';
-import {Event, GuardsCheckEnd, GuardsCheckStart, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, RoutesRecognized} from './events';
+import {ChildActivationEnd, Event, GuardsCheckEnd, GuardsCheckStart, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, RouteEvent, RoutesRecognized} from './events';
+import {PreActivation} from './pre_activation';
import {recognize} from './recognize';
import {DefaultRouteReuseStrategy, DetachedRouteHandleInternal, RouteReuseStrategy} from './route_reuse_strategy';
import {RouterConfigLoader} from './router_config_loader';
-import {ChildrenOutletContexts, OutletContext} from './router_outlet_context';
-import {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot, advanceActivatedRoute, createEmptyState, equalParamsAndUrlSegments, inheritedParamsDataResolve} from './router_state';
+import {ChildrenOutletContexts} from './router_outlet_context';
+import {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot, advanceActivatedRoute, createEmptyState} from './router_state';
import {Params, isNavigationCancelingError} from './shared';
import {DefaultUrlHandlingStrategy, UrlHandlingStrategy} from './url_handling_strategy';
import {UrlSerializer, UrlTree, containsTree, createEmptyUrlTree} from './url_tree';
-import {andObservables, forEach, shallowEqual, waitForMap, wrapIntoObservable} from './utils/collection';
-import {TreeNode} from './utils/tree';
+import {forEach} from './utils/collection';
+import {TreeNode, nodeChildrenAsMap} from './utils/tree';
declare let Zone: any;
@@ -537,7 +533,7 @@ export class Router {
let resolve: any = null;
let reject: any = null;
- const promise = new Promise((res, rej) => {
+ const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
@@ -626,18 +622,19 @@ export class Router {
// run preactivation: guards and data resolvers
let preActivation: PreActivation;
- const preactivationTraverse$ = map.call(
+ const preactivationSetup$ = map.call(
beforePreactivationDone$,
({appliedUrl, snapshot}: {appliedUrl: string, snapshot: RouterStateSnapshot}) => {
const moduleInjector = this.ngModule.injector;
- preActivation =
- new PreActivation(snapshot, this.currentRouterState.snapshot, moduleInjector);
- preActivation.traverse(this.rootContexts);
+ preActivation = new PreActivation(
+ snapshot, this.currentRouterState.snapshot, moduleInjector,
+ (evt: RouteEvent) => this.triggerEvent(evt));
+ preActivation.initalize(this.rootContexts);
return {appliedUrl, snapshot};
});
const preactivationCheckGuards$ = mergeMap.call(
- preactivationTraverse$,
+ preactivationSetup$,
({appliedUrl, snapshot}: {appliedUrl: string, snapshot: RouterStateSnapshot}) => {
if (this.navigationId !== id) return of (false);
@@ -715,7 +712,8 @@ export class Router {
}
}
- new ActivateRoutes(this.routeReuseStrategy, state, storedState)
+ new ActivateRoutes(
+ this.routeReuseStrategy, state, storedState, (evt: Event) => this.triggerEvent(evt))
.activate(this.rootContexts);
navigationIsSuccessful = true;
@@ -763,289 +761,10 @@ export class Router {
}
}
-
-class CanActivate {
- constructor(public path: ActivatedRouteSnapshot[]) {}
- get route(): ActivatedRouteSnapshot { return this.path[this.path.length - 1]; }
-}
-
-class CanDeactivate {
- constructor(public component: Object|null, public route: ActivatedRouteSnapshot) {}
-}
-
-export class PreActivation {
- private canActivateChecks: CanActivate[] = [];
- private canDeactivateChecks: CanDeactivate[] = [];
-
- constructor(
- private future: RouterStateSnapshot, private curr: RouterStateSnapshot,
- private moduleInjector: Injector) {}
-
- traverse(parentContexts: ChildrenOutletContexts): void {
- const futureRoot = this.future._root;
- const currRoot = this.curr ? this.curr._root : null;
- this.traverseChildRoutes(futureRoot, currRoot, parentContexts, [futureRoot.value]);
- }
-
- // TODO(jasonaden): Refactor checkGuards and resolveData so they can collect the checks
- // and guards before mapping into the observable. Likely remove the observable completely
- // and make these pure functions so they are more predictable and don't rely on so much
- // external state.
- checkGuards(): Observable {
- if (!this.isDeactivating() && !this.isActivating()) {
- return of (true);
- }
- const canDeactivate$ = this.runCanDeactivateChecks();
- return mergeMap.call(
- canDeactivate$,
- (canDeactivate: boolean) => canDeactivate ? this.runCanActivateChecks() : of (false));
- }
-
- resolveData(): Observable {
- if (!this.isActivating()) return of (null);
- const checks$ = from(this.canActivateChecks);
- const runningChecks$ =
- concatMap.call(checks$, (check: CanActivate) => this.runResolve(check.route));
- return reduce.call(runningChecks$, (_: any, __: any) => _);
- }
-
- isDeactivating(): boolean { return this.canDeactivateChecks.length !== 0; }
-
- isActivating(): boolean { return this.canActivateChecks.length !== 0; }
-
- private traverseChildRoutes(
- futureNode: TreeNode, currNode: TreeNode|null,
- contexts: ChildrenOutletContexts|null, futurePath: ActivatedRouteSnapshot[]): void {
- const prevChildren = nodeChildrenAsMap(currNode);
-
- // Process the children of the future route
- futureNode.children.forEach(c => {
- this.traverseRoutes(c, prevChildren[c.value.outlet], contexts, futurePath.concat([c.value]));
- delete prevChildren[c.value.outlet];
- });
-
- // Process any children left from the current route (not active for the future route)
- forEach(
- prevChildren, (v: TreeNode, k: string) =>
- this.deactivateRouteAndItsChildren(v, contexts !.getContext(k)));
- }
-
- private traverseRoutes(
- futureNode: TreeNode, currNode: TreeNode,
- parentContexts: ChildrenOutletContexts|null, futurePath: ActivatedRouteSnapshot[]): void {
- const future = futureNode.value;
- const curr = currNode ? currNode.value : null;
- const context = parentContexts ? parentContexts.getContext(futureNode.value.outlet) : null;
-
- // reusing the node
- if (curr && future._routeConfig === curr._routeConfig) {
- const shouldRunGuardsAndResolvers = this.shouldRunGuardsAndResolvers(
- curr, future, future._routeConfig !.runGuardsAndResolvers);
- if (shouldRunGuardsAndResolvers) {
- this.canActivateChecks.push(new CanActivate(futurePath));
- } else {
- // we need to set the data
- future.data = curr.data;
- future._resolvedData = curr._resolvedData;
- }
-
- // If we have a component, we need to go through an outlet.
- if (future.component) {
- this.traverseChildRoutes(
- futureNode, currNode, context ? context.children : null, futurePath);
-
- // if we have a componentless route, we recurse but keep the same outlet map.
- } else {
- this.traverseChildRoutes(futureNode, currNode, parentContexts, futurePath);
- }
-
- if (shouldRunGuardsAndResolvers) {
- const outlet = context !.outlet !;
- this.canDeactivateChecks.push(new CanDeactivate(outlet.component, curr));
- }
- } else {
- if (curr) {
- this.deactivateRouteAndItsChildren(currNode, context);
- }
-
- this.canActivateChecks.push(new CanActivate(futurePath));
- // If we have a component, we need to go through an outlet.
- if (future.component) {
- this.traverseChildRoutes(futureNode, null, context ? context.children : null, futurePath);
-
- // if we have a componentless route, we recurse but keep the same outlet map.
- } else {
- this.traverseChildRoutes(futureNode, null, parentContexts, futurePath);
- }
- }
- }
-
- private shouldRunGuardsAndResolvers(
- curr: ActivatedRouteSnapshot, future: ActivatedRouteSnapshot,
- mode: RunGuardsAndResolvers|undefined): boolean {
- switch (mode) {
- case 'always':
- return true;
-
- case 'paramsOrQueryParamsChange':
- return !equalParamsAndUrlSegments(curr, future) ||
- !shallowEqual(curr.queryParams, future.queryParams);
-
- case 'paramsChange':
- default:
- return !equalParamsAndUrlSegments(curr, future);
- }
- }
-
- private deactivateRouteAndItsChildren(
- route: TreeNode, context: OutletContext|null): void {
- const children = nodeChildrenAsMap(route);
- const r = route.value;
-
- forEach(children, (node: TreeNode, childName: string) => {
- if (!r.component) {
- this.deactivateRouteAndItsChildren(node, context);
- } else if (context) {
- this.deactivateRouteAndItsChildren(node, context.children.getContext(childName));
- } else {
- this.deactivateRouteAndItsChildren(node, null);
- }
- });
-
- if (!r.component) {
- this.canDeactivateChecks.push(new CanDeactivate(null, r));
- } else if (context && context.outlet && context.outlet.isActivated) {
- this.canDeactivateChecks.push(new CanDeactivate(context.outlet.component, r));
- } else {
- this.canDeactivateChecks.push(new CanDeactivate(null, r));
- }
- }
-
- private runCanDeactivateChecks(): Observable {
- const checks$ = from(this.canDeactivateChecks);
- const runningChecks$ = mergeMap.call(
- checks$, (check: CanDeactivate) => this.runCanDeactivate(check.component, check.route));
- return every.call(runningChecks$, (result: boolean) => result === true);
- }
-
- private runCanActivateChecks(): Observable {
- const checks$ = from(this.canActivateChecks);
- const runningChecks$ = concatMap.call(
- checks$, (check: CanActivate) => andObservables(from(
- [this.runCanActivateChild(check.path), this.runCanActivate(check.route)])));
- return every.call(runningChecks$, (result: boolean) => result === true);
- }
-
- private runCanActivate(future: ActivatedRouteSnapshot): Observable {
- const canActivate = future._routeConfig ? future._routeConfig.canActivate : null;
- if (!canActivate || canActivate.length === 0) return of (true);
- const obs = map.call(from(canActivate), (c: any) => {
- const guard = this.getToken(c, future);
- let observable: Observable;
- if (guard.canActivate) {
- observable = wrapIntoObservable(guard.canActivate(future, this.future));
- } else {
- observable = wrapIntoObservable(guard(future, this.future));
- }
- return first.call(observable);
- });
- return andObservables(obs);
- }
-
- private runCanActivateChild(path: ActivatedRouteSnapshot[]): Observable {
- const future = path[path.length - 1];
-
- const canActivateChildGuards = path.slice(0, path.length - 1)
- .reverse()
- .map(p => this.extractCanActivateChild(p))
- .filter(_ => _ !== null);
-
- return andObservables(map.call(from(canActivateChildGuards), (d: any) => {
- const obs = map.call(from(d.guards), (c: any) => {
- const guard = this.getToken(c, d.node);
- let observable: Observable;
- if (guard.canActivateChild) {
- observable = wrapIntoObservable(guard.canActivateChild(future, this.future));
- } else {
- observable = wrapIntoObservable(guard(future, this.future));
- }
- return first.call(observable);
- });
- return andObservables(obs);
- }));
- }
-
- private extractCanActivateChild(p: ActivatedRouteSnapshot):
- {node: ActivatedRouteSnapshot, guards: any[]}|null {
- const canActivateChild = p._routeConfig ? p._routeConfig.canActivateChild : null;
- if (!canActivateChild || canActivateChild.length === 0) return null;
- return {node: p, guards: canActivateChild};
- }
-
- private runCanDeactivate(component: Object|null, curr: ActivatedRouteSnapshot):
- Observable {
- const canDeactivate = curr && curr._routeConfig ? curr._routeConfig.canDeactivate : null;
- if (!canDeactivate || canDeactivate.length === 0) return of (true);
- const canDeactivate$ = mergeMap.call(from(canDeactivate), (c: any) => {
- const guard = this.getToken(c, curr);
- let observable: Observable;
- if (guard.canDeactivate) {
- observable =
- wrapIntoObservable(guard.canDeactivate(component, curr, this.curr, this.future));
- } else {
- observable = wrapIntoObservable(guard(component, curr, this.curr, this.future));
- }
- return first.call(observable);
- });
- return every.call(canDeactivate$, (result: any) => result === true);
- }
-
- private runResolve(future: ActivatedRouteSnapshot): Observable {
- const resolve = future._resolve;
- return map.call(this.resolveNode(resolve, future), (resolvedData: any): any => {
- future._resolvedData = resolvedData;
- future.data = {...future.data, ...inheritedParamsDataResolve(future).resolve};
- return null;
- });
- }
-
- private resolveNode(resolve: ResolveData, future: ActivatedRouteSnapshot): Observable {
- const keys = Object.keys(resolve);
- if (keys.length === 0) {
- return of ({});
- }
- if (keys.length === 1) {
- const key = keys[0];
- return map.call(
- this.getResolver(resolve[key], future), (value: any) => { return {[key]: value}; });
- }
- const data: {[k: string]: any} = {};
- const runningResolvers$ = mergeMap.call(from(keys), (key: string) => {
- return map.call(this.getResolver(resolve[key], future), (value: any) => {
- data[key] = value;
- return value;
- });
- });
- return map.call(last.call(runningResolvers$), () => data);
- }
-
- private getResolver(injectionToken: any, future: ActivatedRouteSnapshot): Observable {
- const resolver = this.getToken(injectionToken, future);
- return resolver.resolve ? wrapIntoObservable(resolver.resolve(future, this.future)) :
- wrapIntoObservable(resolver(future, this.future));
- }
-
- private getToken(token: any, snapshot: ActivatedRouteSnapshot): any {
- const config = closestLoadedConfig(snapshot);
- const injector = config ? config.module.injector : this.moduleInjector;
- return injector.get(token);
- }
-}
-
class ActivateRoutes {
constructor(
private routeReuseStrategy: RouteReuseStrategy, private futureState: RouterState,
- private currState: RouterState) {}
+ private currState: RouterState, private forwardEvent: (evt: RouteEvent) => void) {}
activate(parentContexts: ChildrenOutletContexts): void {
const futureRoot = this.futureState._root;
@@ -1128,7 +847,7 @@ class ActivateRoutes {
const children: {[outletName: string]: any} = nodeChildrenAsMap(route);
const contexts = route.value.component ? context.children : parentContexts;
- forEach(children, (v: any, k: string) => {this.deactivateRouteAndItsChildren(v, contexts)});
+ forEach(children, (v: any, k: string) => this.deactivateRouteAndItsChildren(v, contexts));
if (context.outlet) {
// Destroy the component
@@ -1145,6 +864,9 @@ class ActivateRoutes {
const children: {[outlet: string]: any} = nodeChildrenAsMap(currNode);
futureNode.children.forEach(
c => { this.activateRoutes(c, children[c.value.outlet], contexts); });
+ if (futureNode.children.length && futureNode.value.routeConfig) {
+ this.forwardEvent(new ChildActivationEnd(futureNode.value.routeConfig));
+ }
}
private activateRoutes(
@@ -1220,28 +942,6 @@ function parentLoadedConfig(snapshot: ActivatedRouteSnapshot): LoadedRouterConfi
return null;
}
-function closestLoadedConfig(snapshot: ActivatedRouteSnapshot): LoadedRouterConfig|null {
- if (!snapshot) return null;
-
- for (let s = snapshot.parent; s; s = s.parent) {
- const route = s._routeConfig;
- if (route && route._loadedConfig) return route._loadedConfig;
- }
-
- return null;
-}
-
-// Return the list of T indexed by outlet name
-function nodeChildrenAsMap(node: TreeNode| null) {
- const map: {[outlet: string]: TreeNode} = {};
-
- if (node) {
- node.children.forEach(child => map[child.value.outlet] = child);
- }
-
- return map;
-}
-
function validateCommands(commands: string[]): void {
for (let i = 0; i < commands.length; i++) {
const cmd = commands[i];
diff --git a/packages/router/src/shared.ts b/packages/router/src/shared.ts
index 56814d5edd..d8da4a68d2 100644
--- a/packages/router/src/shared.ts
+++ b/packages/router/src/shared.ts
@@ -6,7 +6,6 @@
* found in the LICENSE file at https://angular.io/license
*/
-
import {Route, UrlMatchResult} from './config';
import {UrlSegment, UrlSegmentGroup} from './url_tree';
diff --git a/packages/router/src/utils/collection.ts b/packages/router/src/utils/collection.ts
index 36ffcca7d8..a72560ae9b 100644
--- a/packages/router/src/utils/collection.ts
+++ b/packages/router/src/utils/collection.ts
@@ -41,14 +41,23 @@ export function shallowEqual(a: {[x: string]: any}, b: {[x: string]: any}): bool
return true;
}
+/**
+ * Flattens single-level nested arrays.
+ */
export function flatten(arr: T[][]): T[] {
return Array.prototype.concat.apply([], arr);
}
+/**
+ * Return the last element of an array.
+ */
export function last(a: T[]): T|null {
return a.length > 0 ? a[a.length - 1] : null;
}
+/**
+ * Verifys all booleans in an array are `true`.
+ */
export function and(bools: boolean[]): boolean {
return !bools.some(v => !v);
}
@@ -64,7 +73,7 @@ export function forEach(map: {[key: string]: V}, callback: (v: V, k: strin
export function waitForMap(
obj: {[k: string]: A}, fn: (k: string, a: A) => Observable): Observable<{[k: string]: B}> {
if (Object.keys(obj).length === 0) {
- return of ({})
+ return of ({});
}
const waitHead: Observable[] = [];
@@ -85,6 +94,10 @@ export function waitForMap(
return map.call(last$, () => res);
}
+/**
+ * ANDs Observables by merging all input observables, reducing to an Observable verifying all
+ * input Observables return `true`.
+ */
export function andObservables(observables: Observable>): Observable {
const merged$ = mergeAll.call(observables);
return every.call(merged$, (result: any) => result === true);
@@ -103,5 +116,5 @@ export function wrapIntoObservable(value: T | NgModuleFactory| Promise|
return fromPromise(Promise.resolve(value));
}
- return of (value);
+ return of (value as T);
}
diff --git a/packages/router/src/utils/tree.ts b/packages/router/src/utils/tree.ts
index bcb6037e62..a811872a68 100644
--- a/packages/router/src/utils/tree.ts
+++ b/packages/router/src/utils/tree.ts
@@ -87,4 +87,15 @@ export class TreeNode {
constructor(public value: T, public children: TreeNode[]) {}
toString(): string { return `TreeNode(${this.value})`; }
+}
+
+// Return the list of T indexed by outlet name
+export function nodeChildrenAsMap(node: TreeNode| null) {
+ const map: {[outlet: string]: TreeNode} = {};
+
+ if (node) {
+ node.children.forEach(child => map[child.value.outlet] = child);
+ }
+
+ return map;
}
\ No newline at end of file
diff --git a/packages/router/test/config.spec.ts b/packages/router/test/config.spec.ts
index ad643a8fae..9ef3037c69 100644
--- a/packages/router/test/config.spec.ts
+++ b/packages/router/test/config.spec.ts
@@ -45,7 +45,7 @@ describe('config', () => {
expect(() => {
validateConfig([
{path: 'a', component: ComponentA},
- [{path: 'b', component: ComponentB}, {path: 'c', component: ComponentC}]
+ [{path: 'b', component: ComponentB}, {path: 'c', component: ComponentC}] as any
]);
}).toThrowError(`Invalid configuration of route '': Array cannot be specified`);
});
diff --git a/packages/router/test/helpers.ts b/packages/router/test/helpers.ts
new file mode 100644
index 0000000000..2ef3b7e141
--- /dev/null
+++ b/packages/router/test/helpers.ts
@@ -0,0 +1,50 @@
+/**
+ * @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 {Type} from '@angular/core';
+
+import {Data, ResolveData, Route} from '../src/config';
+import {ActivatedRouteSnapshot} from '../src/router_state';
+import {PRIMARY_OUTLET, ParamMap, Params, convertToParamMap} from '../src/shared';
+import {UrlSegment, UrlSegmentGroup, UrlTree, equalSegments} from '../src/url_tree';
+
+export class Logger {
+ logs: string[] = [];
+ add(thing: string) { this.logs.push(thing); }
+ empty() { this.logs.length = 0; }
+}
+
+export function provideTokenLogger(token: string, returnValue = true) {
+ return {
+ provide: token,
+ useFactory: (logger: Logger) => () => (logger.add(token), returnValue),
+ deps: [Logger]
+ };
+};
+
+export declare type ARSArgs = {
+ url?: UrlSegment[],
+ params?: Params,
+ queryParams?: Params,
+ fragment?: string,
+ data?: Data,
+ outlet?: string,
+ component: Type| string | null,
+ routeConfig?: Route | null,
+ urlSegment?: UrlSegmentGroup,
+ lastPathIndex?: number,
+ resolve?: ResolveData
+};
+
+export function createActivatedRouteSnapshot(args: ARSArgs): ActivatedRouteSnapshot {
+ return new ActivatedRouteSnapshot(
+ args.url || [], args.params || {}, args.queryParams || null,
+ args.fragment || null, args.data || null, args.outlet || null,
+ args.component, args.routeConfig || {}, args.urlSegment || null,
+ args.lastPathIndex || -1, args.resolve || {});
+}
diff --git a/packages/router/test/integration.spec.ts b/packages/router/test/integration.spec.ts
index adeab68c68..5c42074f5e 100644
--- a/packages/router/test/integration.spec.ts
+++ b/packages/router/test/integration.spec.ts
@@ -11,7 +11,7 @@ import {ChangeDetectionStrategy, Component, Injectable, NgModule, NgModuleFactor
import {ComponentFixture, TestBed, fakeAsync, inject, tick} from '@angular/core/testing';
import {By} from '@angular/platform-browser/src/dom/debug/by';
import {expect} from '@angular/platform-browser/testing/src/matchers';
-import {ActivatedRoute, ActivatedRouteSnapshot, CanActivate, CanDeactivate, DetachedRouteHandle, Event, GuardsCheckEnd, GuardsCheckStart, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, PRIMARY_OUTLET, ParamMap, Params, PreloadAllModules, PreloadingStrategy, Resolve, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, RouteReuseStrategy, Router, RouterModule, RouterPreloader, RouterStateSnapshot, RoutesRecognized, RunGuardsAndResolvers, UrlHandlingStrategy, UrlSegmentGroup, UrlTree} from '@angular/router';
+import {ActivatedRoute, ActivatedRouteSnapshot, CanActivate, CanDeactivate, ChildActivationEnd, ChildActivationStart, DetachedRouteHandle, Event, GuardsCheckEnd, GuardsCheckStart, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, PRIMARY_OUTLET, ParamMap, Params, PreloadAllModules, PreloadingStrategy, Resolve, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, RouteEvent, RouteReuseStrategy, Router, RouterModule, RouterPreloader, RouterStateSnapshot, RoutesRecognized, RunGuardsAndResolvers, UrlHandlingStrategy, UrlSegmentGroup, UrlTree} from '@angular/router';
import {Observable} from 'rxjs/Observable';
import {Observer} from 'rxjs/Observer';
import {of } from 'rxjs/observable/of';
@@ -428,7 +428,7 @@ describe('Integration', () => {
}]);
const recordedEvents: any[] = [];
- router.events.forEach(e => recordedEvents.push(e));
+ router.events.forEach(e => e instanceof RouteEvent || recordedEvents.push(e));
router.navigateByUrl('/team/22/user/victor');
advance(fixture);
@@ -986,7 +986,7 @@ describe('Integration', () => {
[{path: 'simple', component: SimpleCmp, resolve: {error: 'resolveError'}}]);
const recordedEvents: any[] = [];
- router.events.subscribe(e => recordedEvents.push(e));
+ router.events.subscribe(e => e instanceof RouteEvent || recordedEvents.push(e));
let e: any = null;
router.navigateByUrl('/simple') !.catch(error => e = error);
@@ -1070,7 +1070,7 @@ describe('Integration', () => {
return map.call(of (null), () => {
log.push('resolver2');
observer.next(null);
- observer.complete()
+ observer.complete();
});
}
},
@@ -1820,7 +1820,7 @@ describe('Integration', () => {
function delayPromise(delay: number): Promise {
let resolve: (val: boolean) => void;
- const promise = new Promise(res => resolve = res);
+ const promise = new Promise(res => resolve = res);
setTimeout(() => resolve(true), delay);
return promise;
}
@@ -2388,9 +2388,11 @@ describe('Integration', () => {
[RouteConfigLoadEnd],
[RoutesRecognized, '/lazyTrue/loaded'],
[GuardsCheckStart, '/lazyTrue/loaded'],
+ [ChildActivationStart],
[GuardsCheckEnd, '/lazyTrue/loaded'],
[ResolveStart, '/lazyTrue/loaded'],
[ResolveEnd, '/lazyTrue/loaded'],
+ [ChildActivationEnd],
[NavigationEnd, '/lazyTrue/loaded'],
]);
})));
@@ -3342,7 +3344,7 @@ describe('Integration', () => {
}]);
const events: any[] = [];
- router.events.subscribe(e => events.push(e));
+ router.events.subscribe(e => e instanceof RouteEvent || events.push(e));
// supported URL
router.navigateByUrl('/include/user/kate');
@@ -3406,7 +3408,7 @@ describe('Integration', () => {
}]);
const events: any[] = [];
- router.events.subscribe(e => events.push(e));
+ router.events.subscribe(e => e instanceof RouteEvent || events.push(e));
location.go('/include/user/kate(aux:excluded)');
advance(fixture);
diff --git a/packages/router/test/router.spec.ts b/packages/router/test/router.spec.ts
index 7ea6d6bac9..be1e469c6d 100644
--- a/packages/router/test/router.spec.ts
+++ b/packages/router/test/router.spec.ts
@@ -10,13 +10,16 @@ import {Location} from '@angular/common';
import {TestBed, inject} from '@angular/core/testing';
import {ResolveData} from '../src/config';
-import {PreActivation, Router} from '../src/router';
+import {PreActivation} from '../src/pre_activation';
+import {Router} from '../src/router';
import {ChildrenOutletContexts} from '../src/router_outlet_context';
import {ActivatedRouteSnapshot, RouterStateSnapshot, createEmptyStateSnapshot} from '../src/router_state';
import {DefaultUrlSerializer} from '../src/url_tree';
import {TreeNode} from '../src/utils/tree';
import {RouterTestingModule} from '../testing/src/router_testing_module';
+import {Logger, createActivatedRouteSnapshot, provideTokenLogger} from './helpers';
+
describe('Router', () => {
describe('resetRootComponentType', () => {
class NewRootComponent {}
@@ -56,51 +59,295 @@ describe('Router', () => {
const serializer = new DefaultUrlSerializer();
const inj = {get: (token: any) => () => `${token}_value`};
let empty: RouterStateSnapshot;
+ let logger: Logger;
- beforeEach(() => { empty = createEmptyStateSnapshot(serializer.parse('/'), null !); });
+ const CA_CHILD = 'canActivate_child';
+ const CA_CHILD_FALSE = 'canActivate_child_false';
+ const CAC_CHILD = 'canActivateChild_child';
+ const CAC_CHILD_FALSE = 'canActivateChild_child_false';
+ const CA_GRANDCHILD = 'canActivate_grandchild';
+ const CA_GRANDCHILD_FALSE = 'canActivate_grandchild_false';
+ const CDA_CHILD = 'canDeactivate_child';
+ const CDA_CHILD_FALSE = 'canDeactivate_child_false';
+ const CDA_GRANDCHILD = 'canDeactivate_grandchild';
+ const CDA_GRANDCHILD_FALSE = 'canDeactivate_grandchild_false';
- it('should resolve data', () => {
- const r = {data: 'resolver'};
- const n = createActivatedRouteSnapshot('a', {resolve: r});
- const s = new RouterStateSnapshot('url', new TreeNode(empty.root, [new TreeNode(n, [])]));
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ providers: [
+ Logger, provideTokenLogger(CA_CHILD), provideTokenLogger(CA_CHILD_FALSE, false),
+ provideTokenLogger(CAC_CHILD), provideTokenLogger(CAC_CHILD_FALSE, false),
+ provideTokenLogger(CA_GRANDCHILD), provideTokenLogger(CA_GRANDCHILD_FALSE, false),
+ provideTokenLogger(CDA_CHILD), provideTokenLogger(CDA_CHILD_FALSE, false),
+ provideTokenLogger(CDA_GRANDCHILD), provideTokenLogger(CDA_GRANDCHILD_FALSE, false)
+ ]
+ });
- checkResolveData(s, empty, inj, () => {
- expect(s.root.firstChild !.data).toEqual({data: 'resolver_value'});
+ });
+
+ beforeEach(inject([Logger], (_logger: Logger) => {
+ empty = createEmptyStateSnapshot(serializer.parse('/'), null !);
+ logger = _logger;
+ }));
+
+ describe('guards', () => {
+ it('should run CanActivate checks', () => {
+ /**
+ * R --> R
+ * \
+ * child (CA, CAC)
+ * \
+ * grandchild (CA)
+ */
+
+ const childSnapshot = createActivatedRouteSnapshot({
+ component: 'child',
+ routeConfig: {
+
+ canActivate: [CA_CHILD],
+ canActivateChild: [CAC_CHILD]
+ }
+ });
+ const grandchildSnapshot = createActivatedRouteSnapshot(
+ {component: 'grandchild', routeConfig: {canActivate: [CA_GRANDCHILD]}});
+
+ const futureState = new RouterStateSnapshot(
+ 'url',
+ new TreeNode(
+ empty.root, [new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])]));
+
+ checkGuards(futureState, empty, TestBed, (result) => {
+ expect(result).toBe(true);
+ expect(logger.logs).toEqual([CA_CHILD, CAC_CHILD, CA_GRANDCHILD]);
+ });
+ });
+
+ it('should not run grandchild guards if child fails', () => {
+ /**
+ * R --> R
+ * \
+ * child (CA: x, CAC)
+ * \
+ * grandchild (CA)
+ */
+
+ const childSnapshot = createActivatedRouteSnapshot({
+ component: 'child',
+ routeConfig: {canActivate: [CA_CHILD_FALSE], canActivateChild: [CAC_CHILD]}
+ });
+ const grandchildSnapshot = createActivatedRouteSnapshot(
+ {component: 'grandchild', routeConfig: {canActivate: [CA_GRANDCHILD]}});
+
+ const futureState = new RouterStateSnapshot(
+ 'url',
+ new TreeNode(
+ empty.root, [new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])]));
+
+ checkGuards(futureState, empty, TestBed, (result) => {
+ expect(result).toBe(false);
+ expect(logger.logs).toEqual([CA_CHILD_FALSE]);
+ });
+ });
+
+ it('should not run grandchild guards if child canActivateChild fails', () => {
+ /**
+ * R --> R
+ * \
+ * child (CA, CAC: x)
+ * \
+ * grandchild (CA)
+ */
+
+ const childSnapshot = createActivatedRouteSnapshot({
+ component: 'child',
+ routeConfig: {canActivate: [CA_CHILD], canActivateChild: [CAC_CHILD_FALSE]}
+ });
+ const grandchildSnapshot = createActivatedRouteSnapshot(
+ {component: 'grandchild', routeConfig: {canActivate: [CA_GRANDCHILD]}});
+
+ const futureState = new RouterStateSnapshot(
+ 'url',
+ new TreeNode(
+ empty.root, [new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])]));
+
+ checkGuards(futureState, empty, TestBed, (result) => {
+ expect(result).toBe(false);
+ expect(logger.logs).toEqual([CA_CHILD, CAC_CHILD_FALSE]);
+ });
+ });
+
+ it('should run deactivate guards before activate guards', () => {
+ /**
+ * R --> R
+ * / \
+ * prev (CDA) child (CA)
+ * \
+ * grandchild (CA)
+ */
+
+ const prevSnapshot = createActivatedRouteSnapshot(
+ {component: 'prev', routeConfig: {canDeactivate: [CDA_CHILD]}});
+
+ const childSnapshot = createActivatedRouteSnapshot({
+ component: 'child',
+ routeConfig: {canActivate: [CA_CHILD], canActivateChild: [CAC_CHILD]}
+ });
+
+ const grandchildSnapshot = createActivatedRouteSnapshot(
+ {component: 'grandchild', routeConfig: {canActivate: [CA_GRANDCHILD]}});
+
+ const currentState = new RouterStateSnapshot(
+ 'prev', new TreeNode(empty.root, [new TreeNode(prevSnapshot, [])]));
+
+ const futureState = new RouterStateSnapshot(
+ 'url',
+ new TreeNode(
+ empty.root, [new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])]));
+
+ checkGuards(futureState, currentState, TestBed, (result) => {
+ expect(logger.logs).toEqual([CDA_CHILD, CA_CHILD, CAC_CHILD, CA_GRANDCHILD]);
+ });
+ });
+
+ it('should not run activate if deactivate fails guards', () => {
+ /**
+ * R --> R
+ * / \
+ * prev (CDA) child (CA)
+ * \
+ * grandchild (CA)
+ */
+
+ const prevSnapshot = createActivatedRouteSnapshot(
+ {component: 'prev', routeConfig: {canDeactivate: [CDA_CHILD_FALSE]}});
+ const childSnapshot = createActivatedRouteSnapshot({
+ component: 'child',
+ routeConfig: {canActivate: [CA_CHILD], canActivateChild: [CAC_CHILD]}
+ });
+ const grandchildSnapshot = createActivatedRouteSnapshot(
+ {component: 'grandchild', routeConfig: {canActivate: [CA_GRANDCHILD]}});
+
+ const currentState = new RouterStateSnapshot(
+ 'prev', new TreeNode(empty.root, [new TreeNode(prevSnapshot, [])]));
+ const futureState = new RouterStateSnapshot(
+ 'url',
+ new TreeNode(
+ empty.root, [new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])]));
+
+ checkGuards(futureState, currentState, TestBed, (result) => {
+ expect(result).toBe(false);
+ expect(logger.logs).toEqual([CDA_CHILD_FALSE]);
+ });
+ });
+ it('should deactivate from bottom up, then activate top down', () => {
+ /**
+ * R --> R
+ * / \
+ * prevChild (CDA) child (CA)
+ * / \
+ * prevGrandchild(CDA) grandchild (CA)
+ */
+
+ const prevChildSnapshot = createActivatedRouteSnapshot(
+ {component: 'prev_child', routeConfig: {canDeactivate: [CDA_CHILD]}});
+ const prevGrandchildSnapshot = createActivatedRouteSnapshot(
+ {component: 'prev_grandchild', routeConfig: {canDeactivate: [CDA_GRANDCHILD]}});
+ const childSnapshot = createActivatedRouteSnapshot({
+ component: 'child',
+ routeConfig: {canActivate: [CA_CHILD], canActivateChild: [CAC_CHILD]}
+ });
+ const grandchildSnapshot = createActivatedRouteSnapshot(
+ {component: 'grandchild', routeConfig: {canActivate: [CA_GRANDCHILD]}});
+
+ const currentState = new RouterStateSnapshot(
+ 'prev', new TreeNode(empty.root, [
+ new TreeNode(prevChildSnapshot, [new TreeNode(prevGrandchildSnapshot, [])])
+ ]));
+
+ const futureState = new RouterStateSnapshot(
+ 'url',
+ new TreeNode(
+ empty.root, [new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])]));
+
+ checkGuards(futureState, currentState, TestBed, (result) => {
+ expect(result).toBe(true);
+ expect(logger.logs).toEqual([
+ CDA_GRANDCHILD, CDA_CHILD, CA_CHILD, CAC_CHILD, CA_GRANDCHILD
+ ]);
+ });
+
+ logger.empty();
+ checkGuards(currentState, futureState, TestBed, (result) => {
+ expect(result).toBe(true);
+ expect(logger.logs).toEqual([]);
+ });
});
});
- it('should wait for the parent resolve to complete', () => {
- const parentResolve = {data: 'resolver'};
- const childResolve = {};
+ describe('resolve', () => {
- const parent = createActivatedRouteSnapshot(null !, {resolve: parentResolve});
- const child = createActivatedRouteSnapshot('b', {resolve: childResolve});
+ it('should resolve data', () => {
+ /**
+ * R --> R
+ * \
+ * a
+ */
+ const r = {data: 'resolver'};
+ const n = createActivatedRouteSnapshot({component: 'a', resolve: r});
+ const s = new RouterStateSnapshot('url', new TreeNode(empty.root, [new TreeNode(n, [])]));
- const s = new RouterStateSnapshot(
- 'url', new TreeNode(empty.root, [new TreeNode(parent, [new TreeNode(child, [])])]));
-
- const inj = {get: (token: any) => () => Promise.resolve(`${token}_value`)};
-
- checkResolveData(s, empty, inj, () => {
- expect(s.root.firstChild !.firstChild !.data).toEqual({data: 'resolver_value'});
+ checkResolveData(s, empty, inj, () => {
+ expect(s.root.firstChild !.data).toEqual({data: 'resolver_value'});
+ });
});
- });
- it('should copy over data when creating a snapshot', () => {
- const r1 = {data: 'resolver1'};
- const r2 = {data: 'resolver2'};
+ it('should wait for the parent resolve to complete', () => {
+ /**
+ * R --> R
+ * \
+ * null (resolve: parentResolve)
+ * \
+ * b (resolve: childResolve)
+ */
+ const parentResolve = {data: 'resolver'};
+ const childResolve = {};
- const n1 = createActivatedRouteSnapshot('a', {resolve: r1});
- const s1 = new RouterStateSnapshot('url', new TreeNode(empty.root, [new TreeNode(n1, [])]));
- checkResolveData(s1, empty, inj, () => {});
+ const parent = createActivatedRouteSnapshot({component: null !, resolve: parentResolve});
+ const child = createActivatedRouteSnapshot({component: 'b', resolve: childResolve});
- const n21 = createActivatedRouteSnapshot('a', {resolve: r1});
- const n22 = createActivatedRouteSnapshot('b', {resolve: r2});
- const s2 = new RouterStateSnapshot(
- 'url', new TreeNode(empty.root, [new TreeNode(n21, [new TreeNode(n22, [])])]));
- checkResolveData(s2, s1, inj, () => {
- expect(s2.root.firstChild !.data).toEqual({data: 'resolver1_value'});
- expect(s2.root.firstChild !.firstChild !.data).toEqual({data: 'resolver2_value'});
+ const s = new RouterStateSnapshot(
+ 'url', new TreeNode(empty.root, [new TreeNode(parent, [new TreeNode(child, [])])]));
+
+ const inj = {get: (token: any) => () => Promise.resolve(`${token}_value`)};
+
+ checkResolveData(s, empty, inj, () => {
+ expect(s.root.firstChild !.firstChild !.data).toEqual({data: 'resolver_value'});
+ });
+ });
+
+ it('should copy over data when creating a snapshot', () => {
+ /**
+ * R --> R --> R
+ * \ \
+ * n1 (resolve: r1) n21 (resolve: r1)
+ * \
+ * n22 (resolve: r2)
+ */
+ const r1 = {data: 'resolver1'};
+ const r2 = {data: 'resolver2'};
+
+ const n1 = createActivatedRouteSnapshot({component: 'a', resolve: r1});
+ const s1 = new RouterStateSnapshot('url', new TreeNode(empty.root, [new TreeNode(n1, [])]));
+ checkResolveData(s1, empty, inj, () => {});
+
+ const n21 = createActivatedRouteSnapshot({component: 'a', resolve: r1});
+ const n22 = createActivatedRouteSnapshot({component: 'b', resolve: r2});
+ const s2 = new RouterStateSnapshot(
+ 'url', new TreeNode(empty.root, [new TreeNode(n21, [new TreeNode(n22, [])])]));
+ checkResolveData(s2, s1, inj, () => {
+ expect(s2.root.firstChild !.data).toEqual({data: 'resolver1_value'});
+ expect(s2.root.firstChild !.firstChild !.data).toEqual({data: 'resolver2_value'});
+ });
});
});
});
@@ -109,12 +356,14 @@ describe('Router', () => {
function checkResolveData(
future: RouterStateSnapshot, curr: RouterStateSnapshot, injector: any, check: any): void {
const p = new PreActivation(future, curr, injector);
- p.traverse(new ChildrenOutletContexts());
+ p.initalize(new ChildrenOutletContexts());
p.resolveData().subscribe(check, (e) => { throw e; });
}
-function createActivatedRouteSnapshot(cmp: string, extra: any = {}): ActivatedRouteSnapshot {
- return new ActivatedRouteSnapshot(
- [], {}, null, null, null, null, cmp, {}, null, -1,
- extra.resolve);
+function checkGuards(
+ future: RouterStateSnapshot, curr: RouterStateSnapshot, injector: any,
+ check: (result: boolean) => void): void {
+ const p = new PreActivation(future, curr, injector);
+ p.initalize(new ChildrenOutletContexts());
+ p.checkGuards().subscribe(check, (e) => { throw e; });
}
diff --git a/packages/tsc-wrapped/src/collector.ts b/packages/tsc-wrapped/src/collector.ts
index f6d117d200..3ad18800fc 100644
--- a/packages/tsc-wrapped/src/collector.ts
+++ b/packages/tsc-wrapped/src/collector.ts
@@ -26,7 +26,7 @@ const isStatic = (ts as any).ModifierFlags ?
/**
* A set of collector options to use when collecting metadata.
*/
-export class CollectorOptions {
+export interface CollectorOptions {
/**
* Version of the metadata to collect.
*/
@@ -42,6 +42,11 @@ export class CollectorOptions {
* Do not simplify invalid expressions.
*/
verboseInvalidExpression?: boolean;
+
+ /**
+ * An expression substitution callback.
+ */
+ substituteExpression?: (value: MetadataValue, node: ts.Node) => MetadataValue;
}
/**
@@ -54,12 +59,25 @@ export class MetadataCollector {
* Returns a JSON.stringify friendly form describing the decorators of the exported classes from
* the source file that is expected to correspond to a module.
*/
- public getMetadata(sourceFile: ts.SourceFile, strict: boolean = false): ModuleMetadata|undefined {
+ public getMetadata(
+ sourceFile: ts.SourceFile, strict: boolean = false,
+ substituteExpression?: (value: MetadataValue, node: ts.Node) => MetadataValue): ModuleMetadata
+ |undefined {
const locals = new Symbols(sourceFile);
const nodeMap =
new Map();
- const evaluator = new Evaluator(locals, nodeMap, this.options);
+ const composedSubstituter = substituteExpression && this.options.substituteExpression ?
+ (value: MetadataValue, node: ts.Node) =>
+ this.options.substituteExpression !(substituteExpression(value, node), node) :
+ substituteExpression;
+ const evaluatorOptions = substituteExpression ?
+ {...this.options, substituteExpression: composedSubstituter} :
+ this.options;
let metadata: {[name: string]: MetadataValue | ClassMetadata | FunctionMetadata}|undefined;
+ const evaluator = new Evaluator(locals, nodeMap, evaluatorOptions, (name, value) => {
+ if (!metadata) metadata = {};
+ metadata[name] = value;
+ });
let exports: ModuleExportMetadata[]|undefined = undefined;
function objFromDecorator(decoratorNode: ts.Decorator): MetadataSymbolicExpression {
diff --git a/packages/tsc-wrapped/src/evaluator.ts b/packages/tsc-wrapped/src/evaluator.ts
index b77f55c027..27e5afc9af 100644
--- a/packages/tsc-wrapped/src/evaluator.ts
+++ b/packages/tsc-wrapped/src/evaluator.ts
@@ -9,9 +9,10 @@
import * as ts from 'typescript';
import {CollectorOptions} from './collector';
-import {MetadataEntry, MetadataError, MetadataImportedSymbolReferenceExpression, MetadataSymbolicCallExpression, MetadataValue, isMetadataError, isMetadataModuleReferenceExpression, isMetadataSymbolicReferenceExpression, isMetadataSymbolicSpreadExpression} from './schema';
+import {MetadataEntry, MetadataError, MetadataImportedSymbolReferenceExpression, MetadataSymbolicCallExpression, MetadataValue, isMetadataError, isMetadataGlobalReferenceExpression, isMetadataModuleReferenceExpression, isMetadataSymbolicReferenceExpression, isMetadataSymbolicSpreadExpression} from './schema';
import {Symbols} from './symbols';
+
// In TypeScript 2.1 the spread element kind was renamed.
const spreadElementSyntaxKind: ts.SyntaxKind =
(ts.SyntaxKind as any).SpreadElement || (ts.SyntaxKind as any).SpreadElementExpression;
@@ -104,7 +105,8 @@ export function errorSymbol(
export class Evaluator {
constructor(
private symbols: Symbols, private nodeMap: Map,
- private options: CollectorOptions = {}) {}
+ private options: CollectorOptions = {},
+ private recordExport?: (name: string, value: MetadataValue) => void) {}
nameOf(node: ts.Node|undefined): string|MetadataError {
if (node && node.kind == ts.SyntaxKind.Identifier) {
@@ -232,7 +234,14 @@ export class Evaluator {
const t = this;
let error: MetadataError|undefined;
- function recordEntry(entry: T, node: ts.Node): T {
+ function recordEntry(entry: MetadataValue, node: ts.Node): MetadataValue {
+ if (t.options.substituteExpression) {
+ const newEntry = t.options.substituteExpression(entry, node);
+ if (t.recordExport && newEntry != entry && isMetadataGlobalReferenceExpression(newEntry)) {
+ t.recordExport(newEntry.name, entry);
+ }
+ entry = newEntry;
+ }
t.nodeMap.set(entry, node);
return entry;
}
@@ -283,7 +292,7 @@ export class Evaluator {
if (this.options.quotedNames && quoted.length) {
obj['$quoted$'] = quoted;
}
- return obj;
+ return recordEntry(obj, node);
case ts.SyntaxKind.ArrayLiteralExpression:
let arr: MetadataValue[] = [];
ts.forEachChild(node, child => {
@@ -308,7 +317,7 @@ export class Evaluator {
arr.push(value);
});
if (error) return error;
- return arr;
+ return recordEntry(arr, node);
case spreadElementSyntaxKind:
let spreadExpression = this.evaluateNode((node as any).expression);
return recordEntry({__symbolic: 'spread', expression: spreadExpression}, node);
diff --git a/packages/tsc-wrapped/test/collector.spec.ts b/packages/tsc-wrapped/test/collector_spec.ts
similarity index 97%
rename from packages/tsc-wrapped/test/collector.spec.ts
rename to packages/tsc-wrapped/test/collector_spec.ts
index b22f3d1f5b..7f95df423c 100644
--- a/packages/tsc-wrapped/test/collector.spec.ts
+++ b/packages/tsc-wrapped/test/collector_spec.ts
@@ -9,7 +9,7 @@
import * as ts from 'typescript';
import {MetadataCollector} from '../src/collector';
-import {ClassMetadata, ConstructorMetadata, MetadataEntry, ModuleMetadata, isClassMetadata} from '../src/schema';
+import {ClassMetadata, ConstructorMetadata, MetadataEntry, ModuleMetadata, isClassMetadata, isMetadataGlobalReferenceExpression} from '../src/schema';
import {Directory, Host, expectValidSources} from './typescript.mocks';
@@ -939,6 +939,44 @@ describe('Collector', () => {
});
});
+ describe('substitutions', () => {
+ const lambdaTemp = 'lambdaTemp';
+
+ it('should be able to substitute a lambda', () => {
+ const source = createSource(`
+ const b = 1;
+ export const a = () => b;
+ `);
+ const metadata = collector.getMetadata(source, /* strict */ false, (value, node) => {
+ if (node.kind === ts.SyntaxKind.ArrowFunction) {
+ return {__symbolic: 'reference', name: lambdaTemp};
+ }
+ return value;
+ });
+ expect(metadata !.metadata['a']).toEqual({__symbolic: 'reference', name: lambdaTemp});
+ });
+
+ it('should compose substitution functions', () => {
+ const collector = new MetadataCollector({
+ substituteExpression: (value, node) => isMetadataGlobalReferenceExpression(value) &&
+ value.name == lambdaTemp ?
+ {__symbolic: 'reference', name: value.name + '2'} :
+ value
+ });
+ const source = createSource(`
+ const b = 1;
+ export const a = () => b;
+ `);
+ const metadata = collector.getMetadata(source, /* strict */ false, (value, node) => {
+ if (node.kind === ts.SyntaxKind.ArrowFunction) {
+ return {__symbolic: 'reference', name: lambdaTemp};
+ }
+ return value;
+ });
+ expect(metadata !.metadata['a']).toEqual({__symbolic: 'reference', name: lambdaTemp + '2'});
+ });
+ });
+
function override(fileName: string, content: string) {
host.overrideFile(fileName, content);
host.addFile(fileName);
diff --git a/packages/tsc-wrapped/test/evaluator.spec.ts b/packages/tsc-wrapped/test/evaluator_spec.ts
similarity index 91%
rename from packages/tsc-wrapped/test/evaluator.spec.ts
rename to packages/tsc-wrapped/test/evaluator_spec.ts
index 2205a27120..b8aec84a62 100644
--- a/packages/tsc-wrapped/test/evaluator.spec.ts
+++ b/packages/tsc-wrapped/test/evaluator_spec.ts
@@ -223,6 +223,45 @@ describe('Evaluator', () => {
expect(evaluator.evaluateNode(expr.initializer !))
.toEqual({__symbolic: 'new', expression: {__symbolic: 'reference', name: 'f'}});
});
+
+ describe('with substitution', () => {
+ let evaluator: Evaluator;
+ const lambdaTemp = 'lambdaTemp';
+
+ beforeEach(() => {
+ evaluator = new Evaluator(symbols, new Map(), {
+ substituteExpression: (value, node) => {
+ if (node.kind == ts.SyntaxKind.ArrowFunction) {
+ return {__symbolic: 'reference', name: lambdaTemp};
+ }
+ return value;
+ }
+ });
+ });
+
+ it('should be able to substitute a lambda with a reference', () => {
+ const source = sourceFileOf(`
+ var b = 1;
+ export var a = () => b;
+ `);
+ const expr = findVar(source, 'a');
+ expect(evaluator.evaluateNode(expr !.initializer !))
+ .toEqual({__symbolic: 'reference', name: lambdaTemp});
+ });
+
+ it('should be able to substitute a lambda in an expression', () => {
+ const source = sourceFileOf(`
+ var b = 1;
+ export var a = [
+ { provide: 'someValue': useFactory: () => b }
+ ];
+ `);
+ const expr = findVar(source, 'a');
+ expect(evaluator.evaluateNode(expr !.initializer !)).toEqual([
+ {provide: 'someValue', useFactory: {__symbolic: 'reference', name: lambdaTemp}}
+ ]);
+ });
+ });
});
function sourceFileOf(text: string): ts.SourceFile {
diff --git a/packages/tsc-wrapped/test/main.spec.ts b/packages/tsc-wrapped/test/main_spec.ts
similarity index 100%
rename from packages/tsc-wrapped/test/main.spec.ts
rename to packages/tsc-wrapped/test/main_spec.ts
diff --git a/packages/tsc-wrapped/test/symbols.spec.ts b/packages/tsc-wrapped/test/symbols_spec.ts
similarity index 100%
rename from packages/tsc-wrapped/test/symbols.spec.ts
rename to packages/tsc-wrapped/test/symbols_spec.ts
diff --git a/packages/tsc-wrapped/test/tsc.spec.ts b/packages/tsc-wrapped/test/tsc_spec.ts
similarity index 100%
rename from packages/tsc-wrapped/test/tsc.spec.ts
rename to packages/tsc-wrapped/test/tsc_spec.ts
diff --git a/packages/tsc-wrapped/test/typescript.mocks.ts b/packages/tsc-wrapped/test/typescript.mocks.ts
index fbd35bdd5f..aae1a1dd8a 100644
--- a/packages/tsc-wrapped/test/typescript.mocks.ts
+++ b/packages/tsc-wrapped/test/typescript.mocks.ts
@@ -1,3 +1,11 @@
+/**
+ * @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 * as fs from 'fs';
import * as ts from 'typescript';
@@ -70,7 +78,7 @@ export class MockNode implements ts.Node {
public kind: ts.SyntaxKind = ts.SyntaxKind.Identifier, public flags: ts.NodeFlags = 0,
public pos: number = 0, public end: number = 0) {}
getSourceFile(): ts.SourceFile { return null as any as ts.SourceFile; }
- getChildCount(sourceFile?: ts.SourceFile): number { return 0 }
+ getChildCount(sourceFile?: ts.SourceFile): number { return 0; }
getChildAt(index: number, sourceFile?: ts.SourceFile): ts.Node { return null as any as ts.Node; }
getChildren(sourceFile?: ts.SourceFile): ts.Node[] { return []; }
getStart(sourceFile?: ts.SourceFile): number { return 0; }
@@ -90,12 +98,14 @@ export class MockNode implements ts.Node {
export class MockIdentifier extends MockNode implements ts.Identifier {
public text: string;
+ // tslint:disable
public _primaryExpressionBrand: any;
public _memberExpressionBrand: any;
public _leftHandSideExpressionBrand: any;
public _incrementExpressionBrand: any;
public _unaryExpressionBrand: any;
public _expressionBrand: any;
+ // tslint:enable
constructor(
public name: string, public kind: ts.SyntaxKind.Identifier = ts.SyntaxKind.Identifier,
@@ -106,6 +116,7 @@ export class MockIdentifier extends MockNode implements ts.Identifier {
}
export class MockVariableDeclaration extends MockNode implements ts.VariableDeclaration {
+ // tslint:disable-next-line
public _declarationBrand: any;
constructor(
@@ -130,7 +141,7 @@ export class MockSymbol implements ts.Symbol {
getDeclarations(): ts.Declaration[] { return [this.node]; }
getDocumentationComment(): ts.SymbolDisplayPart[] { return []; }
// TODO(vicb): removed in TS 2.2
- getJsDocTags(): any[]{return []};
+ getJsDocTags(): any[] { return []; }
static of (name: string): MockSymbol { return new MockSymbol(name); }
}
@@ -139,6 +150,7 @@ export function expectNoDiagnostics(diagnostics: ts.Diagnostic[]) {
for (const diagnostic of diagnostics) {
const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
const {line, character} = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
+ // tslint:disable-next-line:no-console
console.log(`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`);
}
expect(diagnostics.length).toBe(0);
@@ -159,7 +171,7 @@ export function allChildren(node: ts.Node, cb: (node: ts.Node) => T): T {
return result;
}
return allChildren(child, cb);
- })
+ });
}
export function findClass(sourceFile: ts.SourceFile, name: string): ts.ClassDeclaration|undefined {
diff --git a/packages/upgrade/src/common/downgrade_component_adapter.ts b/packages/upgrade/src/common/downgrade_component_adapter.ts
index 06ead26b3b..e00605c3ab 100644
--- a/packages/upgrade/src/common/downgrade_component_adapter.ts
+++ b/packages/upgrade/src/common/downgrade_component_adapter.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {ApplicationRef, ChangeDetectorRef, ComponentFactory, ComponentRef, EventEmitter, Injector, OnChanges, ReflectiveInjector, SimpleChange, SimpleChanges, Type} from '@angular/core';
+import {ApplicationRef, ChangeDetectorRef, ComponentFactory, ComponentRef, EventEmitter, Injector, OnChanges, SimpleChange, SimpleChanges, Type} from '@angular/core';
import * as angular from './angular1';
import {PropertyBinding} from './component_info';
@@ -57,8 +57,8 @@ export class DowngradeComponentAdapter {
}
createComponent(projectableNodes: Node[][]) {
- const childInjector = ReflectiveInjector.resolveAndCreate(
- [{provide: $SCOPE, useValue: this.componentScope}], this.parentInjector);
+ const childInjector =
+ Injector.create([{provide: $SCOPE, useValue: this.componentScope}], this.parentInjector);
this.componentRef =
this.componentFactory.create(childInjector, projectableNodes, this.element[0]);
diff --git a/packages/upgrade/src/dynamic/upgrade_adapter.ts b/packages/upgrade/src/dynamic/upgrade_adapter.ts
index fe437cd1b4..f4e744007b 100644
--- a/packages/upgrade/src/dynamic/upgrade_adapter.ts
+++ b/packages/upgrade/src/dynamic/upgrade_adapter.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {Compiler, CompilerOptions, Directive, Injector, NgModule, NgModuleRef, NgZone, Provider, Testability, Type} from '@angular/core';
+import {Compiler, CompilerOptions, Directive, Injector, NgModule, NgModuleRef, NgZone, StaticProvider, Testability, Type} from '@angular/core';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import * as angular from '../common/angular1';
@@ -110,7 +110,7 @@ export class UpgradeAdapter {
* @internal
*/
private ng1ComponentsToBeUpgraded: {[name: string]: UpgradeNg1ComponentAdapterBuilder} = {};
- private upgradedProviders: Provider[] = [];
+ private upgradedProviders: StaticProvider[] = [];
private ngZone: NgZone;
private ng1Module: angular.IModule;
private moduleRef: NgModuleRef|null = null;
diff --git a/packages/upgrade/src/static/angular1_providers.ts b/packages/upgrade/src/static/angular1_providers.ts
index 36c8187cae..3481f96e8a 100644
--- a/packages/upgrade/src/static/angular1_providers.ts
+++ b/packages/upgrade/src/static/angular1_providers.ts
@@ -43,7 +43,7 @@ export const angular1Providers = [
// > Metadata collected contains an error that will be reported at runtime:
// > Function calls are not supported.
// > Consider replacing the function or lambda with a reference to an exported function
- {provide: '$injector', useFactory: injectorFactory},
+ {provide: '$injector', useFactory: injectorFactory, deps: []},
{provide: '$rootScope', useFactory: rootScopeFactory, deps: ['$injector']},
{provide: '$compile', useFactory: compileFactory, deps: ['$injector']},
{provide: '$parse', useFactory: parseFactory, deps: ['$injector']}
diff --git a/packages/upgrade/src/static/downgrade_module.ts b/packages/upgrade/src/static/downgrade_module.ts
index 8a82075384..dcd0d5fb79 100644
--- a/packages/upgrade/src/static/downgrade_module.ts
+++ b/packages/upgrade/src/static/downgrade_module.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {Injector, NgModuleFactory, NgModuleRef, Provider} from '@angular/core';
+import {Injector, NgModuleFactory, NgModuleRef, StaticProvider} from '@angular/core';
import {platformBrowser} from '@angular/platform-browser';
import * as angular from '../common/angular1';
@@ -20,11 +20,11 @@ import {NgAdapterInjector} from './util';
/** @experimental */
export function downgradeModule(
moduleFactoryOrBootstrapFn: NgModuleFactory|
- ((extraProviders: Provider[]) => Promise>)): string {
+ ((extraProviders: StaticProvider[]) => Promise>)): string {
const LAZY_MODULE_NAME = UPGRADE_MODULE_NAME + '.lazy';
const bootstrapFn = isFunction(moduleFactoryOrBootstrapFn) ?
moduleFactoryOrBootstrapFn :
- (extraProviders: Provider[]) =>
+ (extraProviders: StaticProvider[]) =>
platformBrowser(extraProviders).bootstrapModuleFactory(moduleFactoryOrBootstrapFn);
let injector: Injector;
diff --git a/packages/upgrade/test/static/integration/downgrade_module_spec.ts b/packages/upgrade/test/static/integration/downgrade_module_spec.ts
index 589d3b759e..4544e5c7b7 100644
--- a/packages/upgrade/test/static/integration/downgrade_module_spec.ts
+++ b/packages/upgrade/test/static/integration/downgrade_module_spec.ts
@@ -6,10 +6,11 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {Component, Inject, Injector, Input, NgModule, NgZone, OnChanges, Provider, destroyPlatform} from '@angular/core';
+import {Component, Inject, Injector, Input, NgModule, NgZone, OnChanges, StaticProvider, destroyPlatform} from '@angular/core';
import {async, fakeAsync, tick} from '@angular/core/testing';
import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
+import {browserDetection} from '@angular/platform-browser/testing/src/browser_util';
import * as angular from '@angular/upgrade/src/common/angular1';
import {$ROOT_SCOPE, INJECTOR_KEY, LAZY_MODULE_REF} from '@angular/upgrade/src/common/constants';
import {LazyModuleRef} from '@angular/upgrade/src/common/util';
@@ -45,7 +46,7 @@ export function main() {
ngDoBootstrap() {}
}
- const bootstrapFn = (extraProviders: Provider[]) =>
+ const bootstrapFn = (extraProviders: StaticProvider[]) =>
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
const lazyModuleName = downgradeModule(bootstrapFn);
const ng1Module =
@@ -107,7 +108,7 @@ export function main() {
ngDoBootstrap() {}
}
- const bootstrapFn = (extraProviders: Provider[]) =>
+ const bootstrapFn = (extraProviders: StaticProvider[]) =>
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
const lazyModuleName = downgradeModule(bootstrapFn);
const ng1Module =
@@ -151,7 +152,7 @@ export function main() {
ngDoBootstrap() {}
}
- const bootstrapFn = (extraProviders: Provider[]) =>
+ const bootstrapFn = (extraProviders: StaticProvider[]) =>
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
const lazyModuleName = downgradeModule(bootstrapFn);
const ng1Module =
@@ -190,7 +191,7 @@ export function main() {
ngDoBootstrap() {}
}
- const bootstrapFn = (extraProviders: Provider[]) =>
+ const bootstrapFn = (extraProviders: StaticProvider[]) =>
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
const lazyModuleName = downgradeModule(bootstrapFn);
const ng1Module =
@@ -244,7 +245,7 @@ export function main() {
ngDoBootstrap() {}
}
- const bootstrapFn = (extraProviders: Provider[]) =>
+ const bootstrapFn = (extraProviders: StaticProvider[]) =>
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
const lazyModuleName = downgradeModule(bootstrapFn);
const ng1Module =
@@ -298,7 +299,8 @@ export function main() {
ngDoBootstrap() {}
}
- const bootstrapFn = (extraProviders: Provider[]) =>
+ const tickDelay = browserDetection.isIE ? 100 : 0;
+ const bootstrapFn = (extraProviders: StaticProvider[]) =>
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
const lazyModuleName = downgradeModule(bootstrapFn);
const ng1Module =
@@ -311,8 +313,8 @@ export function main() {
const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService;
$rootScope.$apply('showNg2 = true');
- tick(); // Wait for the module to be bootstrapped and `$evalAsync()` to propagate
- // inputs.
+ tick(tickDelay); // Wait for the module to be bootstrapped and `$evalAsync()` to
+ // propagate inputs.
const injector = ($injector.get(LAZY_MODULE_REF) as LazyModuleRef).injector !;
const injectorGet = injector.get;
@@ -327,7 +329,7 @@ export function main() {
expect(element.textContent).toBe('');
$rootScope.$apply('showNg2 = true');
- tick(); // Wait for `$evalAsync()` to propagate inputs.
+ tick(tickDelay); // Wait for `$evalAsync()` to propagate inputs.
expect(element.textContent).toBe('Count: 2 | In the zone: true');
$rootScope.$destroy();
@@ -353,7 +355,7 @@ export function main() {
ngDoBootstrap() {}
}
- const bootstrapFn = (extraProviders: Provider[]) =>
+ const bootstrapFn = (extraProviders: StaticProvider[]) =>
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
const lazyModuleName = downgradeModule(bootstrapFn);
const ng1Module =
diff --git a/packages/upgrade/test/static/integration/upgrade_component_spec.ts b/packages/upgrade/test/static/integration/upgrade_component_spec.ts
index a2d31286a3..33f3a8906c 100644
--- a/packages/upgrade/test/static/integration/upgrade_component_spec.ts
+++ b/packages/upgrade/test/static/integration/upgrade_component_spec.ts
@@ -1028,7 +1028,10 @@ export function main() {
// Define `ng1Component`
const ng1ComponentA: angular.IComponent = {template: 'ng1A()'};
const ng1DirectiveB: angular.IDirective = {
- compile: tElem => grandParentNodeName = tElem.parent !().parent !()[0].nodeName
+ compile: tElem => {
+ grandParentNodeName = tElem.parent !().parent !()[0].nodeName;
+ return {};
+ }
};
// Define `Ng1ComponentAFacade`
diff --git a/scripts/ci/deploy.sh b/scripts/ci/deploy.sh
index c5a85f576b..e88bac203a 100755
--- a/scripts/ci/deploy.sh
+++ b/scripts/ci/deploy.sh
@@ -28,6 +28,7 @@ fi
case ${CI_MODE} in
+
e2e)
# Don't deploy if this is a PR build
if [[ ${TRAVIS_PULL_REQUEST} != "false" ]]; then
@@ -39,34 +40,13 @@ case ${CI_MODE} in
${thisDir}/publish-build-artifacts.sh
travisFoldEnd "deploy.packages"
;;
+
aio)
- # Only deploy if this not a PR. PRs are deployed early in `build.sh`.
- if [[ $TRAVIS_PULL_REQUEST == "false" ]]; then
-
- # Don't deploy if this build is not for master or the stable branch.
- if [[ $TRAVIS_BRANCH != "master" ]] && [[ $TRAVIS_BRANCH != $STABLE_BRANCH ]]; then
- echo "Skipping deploy because this build is not for master or the stable branch ($STABLE_BRANCH)."
- exit 0
- fi
-
- travisFoldStart "deploy.aio"
- (
- cd ${TRAVIS_BUILD_DIR}/aio
-
- if [[ $TRAVIS_BRANCH == $STABLE_BRANCH ]]; then
- # This is upstream : Deploy to production.
- travisFoldStart "deploy.aio.production"
- yarn deploy-production
- travisFoldEnd "deploy.aio.production"
- else
- # This is upstream master: Deploy to staging.
- travisFoldStart "deploy.aio.staging"
- yarn deploy-staging
- travisFoldEnd "deploy.aio.staging"
- fi
- )
- travisFoldEnd "deploy.aio"
-
- fi
+ travisFoldStart "deploy.aio"
+ (
+ cd ${TRAVIS_BUILD_DIR}/aio
+ yarn deploy-production
+ )
+ travisFoldEnd "deploy.aio"
;;
esac
diff --git a/scripts/ci/install.sh b/scripts/ci/install.sh
index f335e28be9..45dce806e2 100755
--- a/scripts/ci/install.sh
+++ b/scripts/ci/install.sh
@@ -42,7 +42,7 @@ if [[ ${CI_MODE} != "aio" && ${CI_MODE} != 'docs_test' ]]; then
fi
-if [[ ${TRAVIS} && (${CI_MODE} == "e2e" || ${CI_MODE} == "e2e_2" || ${CI_MODE} == "aio" || ${CI_MODE} == "aio_e2e" || ${CI_MODE} == "docs_test") ]]; then
+if [[ ${TRAVIS} && (${CI_MODE} == "e2e" || ${CI_MODE} == "e2e_2" || ${CI_MODE} == "aio" || ${CI_MODE} == "aio_e2e" || ${CI_MODE} == "aio_tools_test") ]]; then
# Install version of yarn that we are locked against
travisFoldStart "install-yarn"
curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version "${YARN_VERSION}"
@@ -50,7 +50,7 @@ if [[ ${TRAVIS} && (${CI_MODE} == "e2e" || ${CI_MODE} == "e2e_2" || ${CI_MODE} =
fi
-if [[ ${TRAVIS} && (${CI_MODE} == "aio" || ${CI_MODE} == "aio_e2e" || ${CI_MODE} == "docs_test") ]]; then
+if [[ ${TRAVIS} && (${CI_MODE} == "aio" || ${CI_MODE} == "aio_e2e" || ${CI_MODE} == "aio_tools_test") ]]; then
# angular.io: Install all yarn dependencies according to angular.io/yarn.lock
travisFoldStart "yarn-install.aio"
(
@@ -75,18 +75,11 @@ if [[ ${TRAVIS} && ${CI_MODE} == "bazel" ]]; then
travisFoldEnd "bazel-install"
fi
-# Install Chromium
-if [[ ${CI_MODE} == "js" || ${CI_MODE} == "e2e" || ${CI_MODE} == "e2e_2" || ${CI_MODE} == "aio" || ${CI_MODE} == "aio_e2e" ]]; then
- travisFoldStart "install-chromium"
- (
- # Start xvfb for local Chrome used for testing
- if [[ ${TRAVIS} ]]; then
- travisFoldStart "install-chromium.xvfb-start"
- sh -e /etc/init.d/xvfb start
- travisFoldEnd "install-chromium.xvfb-start"
- fi
- )
- travisFoldEnd "install-chromium"
+# Start xvfb for local Chrome testing
+if [[ ${TRAVIS} && (${CI_MODE} == "js" || ${CI_MODE} == "e2e" || ${CI_MODE} == "e2e_2" || ${CI_MODE} == "aio" || ${CI_MODE} == "aio_e2e") ]]; then
+ travisFoldStart "xvfb-start"
+ sh -e /etc/init.d/xvfb start
+ travisFoldEnd "xvfb-start"
fi
diff --git a/scripts/ci/test-docs.sh b/scripts/ci/test-aio-tools.sh
similarity index 92%
rename from scripts/ci/test-docs.sh
rename to scripts/ci/test-aio-tools.sh
index 74d8a90593..cf608c4312 100755
--- a/scripts/ci/test-docs.sh
+++ b/scripts/ci/test-aio-tools.sh
@@ -10,6 +10,6 @@ source ${thisDir}/_travis-fold.sh
travisFoldStart "test.docs"
(
cd ${PROJECT_ROOT}/aio
- yarn docs-test
+ yarn tools-test
)
travisFoldEnd "test.docs"
diff --git a/scripts/ci/test.sh b/scripts/ci/test.sh
index d8f08932b3..a72da1aca7 100755
--- a/scripts/ci/test.sh
+++ b/scripts/ci/test.sh
@@ -37,8 +37,8 @@ case ${CI_MODE} in
browserstack_optional)
${thisDir}/test-browserstack.sh
;;
- docs_test)
- ${thisDir}/test-docs.sh
+ aio_tools_test)
+ ${thisDir}/test-aio-tools.sh
;;
aio)
${thisDir}/test-aio.sh
diff --git a/tools/gulp-tasks/lint.js b/tools/gulp-tasks/lint.js
index cacf7e0d00..6a1496327b 100644
--- a/tools/gulp-tasks/lint.js
+++ b/tools/gulp-tasks/lint.js
@@ -9,9 +9,13 @@ module.exports = (gulp) => () => {
// todo(vicb): add .js files when supported
// see https://github.com/palantir/tslint/pull/1515
'./modules/**/*.ts',
+ './packages/**/*.ts',
'./tools/**/*.ts',
'./*.ts',
+ // Ignore node_modules directories
+ '!**/node_modules/**',
+
// Ignore TypeScript mocks because it's not managed by us
'!./tools/@angular/tsc-wrapped/test/typescript.mocks.ts',
diff --git a/tools/public_api_guard/animations/animations.d.ts b/tools/public_api_guard/animations/animations.d.ts
index d395e86ebf..45dd78afa3 100644
--- a/tools/public_api_guard/animations/animations.d.ts
+++ b/tools/public_api_guard/animations/animations.d.ts
@@ -151,6 +151,11 @@ export interface AnimationStaggerMetadata extends AnimationMetadata {
/** @experimental */
export interface AnimationStateMetadata extends AnimationMetadata {
name: string;
+ options?: {
+ params: {
+ [name: string]: any;
+ };
+ };
styles: AnimationStyleMetadata;
}
@@ -221,7 +226,11 @@ export declare function sequence(steps: AnimationMetadata[], options?: Animation
export declare function stagger(timings: string | number, animation: AnimationMetadata | AnimationMetadata[]): AnimationStaggerMetadata;
/** @experimental */
-export declare function state(name: string, styles: AnimationStyleMetadata): AnimationStateMetadata;
+export declare function state(name: string, styles: AnimationStyleMetadata, options?: {
+ params: {
+ [name: string]: any;
+ };
+}): AnimationStateMetadata;
/** @experimental */
export declare function style(tokens: '*' | {
diff --git a/tools/public_api_guard/core/core.d.ts b/tools/public_api_guard/core/core.d.ts
index 609b804790..2487babe7c 100644
--- a/tools/public_api_guard/core/core.d.ts
+++ b/tools/public_api_guard/core/core.d.ts
@@ -209,7 +209,7 @@ export declare type CompilerOptions = {
/** @deprecated */ useDebug?: boolean;
useJit?: boolean;
defaultEncapsulation?: ViewEncapsulation;
- providers?: any[];
+ providers?: StaticProvider[];
missingTranslation?: MissingTranslationStrategy;
enableLegacyTemplate?: boolean;
};
@@ -289,7 +289,7 @@ export interface ContentChildrenDecorator {
export declare function createPlatform(injector: Injector): PlatformRef;
/** @experimental */
-export declare function createPlatformFactory(parentPlatformFactory: ((extraProviders?: Provider[]) => PlatformRef) | null, name: string, providers?: Provider[]): (extraProviders?: Provider[]) => PlatformRef;
+export declare function createPlatformFactory(parentPlatformFactory: ((extraProviders?: StaticProvider[]) => PlatformRef) | null, name: string, providers?: StaticProvider[]): (extraProviders?: StaticProvider[]) => PlatformRef;
/** @stable */
export declare const CUSTOM_ELEMENTS_SCHEMA: SchemaMetadata;
@@ -491,6 +491,7 @@ export declare abstract class Injector {
/** @deprecated */ abstract get(token: any, notFoundValue?: any): any;
static NULL: Injector;
static THROW_IF_NOT_FOUND: Object;
+ static create(providers: StaticProvider[], parent?: Injector): Injector;
}
/** @stable */
@@ -536,7 +537,7 @@ export declare class IterableDiffers {
constructor(factories: IterableDifferFactory[]);
find(iterable: any): IterableDifferFactory;
static create(factories: IterableDifferFactory[], parent?: IterableDiffers): IterableDiffers;
- static extend(factories: IterableDifferFactory[]): Provider;
+ static extend(factories: IterableDifferFactory[]): StaticProvider;
}
/** @deprecated */
@@ -579,7 +580,7 @@ export declare class KeyValueDiffers {
constructor(factories: KeyValueDifferFactory[]);
find(kv: any): KeyValueDifferFactory;
static create(factories: KeyValueDifferFactory[], parent?: KeyValueDiffers): KeyValueDiffers;
- static extend(factories: KeyValueDifferFactory[]): Provider;
+ static extend(factories: KeyValueDifferFactory[]): StaticProvider;
}
/** @experimental */
@@ -714,7 +715,7 @@ export declare const PLATFORM_ID: InjectionToken