Compare commits
17 Commits
11.0.0-rc.
...
4.1.1
Author | SHA1 | Date | |
---|---|---|---|
1f5dce2128 | |||
14e7e43ad8 | |||
54d4b893fd | |||
4670cf51cc | |||
dd4e501999 | |||
9124994849 | |||
4fbc61469f | |||
8a883f24f6 | |||
07cef367ac | |||
c060110695 | |||
93ff3166ab | |||
85a1b54c6e | |||
acf83b90bc | |||
f66e59ebe4 | |||
d932e724ab | |||
dcaa11a88b | |||
427d63a422 |
16
CHANGELOG.md
16
CHANGELOG.md
@ -1,3 +1,19 @@
|
||||
<a name="4.1.1"></a>
|
||||
## [4.1.1](https://github.com/angular/angular/compare/4.1.0...4.1.1) (2017-05-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **core**: strictNullCheck support. ([#16389](https://github.com/angular/angular/issues/16389)) ([#16389](https://github.com/angular/angular/issues/16389)) ([427d63a](https://github.com/angular/angular/commit/427d63a)), closes [#16357](https://github.com/angular/angular/issues/16357)
|
||||
* **core:** allow directives to inject the component’s `ChangeDetectorRef`. ([#16394](https://github.com/angular/angular/issues/16394)) ([f66e59e](https://github.com/angular/angular/commit/f66e59e)), closes [#12816](https://github.com/angular/angular/issues/12816)
|
||||
* **core:** allow to detach `OnPush` components ([#16394](https://github.com/angular/angular/issues/16394)) ([acf83b9](https://github.com/angular/angular/commit/acf83b9)), closes [#9720](https://github.com/angular/angular/issues/9720)
|
||||
* **core:** don’t set `ng-version` for dynamically created components ([#16394](https://github.com/angular/angular/issues/16394)) ([85a1b54](https://github.com/angular/angular/commit/85a1b54)), closes [#15880](https://github.com/angular/angular/issues/15880)
|
||||
* **core:** don’t stop change detection because of errors ([07cef36](https://github.com/angular/angular/commit/07cef36)), closes [#9531](https://github.com/angular/angular/issues/9531) [#2413](https://github.com/angular/angular/issues/2413) [#15925](https://github.com/angular/angular/issues/15925)
|
||||
* **language-service:** remove asserts for non-null expressions ([#16422](https://github.com/angular/angular/issues/16422)) ([c060110](https://github.com/angular/angular/commit/c060110))
|
||||
* **upgrade:** initialize all inputs in time for `ngOnChanges()` ([dd4e501](https://github.com/angular/angular/commit/dd4e501)), closes [#16212](https://github.com/angular/angular/issues/16212)
|
||||
|
||||
|
||||
|
||||
<a name="4.1.0"></a>
|
||||
# [4.1.0](https://github.com/angular/angular/compare/4.1.0-rc.0...4.1.0) (2017-04-26)
|
||||
|
||||
|
1
integration/.gitignore
vendored
1
integration/.gitignore
vendored
@ -1,4 +1,3 @@
|
||||
/rxjs/
|
||||
built/
|
||||
dist/
|
||||
vendor/
|
||||
|
@ -32,13 +32,7 @@ Angular's `node_modules` is installed.
|
||||
|
||||
## Running integration tests
|
||||
|
||||
The first time you run the tests, you'll need some setup:
|
||||
|
||||
```shell
|
||||
$ ./integration/build_rxjs_es6.sh
|
||||
```
|
||||
|
||||
Now you can iterate on the tests by keeping the dist folder up-to-date.
|
||||
You can iterate on the tests by keeping the dist folder up-to-date.
|
||||
See the `package.json` of the test(s) you're debugging, to see which dist/ folders they install from.
|
||||
Then run the right `tsc --watch` command to keep those dist folders up-to-date, for example:
|
||||
|
||||
|
@ -1,19 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# This script builds rxjs from source, with an ES6 target, and with tsickle turned on.
|
||||
# We need to do this until we work with the RxJS team to get a distribution that works
|
||||
# with Closure Compiler.
|
||||
# Note that in nodejs, we still run the standard RxJS distribution. This one is only
|
||||
# used for the bundle that targets a browser runtime.
|
||||
# TODO(alexeagle): discuss with Jay Phelps once we have a recommendation
|
||||
|
||||
set -e -o pipefail
|
||||
|
||||
cd `dirname $0`
|
||||
|
||||
rm -rf rxjs
|
||||
git clone https://github.com/ReactiveX/rxjs.git --depth=200
|
||||
git -C rxjs/ checkout 5.0.3
|
||||
cp rxjs.tsconfig.json rxjs/
|
||||
TSC="node --max-old-space-size=3000 ../dist/tools/@angular/tsc-wrapped/src/main"
|
||||
$TSC -p rxjs/rxjs.tsconfig.json
|
@ -1,53 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Build a folder using angular ES6 and the closure compiler
|
||||
|
||||
set -e -o pipefail
|
||||
|
||||
# The ES6 distro we built for rxjs works only in the browser, not in nodejs.
|
||||
# Since we installed rxjs in node_modules for ngc to use, we have to point
|
||||
# to the alternate distro when compiling with closure.
|
||||
rm -rf vendor
|
||||
mkdir vendor
|
||||
cp -pr ../rxjs/dist/es6 vendor/rxjs
|
||||
|
||||
CLOSURE_ARGS=(
|
||||
"--language_in=ES6_STRICT"
|
||||
"--language_out=ES5"
|
||||
"--compilation_level=ADVANCED_OPTIMIZATIONS"
|
||||
"--warning_level=QUIET"
|
||||
"--js_output_file=dist/bundle.js"
|
||||
"--create_source_map=%outname%.map"
|
||||
"--variable_renaming_report=dist/variable_renaming_report"
|
||||
"--property_renaming_report=dist/property_renaming_report"
|
||||
|
||||
# Don't include ES6 polyfills
|
||||
"--rewrite_polyfills=false"
|
||||
|
||||
# List of path prefixes to be removed from ES6 & CommonJS modules.
|
||||
"--js_module_root=node_modules/@angular/core"
|
||||
"--js_module_root=node_modules/@angular/common"
|
||||
"--js_module_root=node_modules/@angular/compiler"
|
||||
"--js_module_root=node_modules/@angular/platform-browser"
|
||||
"--js_module_root=vendor"
|
||||
|
||||
# Uncomment for easier debugging
|
||||
# "--formatting=PRETTY_PRINT"
|
||||
|
||||
node_modules/@angular/core/src/testability/testability.externs.js
|
||||
node_modules/zone.js/dist/zone.js
|
||||
$(find -L vendor/rxjs -name *.js)
|
||||
node_modules/@angular/core/@angular/core.js
|
||||
node_modules/@angular/common/@angular/common.js
|
||||
node_modules/@angular/compiler/@angular/compiler.js
|
||||
node_modules/@angular/platform-browser/@angular/platform-browser.js
|
||||
"built/src/*.js"
|
||||
"--entry_point=./built/src/main"
|
||||
)
|
||||
|
||||
java -jar node_modules/google-closure-compiler/compiler.jar $(echo ${CLOSURE_ARGS[*]})
|
||||
# gzip on Travis doesn't have --keep option so copy the original file first
|
||||
cp dist/bundle.js dist/bundle.tmp
|
||||
gzip -f dist/bundle.js
|
||||
mv dist/bundle.tmp dist/bundle.js
|
||||
ls -alH dist/bundle*
|
30
integration/hello_world__closure/closure.conf
Normal file
30
integration/hello_world__closure/closure.conf
Normal file
@ -0,0 +1,30 @@
|
||||
--compilation_level=ADVANCED_OPTIMIZATIONS
|
||||
--language_out=ES5
|
||||
--js_output_file=dist/bundle.js
|
||||
--output_manifest=dist/manifest.MF
|
||||
--variable_renaming_report=dist/variable_renaming_report
|
||||
--property_renaming_report=dist/property_renaming_report
|
||||
--create_source_map=%outname%.map
|
||||
|
||||
--warning_level=QUIET
|
||||
--dependency_mode=STRICT
|
||||
--rewrite_polyfills=false
|
||||
|
||||
node_modules/zone.js/dist/zone_externs.js
|
||||
|
||||
--js node_modules/rxjs/**.js
|
||||
--process_common_js_modules
|
||||
--module_resolution=node
|
||||
|
||||
node_modules/@angular/core/@angular/core.js
|
||||
--js_module_root=node_modules/@angular/core
|
||||
node_modules/@angular/core/src/testability/testability.externs.js
|
||||
|
||||
node_modules/@angular/common/@angular/common.js
|
||||
--js_module_root=node_modules/@angular/common
|
||||
|
||||
node_modules/@angular/platform-browser/@angular/platform-browser.js
|
||||
--js_module_root=node_modules/@angular/platform-browser
|
||||
|
||||
--js built/**.js
|
||||
--entry_point=built/src/main
|
@ -1,14 +1,15 @@
|
||||
{
|
||||
"open": false,
|
||||
"logLevel": "silent",
|
||||
"logLevel": "silent",
|
||||
"port": 8080,
|
||||
"server": {
|
||||
"baseDir": "src",
|
||||
"routes": {
|
||||
"/dist": "dist"
|
||||
"/dist": "dist",
|
||||
"/node_modules": "node_modules"
|
||||
},
|
||||
"middleware": {
|
||||
"0": null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -11,21 +11,22 @@
|
||||
"@angular/platform-browser": "file:../../dist/packages-dist/platform-browser",
|
||||
"@angular/platform-server": "file:../../dist/packages-dist/platform-server",
|
||||
"@angular/tsc-wrapped": "file:../../dist/tools/@angular/tsc-wrapped",
|
||||
"google-closure-compiler": "20161201.0.0",
|
||||
"rxjs": "file:../../node_modules/rxjs",
|
||||
"google-closure-compiler": "20170409.0.0",
|
||||
"rxjs": "5.3.1",
|
||||
"typescript": "2.1.6",
|
||||
"zone.js": "0.7.6"
|
||||
"zone.js": "0.8.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jasmine": "2.5.41",
|
||||
"concurrently": "3.1.0",
|
||||
"concurrently": "3.4.0",
|
||||
"lite-server": "2.2.2",
|
||||
"protractor": "file:../../node_modules/protractor"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "ngc && ./bundle.sh && concurrently \"yarn run serve\" \"yarn run protractor\" --kill-others --success first",
|
||||
"closure": "java -jar node_modules/google-closure-compiler/compiler.jar --flagfile closure.conf",
|
||||
"test": "ngc && yarn run closure && concurrently \"yarn run serve\" \"yarn run protractor\" --kill-others --success first",
|
||||
"serve": "lite-server -c e2e/browser.config.json",
|
||||
"preprotractor": "tsc -p e2e",
|
||||
"protractor": "protractor e2e/protractor.config.js"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +1,18 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Hello World</title>
|
||||
<base href="/">
|
||||
</head>
|
||||
<body>
|
||||
<hello-world-app>Loading...</hello-world-app>
|
||||
|
||||
<script src="dist/bundle.js"></script>
|
||||
<body>
|
||||
<hello-world-app>Loading...</hello-world-app>
|
||||
|
||||
<script src="node_modules/zone.js/dist/zone.min.js"></script>
|
||||
<script src="dist/bundle.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
</html>
|
@ -24,7 +24,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jasmine": "2.5.41",
|
||||
"concurrently": "3.1.0",
|
||||
"concurrently": "3.4.0",
|
||||
"lite-server": "2.2.2",
|
||||
"protractor": "file:../../node_modules/protractor"
|
||||
}
|
||||
|
@ -10,6 +10,6 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"typescript": "2.3.0-dev.20170223"
|
||||
"typescript": "2.3.0"
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,6 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
typescript@2.3.0-dev.20170223:
|
||||
version "2.3.0-dev.20170223"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.3.0-dev.20170223.tgz#286494c36625ea2eb26f963ed205cd9ca5c41447"
|
||||
typescript@2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.3.0.tgz#2e63e09284392bc8158a2444c33e2093795c0418"
|
||||
|
@ -4,12 +4,6 @@ set -e -o pipefail
|
||||
|
||||
cd `dirname $0`
|
||||
|
||||
if [ ! -d "rxjs/dist/es6" ]; then
|
||||
echo "You must run build the ES2015 version of RxJS for some tests:"
|
||||
echo "./integration/build_rxjs_es6.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Workaround https://github.com/yarnpkg/yarn/issues/2165
|
||||
# Yarn will cache file://dist URIs and not update Angular code
|
||||
readonly cache=.yarn_local_cache
|
||||
@ -20,7 +14,7 @@ rm_cache
|
||||
mkdir $cache
|
||||
trap rm_cache EXIT
|
||||
|
||||
for testDir in $(ls | grep -v rxjs | grep -v node_modules) ; do
|
||||
for testDir in $(ls | grep -v node_modules) ; do
|
||||
[[ -d "$testDir" ]] || continue
|
||||
echo "#################################"
|
||||
echo "Running integration test $testDir"
|
||||
|
@ -1,42 +0,0 @@
|
||||
{
|
||||
"angularCompilerOptions": {
|
||||
"skipMetadataEmit": true,
|
||||
"annotationsAs": "static fields",
|
||||
"annotateForClosureCompiler": true
|
||||
},
|
||||
|
||||
"compilerOptions": {
|
||||
"types": [],
|
||||
/**
|
||||
* Remaining options are copied from
|
||||
* https://github.com/ReactiveX/rxjs/blob/cba74135810a8e6bbe0b3c7732e8544b0869589e/tsconfig.json
|
||||
* TODO(alexeagle): use "extends" instead when Angular is on TS 2.1
|
||||
*/
|
||||
"removeComments": false,
|
||||
"preserveConstEnums": true,
|
||||
"sourceMap": true,
|
||||
"declaration": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"suppressImplicitAnyIndexErrors": true,
|
||||
"moduleResolution": "node",
|
||||
"module": "es2015",
|
||||
"target": "es6",
|
||||
"outDir": "dist/es6",
|
||||
"lib": [
|
||||
"es5",
|
||||
"es2015.iterable",
|
||||
"es2015.collection",
|
||||
"es2015.promise",
|
||||
"dom"
|
||||
]
|
||||
},
|
||||
"formatCodeOptions": {
|
||||
"indentSize": 2,
|
||||
"tabSize": 2
|
||||
},
|
||||
"files": [
|
||||
"src/Rx.ts"
|
||||
]
|
||||
}
|
@ -9,6 +9,7 @@
|
||||
import * as compiler from '@angular/compiler';
|
||||
import * as compilerTesting from '@angular/compiler/testing';
|
||||
import * as coreTesting from '@angular/core';
|
||||
import * as forms from '@angular/forms';
|
||||
import * as core from '@angular/core/testing';
|
||||
import * as httpTesting from '@angular/http';
|
||||
import * as http from '@angular/http/testing';
|
||||
@ -26,6 +27,7 @@ export default {
|
||||
compilerTesting,
|
||||
core,
|
||||
coreTesting,
|
||||
forms,
|
||||
http,
|
||||
httpTesting,
|
||||
platformBrowser,
|
||||
|
@ -9,6 +9,7 @@
|
||||
"@angular/compiler": "file:../../dist/packages-dist/compiler",
|
||||
"@angular/compiler-cli": "file:../../dist/packages-dist/compiler-cli",
|
||||
"@angular/core": "file:../../dist/packages-dist/core",
|
||||
"@angular/forms": "file:../../dist/packages-dist/forms",
|
||||
"@angular/http": "file:../../dist/packages-dist/http",
|
||||
"@angular/platform-browser": "file:../../dist/packages-dist/platform-browser",
|
||||
"@angular/platform-browser-dynamic": "file:../../dist/packages-dist/platform-browser-dynamic",
|
||||
|
@ -9,8 +9,7 @@
|
||||
"target": "es5",
|
||||
"lib": ["es5", "dom", "es2015.collection", "es2015.iterable", "es2015.promise"],
|
||||
"types": [],
|
||||
// TODO(i): strictNullChecks should turned on but are temporarily disabled due to #15432
|
||||
"strictNullChecks": false
|
||||
"strictNullChecks": true
|
||||
},
|
||||
"files": [
|
||||
"include-all.ts",
|
||||
|
@ -10,6 +10,7 @@ import * as compiler from '@angular/compiler';
|
||||
import * as compilerTesting from '@angular/compiler/testing';
|
||||
import * as coreTesting from '@angular/core';
|
||||
import * as core from '@angular/core/testing';
|
||||
import * as forms from '@angular/forms';
|
||||
import * as httpTesting from '@angular/http';
|
||||
import * as http from '@angular/http/testing';
|
||||
import * as platformBrowserTesting from '@angular/platform-browser';
|
||||
@ -26,6 +27,7 @@ export default {
|
||||
compilerTesting,
|
||||
core,
|
||||
coreTesting,
|
||||
forms,
|
||||
http,
|
||||
httpTesting,
|
||||
platformBrowser,
|
||||
|
@ -9,6 +9,7 @@
|
||||
"@angular/compiler": "file:../../dist/packages-dist/compiler",
|
||||
"@angular/compiler-cli": "file:../../dist/packages-dist/compiler-cli",
|
||||
"@angular/core": "file:../../dist/packages-dist/core",
|
||||
"@angular/forms": "file:../../dist/packages-dist/forms",
|
||||
"@angular/http": "file:../../dist/packages-dist/http",
|
||||
"@angular/platform-browser": "file:../../dist/packages-dist/platform-browser",
|
||||
"@angular/platform-browser-dynamic": "file:../../dist/packages-dist/platform-browser-dynamic",
|
||||
|
@ -15,11 +15,10 @@
|
||||
"es2015.promise"
|
||||
],
|
||||
"types": [],
|
||||
// TODO(i): strictNullChecks should turned on but are temporarily disabled due to #15432
|
||||
"strictNullChecks": false
|
||||
"strictNullChecks": true
|
||||
},
|
||||
"files": [
|
||||
"include-all.ts",
|
||||
"node_modules/@types/jasmine/index.d.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {NgIf} from '@angular/common';
|
||||
import {ComponentFactory, ComponentFactoryResolver, ComponentRef, Injector, NgModuleRef, RendererFactory2, RootRenderer, Sanitizer, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||
import {ComponentFactory, ComponentFactoryResolver, ComponentRef, ErrorHandler, Injector, NgModuleRef, RendererFactory2, RootRenderer, Sanitizer, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||
import {ArgumentType, BindingFlags, NodeFlags, ViewDefinition, ViewFlags, anchorDef, createComponentFactory, directiveDef, elementDef, initServicesIfNeeded, textDef, viewDef} from '@angular/core/src/view/index';
|
||||
import {DomRendererFactory2} from '@angular/platform-browser/src/dom/dom_renderer';
|
||||
import {DomSanitizerImpl, SafeStyle} from '@angular/platform-browser/src/security/dom_sanitization_service';
|
||||
@ -108,6 +108,7 @@ export class AppModule implements Injector, NgModuleRef<any> {
|
||||
case Sanitizer:
|
||||
return this.sanitizer;
|
||||
case RootRenderer:
|
||||
case ErrorHandler:
|
||||
return null;
|
||||
case NgModuleRef:
|
||||
return this;
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "angular-srcs",
|
||||
"version": "4.1.0",
|
||||
"version": "4.1.1",
|
||||
"private": true,
|
||||
"branchPattern": "2.0.*",
|
||||
"description": "Angular - a web framework for modern web apps",
|
||||
|
@ -22,9 +22,6 @@ import {ComponentFactoryResolver, ComponentRef, Directive, Injector, Input, NgMo
|
||||
* * `ngComponentOutletInjector`: Optional custom {@link Injector} that will be used as parent for
|
||||
* the Component. Defaults to the injector of the current view container.
|
||||
*
|
||||
* * `ngComponentOutletProviders`: Optional injectable objects ({@link Provider}) that are visible
|
||||
* to the component.
|
||||
*
|
||||
* * `ngComponentOutletContent`: Optional list of projectable nodes to insert into the content
|
||||
* section of the component, if exists.
|
||||
*
|
||||
|
@ -14,22 +14,18 @@ import {InjectionToken} from '@angular/core';
|
||||
* `PlatformLocation` encapsulates all calls to DOM apis, which allows the Router to be platform
|
||||
* agnostic.
|
||||
* This means that we can have different implementation of `PlatformLocation` for the different
|
||||
* platforms
|
||||
* that angular supports. For example, the default `PlatformLocation` is {@link
|
||||
* BrowserPlatformLocation},
|
||||
* however when you run your app in a WebWorker you use {@link WebWorkerPlatformLocation}.
|
||||
* platforms that angular supports. For example, `@angular/platform-browser` provides an
|
||||
* implementation specific to the browser environment, while `@angular/platform-webworker` provides
|
||||
* one suitable for use with web workers.
|
||||
*
|
||||
* The `PlatformLocation` class is used directly by all implementations of {@link LocationStrategy}
|
||||
* when
|
||||
* they need to interact with the DOM apis like pushState, popState, etc...
|
||||
* when they need to interact with the DOM apis like pushState, popState, etc...
|
||||
*
|
||||
* {@link LocationStrategy} in turn is used by the {@link Location} service which is used directly
|
||||
* by
|
||||
* the {@link Router} in order to navigate between routes. Since all interactions between {@link
|
||||
* by the {@link Router} in order to navigate between routes. Since all interactions between {@link
|
||||
* Router} /
|
||||
* {@link Location} / {@link LocationStrategy} and DOM apis flow through the `PlatformLocation`
|
||||
* class
|
||||
* they are all platform independent.
|
||||
* class they are all platform independent.
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
|
@ -29,6 +29,7 @@ function main() {
|
||||
|
||||
Promise.resolve()
|
||||
.then(() => codeGenTest())
|
||||
.then(() => codeGenTest(true))
|
||||
.then(() => i18nTest())
|
||||
.then(() => lazyRoutesTest())
|
||||
.then(() => {
|
||||
@ -42,8 +43,9 @@ function main() {
|
||||
});
|
||||
}
|
||||
|
||||
function codeGenTest() {
|
||||
function codeGenTest(forceError = false) {
|
||||
const basePath = path.join(__dirname, '../ngtools_src');
|
||||
const srcPath = path.join(__dirname, '../src');
|
||||
const project = path.join(basePath, 'tsconfig-build.json');
|
||||
const readResources: string[] = [];
|
||||
const wroteFiles: string[] = [];
|
||||
@ -59,6 +61,9 @@ function codeGenTest() {
|
||||
config.ngOptions.basePath = basePath;
|
||||
|
||||
console.log(`>>> running codegen for ${project}`);
|
||||
if (forceError) {
|
||||
console.log(`>>> asserting that missingTranslation param with error value throws`);
|
||||
}
|
||||
return __NGTOOLS_PRIVATE_API_2
|
||||
.codeGen({
|
||||
basePath,
|
||||
@ -67,9 +72,10 @@ function codeGenTest() {
|
||||
angularCompilerOptions: config.ngOptions,
|
||||
|
||||
// i18n options.
|
||||
i18nFormat: undefined,
|
||||
i18nFile: undefined,
|
||||
locale: undefined,
|
||||
i18nFormat: 'xlf',
|
||||
i18nFile: path.join(srcPath, 'messages.fi.xlf'),
|
||||
locale: 'fi',
|
||||
missingTranslation: forceError ? 'error' : 'ignore',
|
||||
|
||||
readResource: (fileName: string) => {
|
||||
readResources.push(fileName);
|
||||
@ -101,10 +107,17 @@ function codeGenTest() {
|
||||
|
||||
console.log(`done, no errors.`);
|
||||
})
|
||||
.catch((e: any) => {
|
||||
console.error(e.stack);
|
||||
console.error('Compilation failed');
|
||||
throw e;
|
||||
.catch((e: Error) => {
|
||||
if (forceError) {
|
||||
assert(
|
||||
e.message.match(`Missing translation for message`),
|
||||
`Expected error message for missing translations`);
|
||||
console.log(`done, error catched`);
|
||||
} else {
|
||||
console.error(e.stack);
|
||||
console.error('Compilation failed');
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -165,7 +178,7 @@ function i18nTest() {
|
||||
|
||||
console.log(`done, no errors.`);
|
||||
})
|
||||
.catch((e: any) => {
|
||||
.catch((e: Error) => {
|
||||
console.error(e.stack);
|
||||
console.error('Extraction failed');
|
||||
throw e;
|
||||
|
@ -9,7 +9,7 @@
|
||||
"ng-xi18n": "./src/extract_i18n.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/tsc-wrapped": "4.1.0",
|
||||
"@angular/tsc-wrapped": "4.1.1",
|
||||
"reflect-metadata": "^0.1.2",
|
||||
"minimist": "^1.2.0"
|
||||
},
|
||||
|
@ -6,8 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AotCompiler, AotCompilerHost, AotCompilerOptions, GeneratedFile, createAotCompiler} from '@angular/compiler';
|
||||
import {RenderComponentType, ɵReflectionCapabilities as ReflectionCapabilities, ɵreflector as reflector} from '@angular/core';
|
||||
import {GeneratedFile} from '@angular/compiler';
|
||||
import {NodeFlags} from '@angular/core/src/view/index';
|
||||
import {async} from '@angular/core/testing';
|
||||
import {MetadataBundler, MetadataCollector, ModuleMetadata, privateEntriesToIndex} from '@angular/tsc-wrapped';
|
||||
@ -15,60 +14,16 @@ import * as ts from 'typescript';
|
||||
|
||||
import {extractSourceMap, originalPositionFor} from '../output/source_map_util';
|
||||
|
||||
import {EmittingCompilerHost, MockAotCompilerHost, MockCompilerHost, MockData, MockDirectory, MockMetadataBundlerHost, settings} from './test_util';
|
||||
|
||||
const DTS = /\.d\.ts$/;
|
||||
|
||||
const minCoreIndex = `
|
||||
export * from './src/application_module';
|
||||
export * from './src/change_detection';
|
||||
export * from './src/metadata';
|
||||
export * from './src/di/metadata';
|
||||
export * from './src/di/injector';
|
||||
export * from './src/di/injection_token';
|
||||
export * from './src/linker';
|
||||
export * from './src/render';
|
||||
export * from './src/codegen_private_exports';
|
||||
`;
|
||||
import {EmittingCompilerHost, MockData, MockDirectory, MockMetadataBundlerHost, arrayToMockDir, arrayToMockMap, compile, settings, setup, toMockFileArray} from './test_util';
|
||||
|
||||
describe('compiler (unbundled Angular)', () => {
|
||||
let angularFiles: Map<string, string>;
|
||||
|
||||
beforeAll(() => {
|
||||
const emittingHost = new EmittingCompilerHost([], {emitMetadata: true});
|
||||
emittingHost.addScript('@angular/core/index.ts', minCoreIndex);
|
||||
const emittingProgram = ts.createProgram(emittingHost.scripts, settings, emittingHost);
|
||||
emittingProgram.emit();
|
||||
|
||||
angularFiles = emittingHost.written;
|
||||
});
|
||||
|
||||
// Restore reflector since AoT compiler will update it with a new static reflector
|
||||
afterEach(() => { reflector.updateCapabilities(new ReflectionCapabilities()); });
|
||||
let angularFiles = setup();
|
||||
|
||||
describe('Quickstart', () => {
|
||||
let host: MockCompilerHost;
|
||||
let aotHost: MockAotCompilerHost;
|
||||
|
||||
beforeEach(() => {
|
||||
host = new MockCompilerHost(QUICKSTART, FILES, angularFiles);
|
||||
aotHost = new MockAotCompilerHost(host);
|
||||
});
|
||||
|
||||
it('should compile',
|
||||
async(() => compile(host, aotHost, expectNoDiagnostics).then(generatedFiles => {
|
||||
expect(generatedFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl)))
|
||||
.toBeDefined();
|
||||
expect(generatedFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl)))
|
||||
.toBeDefined();
|
||||
})));
|
||||
|
||||
it('should compile using summaries',
|
||||
async(() => summaryCompile(host, aotHost).then(generatedFiles => {
|
||||
expect(generatedFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl)))
|
||||
.toBeDefined();
|
||||
expect(generatedFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl)))
|
||||
it('should compile', async(() => compile([QUICKSTART, angularFiles]).then(({genFiles}) => {
|
||||
expect(genFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl)))
|
||||
.toBeDefined();
|
||||
expect(genFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl))).toBeDefined();
|
||||
})));
|
||||
});
|
||||
|
||||
@ -97,17 +52,11 @@ describe('compiler (unbundled Angular)', () => {
|
||||
});
|
||||
|
||||
function compileApp(): Promise<GeneratedFile> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const host = new MockCompilerHost(['/app/app.module.ts'], rootDir, angularFiles);
|
||||
const aotHost = new MockAotCompilerHost(host);
|
||||
let result: GeneratedFile[];
|
||||
let error: Error;
|
||||
resolve(compile(host, aotHost, expectNoDiagnostics, expectNoDiagnostics)
|
||||
.then(
|
||||
(files) => files.find(
|
||||
genFile => genFile.srcFileUrl === componentPath &&
|
||||
genFile.genFileUrl.endsWith('.ts'))));
|
||||
});
|
||||
return compile([rootDir, angularFiles])
|
||||
.then(
|
||||
({genFiles}) => {return genFiles.find(
|
||||
genFile =>
|
||||
genFile.srcFileUrl === componentPath && genFile.genFileUrl.endsWith('.ts'))});
|
||||
}
|
||||
|
||||
function findLineAndColumn(
|
||||
@ -247,7 +196,7 @@ describe('compiler (unbundled Angular)', () => {
|
||||
describe('errors', () => {
|
||||
it('should only warn if not all arguments of an @Injectable class can be resolved',
|
||||
async(() => {
|
||||
const FILES: MockData = {
|
||||
const FILES: MockDirectory = {
|
||||
app: {
|
||||
'app.ts': `
|
||||
import {Injectable} from '@angular/core';
|
||||
@ -259,10 +208,8 @@ describe('compiler (unbundled Angular)', () => {
|
||||
`
|
||||
}
|
||||
};
|
||||
const host = new MockCompilerHost(['/app/app.ts'], FILES, angularFiles);
|
||||
const aotHost = new MockAotCompilerHost(host);
|
||||
const warnSpy = spyOn(console, 'warn');
|
||||
compile(host, aotHost, expectNoDiagnostics).then(() => {
|
||||
compile([FILES, angularFiles]).then(() => {
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
`Warning: Can't resolve all parameters for MyService in /app/app.ts: (?). This will become an error in Angular v5.x`);
|
||||
});
|
||||
@ -271,7 +218,7 @@ describe('compiler (unbundled Angular)', () => {
|
||||
});
|
||||
|
||||
it('should add the preamble to generated files', async(() => {
|
||||
const FILES: MockData = {
|
||||
const FILES: MockDirectory = {
|
||||
app: {
|
||||
'app.ts': `
|
||||
import { NgModule, Component } from '@angular/core';
|
||||
@ -284,22 +231,19 @@ describe('compiler (unbundled Angular)', () => {
|
||||
`
|
||||
}
|
||||
};
|
||||
const host = new MockCompilerHost(['/app/app.ts'], FILES, angularFiles);
|
||||
const aotHost = new MockAotCompilerHost(host);
|
||||
const genFilePreamble = '/* Hello world! */';
|
||||
compile(host, aotHost, expectNoDiagnostics, expectNoDiagnostics, {genFilePreamble})
|
||||
.then((generatedFiles) => {
|
||||
const genFile = generatedFiles.find(
|
||||
gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts'));
|
||||
expect(genFile.source.startsWith(genFilePreamble)).toBe(true);
|
||||
});
|
||||
compile([FILES, angularFiles], {genFilePreamble}).then(({genFiles}) => {
|
||||
const genFile =
|
||||
genFiles.find(gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts'));
|
||||
expect(genFile.source.startsWith(genFilePreamble)).toBe(true);
|
||||
});
|
||||
|
||||
}));
|
||||
|
||||
describe('ComponentFactories', () => {
|
||||
it('should include inputs, outputs and ng-content selectors in the component factory',
|
||||
async(() => {
|
||||
const FILES: MockData = {
|
||||
const FILES: MockDirectory = {
|
||||
app: {
|
||||
'app.ts': `
|
||||
import {Component, NgModule, Input, Output} from '@angular/core';
|
||||
@ -323,11 +267,8 @@ describe('compiler (unbundled Angular)', () => {
|
||||
`
|
||||
}
|
||||
};
|
||||
const host = new MockCompilerHost(['/app/app.ts'], FILES, angularFiles);
|
||||
const aotHost = new MockAotCompilerHost(host);
|
||||
let generatedFiles: GeneratedFile[];
|
||||
compile(host, aotHost, expectNoDiagnostics).then((generatedFiles) => {
|
||||
const genFile = generatedFiles.find(genFile => genFile.srcFileUrl === '/app/app.ts');
|
||||
compile([FILES, angularFiles]).then(({genFiles}) => {
|
||||
const genFile = genFiles.find(genFile => genFile.srcFileUrl === '/app/app.ts');
|
||||
const createComponentFactoryCall =
|
||||
/ɵccf\([^)]*\)/m.exec(genFile.source) ![0].replace(/\s*/g, '');
|
||||
// selector
|
||||
@ -345,7 +286,7 @@ describe('compiler (unbundled Angular)', () => {
|
||||
describe('generated templates', () => {
|
||||
it('should not call `check` for directives without bindings nor ngDoCheck/ngOnInit',
|
||||
async(() => {
|
||||
const FILES: MockData = {
|
||||
const FILES: MockDirectory = {
|
||||
app: {
|
||||
'app.ts': `
|
||||
import { NgModule, Component } from '@angular/core';
|
||||
@ -358,37 +299,16 @@ describe('compiler (unbundled Angular)', () => {
|
||||
`
|
||||
}
|
||||
};
|
||||
const host = new MockCompilerHost(['/app/app.ts'], FILES, angularFiles);
|
||||
const aotHost = new MockAotCompilerHost(host);
|
||||
const genFilePreamble = '/* Hello world! */';
|
||||
compile(host, aotHost, expectNoDiagnostics, expectNoDiagnostics, {genFilePreamble})
|
||||
.then((generatedFiles) => {
|
||||
const genFile = generatedFiles.find(
|
||||
gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts'));
|
||||
expect(genFile.source).not.toContain('check(');
|
||||
});
|
||||
compile([FILES, angularFiles]).then(({genFiles}) => {
|
||||
const genFile = genFiles.find(
|
||||
gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts'));
|
||||
expect(genFile.source).not.toContain('check(');
|
||||
});
|
||||
|
||||
}));
|
||||
});
|
||||
|
||||
describe('inheritance with summaries', () => {
|
||||
function compileWithSummaries(
|
||||
libInput: MockData, appInput: MockData): Promise<GeneratedFile[]> {
|
||||
const libHost = new MockCompilerHost(['/lib/base.ts'], libInput, angularFiles);
|
||||
const libAotHost = new MockAotCompilerHost(libHost);
|
||||
libAotHost.tsFilesOnly();
|
||||
const appHost = new MockCompilerHost(['/app/main.ts'], appInput, angularFiles);
|
||||
const appAotHost = new MockAotCompilerHost(appHost);
|
||||
appAotHost.tsFilesOnly();
|
||||
return compile(libHost, libAotHost, expectNoDiagnostics, expectNoDiagnosticsAndEmit)
|
||||
.then(() => {
|
||||
libHost.writtenFiles.forEach((value, key) => appHost.writeFile(key, value, false));
|
||||
libHost.overrides.forEach((value, key) => appHost.override(key, value));
|
||||
|
||||
return compile(appHost, appAotHost, expectNoDiagnostics, expectNoDiagnosticsAndEmit);
|
||||
});
|
||||
}
|
||||
|
||||
function compileParentAndChild(
|
||||
{parentClassDecorator, parentModuleDecorator, childClassDecorator, childModuleDecorator}: {
|
||||
parentClassDecorator: string,
|
||||
@ -396,7 +316,7 @@ describe('compiler (unbundled Angular)', () => {
|
||||
childClassDecorator: string,
|
||||
childModuleDecorator: string
|
||||
}) {
|
||||
const libInput: MockData = {
|
||||
const libInput: MockDirectory = {
|
||||
'lib': {
|
||||
'base.ts': `
|
||||
import {Injectable, Pipe, Directive, Component, NgModule} from '@angular/core';
|
||||
@ -409,7 +329,7 @@ describe('compiler (unbundled Angular)', () => {
|
||||
`
|
||||
}
|
||||
};
|
||||
const appInput: MockData = {
|
||||
const appInput: MockDirectory = {
|
||||
'app': {
|
||||
'main.ts': `
|
||||
import {Injectable, Pipe, Directive, Component, NgModule} from '@angular/core';
|
||||
@ -424,13 +344,14 @@ describe('compiler (unbundled Angular)', () => {
|
||||
}
|
||||
};
|
||||
|
||||
return compileWithSummaries(libInput, appInput)
|
||||
.then((generatedFiles) => generatedFiles.find(gf => gf.srcFileUrl === '/app/main.ts'));
|
||||
return compile([libInput, angularFiles], {useSummaries: true})
|
||||
.then(({outDir}) => compile([outDir, appInput, angularFiles], {useSummaries: true}))
|
||||
.then(({genFiles}) => genFiles.find(gf => gf.srcFileUrl === '/app/main.ts'));
|
||||
}
|
||||
|
||||
it('should inherit ctor and lifecycle hooks from classes in other compilation units',
|
||||
async(() => {
|
||||
const libInput: MockData = {
|
||||
const libInput: MockDirectory = {
|
||||
'lib': {
|
||||
'base.ts': `
|
||||
export class AParam {}
|
||||
@ -442,7 +363,7 @@ describe('compiler (unbundled Angular)', () => {
|
||||
`
|
||||
}
|
||||
};
|
||||
const appInput: MockData = {
|
||||
const appInput: MockDirectory = {
|
||||
'app': {
|
||||
'main.ts': `
|
||||
import {NgModule, Component} from '@angular/core';
|
||||
@ -459,17 +380,19 @@ describe('compiler (unbundled Angular)', () => {
|
||||
}
|
||||
};
|
||||
|
||||
compileWithSummaries(libInput, appInput).then((generatedFiles) => {
|
||||
const mainNgFactory = generatedFiles.find(gf => gf.srcFileUrl === '/app/main.ts');
|
||||
const flags = NodeFlags.TypeDirective | NodeFlags.Component | NodeFlags.OnDestroy;
|
||||
expect(mainNgFactory.source)
|
||||
.toContain(`${flags},(null as any),0,import1.Extends,[import2.AParam]`);
|
||||
});
|
||||
compile([libInput, angularFiles], {useSummaries: true})
|
||||
.then(({outDir}) => compile([outDir, appInput, angularFiles], {useSummaries: true}))
|
||||
.then(({genFiles}) => {
|
||||
const mainNgFactory = genFiles.find(gf => gf.srcFileUrl === '/app/main.ts');
|
||||
const flags = NodeFlags.TypeDirective | NodeFlags.Component | NodeFlags.OnDestroy;
|
||||
expect(mainNgFactory.source)
|
||||
.toContain(`${flags},(null as any),0,import1.Extends,[import2.AParam]`);
|
||||
});
|
||||
}));
|
||||
|
||||
it('should inherit ctor and lifecycle hooks from classes in other compilation units over 2 levels',
|
||||
async(() => {
|
||||
const lib1Input: MockData = {
|
||||
const lib1Input: MockDirectory = {
|
||||
'lib1': {
|
||||
'base.ts': `
|
||||
export class AParam {}
|
||||
@ -482,7 +405,7 @@ describe('compiler (unbundled Angular)', () => {
|
||||
}
|
||||
};
|
||||
|
||||
const lib2Input: MockData = {
|
||||
const lib2Input: MockDirectory = {
|
||||
'lib2': {
|
||||
'middle.ts': `
|
||||
import {Base} from '../lib1/base';
|
||||
@ -492,7 +415,7 @@ describe('compiler (unbundled Angular)', () => {
|
||||
};
|
||||
|
||||
|
||||
const appInput: MockData = {
|
||||
const appInput: MockDirectory = {
|
||||
'app': {
|
||||
'main.ts': `
|
||||
import {NgModule, Component} from '@angular/core';
|
||||
@ -508,29 +431,11 @@ describe('compiler (unbundled Angular)', () => {
|
||||
`
|
||||
}
|
||||
};
|
||||
const lib1Host = new MockCompilerHost(['/lib1/base.ts'], lib1Input, angularFiles);
|
||||
const lib1AotHost = new MockAotCompilerHost(lib1Host);
|
||||
lib1AotHost.tsFilesOnly();
|
||||
const lib2Host = new MockCompilerHost(['/lib2/middle.ts'], lib2Input, angularFiles);
|
||||
const lib2AotHost = new MockAotCompilerHost(lib2Host);
|
||||
lib2AotHost.tsFilesOnly();
|
||||
const appHost = new MockCompilerHost(['/app/main.ts'], appInput, angularFiles);
|
||||
const appAotHost = new MockAotCompilerHost(appHost);
|
||||
appAotHost.tsFilesOnly();
|
||||
compile(lib1Host, lib1AotHost, expectNoDiagnostics, expectNoDiagnosticsAndEmit)
|
||||
.then(() => {
|
||||
lib1Host.writtenFiles.forEach((value, key) => lib2Host.writeFile(key, value, false));
|
||||
lib1Host.overrides.forEach((value, key) => lib2Host.override(key, value));
|
||||
return compile(
|
||||
lib2Host, lib2AotHost, expectNoDiagnostics, expectNoDiagnosticsAndEmit);
|
||||
})
|
||||
.then(() => {
|
||||
lib2Host.writtenFiles.forEach((value, key) => appHost.writeFile(key, value, false));
|
||||
lib2Host.overrides.forEach((value, key) => appHost.override(key, value));
|
||||
return compile(appHost, appAotHost, expectNoDiagnostics, expectNoDiagnosticsAndEmit);
|
||||
})
|
||||
.then((generatedFiles) => {
|
||||
const mainNgFactory = generatedFiles.find(gf => gf.srcFileUrl === '/app/main.ts');
|
||||
compile([lib1Input, angularFiles], {useSummaries: true})
|
||||
.then(({outDir}) => compile([outDir, lib2Input, angularFiles], {useSummaries: true}))
|
||||
.then(({outDir}) => compile([outDir, appInput, angularFiles], {useSummaries: true}))
|
||||
.then(({genFiles}) => {
|
||||
const mainNgFactory = genFiles.find(gf => gf.srcFileUrl === '/app/main.ts');
|
||||
const flags = NodeFlags.TypeDirective | NodeFlags.Component | NodeFlags.OnDestroy;
|
||||
expect(mainNgFactory.source)
|
||||
.toContain(`${flags},(null as any),0,import1.Extends,[import2.AParam_2]`);
|
||||
@ -660,6 +565,8 @@ describe('compiler (unbundled Angular)', () => {
|
||||
});
|
||||
|
||||
describe('compiler (bundled Angular)', () => {
|
||||
setup({compileAngular: false});
|
||||
|
||||
let angularFiles: Map<string, string>;
|
||||
|
||||
beforeAll(() => {
|
||||
@ -681,34 +588,19 @@ describe('compiler (bundled Angular)', () => {
|
||||
const bundleIndexName = emittingHost.effectiveName('@angular/core/bundle_index.ts');
|
||||
const emittingProgram = ts.createProgram([bundleIndexName], settings, emittingHost);
|
||||
emittingProgram.emit();
|
||||
angularFiles = emittingHost.written;
|
||||
angularFiles = emittingHost.writtenAngularFiles();
|
||||
});
|
||||
|
||||
describe('Quickstart', () => {
|
||||
let host: MockCompilerHost;
|
||||
let aotHost: MockAotCompilerHost;
|
||||
|
||||
beforeEach(() => {
|
||||
host = new MockCompilerHost(QUICKSTART, FILES, angularFiles);
|
||||
aotHost = new MockAotCompilerHost(host);
|
||||
});
|
||||
|
||||
// Restore reflector since AoT compiler will update it with a new static reflector
|
||||
afterEach(() => { reflector.updateCapabilities(new ReflectionCapabilities()); });
|
||||
|
||||
it('should compile',
|
||||
async(() => compile(host, aotHost, expectNoDiagnostics).then(generatedFiles => {
|
||||
expect(generatedFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl)))
|
||||
.toBeDefined();
|
||||
expect(generatedFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl)))
|
||||
it('should compile', async(() => compile([QUICKSTART, angularFiles]).then(({genFiles}) => {
|
||||
expect(genFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl)))
|
||||
.toBeDefined();
|
||||
expect(genFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl))).toBeDefined();
|
||||
})));
|
||||
});
|
||||
|
||||
describe('Bundled library', () => {
|
||||
let host: MockCompilerHost;
|
||||
let aotHost: MockAotCompilerHost;
|
||||
let libraryFiles: Map<string, string>;
|
||||
let libraryFiles: MockDirectory;
|
||||
|
||||
beforeAll(() => {
|
||||
// Emit the library bundle
|
||||
@ -728,135 +620,22 @@ describe('compiler (bundled Angular)', () => {
|
||||
// Emit the sources
|
||||
const emittingProgram = ts.createProgram(['/bolder/index.ts'], settings, emittingHost);
|
||||
emittingProgram.emit();
|
||||
libraryFiles = emittingHost.written;
|
||||
const libFiles = emittingHost.written;
|
||||
|
||||
// Copy the .html file
|
||||
const htmlFileName = '/bolder/src/bolder.component.html';
|
||||
libraryFiles.set(htmlFileName, emittingHost.readFile(htmlFileName));
|
||||
libFiles.set(htmlFileName, emittingHost.readFile(htmlFileName));
|
||||
|
||||
libraryFiles = arrayToMockDir(toMockFileArray(libFiles).map(
|
||||
({fileName, content}) => ({fileName: `/node_modules${fileName}`, content})));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
host = new MockCompilerHost(
|
||||
LIBRARY_USING_APP_MODULE, LIBRARY_USING_APP, angularFiles, [libraryFiles]);
|
||||
aotHost = new MockAotCompilerHost(host);
|
||||
});
|
||||
|
||||
it('should compile',
|
||||
async(() => compile(host, aotHost, expectNoDiagnostics, expectNoDiagnostics)));
|
||||
|
||||
// Restore reflector since AoT compiler will update it with a new static reflector
|
||||
afterEach(() => { reflector.updateCapabilities(new ReflectionCapabilities()); });
|
||||
it('should compile', async(() => compile([LIBRARY_USING_APP, libraryFiles, angularFiles])));
|
||||
});
|
||||
});
|
||||
|
||||
function expectNoDiagnostics(program: ts.Program) {
|
||||
function fileInfo(diagnostic: ts.Diagnostic): string {
|
||||
if (diagnostic.file) {
|
||||
return `${diagnostic.file.fileName}(${diagnostic.start}): `;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function chars(len: number, ch: string): string { return new Array(len).fill(ch).join(''); }
|
||||
|
||||
function lineNoOf(offset: number, text: string): number {
|
||||
let result = 1;
|
||||
for (let i = 0; i < offset; i++) {
|
||||
if (text[i] == '\n') result++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function lineInfo(diagnostic: ts.Diagnostic): string {
|
||||
if (diagnostic.file) {
|
||||
const start = diagnostic.start;
|
||||
let end = diagnostic.start + diagnostic.length;
|
||||
const source = diagnostic.file.text;
|
||||
let lineStart = start;
|
||||
let lineEnd = end;
|
||||
while (lineStart > 0 && source[lineStart] != '\n') lineStart--;
|
||||
if (lineStart < start) lineStart++;
|
||||
while (lineEnd < source.length && source[lineEnd] != '\n') lineEnd++;
|
||||
let line = source.substring(lineStart, lineEnd);
|
||||
const lineIndex = line.indexOf('/n');
|
||||
if (lineIndex > 0) {
|
||||
line = line.substr(0, lineIndex);
|
||||
end = start + lineIndex;
|
||||
}
|
||||
const lineNo = lineNoOf(start, source) + ': ';
|
||||
return '\n' + lineNo + line + '\n' + chars(start - lineStart + lineNo.length, ' ') +
|
||||
chars(end - start, '^');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function expectNoDiagnostics(diagnostics: ts.Diagnostic[]) {
|
||||
if (diagnostics && diagnostics.length) {
|
||||
throw new Error(
|
||||
'Errors from TypeScript:\n' +
|
||||
diagnostics.map(d => `${fileInfo(d)}${d.messageText}${lineInfo(d)}`).join(' \n'));
|
||||
}
|
||||
}
|
||||
expectNoDiagnostics(program.getOptionsDiagnostics());
|
||||
expectNoDiagnostics(program.getSyntacticDiagnostics());
|
||||
expectNoDiagnostics(program.getSemanticDiagnostics());
|
||||
}
|
||||
|
||||
function expectNoDiagnosticsAndEmit(program: ts.Program) {
|
||||
expectNoDiagnostics(program);
|
||||
program.emit();
|
||||
}
|
||||
|
||||
function isDTS(fileName: string): boolean {
|
||||
return /\.d\.ts$/.test(fileName);
|
||||
}
|
||||
|
||||
function isSource(fileName: string): boolean {
|
||||
return /\.ts$/.test(fileName);
|
||||
}
|
||||
|
||||
function isFactory(fileName: string): boolean {
|
||||
return /\.ngfactory\./.test(fileName);
|
||||
}
|
||||
|
||||
function summaryCompile(
|
||||
host: MockCompilerHost, aotHost: MockAotCompilerHost,
|
||||
preCompile?: (program: ts.Program) => void) {
|
||||
// First compile the program to generate the summary files.
|
||||
return compile(host, aotHost).then(generatedFiles => {
|
||||
// Remove generated files that were not generated from a DTS file
|
||||
host.remove(generatedFiles.filter(f => !isDTS(f.srcFileUrl)).map(f => f.genFileUrl));
|
||||
|
||||
// Next compile the program shrowding metadata and only treating .ts files as source.
|
||||
aotHost.hideMetadata();
|
||||
aotHost.tsFilesOnly();
|
||||
|
||||
return compile(host, aotHost);
|
||||
});
|
||||
}
|
||||
|
||||
function compile(
|
||||
host: MockCompilerHost, aotHost: AotCompilerHost, preCompile?: (program: ts.Program) => void,
|
||||
postCompile: (program: ts.Program) => void = expectNoDiagnostics,
|
||||
options: AotCompilerOptions = {}): Promise<GeneratedFile[]> {
|
||||
const scripts = host.scriptNames.slice(0);
|
||||
const program = ts.createProgram(scripts, settings, host);
|
||||
if (preCompile) preCompile(program);
|
||||
const {compiler, reflector} = createAotCompiler(aotHost, options);
|
||||
return compiler.compileAll(program.getSourceFiles().map(sf => sf.fileName))
|
||||
.then(generatedFiles => {
|
||||
generatedFiles.forEach(
|
||||
file => isSource(file.genFileUrl) ? host.addScript(file.genFileUrl, file.source) :
|
||||
host.override(file.genFileUrl, file.source));
|
||||
const scripts = host.scriptNames.slice(0);
|
||||
const newProgram = ts.createProgram(scripts, settings, host);
|
||||
if (postCompile) postCompile(newProgram);
|
||||
return generatedFiles;
|
||||
});
|
||||
}
|
||||
|
||||
const QUICKSTART = ['/quickstart/app/app.module.ts'];
|
||||
const FILES: MockData = {
|
||||
const QUICKSTART: MockDirectory = {
|
||||
quickstart: {
|
||||
app: {
|
||||
'app.component.ts': `
|
||||
@ -891,7 +670,7 @@ const FILES: MockData = {
|
||||
}
|
||||
};
|
||||
|
||||
const LIBRARY: MockData = {
|
||||
const LIBRARY: MockDirectory = {
|
||||
bolder: {
|
||||
'public-api.ts': `
|
||||
export * from './src/bolder.component';
|
||||
@ -927,7 +706,7 @@ const LIBRARY: MockData = {
|
||||
};
|
||||
|
||||
const LIBRARY_USING_APP_MODULE = ['/lib-user/app/app.module.ts'];
|
||||
const LIBRARY_USING_APP: MockData = {
|
||||
const LIBRARY_USING_APP: MockDirectory = {
|
||||
'lib-user': {
|
||||
app: {
|
||||
'app.component.ts': `
|
||||
|
@ -6,19 +6,26 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AotCompilerHost} from '@angular/compiler';
|
||||
import {AotCompilerHost, AotCompilerOptions, GeneratedFile, createAotCompiler} from '@angular/compiler';
|
||||
import {ɵReflectionCapabilities as ReflectionCapabilities, ɵreflector as reflector} from '@angular/core';
|
||||
import {MetadataBundlerHost, MetadataCollector, ModuleMetadata} from '@angular/tsc-wrapped';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
export type MockData = string | MockDirectory;
|
||||
let nodeModulesPath: string;
|
||||
let angularSourcePath: string;
|
||||
let rootPath: string;
|
||||
|
||||
calcPathsOnDisc();
|
||||
|
||||
export type MockFileOrDirectory = string | MockDirectory;
|
||||
|
||||
export type MockDirectory = {
|
||||
[name: string]: MockData | undefined;
|
||||
[name: string]: MockFileOrDirectory | undefined;
|
||||
};
|
||||
|
||||
export function isDirectory(data: MockData | undefined): data is MockDirectory {
|
||||
export function isDirectory(data: MockFileOrDirectory | undefined): data is MockDirectory {
|
||||
return typeof data !== 'string';
|
||||
}
|
||||
|
||||
@ -43,12 +50,21 @@ export const settings: ts.CompilerOptions = {
|
||||
|
||||
export interface EmitterOptions {
|
||||
emitMetadata: boolean;
|
||||
mockData?: MockData;
|
||||
mockData?: MockDirectory;
|
||||
}
|
||||
|
||||
function calcPathsOnDisc() {
|
||||
const moduleFilename = module.filename.replace(/\\/g, '/');
|
||||
const distIndex = moduleFilename.indexOf('/dist/all');
|
||||
if (distIndex >= 0) {
|
||||
rootPath = moduleFilename.substr(0, distIndex);
|
||||
nodeModulesPath = path.join(rootPath, 'node_modules');
|
||||
angularSourcePath = path.join(rootPath, 'packages');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class EmittingCompilerHost implements ts.CompilerHost {
|
||||
private angularSourcePath: string|undefined;
|
||||
private nodeModulesPath: string|undefined;
|
||||
private addedFiles = new Map<string, string>();
|
||||
private writtenFiles = new Map<string, string>();
|
||||
private scriptNames: string[];
|
||||
@ -56,19 +72,18 @@ export class EmittingCompilerHost implements ts.CompilerHost {
|
||||
private collector = new MetadataCollector();
|
||||
|
||||
constructor(scriptNames: string[], private options: EmitterOptions) {
|
||||
const moduleFilename = module.filename.replace(/\\/g, '/');
|
||||
const distIndex = moduleFilename.indexOf('/dist/all');
|
||||
if (distIndex >= 0) {
|
||||
const root = moduleFilename.substr(0, distIndex);
|
||||
this.nodeModulesPath = path.join(root, 'node_modules');
|
||||
this.angularSourcePath = path.join(root, 'packages');
|
||||
// Rewrite references to scripts with '@angular' to its corresponding location in
|
||||
// the source tree.
|
||||
this.scriptNames = scriptNames.map(f => this.effectiveName(f));
|
||||
this.root = rootPath;
|
||||
}
|
||||
|
||||
// Rewrite references to scripts with '@angular' to its corresponding location in
|
||||
// the source tree.
|
||||
this.scriptNames = scriptNames.map(f => this.effectiveName(f));
|
||||
|
||||
this.root = root;
|
||||
}
|
||||
public writtenAngularFiles(target = new Map<string, string>()): Map<string, string> {
|
||||
this.written.forEach((value, key) => {
|
||||
const path = `/node_modules/@angular${key.substring(angularSourcePath.length)}`;
|
||||
target.set(path, value);
|
||||
});
|
||||
return target;
|
||||
}
|
||||
|
||||
public addScript(fileName: string, content: string) {
|
||||
@ -97,7 +112,7 @@ export class EmittingCompilerHost implements ts.CompilerHost {
|
||||
public effectiveName(fileName: string): string {
|
||||
const prefix = '@angular/';
|
||||
return fileName.startsWith('@angular/') ?
|
||||
path.join(this.angularSourcePath, fileName.substr(prefix.length)) :
|
||||
path.join(angularSourcePath, fileName.substr(prefix.length)) :
|
||||
fileName;
|
||||
}
|
||||
|
||||
@ -171,31 +186,17 @@ export class EmittingCompilerHost implements ts.CompilerHost {
|
||||
getNewLine(): string { return '\n'; }
|
||||
}
|
||||
|
||||
const MOCK_NODEMODULES_PREFIX = '/node_modules/';
|
||||
|
||||
export class MockCompilerHost implements ts.CompilerHost {
|
||||
scriptNames: string[];
|
||||
|
||||
private angularSourcePath: string|undefined;
|
||||
private nodeModulesPath: string|undefined;
|
||||
public overrides = new Map<string, string>();
|
||||
public writtenFiles = new Map<string, string>();
|
||||
private sourceFiles = new Map<string, ts.SourceFile>();
|
||||
private assumeExists = new Set<string>();
|
||||
private traces: string[] = [];
|
||||
|
||||
constructor(
|
||||
scriptNames: string[], private data: MockData, private angular: Map<string, string>,
|
||||
private libraries?: Map<string, string>[]) {
|
||||
constructor(scriptNames: string[], private data: MockDirectory) {
|
||||
this.scriptNames = scriptNames.slice(0);
|
||||
const moduleFilename = module.filename.replace(/\\/g, '/');
|
||||
let angularIndex = moduleFilename.indexOf('@angular');
|
||||
let distIndex = moduleFilename.indexOf('/dist/all');
|
||||
if (distIndex >= 0) {
|
||||
const root = moduleFilename.substr(0, distIndex);
|
||||
this.nodeModulesPath = path.join(root, 'node_modules');
|
||||
this.angularSourcePath = path.join(root, 'packages');
|
||||
}
|
||||
}
|
||||
|
||||
// Test API
|
||||
@ -234,22 +235,13 @@ export class MockCompilerHost implements ts.CompilerHost {
|
||||
const effectiveName = this.getEffectiveName(fileName);
|
||||
if (effectiveName == fileName) {
|
||||
let result = open(fileName, this.data) != null;
|
||||
if (!result && fileName.startsWith(MOCK_NODEMODULES_PREFIX)) {
|
||||
const libraryPath = fileName.substr(MOCK_NODEMODULES_PREFIX.length - 1);
|
||||
for (const library of this.libraries !) {
|
||||
if (library.has(libraryPath)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
if (fileName.match(rxjs)) {
|
||||
let result = fs.existsSync(effectiveName);
|
||||
return result;
|
||||
}
|
||||
const result = this.angular.has(effectiveName);
|
||||
return result;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -315,12 +307,6 @@ export class MockCompilerHost implements ts.CompilerHost {
|
||||
let effectiveName = this.getEffectiveName(fileName);
|
||||
if (effectiveName === fileName) {
|
||||
const result = open(fileName, this.data);
|
||||
if (!result && fileName.startsWith(MOCK_NODEMODULES_PREFIX)) {
|
||||
const libraryPath = fileName.substr(MOCK_NODEMODULES_PREFIX.length - 1);
|
||||
for (const library of this.libraries !) {
|
||||
if (library.has(libraryPath)) return library.get(libraryPath);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
if (fileName.match(rxjs)) {
|
||||
@ -328,22 +314,16 @@ export class MockCompilerHost implements ts.CompilerHost {
|
||||
return fs.readFileSync(fileName, 'utf8');
|
||||
}
|
||||
}
|
||||
return this.angular.get(effectiveName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getEffectiveName(name: string): string {
|
||||
const node_modules = 'node_modules';
|
||||
const at_angular = '/@angular';
|
||||
const rxjs = '/rxjs';
|
||||
if (name.startsWith('/' + node_modules)) {
|
||||
if (this.angularSourcePath && name.startsWith('/' + node_modules + at_angular)) {
|
||||
return path.join(
|
||||
this.angularSourcePath, name.substr(node_modules.length + at_angular.length + 1));
|
||||
}
|
||||
if (this.nodeModulesPath && name.startsWith('/' + node_modules + rxjs)) {
|
||||
return path.join(this.nodeModulesPath, name.substr(node_modules.length + 1));
|
||||
if (nodeModulesPath && name.startsWith('/' + node_modules + rxjs)) {
|
||||
return path.join(nodeModulesPath, name.substr(node_modules.length + 1));
|
||||
}
|
||||
}
|
||||
return name;
|
||||
@ -439,11 +419,12 @@ export class MockMetadataBundlerHost implements MetadataBundlerHost {
|
||||
}
|
||||
}
|
||||
|
||||
function find(fileName: string, data: MockData | undefined): MockData|undefined {
|
||||
function find(fileName: string, data: MockFileOrDirectory | undefined): MockFileOrDirectory|
|
||||
undefined {
|
||||
if (!data) return undefined;
|
||||
let names = fileName.split('/');
|
||||
if (names.length && !names[0].length) names.shift();
|
||||
let current: MockData|undefined = data;
|
||||
let current: MockFileOrDirectory|undefined = data;
|
||||
for (let name of names) {
|
||||
if (typeof current === 'string')
|
||||
return undefined;
|
||||
@ -454,7 +435,7 @@ function find(fileName: string, data: MockData | undefined): MockData|undefined
|
||||
return current;
|
||||
}
|
||||
|
||||
function open(fileName: string, data: MockData | undefined): string|undefined {
|
||||
function open(fileName: string, data: MockFileOrDirectory | undefined): string|undefined {
|
||||
let result = find(fileName, data);
|
||||
if (typeof result === 'string') {
|
||||
return result;
|
||||
@ -462,7 +443,204 @@ function open(fileName: string, data: MockData | undefined): string|undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function directoryExists(dirname: string, data: MockData | undefined): boolean {
|
||||
function directoryExists(dirname: string, data: MockFileOrDirectory | undefined): boolean {
|
||||
let result = find(dirname, data);
|
||||
return !!result && typeof result !== 'string';
|
||||
}
|
||||
|
||||
export type MockFileArray = {
|
||||
fileName: string,
|
||||
content: string
|
||||
}[];
|
||||
|
||||
export type MockData = MockDirectory | Map<string, string>| (MockDirectory | Map<string, string>)[];
|
||||
|
||||
export function toMockFileArray(data: MockData, target: MockFileArray = []): MockFileArray {
|
||||
if (data instanceof Map) {
|
||||
mapToMockFileArray(data, target);
|
||||
} else if (Array.isArray(data)) {
|
||||
data.forEach(entry => toMockFileArray(entry, target));
|
||||
} else {
|
||||
mockDirToFileArray(data, '', target);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
function mockDirToFileArray(dir: MockDirectory, path: string, target: MockFileArray) {
|
||||
Object.keys(dir).forEach((localFileName) => {
|
||||
const value = dir[localFileName] !;
|
||||
const fileName = `${path}/${localFileName}`;
|
||||
if (typeof value === 'string') {
|
||||
target.push({fileName, content: value});
|
||||
} else {
|
||||
mockDirToFileArray(value, fileName, target);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function mapToMockFileArray(files: Map<string, string>, target: MockFileArray) {
|
||||
files.forEach((content, fileName) => { target.push({fileName, content}); });
|
||||
}
|
||||
|
||||
export function arrayToMockMap(arr: MockFileArray): Map<string, string> {
|
||||
const map = new Map<string, string>();
|
||||
arr.forEach(({fileName, content}) => { map.set(fileName, content); });
|
||||
return map;
|
||||
}
|
||||
|
||||
export function arrayToMockDir(arr: MockFileArray): MockDirectory {
|
||||
const rootDir: MockDirectory = {};
|
||||
arr.forEach(({fileName, content}) => {
|
||||
let pathParts = fileName.split('/');
|
||||
// trim trailing slash
|
||||
let startIndex = pathParts[0] ? 0 : 1;
|
||||
// get/create the directory
|
||||
let currentDir = rootDir;
|
||||
for (let i = startIndex; i < pathParts.length - 1; i++) {
|
||||
const pathPart = pathParts[i];
|
||||
let localDir = <MockDirectory>currentDir[pathPart];
|
||||
if (!localDir) {
|
||||
currentDir[pathPart] = localDir = {};
|
||||
}
|
||||
currentDir = localDir;
|
||||
}
|
||||
// write the file
|
||||
currentDir[pathParts[pathParts.length - 1]] = content;
|
||||
});
|
||||
return rootDir;
|
||||
}
|
||||
|
||||
const minCoreIndex = `
|
||||
export * from './src/application_module';
|
||||
export * from './src/change_detection';
|
||||
export * from './src/metadata';
|
||||
export * from './src/di/metadata';
|
||||
export * from './src/di/injector';
|
||||
export * from './src/di/injection_token';
|
||||
export * from './src/linker';
|
||||
export * from './src/render';
|
||||
export * from './src/codegen_private_exports';
|
||||
`;
|
||||
|
||||
export function setup(options: {compileAngular: boolean} = {
|
||||
compileAngular: true
|
||||
}) {
|
||||
let angularFiles = new Map<string, string>();
|
||||
|
||||
beforeAll(() => {
|
||||
if (options.compileAngular) {
|
||||
const emittingHost = new EmittingCompilerHost([], {emitMetadata: true});
|
||||
emittingHost.addScript('@angular/core/index.ts', minCoreIndex);
|
||||
const emittingProgram = ts.createProgram(emittingHost.scripts, settings, emittingHost);
|
||||
emittingProgram.emit();
|
||||
emittingHost.writtenAngularFiles(angularFiles);
|
||||
}
|
||||
});
|
||||
// Restore reflector since AoT compiler will update it with a new static reflector
|
||||
afterEach(() => { reflector.updateCapabilities(new ReflectionCapabilities()); });
|
||||
|
||||
return angularFiles;
|
||||
}
|
||||
|
||||
export function expectNoDiagnostics(program: ts.Program) {
|
||||
function fileInfo(diagnostic: ts.Diagnostic): string {
|
||||
if (diagnostic.file) {
|
||||
return `${diagnostic.file.fileName}(${diagnostic.start}): `;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function chars(len: number, ch: string): string { return new Array(len).fill(ch).join(''); }
|
||||
|
||||
function lineNoOf(offset: number, text: string): number {
|
||||
let result = 1;
|
||||
for (let i = 0; i < offset; i++) {
|
||||
if (text[i] == '\n') result++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function lineInfo(diagnostic: ts.Diagnostic): string {
|
||||
if (diagnostic.file) {
|
||||
const start = diagnostic.start;
|
||||
let end = diagnostic.start + diagnostic.length;
|
||||
const source = diagnostic.file.text;
|
||||
let lineStart = start;
|
||||
let lineEnd = end;
|
||||
while (lineStart > 0 && source[lineStart] != '\n') lineStart--;
|
||||
if (lineStart < start) lineStart++;
|
||||
while (lineEnd < source.length && source[lineEnd] != '\n') lineEnd++;
|
||||
let line = source.substring(lineStart, lineEnd);
|
||||
const lineIndex = line.indexOf('/n');
|
||||
if (lineIndex > 0) {
|
||||
line = line.substr(0, lineIndex);
|
||||
end = start + lineIndex;
|
||||
}
|
||||
const lineNo = lineNoOf(start, source) + ': ';
|
||||
return '\n' + lineNo + line + '\n' + chars(start - lineStart + lineNo.length, ' ') +
|
||||
chars(end - start, '^');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function expectNoDiagnostics(diagnostics: ts.Diagnostic[]) {
|
||||
if (diagnostics && diagnostics.length) {
|
||||
throw new Error(
|
||||
'Errors from TypeScript:\n' +
|
||||
diagnostics.map(d => `${fileInfo(d)}${d.messageText}${lineInfo(d)}`).join(' \n'));
|
||||
}
|
||||
}
|
||||
expectNoDiagnostics(program.getOptionsDiagnostics());
|
||||
expectNoDiagnostics(program.getSyntacticDiagnostics());
|
||||
expectNoDiagnostics(program.getSemanticDiagnostics());
|
||||
}
|
||||
|
||||
function isSource(fileName: string): boolean {
|
||||
return !/\.d\.ts$/.test(fileName) && /\.ts$/.test(fileName);
|
||||
}
|
||||
|
||||
export function compile(rootDirs: MockData, options: {
|
||||
emit?: boolean,
|
||||
useSummaries?: boolean,
|
||||
preCompile?: (program: ts.Program) => void,
|
||||
postCompile?: (program: ts.Program) => void,
|
||||
}& AotCompilerOptions = {}): Promise<{genFiles: GeneratedFile[], outDir: MockDirectory}> {
|
||||
// Make sure we always return errors via the promise...
|
||||
return Promise.resolve(null).then(() => {
|
||||
// when using summaries, always emit so the next step can use the results.
|
||||
const emit = options.emit || options.useSummaries;
|
||||
const preCompile = options.preCompile || expectNoDiagnostics;
|
||||
const postCompile = options.postCompile || expectNoDiagnostics;
|
||||
const rootDirArr = toMockFileArray(rootDirs);
|
||||
const scriptNames = rootDirArr.map(entry => entry.fileName).filter(isSource);
|
||||
|
||||
const host = new MockCompilerHost(scriptNames, arrayToMockDir(rootDirArr));
|
||||
const aotHost = new MockAotCompilerHost(host);
|
||||
if (options.useSummaries) {
|
||||
aotHost.hideMetadata();
|
||||
aotHost.tsFilesOnly();
|
||||
}
|
||||
const scripts = host.scriptNames.slice(0);
|
||||
const program = ts.createProgram(scripts, settings, host);
|
||||
if (preCompile) preCompile(program);
|
||||
const {compiler, reflector} = createAotCompiler(aotHost, options);
|
||||
return compiler.compileAll(program.getSourceFiles().map(sf => sf.fileName)).then(genFiles => {
|
||||
genFiles.forEach(
|
||||
file => isSource(file.genFileUrl) ? host.addScript(file.genFileUrl, file.source) :
|
||||
host.override(file.genFileUrl, file.source));
|
||||
const scripts = host.scriptNames.slice(0);
|
||||
const newProgram = ts.createProgram(scripts, settings, host);
|
||||
if (postCompile) postCompile(newProgram);
|
||||
if (emit) {
|
||||
newProgram.emit();
|
||||
}
|
||||
let outDir: MockDirectory = {};
|
||||
if (emit) {
|
||||
outDir = arrayToMockDir(toMockFileArray([
|
||||
host.writtenFiles, host.overrides
|
||||
]).filter((entry) => !isSource(entry.fileName)));
|
||||
}
|
||||
return {genFiles, outDir};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -162,8 +162,8 @@ export function getPlatform(): PlatformRef|null {
|
||||
* has exactly one platform, and services (such as reflection) which are common
|
||||
* to every Angular application running on the page are bound in its scope.
|
||||
*
|
||||
* A page's platform is initialized implicitly when {@link bootstrap}() is called, or
|
||||
* explicitly by calling {@link createPlatform}().
|
||||
* A page's platform is initialized implicitly when a platform is created via a platform factory
|
||||
* (e.g. {@link platformBrowser}), or explicitly by calling the {@link createPlatform} function.
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
@ -344,8 +344,6 @@ export class PlatformRef_ extends PlatformRef {
|
||||
/**
|
||||
* A reference to an Angular application running on a page.
|
||||
*
|
||||
* For more about Angular applications, see the documentation for {@link bootstrap}.
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
export abstract class ApplicationRef {
|
||||
@ -553,6 +551,9 @@ export class ApplicationRef_ extends ApplicationRef {
|
||||
if (this._enforceNoNewChanges) {
|
||||
this._views.forEach((view) => view.checkNoChanges());
|
||||
}
|
||||
} catch (e) {
|
||||
// Attention: Don't rethrow as it could cancel subscriptions to Observables!
|
||||
this._exceptionHandler.handleError(e);
|
||||
} finally {
|
||||
this._runningTick = false;
|
||||
wtfLeave(scope);
|
||||
|
@ -50,7 +50,7 @@ export abstract class Injector {
|
||||
/**
|
||||
* Retrieves an instance from the injector based on the provided token.
|
||||
* If not found:
|
||||
* - Throws {@link NoProviderError} if no `notFoundValue` that is not equal to
|
||||
* - Throws an error if no `notFoundValue` that is not equal to
|
||||
* Injector.THROW_IF_NOT_FOUND is given
|
||||
* - Returns the `notFoundValue` otherwise
|
||||
*/
|
||||
|
@ -54,14 +54,14 @@ export class Compiler {
|
||||
compileModuleAsync<T>(moduleType: Type<T>): Promise<NgModuleFactory<T>> { throw _throwError(); }
|
||||
|
||||
/**
|
||||
* Same as {@link compileModuleSync} but also creates ComponentFactories for all components.
|
||||
* Same as {@link #compileModuleSync} but also creates ComponentFactories for all components.
|
||||
*/
|
||||
compileModuleAndAllComponentsSync<T>(moduleType: Type<T>): ModuleWithComponentFactories<T> {
|
||||
throw _throwError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link compileModuleAsync} but also creates ComponentFactories for all components.
|
||||
* Same as {@link #compileModuleAsync} but also creates ComponentFactories for all components.
|
||||
*/
|
||||
compileModuleAndAllComponentsAsync<T>(moduleType: Type<T>):
|
||||
Promise<ModuleWithComponentFactories<T>> {
|
||||
|
@ -16,7 +16,8 @@ import {getSymbolIterator} from '../util';
|
||||
* An unmodifiable list of items that Angular keeps up to date when the state
|
||||
* of the application changes.
|
||||
*
|
||||
* The type of object that {@link Query} and {@link ViewQueryMetadata} provide.
|
||||
* The type of object that {@link ViewChildren}, {@link ContentChildren}, and {@link QueryList}
|
||||
* provide.
|
||||
*
|
||||
* Implements an iterable interface, therefore it can be used in both ES6
|
||||
* javascript `for (var i of items)` loops as well as in Angular templates with
|
||||
|
@ -287,7 +287,7 @@ export const ContentChild: ContentChildDecorator = makePropDecorator(
|
||||
/**
|
||||
* Type of the ViewChildren decorator / constructor function.
|
||||
*
|
||||
* See {@ViewChildren}.
|
||||
* See {@link ViewChildren}.
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
|
@ -662,8 +662,8 @@ export interface Component extends Directive {
|
||||
* encapsulation.
|
||||
*
|
||||
* When no `encapsulation` is defined for the component, the default value from the
|
||||
* {@link CompilerConfig} is used. The default is `ViewEncapsulation.Emulated`}. Provide a new
|
||||
* `CompilerConfig` to override this value.
|
||||
* {@link CompilerOptions} is used. The default is `ViewEncapsulation.Emulated`}. Provide a new
|
||||
* `CompilerOptions` to override this value.
|
||||
*
|
||||
* If the encapsulation is set to `ViewEncapsulation.Emulated` and the component has no `styles`
|
||||
* nor `styleUrls` the encapsulation will automatically be switched to `ViewEncapsulation.None`.
|
||||
|
@ -9,14 +9,14 @@
|
||||
/**
|
||||
* Defines template and style encapsulation options available for Component's {@link Component}.
|
||||
*
|
||||
* See {@link ViewMetadata#encapsulation}.
|
||||
* See {@link Component#encapsulation}.
|
||||
* @stable
|
||||
*/
|
||||
export enum ViewEncapsulation {
|
||||
/**
|
||||
* Emulate `Native` scoping of styles by adding an attribute containing surrogate id to the Host
|
||||
* Element and pre-processing the style rules provided via
|
||||
* {@link ViewMetadata#styles} or {@link ViewMetadata#stylesUrls}, and adding the new Host Element
|
||||
* {@link Component#styles} or {@link Component#styleUrls}, and adding the new Host Element
|
||||
* attribute to all selectors.
|
||||
*
|
||||
* This is the default option.
|
||||
|
@ -189,7 +189,14 @@ export function listenToElementOutputs(view: ViewData, compView: ViewData, def:
|
||||
}
|
||||
|
||||
function renderEventHandlerClosure(view: ViewData, index: number, eventName: string) {
|
||||
return (event: any) => dispatchEvent(view, index, eventName, event);
|
||||
return (event: any) => {
|
||||
try {
|
||||
return dispatchEvent(view, index, eventName, event);
|
||||
} catch (e) {
|
||||
// Attention: Don't rethrow, to keep in sync with directive events.
|
||||
view.root.errorHandler.handleError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -148,7 +148,14 @@ export function createDirectiveInstance(view: ViewData, def: NodeDef): any {
|
||||
}
|
||||
|
||||
function eventHandlerClosure(view: ViewData, index: number, eventName: string) {
|
||||
return (event: any) => dispatchEvent(view, index, eventName, event);
|
||||
return (event: any) => {
|
||||
try {
|
||||
return dispatchEvent(view, index, eventName, event);
|
||||
} catch (e) {
|
||||
// Attention: Don't rethrow, as it would cancel Observable subscriptions!
|
||||
view.root.errorHandler.handleError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function checkAndUpdateDirectiveInline(
|
||||
@ -355,6 +362,12 @@ export function resolveDep(
|
||||
}
|
||||
const tokenKey = depDef.tokenKey;
|
||||
|
||||
if (tokenKey === ChangeDetectorRefTokenKey) {
|
||||
// directives on the same element as a component should be able to control the change detector
|
||||
// of that component as well.
|
||||
allowPrivateServices = !!(elDef && elDef.element !.componentView);
|
||||
}
|
||||
|
||||
if (elDef && (depDef.flags & DepFlags.SkipSelf)) {
|
||||
allowPrivateServices = false;
|
||||
elDef = elDef.parent !;
|
||||
|
@ -90,7 +90,9 @@ class ComponentFactory_ extends ComponentFactory<any> {
|
||||
const view = Services.createRootView(
|
||||
injector, projectableNodes || [], rootSelectorOrNode, viewDef, ngModule, EMPTY_CONTEXT);
|
||||
const component = asProviderData(view, componentNodeIndex).instance;
|
||||
view.renderer.setAttribute(asElementData(view, 0).renderElement, 'ng-version', VERSION.full);
|
||||
if (rootSelectorOrNode) {
|
||||
view.renderer.setAttribute(asElementData(view, 0).renderElement, 'ng-version', VERSION.full);
|
||||
}
|
||||
|
||||
return new ComponentRef_(view, new ViewRef_(view), component);
|
||||
}
|
||||
@ -236,11 +238,11 @@ export class ViewRef_ implements EmbeddedViewRef<any>, InternalViewRef {
|
||||
get destroyed(): boolean { return (this._view.state & ViewState.Destroyed) !== 0; }
|
||||
|
||||
markForCheck(): void { markParentViewsForCheck(this._view); }
|
||||
detach(): void { this._view.state &= ~ViewState.ChecksEnabled; }
|
||||
detach(): void { this._view.state &= ~ViewState.Attached; }
|
||||
detectChanges(): void { Services.checkAndUpdateView(this._view); }
|
||||
checkNoChanges(): void { Services.checkNoChangesView(this._view); }
|
||||
|
||||
reattach(): void { this._view.state |= ViewState.ChecksEnabled; }
|
||||
reattach(): void { this._view.state |= ViewState.Attached; }
|
||||
onDestroy(callback: Function) {
|
||||
if (!this._view.disposables) {
|
||||
this._view.disposables = [];
|
||||
|
@ -9,6 +9,7 @@
|
||||
import {isDevMode} from '../application_ref';
|
||||
import {DebugElement, DebugNode, EventListener, getDebugNode, indexDebugNode, removeDebugNodeFromIndex} from '../debug/debug_node';
|
||||
import {Injector} from '../di';
|
||||
import {ErrorHandler} from '../error_handler';
|
||||
import {NgModuleRef} from '../linker/ng_module_factory';
|
||||
import {Renderer2, RendererFactory2, RendererStyleFlags2, RendererType2} from '../render/api';
|
||||
import {Sanitizer} from '../security';
|
||||
@ -104,11 +105,12 @@ function createRootData(
|
||||
elInjector: Injector, ngModule: NgModuleRef<any>, rendererFactory: RendererFactory2,
|
||||
projectableNodes: any[][], rootSelectorOrNode: any): RootData {
|
||||
const sanitizer = ngModule.injector.get(Sanitizer);
|
||||
const errorHandler = ngModule.injector.get(ErrorHandler);
|
||||
const renderer = rendererFactory.createRenderer(null, null);
|
||||
return {
|
||||
ngModule,
|
||||
injector: elInjector, projectableNodes,
|
||||
selectorOrNode: rootSelectorOrNode, sanitizer, rendererFactory, renderer
|
||||
selectorOrNode: rootSelectorOrNode, sanitizer, rendererFactory, renderer, errorHandler
|
||||
};
|
||||
}
|
||||
|
||||
@ -439,7 +441,6 @@ function callWithDebugContext(action: DebugAction, fn: any, self: any, args: any
|
||||
if (isViewDebugError(e) || !_currentView) {
|
||||
throw e;
|
||||
}
|
||||
_currentView.state |= ViewState.Errored;
|
||||
throw viewWrappedDebugError(e, getCurrentDebugContext() !);
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import {Injector} from '../di';
|
||||
import {ErrorHandler} from '../error_handler';
|
||||
import {NgModuleRef} from '../linker/ng_module_factory';
|
||||
import {QueryList} from '../linker/query_list';
|
||||
import {TemplateRef} from '../linker/template_ref';
|
||||
@ -323,10 +324,14 @@ export interface ViewData {
|
||||
* Bitmask of states
|
||||
*/
|
||||
export const enum ViewState {
|
||||
FirstCheck = 1 << 0,
|
||||
ChecksEnabled = 1 << 1,
|
||||
Errored = 1 << 2,
|
||||
Destroyed = 1 << 3
|
||||
BeforeFirstCheck = 1 << 0,
|
||||
FirstCheck = 1 << 1,
|
||||
Attached = 1 << 2,
|
||||
ChecksEnabled = 1 << 3,
|
||||
Destroyed = 1 << 4,
|
||||
|
||||
CatDetectChanges = Attached | ChecksEnabled,
|
||||
CatInit = BeforeFirstCheck | CatDetectChanges
|
||||
}
|
||||
|
||||
export interface DisposableFn { (): void; }
|
||||
@ -429,6 +434,7 @@ export interface RootData {
|
||||
selectorOrNode: any;
|
||||
renderer: Renderer2;
|
||||
rendererFactory: RendererFactory2;
|
||||
errorHandler: ErrorHandler;
|
||||
sanitizer: Sanitizer;
|
||||
}
|
||||
|
||||
|
@ -100,10 +100,10 @@ export function checkAndUpdateBinding(
|
||||
export function checkBindingNoChanges(
|
||||
view: ViewData, def: NodeDef, bindingIdx: number, value: any) {
|
||||
const oldValue = view.oldValues[def.bindingIndex + bindingIdx];
|
||||
if ((view.state & ViewState.FirstCheck) || !devModeEqual(oldValue, value)) {
|
||||
if ((view.state & ViewState.BeforeFirstCheck) || !devModeEqual(oldValue, value)) {
|
||||
throw expressionChangedAfterItHasBeenCheckedError(
|
||||
Services.createDebugContext(view, def.index), oldValue, value,
|
||||
(view.state & ViewState.FirstCheck) !== 0);
|
||||
(view.state & ViewState.BeforeFirstCheck) !== 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -211,7 +211,7 @@ function createView(
|
||||
viewContainerParent: null, parentNodeDef,
|
||||
context: null,
|
||||
component: null, nodes,
|
||||
state: ViewState.FirstCheck | ViewState.ChecksEnabled, root, renderer,
|
||||
state: ViewState.CatInit, root, renderer,
|
||||
oldValues: new Array(def.bindingCount), disposables
|
||||
};
|
||||
return view;
|
||||
@ -323,6 +323,12 @@ export function checkNoChangesView(view: ViewData) {
|
||||
}
|
||||
|
||||
export function checkAndUpdateView(view: ViewData) {
|
||||
if (view.state & ViewState.BeforeFirstCheck) {
|
||||
view.state &= ~ViewState.BeforeFirstCheck;
|
||||
view.state |= ViewState.FirstCheck;
|
||||
} else {
|
||||
view.state &= ~ViewState.FirstCheck;
|
||||
}
|
||||
Services.updateDirectives(view, CheckType.CheckAndUpdate);
|
||||
execEmbeddedViewsAction(view, ViewAction.CheckAndUpdate);
|
||||
execQueriesAction(
|
||||
@ -345,7 +351,6 @@ export function checkAndUpdateView(view: ViewData) {
|
||||
if (view.def.flags & ViewFlags.OnPush) {
|
||||
view.state &= ~ViewState.ChecksEnabled;
|
||||
}
|
||||
view.state &= ~ViewState.FirstCheck;
|
||||
}
|
||||
|
||||
export function checkAndUpdateNode(
|
||||
@ -453,7 +458,7 @@ function checkNoChangesQuery(view: ViewData, nodeDef: NodeDef) {
|
||||
if (queryList.dirty) {
|
||||
throw expressionChangedAfterItHasBeenCheckedError(
|
||||
Services.createDebugContext(view, nodeDef.index), `Query ${nodeDef.query!.id} not dirty`,
|
||||
`Query ${nodeDef.query!.id} dirty`, (view.state & ViewState.FirstCheck) !== 0);
|
||||
`Query ${nodeDef.query!.id} dirty`, (view.state & ViewState.BeforeFirstCheck) !== 0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -542,14 +547,14 @@ function callViewAction(view: ViewData, action: ViewAction) {
|
||||
const viewState = view.state;
|
||||
switch (action) {
|
||||
case ViewAction.CheckNoChanges:
|
||||
if ((viewState & ViewState.ChecksEnabled) &&
|
||||
(viewState & (ViewState.Errored | ViewState.Destroyed)) === 0) {
|
||||
if ((viewState & ViewState.CatDetectChanges) === ViewState.CatDetectChanges &&
|
||||
(viewState & ViewState.Destroyed) === 0) {
|
||||
checkNoChangesView(view);
|
||||
}
|
||||
break;
|
||||
case ViewAction.CheckAndUpdate:
|
||||
if ((viewState & ViewState.ChecksEnabled) &&
|
||||
(viewState & (ViewState.Errored | ViewState.Destroyed)) === 0) {
|
||||
if ((viewState & ViewState.CatDetectChanges) === ViewState.CatDetectChanges &&
|
||||
(viewState & ViewState.Destroyed) === 0) {
|
||||
checkAndUpdateView(view);
|
||||
}
|
||||
break;
|
||||
|
@ -13,8 +13,8 @@ import {EventEmitter} from '../event_emitter';
|
||||
*
|
||||
* The most common use of this service is to optimize performance when starting a work consisting of
|
||||
* one or more asynchronous tasks that don't require UI updates or error handling to be handled by
|
||||
* Angular. Such tasks can be kicked off via {@link runOutsideAngular} and if needed, these tasks
|
||||
* can reenter the Angular zone via {@link run}.
|
||||
* Angular. Such tasks can be kicked off via {@link #runOutsideAngular} and if needed, these tasks
|
||||
* can reenter the Angular zone via {@link #run}.
|
||||
*
|
||||
* <!-- TODO: add/fix links to:
|
||||
* - docs explaining zones and the use of zones in Angular and change-detection
|
||||
@ -132,7 +132,7 @@ export class NgZone {
|
||||
* the function.
|
||||
*
|
||||
* Running functions via `run` allows you to reenter Angular zone from a task that was executed
|
||||
* outside of the Angular zone (typically started via {@link runOutsideAngular}).
|
||||
* outside of the Angular zone (typically started via {@link #runOutsideAngular}).
|
||||
*
|
||||
* Any future tasks or microtasks scheduled from within this function will continue executing from
|
||||
* within the Angular zone.
|
||||
@ -151,13 +151,14 @@ export class NgZone {
|
||||
* Executes the `fn` function synchronously in Angular's parent zone and returns value returned by
|
||||
* the function.
|
||||
*
|
||||
* Running functions via `runOutsideAngular` allows you to escape Angular's zone and do work that
|
||||
* Running functions via {@link #runOutsideAngular} allows you to escape Angular's zone and do
|
||||
* work that
|
||||
* doesn't trigger Angular change-detection or is subject to Angular's error handling.
|
||||
*
|
||||
* Any future tasks or microtasks scheduled from within this function will continue executing from
|
||||
* outside of the Angular zone.
|
||||
*
|
||||
* Use {@link run} to reenter the Angular zone and do work that updates the application model.
|
||||
* Use {@link #run} to reenter the Angular zone and do work that updates the application model.
|
||||
*/
|
||||
runOutsideAngular(fn: () => any): any { return this.outer.run(fn); }
|
||||
|
||||
|
@ -103,20 +103,33 @@ export function main() {
|
||||
describe('ApplicationRef', () => {
|
||||
beforeEach(() => { TestBed.configureTestingModule({imports: [createModule()]}); });
|
||||
|
||||
it('should throw when reentering tick', inject([ApplicationRef], (ref: ApplicationRef_) => {
|
||||
const view = jasmine.createSpyObj('view', ['detach', 'attachToAppRef']);
|
||||
const viewRef = jasmine.createSpyObj(
|
||||
'viewRef', ['detectChanges', 'detachFromAppRef', 'attachToAppRef']);
|
||||
viewRef.internalView = view;
|
||||
view.ref = viewRef;
|
||||
try {
|
||||
ref.attachView(viewRef);
|
||||
viewRef.detectChanges.and.callFake(() => ref.tick());
|
||||
expect(() => ref.tick()).toThrowError('ApplicationRef.tick is called recursively');
|
||||
} finally {
|
||||
ref.detachView(viewRef);
|
||||
}
|
||||
}));
|
||||
it('should throw when reentering tick', () => {
|
||||
@Component({template: '{{reenter()}}'})
|
||||
class ReenteringComponent {
|
||||
reenterCount = 1;
|
||||
reenterErr: any;
|
||||
|
||||
constructor(private appRef: ApplicationRef) {}
|
||||
|
||||
reenter() {
|
||||
if (this.reenterCount--) {
|
||||
try {
|
||||
this.appRef.tick();
|
||||
} catch (e) {
|
||||
this.reenterErr = e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const fixture = TestBed.configureTestingModule({declarations: [ReenteringComponent]})
|
||||
.createComponent(ReenteringComponent);
|
||||
const appRef = TestBed.get(ApplicationRef) as ApplicationRef;
|
||||
appRef.attachView(fixture.componentRef.hostView);
|
||||
appRef.tick();
|
||||
expect(fixture.componentInstance.reenterErr.message)
|
||||
.toBe('ApplicationRef.tick is called recursively');
|
||||
});
|
||||
|
||||
describe('APP_BOOTSTRAP_LISTENER', () => {
|
||||
let capturedCompRefs: ComponentRef<any>[];
|
||||
|
@ -765,6 +765,7 @@ export function main() {
|
||||
try {
|
||||
ctx.detectChanges(false);
|
||||
} catch (e) {
|
||||
expect(e.message).toBe('Boom!');
|
||||
errored = true;
|
||||
}
|
||||
expect(errored).toBe(true);
|
||||
@ -776,7 +777,8 @@ export function main() {
|
||||
try {
|
||||
ctx.detectChanges(false);
|
||||
} catch (e) {
|
||||
throw new Error('Second detectChanges() should not have run detection.');
|
||||
expect(e.message).toBe('Boom!');
|
||||
throw new Error('Second detectChanges() should not have called ngOnInit.');
|
||||
}
|
||||
expect(directiveLog.filter(['ngOnInit'])).toEqual([]);
|
||||
}));
|
||||
@ -1175,6 +1177,21 @@ export function main() {
|
||||
expect(renderLog.log).toEqual([]);
|
||||
}));
|
||||
|
||||
it('Detached should disable OnPush', fakeAsync(() => {
|
||||
const ctx = createCompFixture('<push-cmp [value]="value"></push-cmp>');
|
||||
ctx.componentInstance.value = 0;
|
||||
ctx.detectChanges();
|
||||
renderLog.clear();
|
||||
|
||||
const cmp: CompWithRef = queryDirs(ctx.debugElement, PushComp)[0];
|
||||
cmp.changeDetectorRef.detach();
|
||||
|
||||
ctx.componentInstance.value = 1;
|
||||
ctx.detectChanges();
|
||||
|
||||
expect(renderLog.log).toEqual([]);
|
||||
}));
|
||||
|
||||
it('Detached view can be checked locally', fakeAsync(() => {
|
||||
const ctx = createCompFixture('<wrap-comp-with-ref></wrap-comp-with-ref>');
|
||||
const cmp: CompWithRef = queryDirs(ctx.debugElement, CompWithRef)[0];
|
||||
@ -1225,7 +1242,6 @@ export function main() {
|
||||
|
||||
ctx.detectChanges();
|
||||
expect(cmp.renderCount).toBe(count);
|
||||
|
||||
}));
|
||||
|
||||
});
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {Compiler, ComponentFactory, 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, ReflectiveInjector, 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';
|
||||
@ -1480,16 +1480,18 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||
|
||||
const tc = fixture.debugElement.children[0];
|
||||
|
||||
try {
|
||||
tc.injector.get(DirectiveEmittingEvent).fireEvent('boom');
|
||||
} catch (e) {
|
||||
const c = getDebugContext(e);
|
||||
expect(getDOM().nodeName(c.renderNode).toUpperCase()).toEqual('SPAN');
|
||||
expect(getDOM().nodeName(c.componentRenderElement).toUpperCase()).toEqual('DIV');
|
||||
expect((<Injector>c.injector).get).toBeTruthy();
|
||||
expect(c.context).toBe(fixture.componentInstance);
|
||||
expect(c.references['local']).toBeDefined();
|
||||
}
|
||||
const errorHandler = tc.injector.get(ErrorHandler);
|
||||
let err: any;
|
||||
spyOn(errorHandler, 'handleError').and.callFake((e: any) => err = e);
|
||||
tc.injector.get(DirectiveEmittingEvent).fireEvent('boom');
|
||||
|
||||
expect(err).toBeTruthy();
|
||||
const c = getDebugContext(err);
|
||||
expect(getDOM().nodeName(c.renderNode).toUpperCase()).toEqual('SPAN');
|
||||
expect(getDOM().nodeName(c.componentRenderElement).toUpperCase()).toEqual('DIV');
|
||||
expect((<Injector>c.injector).get).toBeTruthy();
|
||||
expect(c.context).toBe(fixture.componentInstance);
|
||||
expect(c.references['local']).toBeDefined();
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
@ -6,15 +6,19 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ANALYZE_FOR_ENTRY_COMPONENTS, Component, ContentChild, Directive, InjectionToken, Injector, Input, Pipe, PipeTransform, Provider, QueryList, Renderer2, SimpleChanges, TemplateRef, ViewChildren, ViewContainerRef} from '@angular/core';
|
||||
import {TestBed, fakeAsync, tick} from '@angular/core/testing';
|
||||
import {By} from '@angular/platform-browser';
|
||||
import {ANALYZE_FOR_ENTRY_COMPONENTS, ApplicationRef, Component, ComponentRef, ContentChild, Directive, ErrorHandler, EventEmitter, HostListener, InjectionToken, Injector, Input, NgModule, NgModuleRef, NgZone, Output, Pipe, PipeTransform, Provider, QueryList, Renderer2, SimpleChanges, TemplateRef, ViewChildren, ViewContainerRef, destroyPlatform} from '@angular/core';
|
||||
import {TestBed, async, fakeAsync, inject, tick} from '@angular/core/testing';
|
||||
import {BrowserModule, By, DOCUMENT} from '@angular/platform-browser';
|
||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
|
||||
export function main() {
|
||||
describe('jit', () => { declareTests({useJit: true}); });
|
||||
|
||||
describe('no jit', () => { declareTests({useJit: false}); });
|
||||
|
||||
declareTestsUsingBootstrap();
|
||||
}
|
||||
|
||||
function declareTests({useJit}: {useJit: boolean}) {
|
||||
@ -365,6 +369,132 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||
expect(testDirs[1].tpl).toBeDefined();
|
||||
expect(testDirs[2].tpl).toBeDefined();
|
||||
});
|
||||
|
||||
it('should not add ng-version for dynamically created components', () => {
|
||||
@Component({template: ''})
|
||||
class App {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [App], entryComponents: [App]})
|
||||
class MyModule {
|
||||
}
|
||||
|
||||
const modRef = TestBed.configureTestingModule({imports: [MyModule]})
|
||||
.get(NgModuleRef) as NgModuleRef<MyModule>;
|
||||
const compRef =
|
||||
modRef.componentFactoryResolver.resolveComponentFactory(App).create(Injector.NULL);
|
||||
|
||||
expect(getDOM().hasAttribute(compRef.location.nativeElement, 'ng-version')).toBe(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function declareTestsUsingBootstrap() {
|
||||
// Place to put reproductions for regressions
|
||||
describe('regressions using bootstrap', () => {
|
||||
const COMP_SELECTOR = 'root-comp';
|
||||
|
||||
class MockConsole {
|
||||
errors: any[][] = [];
|
||||
error(...s: any[]): void { this.errors.push(s); }
|
||||
}
|
||||
|
||||
let logger: MockConsole;
|
||||
let errorHandler: ErrorHandler;
|
||||
|
||||
beforeEach(inject([DOCUMENT], (doc: any) => {
|
||||
destroyPlatform();
|
||||
const el = getDOM().createElement(COMP_SELECTOR, doc);
|
||||
getDOM().appendChild(doc.body, el);
|
||||
|
||||
logger = new MockConsole();
|
||||
errorHandler = new ErrorHandler();
|
||||
errorHandler._console = logger as any;
|
||||
}));
|
||||
|
||||
afterEach(() => { destroyPlatform(); });
|
||||
|
||||
if (getDOM().supportsDOMEvents()) {
|
||||
// This test needs a real DOM....
|
||||
|
||||
it('should keep change detecting if there was an error', (done) => {
|
||||
@Component({
|
||||
selector: COMP_SELECTOR,
|
||||
template:
|
||||
'<button (click)="next()"></button><button (click)="nextAndThrow()"></button><button (dirClick)="nextAndThrow()"></button><span>Value:{{value}}</span><span>{{throwIfNeeded()}}</span>'
|
||||
})
|
||||
class ErrorComp {
|
||||
value = 0;
|
||||
thrownValue = 0;
|
||||
next() { this.value++; }
|
||||
nextAndThrow() {
|
||||
this.value++;
|
||||
this.throwIfNeeded();
|
||||
}
|
||||
throwIfNeeded() {
|
||||
NgZone.assertInAngularZone();
|
||||
if (this.thrownValue !== this.value) {
|
||||
this.thrownValue = this.value;
|
||||
throw new Error(`Error: ${this.value}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Directive({selector: '[dirClick]'})
|
||||
class EventDir {
|
||||
@Output()
|
||||
dirClick = new EventEmitter();
|
||||
|
||||
@HostListener('click', ['$event'])
|
||||
onClick(event: any) { this.dirClick.next(event); }
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [BrowserModule],
|
||||
declarations: [ErrorComp, EventDir],
|
||||
bootstrap: [ErrorComp],
|
||||
providers: [{provide: ErrorHandler, useValue: errorHandler}],
|
||||
})
|
||||
class TestModule {
|
||||
}
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(TestModule).then((ref) => {
|
||||
NgZone.assertNotInAngularZone();
|
||||
const appRef = ref.injector.get(ApplicationRef) as ApplicationRef;
|
||||
const compRef = appRef.components[0] as ComponentRef<ErrorComp>;
|
||||
const compEl = compRef.location.nativeElement;
|
||||
const nextBtn = compEl.children[0];
|
||||
const nextAndThrowBtn = compEl.children[1];
|
||||
const nextAndThrowDirBtn = compEl.children[2];
|
||||
|
||||
nextBtn.click();
|
||||
assertValueAndErrors(compEl, 1, 0);
|
||||
nextBtn.click();
|
||||
assertValueAndErrors(compEl, 2, 2);
|
||||
|
||||
nextAndThrowBtn.click();
|
||||
assertValueAndErrors(compEl, 3, 4);
|
||||
nextAndThrowBtn.click();
|
||||
assertValueAndErrors(compEl, 4, 6);
|
||||
|
||||
nextAndThrowDirBtn.click();
|
||||
assertValueAndErrors(compEl, 5, 8);
|
||||
nextAndThrowDirBtn.click();
|
||||
assertValueAndErrors(compEl, 6, 10);
|
||||
|
||||
// Assert that there were no more errors
|
||||
expect(logger.errors.length).toBe(12);
|
||||
done();
|
||||
});
|
||||
|
||||
function assertValueAndErrors(compEl: any, value: number, errorIndex: number) {
|
||||
expect(compEl).toHaveText(`Value:${value}`);
|
||||
expect(logger.errors[errorIndex][0]).toBe('ERROR');
|
||||
expect(logger.errors[errorIndex][1].message).toBe(`Error: ${value}`);
|
||||
expect(logger.errors[errorIndex + 1][0]).toBe('ERROR CONTEXT');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ import {ResourceLoader} from '@angular/compiler';
|
||||
import {SourceMap} from '@angular/compiler/src/output/source_map';
|
||||
import {extractSourceMap, originalPositionFor} from '@angular/compiler/test/output/source_map_util';
|
||||
import {MockResourceLoader} from '@angular/compiler/testing/src/resource_loader_mock';
|
||||
import {Attribute, Component, Directive, ɵglobal} from '@angular/core';
|
||||
import {Attribute, Component, Directive, ErrorHandler, ɵglobal} from '@angular/core';
|
||||
import {getErrorLogger} from '@angular/core/src/errors';
|
||||
import {ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing';
|
||||
|
||||
@ -231,11 +231,10 @@ export function main() {
|
||||
const comp = compileAndCreateComponent(MyComp);
|
||||
|
||||
let error: any;
|
||||
try {
|
||||
comp.debugElement.children[0].children[0].triggerEventHandler('click', 'EVENT');
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
const errorHandler = TestBed.get(ErrorHandler);
|
||||
spyOn(errorHandler, 'handleError').and.callFake((e: any) => error = e);
|
||||
comp.debugElement.children[0].children[0].triggerEventHandler('click', 'EVENT');
|
||||
expect(error).toBeTruthy();
|
||||
// the stack should point to the binding
|
||||
expect(getSourcePositionForStack(error.stack)).toEqual({
|
||||
line: 2,
|
||||
|
@ -652,6 +652,46 @@ export function main() {
|
||||
expect(compEl.nativeElement).toHaveText('1');
|
||||
});
|
||||
|
||||
it('should inject ChangeDetectorRef of a same element component into a directive', () => {
|
||||
TestBed.configureTestingModule(
|
||||
{declarations: [PushComponentNeedsChangeDetectorRef, DirectiveNeedsChangeDetectorRef]});
|
||||
const cf = createComponentFixture(
|
||||
'<div componentNeedsChangeDetectorRef directiveNeedsChangeDetectorRef></div>');
|
||||
cf.detectChanges();
|
||||
const compEl = cf.debugElement.children[0];
|
||||
const comp = compEl.injector.get(PushComponentNeedsChangeDetectorRef);
|
||||
const dir = compEl.injector.get(DirectiveNeedsChangeDetectorRef);
|
||||
comp.counter = 1;
|
||||
cf.detectChanges();
|
||||
expect(compEl.nativeElement).toHaveText('0');
|
||||
dir.changeDetectorRef.markForCheck();
|
||||
cf.detectChanges();
|
||||
expect(compEl.nativeElement).toHaveText('1');
|
||||
});
|
||||
|
||||
it(`should not inject ChangeDetectorRef of a parent element's component into a directive`, () => {
|
||||
TestBed
|
||||
.configureTestingModule({
|
||||
declarations: [PushComponentNeedsChangeDetectorRef, DirectiveNeedsChangeDetectorRef]
|
||||
})
|
||||
.overrideComponent(
|
||||
PushComponentNeedsChangeDetectorRef,
|
||||
{set: {template: '<ng-content></ng-content>{{counter}}'}});
|
||||
const cf = createComponentFixture(
|
||||
'<div componentNeedsChangeDetectorRef><div directiveNeedsChangeDetectorRef></div></div>');
|
||||
cf.detectChanges();
|
||||
const compEl = cf.debugElement.children[0];
|
||||
const comp = compEl.injector.get(PushComponentNeedsChangeDetectorRef);
|
||||
const dirEl = compEl.children[0];
|
||||
const dir = dirEl.injector.get(DirectiveNeedsChangeDetectorRef);
|
||||
comp.counter = 1;
|
||||
cf.detectChanges();
|
||||
expect(compEl.nativeElement).toHaveText('0');
|
||||
dir.changeDetectorRef.markForCheck();
|
||||
cf.detectChanges();
|
||||
expect(compEl.nativeElement).toHaveText('0');
|
||||
});
|
||||
|
||||
it('should inject ViewContainerRef', () => {
|
||||
TestBed.configureTestingModule({declarations: [NeedsViewContainerRef]});
|
||||
const el = createComponent('<div needsViewContainerRef></div>');
|
||||
|
@ -230,7 +230,7 @@ export function main() {
|
||||
});
|
||||
}
|
||||
|
||||
it('should stop dirty checking views that threw errors in change detection', () => {
|
||||
it('should not stop dirty checking views that threw errors in change detection', () => {
|
||||
class AComp {
|
||||
a: any;
|
||||
}
|
||||
@ -255,8 +255,8 @@ export function main() {
|
||||
expect(update).toHaveBeenCalled();
|
||||
|
||||
update.calls.reset();
|
||||
Services.checkAndUpdateView(view);
|
||||
expect(update).not.toHaveBeenCalled();
|
||||
expect(() => Services.checkAndUpdateView(view)).toThrowError('Test');
|
||||
expect(update).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -6,9 +6,10 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Injector, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation, WrappedValue, getDebugNode} from '@angular/core';
|
||||
import {ErrorHandler, Injector, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation, WrappedValue, getDebugNode} from '@angular/core';
|
||||
import {getDebugContext} from '@angular/core/src/errors';
|
||||
import {ArgumentType, BindingFlags, DebugContext, NodeDef, NodeFlags, OutputType, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, elementDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
|
||||
import {ARG_TYPE_VALUES, checkNodeInlineOrDynamic, createRootView, isBrowser, removeNodes} from './helper';
|
||||
@ -282,17 +283,14 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should report debug info on event errors', () => {
|
||||
const handleErrorSpy = spyOn(TestBed.get(ErrorHandler), 'handleError');
|
||||
const addListenerSpy = spyOn(HTMLElement.prototype, 'addEventListener').and.callThrough();
|
||||
const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef([elementDef(
|
||||
NodeFlags.None, null !, null !, 0, 'button', null !, null !, [[null !, 'click']],
|
||||
() => { throw new Error('Test'); })]));
|
||||
|
||||
let err: any;
|
||||
try {
|
||||
addListenerSpy.calls.mostRecent().args[1]('SomeEvent');
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
addListenerSpy.calls.mostRecent().args[1]('SomeEvent');
|
||||
const err = handleErrorSpy.calls.mostRecent().args[0];
|
||||
expect(err).toBeTruthy();
|
||||
expect(err.message).toBe('Test');
|
||||
const debugCtx = getDebugContext(err);
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, ChangeDetectorRef, DoCheck, ElementRef, EventEmitter, Injector, OnChanges, OnDestroy, OnInit, RenderComponentType, Renderer, Renderer2, RootRenderer, Sanitizer, SecurityContext, SimpleChange, TemplateRef, ViewContainerRef, ViewEncapsulation, WrappedValue, getDebugNode} from '@angular/core';
|
||||
import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, ChangeDetectorRef, DoCheck, ElementRef, ErrorHandler, EventEmitter, Injector, OnChanges, OnDestroy, OnInit, RenderComponentType, Renderer, Renderer2, RootRenderer, Sanitizer, SecurityContext, SimpleChange, TemplateRef, ViewContainerRef, ViewEncapsulation, WrappedValue, getDebugNode} from '@angular/core';
|
||||
import {getDebugContext} from '@angular/core/src/errors';
|
||||
import {ArgumentType, BindingFlags, DebugContext, DepFlags, NodeDef, NodeFlags, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, asProviderData, directiveDef, elementDef, providerDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index';
|
||||
import {TestBed, inject, withModule} from '@angular/core/testing';
|
||||
@ -381,6 +381,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should report debug info on event errors', () => {
|
||||
const handleErrorSpy = spyOn(TestBed.get(ErrorHandler), 'handleError');
|
||||
let emitter = new EventEmitter<any>();
|
||||
|
||||
class SomeService {
|
||||
@ -395,12 +396,8 @@ export function main() {
|
||||
NodeFlags.None, null !, 0, SomeService, [], null !, {emitter: 'someEventName'})
|
||||
]));
|
||||
|
||||
let err: any;
|
||||
try {
|
||||
emitter.emit('someEventInstance');
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
emitter.emit('someEventInstance');
|
||||
const err = handleErrorSpy.calls.mostRecent().args[0];
|
||||
expect(err).toBeTruthy();
|
||||
const debugCtx = getDebugContext(err);
|
||||
expect(debugCtx.view).toBe(view);
|
||||
|
@ -59,7 +59,7 @@ const resolvedPromise = Promise.resolve(null);
|
||||
* This directive can be used by itself or as part of a larger form. All you need is the
|
||||
* `ngModel` selector to activate it.
|
||||
*
|
||||
* It accepts a domain model as an optional {@link @Input}. If you have a one-way binding
|
||||
* It accepts a domain model as an optional {@link Input}. If you have a one-way binding
|
||||
* to `ngModel` with `[]` syntax, changing the value of the domain model in the component
|
||||
* class will set the value in the view. If you have a two-way binding with `[()]` syntax
|
||||
* (also known as 'banana-box syntax'), the value in the UI will always be synced back to
|
||||
|
@ -45,12 +45,12 @@ export const formControlBinding: any = {
|
||||
* {@link AbstractControl}.
|
||||
*
|
||||
* **Set the value**: You can pass in an initial value when instantiating the {@link FormControl},
|
||||
* or you can set it programmatically later using {@link AbstractControl.setValue} or
|
||||
* {@link AbstractControl.patchValue}.
|
||||
* or you can set it programmatically later using {@link AbstractControl#setValue} or
|
||||
* {@link AbstractControl#patchValue}.
|
||||
*
|
||||
* **Listen to value**: If you want to listen to changes in the value of the control, you can
|
||||
* subscribe to the {@link AbstractControl.valueChanges} event. You can also listen to
|
||||
* {@link AbstractControl.statusChanges} to be notified when the validation status is
|
||||
* subscribe to the {@link AbstractControl#valueChanges} event. You can also listen to
|
||||
* {@link AbstractControl#statusChanges} to be notified when the validation status is
|
||||
* re-calculated.
|
||||
*
|
||||
* ### Example
|
||||
|
@ -45,7 +45,7 @@ export const controlNameBinding: any = {
|
||||
* closest {@link FormGroup} or {@link FormArray} above it.
|
||||
*
|
||||
* **Access the control**: You can access the {@link FormControl} associated with
|
||||
* this directive by using the {@link AbstractControl.get} method.
|
||||
* this directive by using the {@link AbstractControl#get} method.
|
||||
* Ex: `this.form.get('first');`
|
||||
*
|
||||
* **Get value**: the `value` property is always synced and available on the {@link FormControl}.
|
||||
@ -53,11 +53,11 @@ export const controlNameBinding: any = {
|
||||
*
|
||||
* **Set value**: You can set an initial value for the control when instantiating the
|
||||
* {@link FormControl}, or you can set it programmatically later using
|
||||
* {@link AbstractControl.setValue} or {@link AbstractControl.patchValue}.
|
||||
* {@link AbstractControl#setValue} or {@link AbstractControl#patchValue}.
|
||||
*
|
||||
* **Listen to value**: If you want to listen to changes in the value of the control, you can
|
||||
* subscribe to the {@link AbstractControl.valueChanges} event. You can also listen to
|
||||
* {@link AbstractControl.statusChanges} to be notified when the validation status is
|
||||
* subscribe to the {@link AbstractControl#valueChanges} event. You can also listen to
|
||||
* {@link AbstractControl#statusChanges} to be notified when the validation status is
|
||||
* re-calculated.
|
||||
*
|
||||
* ### Example
|
||||
|
@ -34,11 +34,11 @@ export const formDirectiveProvider: any = {
|
||||
*
|
||||
* **Set value**: You can set the form's initial value when instantiating the
|
||||
* {@link FormGroup}, or you can set it programmatically later using the {@link FormGroup}'s
|
||||
* {@link AbstractControl.setValue} or {@link AbstractControl.patchValue} methods.
|
||||
* {@link AbstractControl#setValue} or {@link AbstractControl#patchValue} methods.
|
||||
*
|
||||
* **Listen to value**: If you want to listen to changes in the value of the form, you can subscribe
|
||||
* to the {@link FormGroup}'s {@link AbstractControl.valueChanges} event. You can also listen to
|
||||
* its {@link AbstractControl.statusChanges} event to be notified when the validation status is
|
||||
* to the {@link FormGroup}'s {@link AbstractControl#valueChanges} event. You can also listen to
|
||||
* its {@link AbstractControl#statusChanges} event to be notified when the validation status is
|
||||
* re-calculated.
|
||||
*
|
||||
* Furthermore, you can listen to the directive's `ngSubmit` event to be notified when the user has
|
||||
|
@ -40,7 +40,7 @@ export const formGroupNameProvider: any = {
|
||||
* controls into their own nested object.
|
||||
*
|
||||
* **Access the group**: You can access the associated {@link FormGroup} using the
|
||||
* {@link AbstractControl.get} method. Ex: `this.form.get('name')`.
|
||||
* {@link AbstractControl#get} method. Ex: `this.form.get('name')`.
|
||||
*
|
||||
* You can also access individual controls within the group using dot syntax.
|
||||
* Ex: `this.form.get('name.first')`
|
||||
@ -50,11 +50,11 @@ export const formGroupNameProvider: any = {
|
||||
*
|
||||
* **Set the value**: You can set an initial value for each child control when instantiating
|
||||
* the {@link FormGroup}, or you can set it programmatically later using
|
||||
* {@link AbstractControl.setValue} or {@link AbstractControl.patchValue}.
|
||||
* {@link AbstractControl#setValue} or {@link AbstractControl#patchValue}.
|
||||
*
|
||||
* **Listen to value**: If you want to listen to changes in the value of the group, you can
|
||||
* subscribe to the {@link AbstractControl.valueChanges} event. You can also listen to
|
||||
* {@link AbstractControl.statusChanges} to be notified when the validation status is
|
||||
* subscribe to the {@link AbstractControl#valueChanges} event. You can also listen to
|
||||
* {@link AbstractControl#statusChanges} to be notified when the validation status is
|
||||
* re-calculated.
|
||||
*
|
||||
* ### Example
|
||||
@ -111,7 +111,7 @@ export const formArrayNameProvider: any = {
|
||||
* form controls dynamically.
|
||||
*
|
||||
* **Access the array**: You can access the associated {@link FormArray} using the
|
||||
* {@link AbstractControl.get} method on the parent {@link FormGroup}.
|
||||
* {@link AbstractControl#get} method on the parent {@link FormGroup}.
|
||||
* Ex: `this.form.get('cities')`.
|
||||
*
|
||||
* **Get the value**: the `value` property is always synced and available on the
|
||||
@ -119,12 +119,12 @@ export const formArrayNameProvider: any = {
|
||||
*
|
||||
* **Set the value**: You can set an initial value for each child control when instantiating
|
||||
* the {@link FormArray}, or you can set the value programmatically later using the
|
||||
* {@link FormArray}'s {@link AbstractControl.setValue} or {@link AbstractControl.patchValue}
|
||||
* {@link FormArray}'s {@link AbstractControl#setValue} or {@link AbstractControl#patchValue}
|
||||
* methods.
|
||||
*
|
||||
* **Listen to value**: If you want to listen to changes in the value of the array, you can
|
||||
* subscribe to the {@link FormArray}'s {@link AbstractControl.valueChanges} event. You can also
|
||||
* listen to its {@link AbstractControl.statusChanges} event to be notified when the validation
|
||||
* subscribe to the {@link FormArray}'s {@link AbstractControl#valueChanges} event. You can also
|
||||
* listen to its {@link AbstractControl#statusChanges} event to be notified when the validation
|
||||
* status is re-calculated.
|
||||
*
|
||||
* **Add new controls**: You can add new controls to the {@link FormArray} dynamically by
|
||||
|
@ -659,17 +659,18 @@ export class FormControl extends AbstractControl {
|
||||
* If `emitViewToModelChange` is `true`, an ngModelChange event will be fired to update the
|
||||
* model. This is the default behavior if `emitViewToModelChange` is not specified.
|
||||
*/
|
||||
setValue(value: any, {onlySelf, emitEvent, emitModelToViewChange, emitViewToModelChange}: {
|
||||
setValue(value: any, options: {
|
||||
onlySelf?: boolean,
|
||||
emitEvent?: boolean,
|
||||
emitModelToViewChange?: boolean,
|
||||
emitViewToModelChange?: boolean
|
||||
} = {}): void {
|
||||
this._value = value;
|
||||
if (this._onChange.length && emitModelToViewChange !== false) {
|
||||
this._onChange.forEach((changeFn) => changeFn(this._value, emitViewToModelChange !== false));
|
||||
if (this._onChange.length && options.emitModelToViewChange !== false) {
|
||||
this._onChange.forEach(
|
||||
(changeFn) => changeFn(this._value, options.emitViewToModelChange !== false));
|
||||
}
|
||||
this.updateValueAndValidity({onlySelf, emitEvent});
|
||||
this.updateValueAndValidity(options);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -716,12 +717,11 @@ export class FormControl extends AbstractControl {
|
||||
* console.log(this.control.status); // 'DISABLED'
|
||||
* ```
|
||||
*/
|
||||
reset(formState: any = null, {onlySelf, emitEvent}: {onlySelf?: boolean,
|
||||
emitEvent?: boolean} = {}): void {
|
||||
reset(formState: any = null, options: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
|
||||
this._applyFormState(formState);
|
||||
this.markAsPristine({onlySelf});
|
||||
this.markAsUntouched({onlySelf});
|
||||
this.setValue(this._value, {onlySelf, emitEvent});
|
||||
this.markAsPristine(options);
|
||||
this.markAsUntouched(options);
|
||||
this.setValue(this._value, options);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -886,7 +886,7 @@ export class FormGroup extends AbstractControl {
|
||||
* Check whether there is an enabled control with the given name in the group.
|
||||
*
|
||||
* It will return false for disabled controls. If you'd like to check for
|
||||
* existence in the group only, use {@link AbstractControl.get} instead.
|
||||
* existence in the group only, use {@link AbstractControl#get} instead.
|
||||
*/
|
||||
contains(controlName: string): boolean {
|
||||
return this.controls.hasOwnProperty(controlName) && this.controls[controlName].enabled;
|
||||
@ -914,15 +914,14 @@ export class FormGroup extends AbstractControl {
|
||||
*
|
||||
* ```
|
||||
*/
|
||||
setValue(
|
||||
value: {[key: string]: any},
|
||||
{onlySelf, emitEvent}: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
|
||||
setValue(value: {[key: string]: any}, options: {onlySelf?: boolean, emitEvent?: boolean} = {}):
|
||||
void {
|
||||
this._checkAllValuesPresent(value);
|
||||
Object.keys(value).forEach(name => {
|
||||
this._throwIfControlMissing(name);
|
||||
this.controls[name].setValue(value[name], {onlySelf: true, emitEvent});
|
||||
this.controls[name].setValue(value[name], {onlySelf: true, emitEvent: options.emitEvent});
|
||||
});
|
||||
this.updateValueAndValidity({onlySelf, emitEvent});
|
||||
this.updateValueAndValidity(options);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -946,15 +945,14 @@ export class FormGroup extends AbstractControl {
|
||||
*
|
||||
* ```
|
||||
*/
|
||||
patchValue(
|
||||
value: {[key: string]: any},
|
||||
{onlySelf, emitEvent}: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
|
||||
patchValue(value: {[key: string]: any}, options: {onlySelf?: boolean, emitEvent?: boolean} = {}):
|
||||
void {
|
||||
Object.keys(value).forEach(name => {
|
||||
if (this.controls[name]) {
|
||||
this.controls[name].patchValue(value[name], {onlySelf: true, emitEvent});
|
||||
this.controls[name].patchValue(value[name], {onlySelf: true, emitEvent: options.emitEvent});
|
||||
}
|
||||
});
|
||||
this.updateValueAndValidity({onlySelf, emitEvent});
|
||||
this.updateValueAndValidity(options);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -989,14 +987,13 @@ export class FormGroup extends AbstractControl {
|
||||
* console.log(this.form.get('first').status); // 'DISABLED'
|
||||
* ```
|
||||
*/
|
||||
reset(value: any = {}, {onlySelf, emitEvent}: {onlySelf?: boolean, emitEvent?: boolean} = {}):
|
||||
void {
|
||||
reset(value: any = {}, options: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
|
||||
this._forEachChild((control: AbstractControl, name: string) => {
|
||||
control.reset(value[name], {onlySelf: true, emitEvent});
|
||||
control.reset(value[name], {onlySelf: true, emitEvent: options.emitEvent});
|
||||
});
|
||||
this.updateValueAndValidity({onlySelf, emitEvent});
|
||||
this._updatePristine({onlySelf});
|
||||
this._updateTouched({onlySelf});
|
||||
this.updateValueAndValidity(options);
|
||||
this._updatePristine(options);
|
||||
this._updateTouched(options);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1222,14 +1219,13 @@ export class FormArray extends AbstractControl {
|
||||
* console.log(arr.value); // ['Nancy', 'Drew']
|
||||
* ```
|
||||
*/
|
||||
setValue(value: any[], {onlySelf, emitEvent}: {onlySelf?: boolean, emitEvent?: boolean} = {}):
|
||||
void {
|
||||
setValue(value: any[], options: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
|
||||
this._checkAllValuesPresent(value);
|
||||
value.forEach((newValue: any, index: number) => {
|
||||
this._throwIfControlMissing(index);
|
||||
this.at(index).setValue(newValue, {onlySelf: true, emitEvent});
|
||||
this.at(index).setValue(newValue, {onlySelf: true, emitEvent: options.emitEvent});
|
||||
});
|
||||
this.updateValueAndValidity({onlySelf, emitEvent});
|
||||
this.updateValueAndValidity(options);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1252,14 +1248,13 @@ export class FormArray extends AbstractControl {
|
||||
* console.log(arr.value); // ['Nancy', null]
|
||||
* ```
|
||||
*/
|
||||
patchValue(value: any[], {onlySelf, emitEvent}: {onlySelf?: boolean, emitEvent?: boolean} = {}):
|
||||
void {
|
||||
patchValue(value: any[], options: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
|
||||
value.forEach((newValue: any, index: number) => {
|
||||
if (this.at(index)) {
|
||||
this.at(index).patchValue(newValue, {onlySelf: true, emitEvent});
|
||||
this.at(index).patchValue(newValue, {onlySelf: true, emitEvent: options.emitEvent});
|
||||
}
|
||||
});
|
||||
this.updateValueAndValidity({onlySelf, emitEvent});
|
||||
this.updateValueAndValidity(options);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1293,14 +1288,13 @@ export class FormArray extends AbstractControl {
|
||||
* console.log(this.arr.get(0).status); // 'DISABLED'
|
||||
* ```
|
||||
*/
|
||||
reset(value: any = [], {onlySelf, emitEvent}: {onlySelf?: boolean, emitEvent?: boolean} = {}):
|
||||
void {
|
||||
reset(value: any = [], options: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
|
||||
this._forEachChild((control: AbstractControl, index: number) => {
|
||||
control.reset(value[index], {onlySelf: true, emitEvent});
|
||||
control.reset(value[index], {onlySelf: true, emitEvent: options.emitEvent});
|
||||
});
|
||||
this.updateValueAndValidity({onlySelf, emitEvent});
|
||||
this._updatePristine({onlySelf});
|
||||
this._updateTouched({onlySelf});
|
||||
this.updateValueAndValidity(options);
|
||||
this._updatePristine(options);
|
||||
this._updateTouched(options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -24,9 +24,6 @@ function isEmptyInputValue(value: any): boolean {
|
||||
*
|
||||
* Provide this using `multi: true` to add validators.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* {@example core/forms/ts/ng_validators/ng_validators.ts region='ng_validators'}
|
||||
* @stable
|
||||
*/
|
||||
export const NG_VALIDATORS = new InjectionToken<Array<Validator|Function>>('NgValidators');
|
||||
|
@ -13,7 +13,9 @@ export class AstPath<T> {
|
||||
get head(): T|undefined { return this.path[0]; }
|
||||
get tail(): T|undefined { return this.path[this.path.length - 1]; }
|
||||
|
||||
parentOf(node: T): T|undefined { return this.path[this.path.indexOf(node) - 1]; }
|
||||
parentOf(node: T|undefined): T|undefined {
|
||||
return node && this.path[this.path.indexOf(node) - 1];
|
||||
}
|
||||
childOf(node: T): T|undefined { return this.path[this.path.indexOf(node) + 1]; }
|
||||
|
||||
first<N extends T>(ctor: {new (...args: any[]): N}): N|undefined {
|
||||
|
@ -33,71 +33,73 @@ export function getTemplateCompletions(templateInfo: TemplateInfo): Completions|
|
||||
let result: Completions|undefined = undefined;
|
||||
let {htmlAst, templateAst, template} = templateInfo;
|
||||
// The templateNode starts at the delimiter character so we add 1 to skip it.
|
||||
let templatePosition = templateInfo.position ! - template.span.start;
|
||||
let path = new HtmlAstPath(htmlAst, templatePosition);
|
||||
let mostSpecific = path.tail;
|
||||
if (path.empty) {
|
||||
result = elementCompletions(templateInfo, path);
|
||||
} else {
|
||||
let astPosition = templatePosition - mostSpecific !.sourceSpan !.start.offset;
|
||||
mostSpecific !.visit(
|
||||
{
|
||||
visitElement(ast) {
|
||||
let startTagSpan = spanOf(ast.sourceSpan);
|
||||
let tagLen = ast.name.length;
|
||||
if (templatePosition <=
|
||||
startTagSpan !.start + tagLen + 1 /* 1 for the opening angle bracked */) {
|
||||
// If we are in the tag then return the element completions.
|
||||
result = elementCompletions(templateInfo, path);
|
||||
} else if (templatePosition < startTagSpan !.end) {
|
||||
// We are in the attribute section of the element (but not in an attribute).
|
||||
// Return the attribute completions.
|
||||
result = attributeCompletions(templateInfo, path);
|
||||
}
|
||||
},
|
||||
visitAttribute(ast) {
|
||||
if (!ast.valueSpan || !inSpan(templatePosition, spanOf(ast.valueSpan))) {
|
||||
// We are in the name of an attribute. Show attribute completions.
|
||||
result = attributeCompletions(templateInfo, path);
|
||||
} else if (ast.valueSpan && inSpan(templatePosition, spanOf(ast.valueSpan))) {
|
||||
result = attributeValueCompletions(templateInfo, templatePosition, ast);
|
||||
}
|
||||
},
|
||||
visitText(ast) {
|
||||
// Check if we are in a entity.
|
||||
result = entityCompletions(getSourceText(template, spanOf(ast) !), astPosition);
|
||||
if (result) return result;
|
||||
result = interpolationCompletions(templateInfo, templatePosition);
|
||||
if (result) return result;
|
||||
let element = path.first(Element);
|
||||
if (element) {
|
||||
let definition = getHtmlTagDefinition(element.name);
|
||||
if (definition.contentType === TagContentType.PARSABLE_DATA) {
|
||||
if (templateInfo.position != null) {
|
||||
let templatePosition = templateInfo.position - template.span.start;
|
||||
let path = new HtmlAstPath(htmlAst, templatePosition);
|
||||
let mostSpecific = path.tail;
|
||||
if (path.empty || !mostSpecific) {
|
||||
result = elementCompletions(templateInfo, path);
|
||||
} else {
|
||||
let astPosition = templatePosition - mostSpecific.sourceSpan.start.offset;
|
||||
mostSpecific.visit(
|
||||
{
|
||||
visitElement(ast) {
|
||||
let startTagSpan = spanOf(ast.sourceSpan);
|
||||
let tagLen = ast.name.length;
|
||||
if (templatePosition <=
|
||||
startTagSpan.start + tagLen + 1 /* 1 for the opening angle bracked */) {
|
||||
// If we are in the tag then return the element completions.
|
||||
result = elementCompletions(templateInfo, path);
|
||||
} else if (templatePosition < startTagSpan.end) {
|
||||
// We are in the attribute section of the element (but not in an attribute).
|
||||
// Return the attribute completions.
|
||||
result = attributeCompletions(templateInfo, path);
|
||||
}
|
||||
},
|
||||
visitAttribute(ast) {
|
||||
if (!ast.valueSpan || !inSpan(templatePosition, spanOf(ast.valueSpan))) {
|
||||
// We are in the name of an attribute. Show attribute completions.
|
||||
result = attributeCompletions(templateInfo, path);
|
||||
} else if (ast.valueSpan && inSpan(templatePosition, spanOf(ast.valueSpan))) {
|
||||
result = attributeValueCompletions(templateInfo, templatePosition, ast);
|
||||
}
|
||||
},
|
||||
visitText(ast) {
|
||||
// Check if we are in a entity.
|
||||
result = entityCompletions(getSourceText(template, spanOf(ast)), astPosition);
|
||||
if (result) return result;
|
||||
result = interpolationCompletions(templateInfo, templatePosition);
|
||||
if (result) return result;
|
||||
let element = path.first(Element);
|
||||
if (element) {
|
||||
let definition = getHtmlTagDefinition(element.name);
|
||||
if (definition.contentType === TagContentType.PARSABLE_DATA) {
|
||||
result = voidElementAttributeCompletions(templateInfo, path);
|
||||
if (!result) {
|
||||
// If the element can hold content Show element completions.
|
||||
result = elementCompletions(templateInfo, path);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If no element container, implies parsable data so show elements.
|
||||
result = voidElementAttributeCompletions(templateInfo, path);
|
||||
if (!result) {
|
||||
// If the element can hold content Show element completions.
|
||||
result = elementCompletions(templateInfo, path);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If no element container, implies parsable data so show elements.
|
||||
result = voidElementAttributeCompletions(templateInfo, path);
|
||||
if (!result) {
|
||||
result = elementCompletions(templateInfo, path);
|
||||
}
|
||||
}
|
||||
},
|
||||
visitComment(ast) {},
|
||||
visitExpansion(ast) {},
|
||||
visitExpansionCase(ast) {}
|
||||
},
|
||||
visitComment(ast) {},
|
||||
visitExpansion(ast) {},
|
||||
visitExpansionCase(ast) {}
|
||||
},
|
||||
null);
|
||||
null);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function attributeCompletions(info: TemplateInfo, path: HtmlAstPath): Completions|undefined {
|
||||
let item = path.tail instanceof Element ? path.tail : path.parentOf(path.tail !);
|
||||
let item = path.tail instanceof Element ? path.tail : path.parentOf(path.tail);
|
||||
if (item instanceof Element) {
|
||||
return attributeCompletionsForElement(info, item.name, item);
|
||||
}
|
||||
@ -213,11 +215,12 @@ function elementCompletions(info: TemplateInfo, path: HtmlAstPath): Completions|
|
||||
let htmlNames = elementNames().filter(name => !(name in hiddenHtmlElements));
|
||||
|
||||
// Collect the elements referenced by the selectors
|
||||
let directiveElements =
|
||||
getSelectors(info).selectors.map(selector => selector.element).filter(name => !!name);
|
||||
let directiveElements = getSelectors(info)
|
||||
.selectors.map(selector => selector.element)
|
||||
.filter(name => !!name) as string[];
|
||||
|
||||
let components =
|
||||
directiveElements.map<Completion>(name => ({kind: 'component', name: name !, sort: name !}));
|
||||
directiveElements.map<Completion>(name => ({kind: 'component', name, sort: name}));
|
||||
let htmlElements = htmlNames.map<Completion>(name => ({kind: 'element', name: name, sort: name}));
|
||||
|
||||
// Return components and html elements
|
||||
@ -262,25 +265,25 @@ function voidElementAttributeCompletions(info: TemplateInfo, path: HtmlAstPath):
|
||||
undefined {
|
||||
let tail = path.tail;
|
||||
if (tail instanceof Text) {
|
||||
let match = tail.value.match(/<(\w(\w|\d|-)*:)?(\w(\w|\d|-)*)\s/) !;
|
||||
let match = tail.value.match(/<(\w(\w|\d|-)*:)?(\w(\w|\d|-)*)\s/);
|
||||
// The position must be after the match, otherwise we are still in a place where elements
|
||||
// are expected (such as `<|a` or `<a|`; we only want attributes for `<a |` or after).
|
||||
if (match && path.position >= match.index ! + match[0].length + tail.sourceSpan.start.offset) {
|
||||
if (match &&
|
||||
path.position >= (match.index || 0) + match[0].length + tail.sourceSpan.start.offset) {
|
||||
return attributeCompletionsForElement(info, match[3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ExpressionVisitor extends NullTemplateVisitor {
|
||||
private getExpressionScope: () => SymbolTable;
|
||||
result: Completions;
|
||||
|
||||
constructor(
|
||||
private info: TemplateInfo, private position: number, private attr?: Attribute,
|
||||
private getExpressionScope?: () => SymbolTable) {
|
||||
getExpressionScope?: () => SymbolTable) {
|
||||
super();
|
||||
if (!getExpressionScope) {
|
||||
this.getExpressionScope = () => info.template.members;
|
||||
}
|
||||
this.getExpressionScope = getExpressionScope || (() => info.template.members);
|
||||
}
|
||||
|
||||
visitDirectiveProperty(ast: BoundDirectivePropertyAst): void {
|
||||
@ -311,7 +314,8 @@ class ExpressionVisitor extends NullTemplateVisitor {
|
||||
this.info.expressionParser.parseTemplateBindings(key, this.attr.value, null);
|
||||
|
||||
// find the template binding that contains the position
|
||||
const valueRelativePosition = this.position - this.attr.valueSpan !.start.offset - 1;
|
||||
if (!this.attr.valueSpan) return;
|
||||
const valueRelativePosition = this.position - this.attr.valueSpan.start.offset - 1;
|
||||
const bindings = templateBindingResult.templateBindings;
|
||||
const binding =
|
||||
bindings.find(
|
||||
@ -340,10 +344,12 @@ class ExpressionVisitor extends NullTemplateVisitor {
|
||||
// We are after the '=' in a let clause. The valid values here are the members of the
|
||||
// template reference's type parameter.
|
||||
const directiveMetadata = selectorInfo.map.get(selector);
|
||||
const contextTable =
|
||||
this.info.template.query.getTemplateContext(directiveMetadata !.type.reference);
|
||||
if (contextTable) {
|
||||
this.result = this.symbolsToCompletions(contextTable.values());
|
||||
if (directiveMetadata) {
|
||||
const contextTable =
|
||||
this.info.template.query.getTemplateContext(directiveMetadata.type.reference);
|
||||
if (contextTable) {
|
||||
this.result = this.symbolsToCompletions(contextTable.values());
|
||||
}
|
||||
}
|
||||
} else if (binding.key && valueRelativePosition <= (binding.key.length - key.length)) {
|
||||
keyCompletions();
|
||||
@ -371,7 +377,7 @@ class ExpressionVisitor extends NullTemplateVisitor {
|
||||
const expressionPosition = this.position - ast.sourceSpan.start.offset;
|
||||
if (inSpan(expressionPosition, ast.value.span)) {
|
||||
const completions = getExpressionCompletions(
|
||||
this.getExpressionScope !(), ast.value, expressionPosition, this.info.template.query);
|
||||
this.getExpressionScope(), ast.value, expressionPosition, this.info.template.query);
|
||||
if (completions) {
|
||||
this.result = this.symbolsToCompletions(completions);
|
||||
}
|
||||
@ -380,8 +386,8 @@ class ExpressionVisitor extends NullTemplateVisitor {
|
||||
|
||||
private attributeValueCompletions(value: AST, position?: number) {
|
||||
const symbols = getExpressionCompletions(
|
||||
this.getExpressionScope !(), value,
|
||||
position == null ? this.attributeValuePosition : position, this.info.template.query);
|
||||
this.getExpressionScope(), value, position == null ? this.attributeValuePosition : position,
|
||||
this.info.template.query);
|
||||
if (symbols) {
|
||||
this.result = this.symbolsToCompletions(symbols);
|
||||
}
|
||||
@ -393,12 +399,13 @@ class ExpressionVisitor extends NullTemplateVisitor {
|
||||
}
|
||||
|
||||
private get attributeValuePosition() {
|
||||
return this.position - this.attr !.valueSpan !.start.offset - 1;
|
||||
if (this.attr && this.attr.valueSpan) {
|
||||
return this.position - this.attr.valueSpan.start.offset - 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function getSourceText(template: TemplateSource, span: Span): string {
|
||||
return template.source.substring(span.start, span.end);
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ export function getTemplateDiagnostics(
|
||||
results.push(...ast.parseErrors.map<Diagnostic>(
|
||||
e => ({
|
||||
kind: DiagnosticKind.Error,
|
||||
span: offsetSpan(spanOf(e.span) !, template.span.start),
|
||||
span: offsetSpan(spanOf(e.span), template.span.start),
|
||||
message: e.msg
|
||||
})));
|
||||
} else if (ast.templateAst) {
|
||||
@ -91,20 +91,24 @@ export function getDeclarationDiagnostics(
|
||||
|
||||
function getTemplateExpressionDiagnostics(
|
||||
template: TemplateSource, astResult: AstResult): Diagnostics {
|
||||
const info: TemplateInfo = {
|
||||
template,
|
||||
htmlAst: astResult.htmlAst !,
|
||||
directive: astResult.directive !,
|
||||
directives: astResult.directives !,
|
||||
pipes: astResult.pipes !,
|
||||
templateAst: astResult.templateAst !,
|
||||
expressionParser: astResult.expressionParser !
|
||||
};
|
||||
const visitor = new ExpressionDiagnosticsVisitor(
|
||||
info, (path: TemplateAstPath, includeEvent: boolean) =>
|
||||
getExpressionScope(info, path, includeEvent));
|
||||
templateVisitAll(visitor, astResult.templateAst !);
|
||||
return visitor.diagnostics;
|
||||
if (astResult.htmlAst && astResult.directive && astResult.directives && astResult.pipes &&
|
||||
astResult.templateAst && astResult.expressionParser) {
|
||||
const info: TemplateInfo = {
|
||||
template,
|
||||
htmlAst: astResult.htmlAst,
|
||||
directive: astResult.directive,
|
||||
directives: astResult.directives,
|
||||
pipes: astResult.pipes,
|
||||
templateAst: astResult.templateAst,
|
||||
expressionParser: astResult.expressionParser
|
||||
};
|
||||
const visitor = new ExpressionDiagnosticsVisitor(
|
||||
info, (path: TemplateAstPath, includeEvent: boolean) =>
|
||||
getExpressionScope(info, path, includeEvent));
|
||||
templateVisitAll(visitor, astResult.templateAst !);
|
||||
return visitor.diagnostics;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
class ExpressionDiagnosticsVisitor extends TemplateAstChildVisitor {
|
||||
@ -158,11 +162,11 @@ class ExpressionDiagnosticsVisitor extends TemplateAstChildVisitor {
|
||||
if (context && !context.has(ast.value)) {
|
||||
if (ast.value === '$implicit') {
|
||||
this.reportError(
|
||||
'The template context does not have an implicit value', spanOf(ast.sourceSpan) !);
|
||||
'The template context does not have an implicit value', spanOf(ast.sourceSpan));
|
||||
} else {
|
||||
this.reportError(
|
||||
`The template context does not defined a member called '${ast.value}'`,
|
||||
spanOf(ast.sourceSpan) !);
|
||||
spanOf(ast.sourceSpan));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -233,11 +237,13 @@ class ExpressionDiagnosticsVisitor extends TemplateAstChildVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
private reportError(message: string, span: Span) {
|
||||
this.diagnostics.push({
|
||||
span: offsetSpan(span, this.info.template.span.start),
|
||||
kind: DiagnosticKind.Error, message
|
||||
});
|
||||
private reportError(message: string, span: Span|undefined) {
|
||||
if (span) {
|
||||
this.diagnostics.push({
|
||||
span: offsetSpan(span, this.info.template.span.start),
|
||||
kind: DiagnosticKind.Error, message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private reportWarning(message: string, span: Span) {
|
||||
|
@ -495,8 +495,9 @@ class AstType implements ExpressionVisitor {
|
||||
// The type of a method is the selected methods result type.
|
||||
const method = receiverType.members().get(ast.name);
|
||||
if (!method) return this.reportError(`Unknown method ${ast.name}`, ast);
|
||||
if (!method.type !.callable) return this.reportError(`Member ${ast.name} is not callable`, ast);
|
||||
const signature = method.type !.selectSignature(ast.args.map(arg => this.getType(arg)));
|
||||
if (!method.type) return this.reportError(`Could not find a type for ${ast.name}`, ast);
|
||||
if (!method.type.callable) return this.reportError(`Member ${ast.name} is not callable`, ast);
|
||||
const signature = method.type.selectSignature(ast.args.map(arg => this.getType(arg)));
|
||||
if (!signature)
|
||||
return this.reportError(`Unable to resolve signature for call of method ${ast.name}`, ast);
|
||||
return signature.result;
|
||||
@ -601,7 +602,9 @@ function visitChildren(ast: AST, visitor: ExpressionVisitor) {
|
||||
visit(ast.falseExp);
|
||||
},
|
||||
visitFunctionCall(ast) {
|
||||
visit(ast.target !);
|
||||
if (ast.target) {
|
||||
visit(ast.target);
|
||||
}
|
||||
visitAll(ast.args);
|
||||
},
|
||||
visitImplicitReceiver(ast) {},
|
||||
@ -676,7 +679,7 @@ function getReferences(info: TemplateInfo): SymbolDeclaration[] {
|
||||
|
||||
function processReferences(references: ReferenceAst[]) {
|
||||
for (const reference of references) {
|
||||
let type: Symbol = undefined !;
|
||||
let type: Symbol|undefined = undefined;
|
||||
if (reference.value) {
|
||||
type = info.template.query.getTypeSymbol(tokenReference(reference.value));
|
||||
}
|
||||
@ -721,7 +724,7 @@ function getVarDeclarations(info: TemplateInfo, path: TemplateAstPath): SymbolDe
|
||||
.find(c => !!c);
|
||||
|
||||
// Determine the type of the context field referenced by variable.value.
|
||||
let type: Symbol = undefined !;
|
||||
let type: Symbol|undefined = undefined;
|
||||
if (context) {
|
||||
const value = context.get(variable.value);
|
||||
if (value) {
|
||||
@ -762,7 +765,10 @@ function refinedVariableType(
|
||||
const bindingType =
|
||||
new AstType(info.template.members, info.template.query, {}).getType(ngForOfBinding.value);
|
||||
if (bindingType) {
|
||||
return info.template.query.getElementType(bindingType) !;
|
||||
const result = info.template.query.getElementType(bindingType);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -81,24 +81,25 @@ class LanguageServiceImpl implements LanguageService {
|
||||
let template = this.host.getTemplateAt(fileName, position);
|
||||
if (template) {
|
||||
let astResult = this.getTemplateAst(template, fileName);
|
||||
if (astResult && astResult.htmlAst && astResult.templateAst)
|
||||
if (astResult && astResult.htmlAst && astResult.templateAst && astResult.directive &&
|
||||
astResult.directives && astResult.pipes && astResult.expressionParser)
|
||||
return {
|
||||
position,
|
||||
fileName,
|
||||
template,
|
||||
htmlAst: astResult.htmlAst,
|
||||
directive: astResult.directive !,
|
||||
directives: astResult.directives !,
|
||||
pipes: astResult.pipes !,
|
||||
directive: astResult.directive,
|
||||
directives: astResult.directives,
|
||||
pipes: astResult.pipes,
|
||||
templateAst: astResult.templateAst,
|
||||
expressionParser: astResult.expressionParser !
|
||||
expressionParser: astResult.expressionParser
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getTemplateAst(template: TemplateSource, contextFile: string): AstResult {
|
||||
let result: AstResult = undefined !;
|
||||
let result: AstResult|undefined = undefined;
|
||||
try {
|
||||
const resolvedMetadata =
|
||||
this.metadataResolver.getNonNormalizedDirectiveMetadata(template.type as any);
|
||||
@ -112,7 +113,7 @@ class LanguageServiceImpl implements LanguageService {
|
||||
config, expressionParser, new DomElementSchemaRegistry(), htmlParser, null !, []);
|
||||
const htmlResult = htmlParser.parse(template.source, '', true);
|
||||
const analyzedModules = this.host.getAnalyzedModules();
|
||||
let errors: Diagnostic[] = undefined !;
|
||||
let errors: Diagnostic[]|undefined = undefined;
|
||||
let ngModule = analyzedModules.ngModuleByPipeOrDirective.get(template.type);
|
||||
if (!ngModule) {
|
||||
// Reported by the the declaration diagnostics.
|
||||
@ -121,8 +122,7 @@ class LanguageServiceImpl implements LanguageService {
|
||||
if (ngModule) {
|
||||
const resolvedDirectives = ngModule.transitiveModule.directives.map(
|
||||
d => this.host.resolver.getNonNormalizedDirectiveMetadata(d.reference));
|
||||
const directives =
|
||||
resolvedDirectives.filter(d => d !== null).map(d => d !.metadata.toSummary());
|
||||
const directives = removeMissing(resolvedDirectives).map(d => d.metadata.toSummary());
|
||||
const pipes = ngModule.transitiveModule.pipes.map(
|
||||
p => this.host.resolver.getOrLoadPipeMetadata(p.reference).toSummary());
|
||||
const schemas = ngModule.schemas;
|
||||
@ -142,10 +142,14 @@ class LanguageServiceImpl implements LanguageService {
|
||||
}
|
||||
result = {errors: [{kind: DiagnosticKind.Error, message: e.message, span}]};
|
||||
}
|
||||
return result;
|
||||
return result || {};
|
||||
}
|
||||
}
|
||||
|
||||
function removeMissing<T>(values: (T | null | undefined)[]): T[] {
|
||||
return values.filter(e => !!e) as T[];
|
||||
}
|
||||
|
||||
function uniqueBySpan < T extends {
|
||||
span: Span;
|
||||
}
|
||||
@ -169,8 +173,8 @@ function uniqueBySpan < T extends {
|
||||
}
|
||||
}
|
||||
|
||||
function findSuitableDefaultModule(modules: NgAnalyzedModules): CompileNgModuleMetadata {
|
||||
let result: CompileNgModuleMetadata = undefined !;
|
||||
function findSuitableDefaultModule(modules: NgAnalyzedModules): CompileNgModuleMetadata|undefined {
|
||||
let result: CompileNgModuleMetadata|undefined = undefined;
|
||||
let resultSize = 0;
|
||||
for (const module of modules.ngModules) {
|
||||
const moduleSize = module.transitiveModule.directives.length;
|
||||
|
@ -21,22 +21,25 @@ export interface SymbolInfo {
|
||||
}
|
||||
|
||||
export function locateSymbol(info: TemplateInfo): SymbolInfo|undefined {
|
||||
const templatePosition = info.position ! - info.template.span.start;
|
||||
if (!info.position) return undefined;
|
||||
const templatePosition = info.position - info.template.span.start;
|
||||
const path = new TemplateAstPath(info.templateAst, templatePosition);
|
||||
if (path.tail) {
|
||||
let symbol: Symbol = undefined !;
|
||||
let span: Span = undefined !;
|
||||
let symbol: Symbol|undefined = undefined;
|
||||
let span: Span|undefined = undefined;
|
||||
const attributeValueSymbol = (ast: AST, inEvent: boolean = false): boolean => {
|
||||
const attribute = findAttribute(info);
|
||||
if (attribute) {
|
||||
if (inSpan(templatePosition, spanOf(attribute.valueSpan))) {
|
||||
const scope = getExpressionScope(info, path, inEvent);
|
||||
const expressionOffset = attribute.valueSpan !.start.offset + 1;
|
||||
const result = getExpressionSymbol(
|
||||
scope, ast, templatePosition - expressionOffset, info.template.query);
|
||||
if (result) {
|
||||
symbol = result.symbol;
|
||||
span = offsetSpan(result.span, expressionOffset);
|
||||
if (attribute.valueSpan) {
|
||||
const expressionOffset = attribute.valueSpan.start.offset + 1;
|
||||
const result = getExpressionSymbol(
|
||||
scope, ast, templatePosition - expressionOffset, info.template.query);
|
||||
if (result) {
|
||||
symbol = result.symbol;
|
||||
span = offsetSpan(result.span, expressionOffset);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -52,28 +55,28 @@ export function locateSymbol(info: TemplateInfo): SymbolInfo|undefined {
|
||||
if (component) {
|
||||
symbol = info.template.query.getTypeSymbol(component.directive.type.reference);
|
||||
symbol = symbol && new OverrideKindSymbol(symbol, 'component');
|
||||
span = spanOf(ast) !;
|
||||
span = spanOf(ast);
|
||||
} else {
|
||||
// Find a directive that matches the element name
|
||||
const directive =
|
||||
ast.directives.find(d => d.directive.selector !.indexOf(ast.name) >= 0);
|
||||
const directive = ast.directives.find(
|
||||
d => d.directive.selector != null && d.directive.selector.indexOf(ast.name) >= 0);
|
||||
if (directive) {
|
||||
symbol = info.template.query.getTypeSymbol(directive.directive.type.reference);
|
||||
symbol = symbol && new OverrideKindSymbol(symbol, 'directive');
|
||||
span = spanOf(ast) !;
|
||||
span = spanOf(ast);
|
||||
}
|
||||
}
|
||||
},
|
||||
visitReference(ast) {
|
||||
symbol = info.template.query.getTypeSymbol(tokenReference(ast.value));
|
||||
span = spanOf(ast) !;
|
||||
span = spanOf(ast);
|
||||
},
|
||||
visitVariable(ast) {},
|
||||
visitEvent(ast) {
|
||||
if (!attributeValueSymbol(ast.handler, /* inEvent */ true)) {
|
||||
symbol = findOutputBinding(info, path, ast) !;
|
||||
symbol = findOutputBinding(info, path, ast);
|
||||
symbol = symbol && new OverrideKindSymbol(symbol, 'event');
|
||||
span = spanOf(ast) !;
|
||||
span = spanOf(ast);
|
||||
}
|
||||
},
|
||||
visitElementProperty(ast) { attributeValueSymbol(ast.value); },
|
||||
@ -93,12 +96,12 @@ export function locateSymbol(info: TemplateInfo): SymbolInfo|undefined {
|
||||
visitText(ast) {},
|
||||
visitDirective(ast) {
|
||||
symbol = info.template.query.getTypeSymbol(ast.directive.type.reference);
|
||||
span = spanOf(ast) !;
|
||||
span = spanOf(ast);
|
||||
},
|
||||
visitDirectiveProperty(ast) {
|
||||
if (!attributeValueSymbol(ast.value)) {
|
||||
symbol = findInputBinding(info, path, ast) !;
|
||||
span = spanOf(ast) !;
|
||||
symbol = findInputBinding(info, path, ast);
|
||||
span = spanOf(ast);
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -110,9 +113,11 @@ export function locateSymbol(info: TemplateInfo): SymbolInfo|undefined {
|
||||
}
|
||||
|
||||
function findAttribute(info: TemplateInfo): Attribute|undefined {
|
||||
const templatePosition = info.position ! - info.template.span.start;
|
||||
const path = new HtmlAstPath(info.htmlAst, templatePosition);
|
||||
return path.first(Attribute);
|
||||
if (info.position) {
|
||||
const templatePosition = info.position - info.template.span.start;
|
||||
const path = new HtmlAstPath(info.htmlAst, templatePosition);
|
||||
return path.first(Attribute);
|
||||
}
|
||||
}
|
||||
|
||||
function findInputBinding(
|
||||
|
@ -22,18 +22,24 @@ class ReflectorModuleModuleResolutionHost implements ts.ModuleResolutionHost {
|
||||
if (snapshot) {
|
||||
return snapshot.getText(0, snapshot.getLength());
|
||||
}
|
||||
|
||||
// Typescript readFile() declaration should be `readFile(fileName: string): string | undefined
|
||||
return undefined !;
|
||||
}
|
||||
|
||||
directoryExists: (directoryName: string) => boolean;
|
||||
}
|
||||
|
||||
// This reflector host's purpose is to first set verboseInvalidExpressions to true so the
|
||||
// reflector will collect errors instead of throwing, and second to all deferring the creation
|
||||
// of the program until it is actually needed.
|
||||
export class ReflectorHost extends CompilerHost {
|
||||
constructor(
|
||||
private getProgram: () => ts.Program, serviceHost: ts.LanguageServiceHost,
|
||||
options: AngularCompilerOptions) {
|
||||
super(
|
||||
null !, options,
|
||||
// The ancestor value for program is overridden below so passing null here is safe.
|
||||
/* program */ null !, options,
|
||||
new ModuleResolutionHostAdapter(new ReflectorModuleModuleResolutionHost(serviceHost)),
|
||||
{verboseInvalidExpression: true});
|
||||
}
|
||||
|
@ -104,10 +104,10 @@ class TemplateAstPathBuilder extends TemplateAstChildVisitor {
|
||||
constructor(private position: number, private allowWidening: boolean) { super(); }
|
||||
|
||||
visit(ast: TemplateAst, context: any): any {
|
||||
let span = spanOf(ast) !;
|
||||
let span = spanOf(ast);
|
||||
if (inSpan(this.position, span)) {
|
||||
const len = this.path.length;
|
||||
if (!len || this.allowWidening || isNarrower(span, spanOf(this.path[len - 1]) !)) {
|
||||
if (!len || this.allowWidening || isNarrower(span, spanOf(this.path[len - 1]))) {
|
||||
this.path.push(ast);
|
||||
}
|
||||
} else {
|
||||
|
@ -25,14 +25,16 @@ export function create(info: any /* ts.server.PluginCreateInfo */): ts.LanguageS
|
||||
}
|
||||
|
||||
function diagnosticToDiagnostic(d: Diagnostic, file: ts.SourceFile): ts.Diagnostic {
|
||||
return {
|
||||
const result = {
|
||||
file,
|
||||
start: d.span.start,
|
||||
length: d.span.end - d.span.start,
|
||||
messageText: d.message,
|
||||
category: ts.DiagnosticCategory.Error,
|
||||
code: 0
|
||||
code: 0,
|
||||
source: 'ng'
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
function tryOperation(attempting: string, callback: () => void) {
|
||||
@ -78,7 +80,7 @@ export function create(info: any /* ts.server.PluginCreateInfo */): ts.LanguageS
|
||||
if (ours) {
|
||||
const displayParts: typeof base.displayParts = [];
|
||||
for (const part of ours.text) {
|
||||
displayParts.push({kind: part.language !, text: part.text});
|
||||
displayParts.push({kind: part.language || 'angular', text: part.text});
|
||||
}
|
||||
base = <any>{
|
||||
displayParts,
|
||||
|
@ -8,8 +8,6 @@
|
||||
|
||||
import {CompileDirectiveMetadata, CompileMetadataResolver, NgAnalyzedModules, StaticSymbol} from '@angular/compiler';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The range of a span of text in a source file.
|
||||
*
|
||||
@ -455,7 +453,7 @@ export interface LanguageServiceHost {
|
||||
* refers to a template file then the `position` should be ignored. If the `position` is not in a
|
||||
* template literal string then this method should return `undefined`.
|
||||
*/
|
||||
getTemplateAt(fileName: string, position: number): TemplateSource /* |undefined */;
|
||||
getTemplateAt(fileName: string, position: number): TemplateSource|undefined;
|
||||
|
||||
/**
|
||||
* Return the template source information for all templates in `fileName` or for `fileName` if it
|
||||
|
@ -74,22 +74,22 @@ export class DummyResourceLoader extends ResourceLoader {
|
||||
* @experimental
|
||||
*/
|
||||
export class TypeScriptServiceHost implements LanguageServiceHost {
|
||||
private _resolver: CompileMetadataResolver;
|
||||
private _resolver: CompileMetadataResolver|null;
|
||||
private _staticSymbolCache = new StaticSymbolCache();
|
||||
private _summaryResolver: AotSummaryResolver;
|
||||
private _staticSymbolResolver: StaticSymbolResolver;
|
||||
private _reflector: StaticReflector;
|
||||
private _reflector: StaticReflector|null;
|
||||
private _reflectorHost: ReflectorHost;
|
||||
private _checker: ts.TypeChecker;
|
||||
private _checker: ts.TypeChecker|null;
|
||||
private _typeCache: Symbol[] = [];
|
||||
private context: string|undefined;
|
||||
private lastProgram: ts.Program|undefined;
|
||||
private modulesOutOfDate: boolean = true;
|
||||
private analyzedModules: NgAnalyzedModules;
|
||||
private analyzedModules: NgAnalyzedModules|null;
|
||||
private service: LanguageService;
|
||||
private fileToComponent: Map<string, StaticSymbol>;
|
||||
private templateReferences: string[];
|
||||
private collectedErrors: Map<string, any[]>;
|
||||
private fileToComponent: Map<string, StaticSymbol>|null;
|
||||
private templateReferences: string[]|null;
|
||||
private collectedErrors: Map<string, any[]>|null;
|
||||
private fileVersions = new Map<string, string>();
|
||||
|
||||
constructor(private host: ts.LanguageServiceHost, private tsService: ts.LanguageService) {}
|
||||
@ -127,28 +127,28 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
|
||||
|
||||
getTemplateReferences(): string[] {
|
||||
this.ensureTemplateMap();
|
||||
return this.templateReferences;
|
||||
return this.templateReferences || [];
|
||||
}
|
||||
|
||||
getTemplateAt(fileName: string, position: number): TemplateSource {
|
||||
getTemplateAt(fileName: string, position: number): TemplateSource|undefined {
|
||||
let sourceFile = this.getSourceFile(fileName);
|
||||
if (sourceFile) {
|
||||
this.context = sourceFile.fileName;
|
||||
let node = this.findNode(sourceFile, position);
|
||||
if (node) {
|
||||
return this.getSourceFromNode(
|
||||
fileName, this.host.getScriptVersion(sourceFile.fileName), node) !;
|
||||
fileName, this.host.getScriptVersion(sourceFile.fileName), node);
|
||||
}
|
||||
} else {
|
||||
this.ensureTemplateMap();
|
||||
// TODO: Cannocalize the file?
|
||||
const componentType = this.fileToComponent.get(fileName);
|
||||
const componentType = this.fileToComponent !.get(fileName);
|
||||
if (componentType) {
|
||||
return this.getSourceFromType(
|
||||
fileName, this.host.getScriptVersion(fileName), componentType) !;
|
||||
fileName, this.host.getScriptVersion(fileName), componentType);
|
||||
}
|
||||
}
|
||||
return null !;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getAnalyzedModules(): NgAnalyzedModules {
|
||||
@ -172,7 +172,7 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
|
||||
|
||||
getTemplates(fileName: string): TemplateSources {
|
||||
this.ensureTemplateMap();
|
||||
const componentType = this.fileToComponent.get(fileName);
|
||||
const componentType = this.fileToComponent !.get(fileName);
|
||||
if (componentType) {
|
||||
const templateSource = this.getTemplateAt(fileName, 0);
|
||||
if (templateSource) {
|
||||
@ -225,10 +225,10 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
|
||||
updateAnalyzedModules() {
|
||||
this.validate();
|
||||
if (this.modulesOutOfDate) {
|
||||
this.analyzedModules = null !;
|
||||
this._reflector = null !;
|
||||
this.templateReferences = null !;
|
||||
this.fileToComponent = null !;
|
||||
this.analyzedModules = null;
|
||||
this._reflector = null;
|
||||
this.templateReferences = null;
|
||||
this.fileToComponent = null;
|
||||
this.ensureAnalyzedModules();
|
||||
this.modulesOutOfDate = false;
|
||||
}
|
||||
@ -273,10 +273,10 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
|
||||
}
|
||||
|
||||
private clearCaches() {
|
||||
this._checker = null !;
|
||||
this._checker = null;
|
||||
this._typeCache = [];
|
||||
this._resolver = null !;
|
||||
this.collectedErrors = null !;
|
||||
this._resolver = null;
|
||||
this.collectedErrors = null;
|
||||
this.modulesOutOfDate = true;
|
||||
}
|
||||
|
||||
@ -345,7 +345,7 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
|
||||
if (declaration && declaration.name) {
|
||||
const sourceFile = this.getSourceFile(fileName);
|
||||
return this.getSourceFromDeclaration(
|
||||
fileName, version, this.stringOf(node) !, shrink(spanOf(node)),
|
||||
fileName, version, this.stringOf(node) || '', shrink(spanOf(node)),
|
||||
this.reflector.getStaticSymbol(sourceFile.fileName, declaration.name.text),
|
||||
declaration, node, sourceFile);
|
||||
}
|
||||
@ -359,11 +359,13 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
|
||||
let result: TemplateSource|undefined = undefined;
|
||||
const declaration = this.getTemplateClassFromStaticSymbol(type);
|
||||
if (declaration) {
|
||||
const snapshot = this.host.getScriptSnapshot(fileName) !;
|
||||
const source = snapshot.getText(0, snapshot.getLength());
|
||||
result = this.getSourceFromDeclaration(
|
||||
fileName, version, source, {start: 0, end: source.length}, type, declaration, declaration,
|
||||
declaration.getSourceFile());
|
||||
const snapshot = this.host.getScriptSnapshot(fileName);
|
||||
if (snapshot) {
|
||||
const source = snapshot.getText(0, snapshot.getLength());
|
||||
result = this.getSourceFromDeclaration(
|
||||
fileName, version, source, {start: 0, end: source.length}, type, declaration,
|
||||
declaration, declaration.getSourceFile());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -398,17 +400,19 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
|
||||
return result;
|
||||
}
|
||||
|
||||
private collectError(error: any, filePath: string) {
|
||||
let errorMap = this.collectedErrors;
|
||||
if (!errorMap) {
|
||||
errorMap = this.collectedErrors = new Map();
|
||||
private collectError(error: any, filePath: string|null) {
|
||||
if (filePath) {
|
||||
let errorMap = this.collectedErrors;
|
||||
if (!errorMap || !this.collectedErrors) {
|
||||
errorMap = this.collectedErrors = new Map();
|
||||
}
|
||||
let errors = errorMap.get(filePath);
|
||||
if (!errors) {
|
||||
errors = [];
|
||||
this.collectedErrors.set(filePath, errors);
|
||||
}
|
||||
errors.push(error);
|
||||
}
|
||||
let errors = errorMap.get(filePath);
|
||||
if (!errors) {
|
||||
errors = [];
|
||||
this.collectedErrors.set(filePath, errors);
|
||||
}
|
||||
errors.push(error);
|
||||
}
|
||||
|
||||
private get staticSymbolResolver(): StaticSymbolResolver {
|
||||
@ -416,9 +420,9 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
|
||||
if (!result) {
|
||||
this._summaryResolver = new AotSummaryResolver(
|
||||
{
|
||||
loadSummary(filePath: string) { return null !; },
|
||||
isSourceFile(sourceFilePath: string) { return true !; },
|
||||
getOutputFileName(sourceFilePath: string) { return null !; }
|
||||
loadSummary(filePath: string) { return null; },
|
||||
isSourceFile(sourceFilePath: string) { return true; },
|
||||
getOutputFileName(sourceFilePath: string) { return sourceFilePath; }
|
||||
},
|
||||
this._staticSymbolCache);
|
||||
result = this._staticSymbolResolver = new StaticSymbolResolver(
|
||||
@ -445,7 +449,7 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
|
||||
const declarationNode = ts.forEachChild(source, child => {
|
||||
if (child.kind === ts.SyntaxKind.ClassDeclaration) {
|
||||
const classDeclaration = child as ts.ClassDeclaration;
|
||||
if (classDeclaration.name !.text === type.name) {
|
||||
if (classDeclaration.name != null && classDeclaration.name.text === type.name) {
|
||||
return classDeclaration;
|
||||
}
|
||||
}
|
||||
@ -614,7 +618,7 @@ class TypeScriptSymbolQuery implements SymbolQuery {
|
||||
private program: ts.Program, private checker: ts.TypeChecker, private source: ts.SourceFile,
|
||||
private fetchPipes: () => SymbolTable) {}
|
||||
|
||||
getTypeKind(symbol: Symbol): BuiltinType { return typeKindOf(this.getTsTypeOf(symbol) !); }
|
||||
getTypeKind(symbol: Symbol): BuiltinType { return typeKindOf(this.getTsTypeOf(symbol)); }
|
||||
|
||||
getBuiltinType(kind: BuiltinType): Symbol {
|
||||
// TODO: Replace with typeChecker API when available.
|
||||
@ -1303,7 +1307,7 @@ function getTypeParameterOf(type: ts.Type, name: string): ts.Type|undefined {
|
||||
}
|
||||
}
|
||||
|
||||
function typeKindOf(type: ts.Type): BuiltinType {
|
||||
function typeKindOf(type: ts.Type | undefined): BuiltinType {
|
||||
if (type) {
|
||||
if (type.flags & ts.TypeFlags.Any) {
|
||||
return BuiltinType.Any;
|
||||
@ -1318,17 +1322,19 @@ function typeKindOf(type: ts.Type): BuiltinType {
|
||||
return BuiltinType.Null;
|
||||
} else if (type.flags & ts.TypeFlags.Union) {
|
||||
// If all the constituent types of a union are the same kind, it is also that kind.
|
||||
let candidate: BuiltinType = undefined !;
|
||||
let candidate: BuiltinType|null = null;
|
||||
const unionType = type as ts.UnionType;
|
||||
if (unionType.types.length > 0) {
|
||||
candidate = typeKindOf(unionType.types[0]) !;
|
||||
candidate = typeKindOf(unionType.types[0]);
|
||||
for (const subType of unionType.types) {
|
||||
if (candidate != typeKindOf(subType)) {
|
||||
return BuiltinType.Other;
|
||||
}
|
||||
}
|
||||
}
|
||||
return candidate;
|
||||
if (candidate != null) {
|
||||
return candidate;
|
||||
}
|
||||
} else if (type.flags & ts.TypeFlags.TypeParameter) {
|
||||
return BuiltinType.Unbound;
|
||||
}
|
||||
|
@ -21,6 +21,9 @@ export function isParseSourceSpan(value: any): value is ParseSourceSpan {
|
||||
return value && !!value.start;
|
||||
}
|
||||
|
||||
export function spanOf(span: SpanHolder): Span;
|
||||
export function spanOf(span: ParseSourceSpan): Span;
|
||||
export function spanOf(span: SpanHolder | ParseSourceSpan | undefined): Span|undefined;
|
||||
export function spanOf(span?: SpanHolder | ParseSourceSpan): Span|undefined {
|
||||
if (!span) return undefined;
|
||||
if (isParseSourceSpan(span)) {
|
||||
@ -39,8 +42,8 @@ export function spanOf(span?: SpanHolder | ParseSourceSpan): Span|undefined {
|
||||
}
|
||||
|
||||
export function inSpan(position: number, span?: Span, exclusive?: boolean): boolean {
|
||||
return span && exclusive ? position >= span.start && position < span.end :
|
||||
position >= span !.start && position <= span !.end;
|
||||
return span != null && (exclusive ? position >= span.start && position < span.end :
|
||||
position >= span.start && position <= span.end);
|
||||
}
|
||||
|
||||
export function offsetSpan(span: Span, amount: number): Span {
|
||||
@ -54,7 +57,8 @@ export function isNarrower(spanA: Span, spanB: Span): boolean {
|
||||
export function hasTemplateReference(type: CompileTypeMetadata): boolean {
|
||||
if (type.diDeps) {
|
||||
for (let diDep of type.diDeps) {
|
||||
if (diDep.token !.identifier && identifierName(diDep.token !.identifier !) == 'TemplateRef')
|
||||
if (diDep.token && diDep.token.identifier &&
|
||||
identifierName(diDep.token !.identifier !) == 'TemplateRef')
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -13,8 +13,9 @@ import {WebWorkerPlatformLocation} from './platform_location';
|
||||
|
||||
|
||||
/**
|
||||
* Those providers should be added when the router is used in a worker context in addition to the
|
||||
* {@link ROUTER_PROVIDERS} and after them.
|
||||
* The {@link PlatformLocation} providers that should be added when the {@link Location} is used in
|
||||
* a worker context.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export const WORKER_APP_LOCATION_PROVIDERS = [
|
||||
|
@ -20,7 +20,6 @@ import {UrlSegment, UrlSegmentGroup} from './url_tree';
|
||||
* - `path` is a string that uses the route matcher DSL.
|
||||
* - `pathMatch` is a string that specifies the matching strategy.
|
||||
* - `matcher` defines a custom strategy for path matching and supersedes `path` and `pathMatch`.
|
||||
* See {@link UrlMatcher} for more info.
|
||||
* - `component` is a component type.
|
||||
* - `redirectTo` is the url fragment which will replace the current matched segment.
|
||||
* - `outlet` is the name of the outlet the component should be placed into.
|
||||
|
@ -92,7 +92,7 @@ export class RouterOutlet implements OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
/** @deprecated since v4, use {@link activateWith} */
|
||||
/** @deprecated since v4, use {@link #activateWith} */
|
||||
activate(
|
||||
activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver, injector: Injector,
|
||||
providers: ResolvedReflectiveProvider[], outletMap: RouterOutletMap): void {
|
||||
|
@ -11,7 +11,7 @@ import {ChangeDetectorRef, ComponentFactory, ComponentRef, EventEmitter, Injecto
|
||||
import * as angular from './angular1';
|
||||
import {PropertyBinding} from './component_info';
|
||||
import {$SCOPE} from './constants';
|
||||
import {getAttributesAsArray, getComponentName, hookupNgModel} from './util';
|
||||
import {getAttributesAsArray, getComponentName, hookupNgModel, strictEquals} from './util';
|
||||
|
||||
const INITIAL_VALUE = {
|
||||
__UNINITIALIZED__: true
|
||||
@ -75,16 +75,28 @@ export class DowngradeComponentAdapter {
|
||||
const observeFn = (prop => {
|
||||
let prevValue = INITIAL_VALUE;
|
||||
return (currValue: any) => {
|
||||
if (prevValue === INITIAL_VALUE) {
|
||||
// Initially, both `$observe()` and `$watch()` will call this function.
|
||||
if (!strictEquals(prevValue, currValue)) {
|
||||
if (prevValue === INITIAL_VALUE) {
|
||||
prevValue = currValue;
|
||||
}
|
||||
|
||||
this.updateInput(prop, prevValue, currValue);
|
||||
prevValue = currValue;
|
||||
}
|
||||
|
||||
this.updateInput(prop, prevValue, currValue);
|
||||
prevValue = currValue;
|
||||
};
|
||||
})(input.prop);
|
||||
attrs.$observe(input.attr, observeFn);
|
||||
|
||||
// Use `$watch()` (in addition to `$observe()`) in order to initialize the input in time
|
||||
// for `ngOnChanges()`. This is necessary if we are already in a `$digest`, which means that
|
||||
// `ngOnChanges()` (which is called by a watcher) will run before the `$observe()` callback.
|
||||
let unwatch: any = this.componentScope.$watch(() => {
|
||||
unwatch();
|
||||
unwatch = null;
|
||||
observeFn((attrs as any)[input.attr]);
|
||||
});
|
||||
|
||||
} else if (attrs.hasOwnProperty(input.bindAttr)) {
|
||||
expr = (attrs as any /** TODO #9100 */)[input.bindAttr];
|
||||
} else if (attrs.hasOwnProperty(input.bracketAttr)) {
|
||||
|
@ -75,3 +75,10 @@ export function hookupNgModel(ngModel: angular.INgModelController, component: an
|
||||
component.registerOnChange(ngModel.$setViewValue.bind(ngModel));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test two values for strict equality, accounting for the fact that `NaN !== NaN`.
|
||||
*/
|
||||
export function strictEquals(val1: any, val2: any): boolean {
|
||||
return val1 === val2 || (val1 !== val1 && val2 !== val2);
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import {Directive, DoCheck, ElementRef, EventEmitter, Inject, OnChanges, OnInit,
|
||||
|
||||
import * as angular from '../common/angular1';
|
||||
import {$COMPILE, $CONTROLLER, $HTTP_BACKEND, $SCOPE, $TEMPLATE_CACHE} from '../common/constants';
|
||||
import {controllerKey} from '../common/util';
|
||||
import {controllerKey, strictEquals} from '../common/util';
|
||||
|
||||
|
||||
interface IBindingDestination {
|
||||
@ -309,8 +309,7 @@ class UpgradeNg1ComponentAdapter implements OnInit, OnChanges, DoCheck {
|
||||
checkProperties.forEach((propName, i) => {
|
||||
const value = destinationObj ![propName];
|
||||
const last = lastValues[i];
|
||||
if (value !== last &&
|
||||
(value === value || last === last)) { // ignore NaN values (NaN !== NaN)
|
||||
if (!strictEquals(last, value)) {
|
||||
const eventEmitter: EventEmitter<any> = (this as any)[propOuts[i]];
|
||||
eventEmitter.emit(lastValues[i] = value);
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ChangeDetectorRef, Class, Component, EventEmitter, Input, NO_ERRORS_SCHEMA, NgModule, NgZone, SimpleChange, SimpleChanges, Testability, destroyPlatform, forwardRef} from '@angular/core';
|
||||
import {ChangeDetectorRef, Class, Component, EventEmitter, Input, NO_ERRORS_SCHEMA, NgModule, NgZone, OnChanges, SimpleChange, SimpleChanges, Testability, destroyPlatform, forwardRef} from '@angular/core';
|
||||
import {async, fakeAsync, flushMicrotasks, tick} from '@angular/core/testing';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||
@ -410,6 +410,63 @@ export function main() {
|
||||
|
||||
}));
|
||||
|
||||
it('should initialize inputs in time for `ngOnChanges`', async(() => {
|
||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
||||
|
||||
@Component({
|
||||
selector: 'ng2',
|
||||
template: `
|
||||
ngOnChangesCount: {{ ngOnChangesCount }} |
|
||||
firstChangesCount: {{ firstChangesCount }} |
|
||||
initialValue: {{ initialValue }}`
|
||||
})
|
||||
class Ng2Component implements OnChanges {
|
||||
ngOnChangesCount = 0;
|
||||
firstChangesCount = 0;
|
||||
initialValue: string;
|
||||
@Input() foo: string;
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
this.ngOnChangesCount++;
|
||||
|
||||
if (this.ngOnChangesCount === 1) {
|
||||
this.initialValue = this.foo;
|
||||
}
|
||||
|
||||
if (changes['foo'] && changes['foo'].isFirstChange()) {
|
||||
this.firstChangesCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NgModule({imports: [BrowserModule], declarations: [Ng2Component]})
|
||||
class Ng2Module {
|
||||
}
|
||||
|
||||
const ng1Module = angular.module('ng1', []).directive(
|
||||
'ng2', adapter.downgradeNg2Component(Ng2Component));
|
||||
|
||||
const element = html(`
|
||||
<ng2 [foo]="'foo'"></ng2>
|
||||
<ng2 foo="bar"></ng2>
|
||||
<ng2 [foo]="'baz'" ng-if="true"></ng2>
|
||||
<ng2 foo="qux" ng-if="true"></ng2>
|
||||
`);
|
||||
|
||||
adapter.bootstrap(element, ['ng1']).ready(ref => {
|
||||
const nodes = element.querySelectorAll('ng2');
|
||||
const expectedTextWith = (value: string) =>
|
||||
`ngOnChangesCount: 1 | firstChangesCount: 1 | initialValue: ${value}`;
|
||||
|
||||
expect(multiTrim(nodes[0].textContent)).toBe(expectedTextWith('foo'));
|
||||
expect(multiTrim(nodes[1].textContent)).toBe(expectedTextWith('bar'));
|
||||
expect(multiTrim(nodes[2].textContent)).toBe(expectedTextWith('baz'));
|
||||
expect(multiTrim(nodes[3].textContent)).toBe(expectedTextWith('qux'));
|
||||
|
||||
ref.dispose();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should bind to ng-model', async(() => {
|
||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
||||
const ng1Module = angular.module('ng1', []);
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Compiler, Component, ComponentFactoryResolver, EventEmitter, Injector, NgModule, NgModuleRef, OnChanges, OnDestroy, SimpleChanges, destroyPlatform} from '@angular/core';
|
||||
import {Compiler, Component, ComponentFactoryResolver, EventEmitter, Injector, Input, NgModule, NgModuleRef, OnChanges, OnDestroy, SimpleChanges, destroyPlatform} from '@angular/core';
|
||||
import {async} from '@angular/core/testing';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||
@ -148,6 +148,64 @@ export function main() {
|
||||
});
|
||||
}));
|
||||
|
||||
it('should initialize inputs in time for `ngOnChanges`', async(() => {
|
||||
@Component({
|
||||
selector: 'ng2',
|
||||
template: `
|
||||
ngOnChangesCount: {{ ngOnChangesCount }} |
|
||||
firstChangesCount: {{ firstChangesCount }} |
|
||||
initialValue: {{ initialValue }}`
|
||||
})
|
||||
class Ng2Component implements OnChanges {
|
||||
ngOnChangesCount = 0;
|
||||
firstChangesCount = 0;
|
||||
initialValue: string;
|
||||
@Input() foo: string;
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
this.ngOnChangesCount++;
|
||||
|
||||
if (this.ngOnChangesCount === 1) {
|
||||
this.initialValue = this.foo;
|
||||
}
|
||||
|
||||
if (changes['foo'] && changes['foo'].isFirstChange()) {
|
||||
this.firstChangesCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [BrowserModule, UpgradeModule],
|
||||
declarations: [Ng2Component],
|
||||
entryComponents: [Ng2Component]
|
||||
})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
|
||||
const ng1Module = angular.module('ng1', []).directive(
|
||||
'ng2', downgradeComponent({component: Ng2Component}));
|
||||
|
||||
const element = html(`
|
||||
<ng2 [foo]="'foo'"></ng2>
|
||||
<ng2 foo="bar"></ng2>
|
||||
<ng2 [foo]="'baz'" ng-if="true"></ng2>
|
||||
<ng2 foo="qux" ng-if="true"></ng2>
|
||||
`);
|
||||
|
||||
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => {
|
||||
const nodes = element.querySelectorAll('ng2');
|
||||
const expectedTextWith = (value: string) =>
|
||||
`ngOnChangesCount: 1 | firstChangesCount: 1 | initialValue: ${value}`;
|
||||
|
||||
expect(multiTrim(nodes[0].textContent)).toBe(expectedTextWith('foo'));
|
||||
expect(multiTrim(nodes[1].textContent)).toBe(expectedTextWith('bar'));
|
||||
expect(multiTrim(nodes[2].textContent)).toBe(expectedTextWith('baz'));
|
||||
expect(multiTrim(nodes[3].textContent)).toBe(expectedTextWith('qux'));
|
||||
});
|
||||
}));
|
||||
|
||||
it('should bind to ng-model', async(() => {
|
||||
const ng1Module = angular.module('ng1', []).run(
|
||||
($rootScope: angular.IScope) => { $rootScope['modelA'] = 'A'; });
|
||||
|
@ -37,16 +37,6 @@ travisFoldStart "tsc a bunch of useless stuff"
|
||||
travisFoldEnd "tsc a bunch of useless stuff"
|
||||
|
||||
|
||||
# Build integration tests
|
||||
if [[ ${CI_MODE:-} == "e2e_2" ]]; then
|
||||
travisFoldStart "build.integration"
|
||||
cd "`dirname $0`/../../integration"
|
||||
./build_rxjs_es6.sh
|
||||
cd -
|
||||
travisFoldEnd "build.integration"
|
||||
fi
|
||||
|
||||
|
||||
# Build angular.io
|
||||
if [[ ${CI_MODE:-} == "aio" ]]; then
|
||||
travisFoldStart "build.aio"
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@angular/tsc-wrapped",
|
||||
"version": "4.1.0",
|
||||
"version": "4.1.1",
|
||||
"description": "Wraps the tsc CLI, allowing extensions.",
|
||||
"homepage": "https://github.com/angular/angular/tree/master/tools/tsc-wrapped",
|
||||
"bugs": "https://github.com/angular/angular/issues",
|
||||
|
18
tools/public_api_guard/forms/forms.d.ts
vendored
18
tools/public_api_guard/forms/forms.d.ts
vendored
@ -179,18 +179,18 @@ export declare class FormArray extends AbstractControl {
|
||||
at(index: number): AbstractControl;
|
||||
getRawValue(): any[];
|
||||
insert(index: number, control: AbstractControl): void;
|
||||
patchValue(value: any[], {onlySelf, emitEvent}?: {
|
||||
patchValue(value: any[], options?: {
|
||||
onlySelf?: boolean;
|
||||
emitEvent?: boolean;
|
||||
}): void;
|
||||
push(control: AbstractControl): void;
|
||||
removeAt(index: number): void;
|
||||
reset(value?: any, {onlySelf, emitEvent}?: {
|
||||
reset(value?: any, options?: {
|
||||
onlySelf?: boolean;
|
||||
emitEvent?: boolean;
|
||||
}): void;
|
||||
setControl(index: number, control: AbstractControl): void;
|
||||
setValue(value: any[], {onlySelf, emitEvent}?: {
|
||||
setValue(value: any[], options?: {
|
||||
onlySelf?: boolean;
|
||||
emitEvent?: boolean;
|
||||
}): void;
|
||||
@ -231,11 +231,11 @@ export declare class FormControl extends AbstractControl {
|
||||
}): void;
|
||||
registerOnChange(fn: Function): void;
|
||||
registerOnDisabledChange(fn: (isDisabled: boolean) => void): void;
|
||||
reset(formState?: any, {onlySelf, emitEvent}?: {
|
||||
reset(formState?: any, options?: {
|
||||
onlySelf?: boolean;
|
||||
emitEvent?: boolean;
|
||||
}): void;
|
||||
setValue(value: any, {onlySelf, emitEvent, emitModelToViewChange, emitViewToModelChange}?: {
|
||||
setValue(value: any, options?: {
|
||||
onlySelf?: boolean;
|
||||
emitEvent?: boolean;
|
||||
emitModelToViewChange?: boolean;
|
||||
@ -289,20 +289,20 @@ export declare class FormGroup extends AbstractControl {
|
||||
getRawValue(): any;
|
||||
patchValue(value: {
|
||||
[key: string]: any;
|
||||
}, {onlySelf, emitEvent}?: {
|
||||
}, options?: {
|
||||
onlySelf?: boolean;
|
||||
emitEvent?: boolean;
|
||||
}): void;
|
||||
registerControl(name: string, control: AbstractControl): AbstractControl;
|
||||
removeControl(name: string): void;
|
||||
reset(value?: any, {onlySelf, emitEvent}?: {
|
||||
reset(value?: any, options?: {
|
||||
onlySelf?: boolean;
|
||||
emitEvent?: boolean;
|
||||
}): void;
|
||||
setControl(name: string, control: AbstractControl): void;
|
||||
setValue(value: {
|
||||
[key: string]: any;
|
||||
}, {onlySelf, emitEvent}?: {
|
||||
}, options?: {
|
||||
onlySelf?: boolean;
|
||||
emitEvent?: boolean;
|
||||
}): void;
|
||||
@ -532,8 +532,8 @@ export interface ValidatorFn {
|
||||
|
||||
/** @stable */
|
||||
export declare class Validators {
|
||||
static compose(validators: null): null;
|
||||
static compose(validators: (ValidatorFn | null | undefined)[]): ValidatorFn | null;
|
||||
static compose(validators: null): null;
|
||||
static composeAsync(validators: (AsyncValidatorFn | null)[]): AsyncValidatorFn | null;
|
||||
static email(control: AbstractControl): ValidationErrors | null;
|
||||
static maxLength(maxLength: number): ValidatorFn;
|
||||
|
Reference in New Issue
Block a user