Compare commits

...

17 Commits
4.3.0 ... 4.1.1

Author SHA1 Message Date
1f5dce2128 docs: add changelog for 4.1.1 2017-05-04 14:22:43 -07:00
14e7e43ad8 release: cut the 4.1.1 release 2017-05-04 14:18:24 -07:00
54d4b893fd test(compiler-cli): add test for missingTranslation parameter 2017-05-04 14:05:17 -07:00
4670cf51cc test: cleanup rxjs custom build
The latest rxjs release works with closure compiler out of the box.
We no longer need to compile our own.

Also put closure options into a file rather than using a shell script.
2017-05-04 14:05:16 -07:00
dd4e501999 fix(upgrade): initialize all inputs in time for ngOnChanges()
Previously, non-bracketed inputs (e.g. `xyz="foo"`) on downgraded components
were initialized using `attrs.$observe()` (which uses `$evalAsync()` under the
hood), while bracketed inputs (e.g. `[xyz]="'foo'"`) were initialized using
`$watch()`. If the downgraded component was created during a `$digest` (e.g. by
an `ng-if` watcher), the non-bracketed inputs were not initialized in time for
the initial call to `ngOnChanges()` and `ngOnInit()`.

This commit fixes it by using `$watch()` to initialize all inputs. `$observe()`
is still used for subsequent updates on non-bracketed inputs, because it is more
performant.

Fixes #16212
2017-05-04 14:05:16 -07:00
9124994849 build: update concurrently to latest version
The regression in `concurrently` v3.2.0 (which made us roll back to v3.1.0
in #14378) has been fixed in v3.3.0 (see kimmobrunfeldt/concurrently#89).
2017-05-04 14:05:16 -07:00
4fbc61469f docs: fix links in api docs 2017-05-04 14:05:16 -07:00
8a883f24f6 refactor(compiler): simplify AOT tests 2017-05-04 14:05:15 -07:00
07cef367ac fix(core): don’t stop change detection because of errors
- prevents unsubscribing from the zone on error
- prevents unsubscribing from directive `EventEmitter`s on error
- prevents detaching views in dev mode if there on error
- ensures that `ngOnInit` is only called 1x (also in prod mode)

Fixes #9531
Fixes #2413
Fixes #15925
2017-05-04 14:05:15 -07:00
c060110695 fix(language-service): remove asserts for non-null expressions (#16422)
Reworked some of the code so asserts are no longer necessary.
Added additional and potentially redundant checks
Added checks where the null checker found real problems.

PR Close #16422
2017-05-04 14:05:15 -07:00
93ff3166ab docs(common): fix API docs for NgComponentOutlet (#16411)
fixes #16373

PR Close #16411
2017-05-04 14:05:14 -07:00
85a1b54c6e fix(core): don’t set ng-version for dynamically created components (#16394)
Angular uses the `ng-version` attribute to indicate which elements
were used to bootstrap an application. However, after 4.0 we also
added this attribute for all dynamically created components.

Fixes #15880

PR Close #16394
2017-05-04 14:05:14 -07:00
acf83b90bc fix(core): allow to detach OnPush components (#16394)
Fixes #9720
2017-05-04 14:05:14 -07:00
f66e59ebe4 fix(core): allow directives to inject the component’s ChangeDetectorRef. (#16394)
When a directive lives on the same element as a component
(e.g. `<my-comp myDir>`), the directive was not able to get hold
of the `ChangeDetectorRef` of the component on that element. However,
as directives are supposed to decorate components, this is incorrect.

This commit enables this use case.

Closes #12816
2017-05-04 14:05:14 -07:00
d932e724ab ci(language-service): update ci tests to official 2.3 build (#16415)
PR Close #16415
2017-05-04 14:05:13 -07:00
dcaa11a88b fix: public API golden files (#16414) 2017-05-04 14:05:13 -07:00
427d63a422 fix: strictNullCheck support. (#16389) (#16389)
Fix #16357

Workaround for https://github.com/Microsoft/TypeScript/issues/10078

Closes #16389

PR Close #16389
2017-05-04 14:05:13 -07:00
82 changed files with 1196 additions and 911 deletions

View File

@ -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 components `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:** dont 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:** dont 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)

View File

@ -1,4 +1,3 @@
/rxjs/
built/
dist/
vendor/

View File

@ -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:

View File

@ -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

View File

@ -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*

View 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

View File

@ -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
}
}
}
}

View File

@ -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"
}
}
}

View File

@ -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>

View File

@ -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"
}

View File

@ -10,6 +10,6 @@
"author": "",
"license": "ISC",
"dependencies": {
"typescript": "2.3.0-dev.20170223"
"typescript": "2.3.0"
}
}

View File

@ -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"

View File

@ -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"

View File

@ -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"
]
}

View File

@ -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,

View File

@ -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",

View File

@ -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",

View File

@ -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,

View File

@ -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",

View File

@ -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"
]
}
}

View File

@ -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;

View File

@ -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",

View File

@ -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.
*

View File

@ -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
*/

View File

@ -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;

View File

@ -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"
},

View File

@ -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': `

View File

@ -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};
});
});
}

View File

@ -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);

View File

@ -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
*/

View File

@ -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>> {

View File

@ -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

View File

@ -287,7 +287,7 @@ export const ContentChild: ContentChildDecorator = makePropDecorator(
/**
* Type of the ViewChildren decorator / constructor function.
*
* See {@ViewChildren}.
* See {@link ViewChildren}.
*
* @stable
*/

View File

@ -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`.

View File

@ -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.

View File

@ -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);
}
}
}

View File

@ -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 !;

View File

@ -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 = [];

View File

@ -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() !);
}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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); }

View File

@ -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>[];

View File

@ -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);
}));
});

View File

@ -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();
}));
}
});

View File

@ -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');
}
});
}
});
}

View File

@ -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,

View File

@ -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>');

View File

@ -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();
});
});

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);
}
/**

View File

@ -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');

View File

@ -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 {

View File

@ -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);
}

View File

@ -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) {

View File

@ -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;
}
}
}
}

View File

@ -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;

View File

@ -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(

View File

@ -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});
}

View File

@ -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 {

View File

@ -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,

View File

@ -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

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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 = [

View File

@ -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.

View File

@ -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 {

View File

@ -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)) {

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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', []);

View File

@ -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'; });

View File

@ -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"

View File

@ -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",

View File

@ -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;