Compare commits

..

26 Commits
7.0.3 ... 7.0.4

Author SHA1 Message Date
0e95a20576 release: cut the v7.0.4 release 2018-11-14 13:48:18 -08:00
9c7428e7ad build(bazel): turn on --nolegacy-external-runfiles (#26770)
PR Close #26770
2018-11-14 12:23:39 -08:00
d3044271dc fix(router): add relativeLinkResolution to recognize operator (#26990)
Close #26983

PR Close #26990
2018-11-13 16:18:08 -08:00
4348c472d6 fix(compiler-cli): add missing tslib dependency (#27063)
PR Close #27063
2018-11-13 13:59:42 -08:00
2ec05415e2 docs: new doc for core directives (#26923)
PR Close #26923
2018-11-13 10:57:57 -08:00
188e9ceb1f fix(compiler-cli): only pass canonical genfile paths to compiler host (#27062)
In a more specific scenario: Considering people use a custom TypeScript compiler host with `NGC`, they _could_ expect only posix paths in methods like `writeFile`. This at first glance sounds like a trivial issue that should be just fixed by the actual compiler host, but usually TypeScript internal API's just pass around posix normalized paths, and therefore it would be good to follow the same standards when passing JSON genfiles to the `CompilerHost`.

For normal TypeScript files (and TS genfiles), this is already consistent because those will be handled by the actual TypeScript `Program` (see `emitCallback`).

PR Close #27062
2018-11-13 10:51:20 -08:00
de7b554a14 build: avoid writing \r in windows (#26888)
In windows when using readFile `\n` are replaced with `\r\n` which causes issues when comparing golden files

PR Close #26888
2018-11-13 10:50:23 -08:00
1d252a559b build: fix api guardian path for windows (#26888)
At the moment, `path.posix.relative` will break paths in windows as it will return something like
```
Error: Source file "../C:/users/alag/_bazel_alag/3tbqurya/execroot/angular/bazel-out/x64_windows-fastbuild/bin/packages/core/core.d.ts" not found
```

PR Close #26888
2018-11-13 10:50:22 -08:00
bff5c24ed2 docs: fix typo (#26878)
PR Close #26878
2018-11-13 10:49:36 -08:00
53197de80c docs: Fix wrong quoting in SSR guide (#27057)
PR Close #27057
2018-11-12 12:52:33 -08:00
12757474e6 docs: fix typo in toh-pt0.md (#27044)
PR Close #27044
2018-11-12 12:51:54 -08:00
057423e3e7 docs(forms): update API reference for value accessors (#26946)
PR Close #26946
2018-11-12 12:48:33 -08:00
7a1b8763b2 docs: removed unused pull-left classes from hero form component (#26178)
PR Close #26178
2018-11-09 10:23:17 -08:00
cbd420656d docs(core): cleanup todo notes in ContentChild documentation examples (#26543)
PR Close #26543
2018-11-09 09:47:26 -08:00
f33307badb docs(router): add RxJS filter pipe operator to example (#25686)
This example for Angular 6 requires piping the RxJS filter operator in order to traverse the multitude of Router Events.

PR Close #25686
2018-11-09 09:46:11 -08:00
b00588a240 docs: add missing instruction in HTTP section (#25715)
PR Close #25715
2018-11-09 09:44:12 -08:00
fb0d93d2c1 docs: override code formatting for CLI commands (#26619)
Closes 26614

PR Close #26619
2018-11-09 09:43:01 -08:00
cc0fd6036d docs: update links to TC39 and observable proposal (#26715)
Before (404 Not Found):
http://www.ecma-international.org/memento/TC39.htm
After:
https://www.ecma-international.org/memento/tc39-m.htm

PR Close #26715
2018-11-09 09:41:58 -08:00
682034fcc0 docs: Fix links (#27027)
Fix the links.
PR Close #27027
2018-11-09 09:40:45 -08:00
0475b8d686 ci: exclude symbol golden files from build-and-ci pullapprove group (#27004)
these files are test data and note test infrastructure, so they don't belong to this group.

this means that the core group can now approve the golden symbol changes

PR Close #27004
2018-11-08 13:10:54 -08:00
6be6125c5c docs: add Angular subreddit to resources.json (#26976)
Close #26941

PR Close #26976
2018-11-08 13:09:36 -08:00
1b7b64ae18 docs: add ngx-api-utils to resources (#26120)
PR Close #26120
2018-11-08 13:07:43 -08:00
d284fa6bd2 docs: update hero search component to use input event (#26440)
PR Close #26440
2018-11-08 11:29:11 -08:00
8e4dca0ddf build: add 'yarn list-fixme-ivy-targets' to list all the fixme-ivy-* targest (#26834)
PR Close #26834
2018-11-08 11:09:00 -08:00
006a43f951 docs: correct https://angularconsole.com/ link (#26999)
PR Close #26999
2018-11-08 11:06:24 -08:00
5469b6f64b build(bazel): upgrade benchmarks to protractor_web_test_suite for CI without local chrome (#26908)
PR Close #26908
2018-11-07 16:52:01 -08:00
41 changed files with 714 additions and 237 deletions

View File

@ -24,6 +24,10 @@ build --symlink_prefix=/
# Performance: avoid stat'ing input files
build --watchfs
# Turn off legacy external runfiles
run --nolegacy_external_runfiles
test --nolegacy_external_runfiles
###############################
# Release support #
###############################

View File

@ -108,8 +108,9 @@ groups:
- "*.lock"
- "tools/*"
exclude:
- "tools/public_api_guard/*"
- "aio/*"
- "packages/core/test/bundling/*"
- "tools/public_api_guard/*"
users:
- IgorMinar #primary
- alexeagle

View File

@ -1,3 +1,15 @@
<a name="7.0.4"></a>
## [7.0.4](https://github.com/angular/angular/compare/7.0.3...7.0.4) (2018-11-14)
### Bug Fixes
* **compiler-cli:** add missing tslib dependency ([#27063](https://github.com/angular/angular/issues/27063)) ([4348c47](https://github.com/angular/angular/commit/4348c47))
* **compiler-cli:** only pass canonical genfile paths to compiler host ([#27062](https://github.com/angular/angular/issues/27062)) ([188e9ce](https://github.com/angular/angular/commit/188e9ce))
* **router:** add `relativeLinkResolution` to `recognize` operator ([#26990](https://github.com/angular/angular/issues/26990)) ([d304427](https://github.com/angular/angular/commit/d304427)), closes [#26983](https://github.com/angular/angular/issues/26983)
<a name="7.0.3"></a>
## [7.0.3](https://github.com/angular/angular/compare/7.0.2...7.0.3) (2018-11-07)

View File

@ -72,15 +72,15 @@
<h2>You submitted the following:</h2>
<div class="row">
<div class="col-xs-3">Name</div>
<div class="col-xs-9 pull-left">{{ model.name }}</div>
<div class="col-xs-9">{{ model.name }}</div>
</div>
<div class="row">
<div class="col-xs-3">Alter Ego</div>
<div class="col-xs-9 pull-left">{{ model.alterEgo }}</div>
<div class="col-xs-9">{{ model.alterEgo }}</div>
</div>
<div class="row">
<div class="col-xs-3">Power</div>
<div class="col-xs-9 pull-left">{{ model.power }}</div>
<div class="col-xs-9">{{ model.power }}</div>
</div>
<br>
<button class="btn btn-primary" (click)="submitted=false">Edit</button>

View File

@ -2,7 +2,7 @@
<h4>Hero Search</h4>
<!-- #docregion input -->
<input #searchBox id="search-box" (keyup)="search(searchBox.value)" />
<input #searchBox id="search-box" (input)="search(searchBox.value)" />
<!-- #enddocregion input -->
<ul class="search-result">

View File

@ -1,7 +1,11 @@
// #docregion , init
import { InMemoryDbService } from 'angular-in-memory-web-api';
import { Hero } from './hero';
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class InMemoryDataService implements InMemoryDbService {
createDb() {
const heroes = [

View File

@ -1,7 +1,7 @@
<div id="search-component">
<h4>Hero Search</h4>
<input #searchBox id="search-box" (keyup)="search(searchBox.value)" />
<input #searchBox id="search-box" (input)="search(searchBox.value)" />
<ul class="search-result">
<li *ngFor="let hero of heroes | async" >

View File

@ -48,7 +48,7 @@ Similarly, use the `@Injectable()` decorator to indicate that a component or oth
* An injector creates dependencies, and maintains a *container* of dependency instances that it reuses if possible.
* A *provider* is an object that tell an injector how to obtain or create a dependency.
* A *provider* is an object that tells an injector how to obtain or create a dependency.
For any dependency that you need in your app, you must register a provider with the app's injector,
so that the injector can use the provider to create new instances.

View File

@ -41,7 +41,7 @@ When the workspace file structure is in place, you can use the `ng generate` com
<div class="alert is-helpful">
Besides using the CLI on the command line, you can also use an interactive development environment like [Angular Console](https://angular.console.com), or manipulate files directly in the app's source folder and configuration files.
Besides using the CLI on the command line, you can also use an interactive development environment like [Angular Console](https://angularconsole.com/), or manipulate files directly in the app's source folder and configuration files.
</div>

View File

@ -81,7 +81,7 @@ The following support packages are included as dependencies in the default `pack
Package name | Description
---------------------------------------- | --------------------------------------------------
[**rxjs**](https://github.com/ReactiveX/rxjs) | Many Angular APIs return [_observables_](guide/glossary#observable). RxJS is an implementation of the proposed [Observables specification](https://github.com/zenparsing/es-observable) currently before the [TC39](http://www.ecma-international.org/memento/TC39.htm) committee, which determines standards for the JavaScript language.
[**rxjs**](https://github.com/ReactiveX/rxjs) | Many Angular APIs return [_observables_](guide/glossary#observable). RxJS is an implementation of the proposed [Observables specification](https://github.com/tc39/proposal-observable) currently before the [TC39](https://www.ecma-international.org/memento/tc39-m.htm) committee, which determines standards for the JavaScript language.
[**zone.js**](https://github.com/angular/zone.js) | Angular relies on zone.js to run Angular's change detection processes when native JavaScript operations raise events. Zone.js is an implementation of a [specification](https://gist.github.com/mhevery/63fdcdf7c65886051d55) currently before the [TC39](http://www.ecma-international.org/memento/TC39.htm) committee that determines standards for the JavaScript language.

View File

@ -62,7 +62,7 @@ Your app may have to launch faster to engage these users before they decide to d
With Angular Universal, you can generate landing pages for the app that look like the complete app.
The pages are pure HTML, and can display even if JavaScript is disabled.
The pages don't handle browser events, but they _do_ support navigation through the site using `[routerLink](guide/router#router-link)`.
The pages don't handle browser events, but they _do_ support navigation through the site using [`routerLink`](guide/router#router-link).
In practice, you'll serve a static version of the landing page to hold the user's attention.
At the same time, you'll load the full Angular app behind it.

View File

@ -78,7 +78,7 @@ Each target object specifies the `builder` for that target, which is the npm pac
}
</code-example>
* The `architect/build` section configures defaults for options of the `ng build` command. See [Build target]{#build-target} below for more information.
* The `architect/build` section configures defaults for options of the `ng build` command. See [Build target](#build-target) below for more information.
* The `architect/serve` section overrides build defaults and supplies additional serve defaults for the `ng serve` command. In addition to the options available for the `ng build` command, it adds options related to serving the app.

View File

@ -22,6 +22,12 @@
"rev": true,
"title": "Made with Angular",
"url": "https://www.madewithangular.com/"
},
"angular-subreddit": {
"desc": "An Angular-dedicated subreddit.",
"rev": true,
"title": "Angular Subreddit",
"url": "https://www.reddit.com/r/Angular2/"
}
}
},
@ -149,6 +155,13 @@
"rev": true,
"title": "AngularCommerce",
"url": "https://github.com/NodeArt/angular-commerce"
},
"ngx-api-utils": {
"desc": "ngx-api-utils is a lean library of utilities and helpers to quickly integrate any HTTP API (REST, Ajax, and any other) with Angular.",
"logo": "",
"rev": true,
"title": "ngx-api-utils",
"url": "https://github.com/ngx-api-utils/ngx-api-utils"
}
}
},

View File

@ -19,7 +19,7 @@ To set up your development environment, follow these instructions in [Getting St
<div class="alert is-helpful">
**Note:**: You do not need to complete the entire Getting Started. After you complete the above two sections of Getting Started, your environment is set up. Continue below to create the Tour of Heroes workspace and an initial app project.
**Note:** You do not need to complete the entire Getting Started. After you complete the above two sections of Getting Started, your environment is set up. Continue below to create the Tour of Heroes workspace and an initial app project.
</div>

View File

@ -77,8 +77,13 @@ _after importing the `HttpClientModule`_,
The `forRoot()` configuration method takes an `InMemoryDataService` class
that primes the in-memory database.
The _Tour of Heroes_ sample creates such a class
`src/app/in-memory-data.service.ts` which has the following content:
The class `src/app/in-memory-data.service.ts` is generated by the following command:
<code-example language="sh" class="code-shell">
ng generate service InMemoryData
</code-example>
This class has the following content:
<code-example path="toh-pt6/src/app/in-memory-data.service.ts" region="init" header="src/app/in-memory-data.service.ts" linenums="false"></code-example>
@ -461,7 +466,7 @@ Replace the generated `HeroSearchComponent` _template_ with a text box and a lis
Add private CSS styles to `hero-search.component.css`
as listed in the [final code review](#herosearchcomponent) below.
As the user types in the search box, a *keyup* event binding calls the component's `search()`
As the user types in the search box, an *input* event binding calls the component's `search()`
method with the new search box value.
{@a asyncpipe}
@ -511,7 +516,7 @@ You can also push values into that `Observable` by calling its `next(value)` met
as the `search()` method does.
The `search()` method is called via an _event binding_ to the
textbox's `keystroke` event.
textbox's `input` event.
<code-example path="toh-pt6/src/app/hero-search/hero-search.component.html" region="input"></code-example>

View File

@ -1,5 +1,7 @@
.cli-name {
font-weight: bold;
.kwd { color: initial } /* override code format */
}
.cli-option-syntax {

View File

@ -1,7 +1,7 @@
package(default_visibility = ["//visibility:public"])
load("//tools:defaults.bzl", "ng_rollup_bundle", "ts_library")
load("//packages/bazel:index.bzl", "protractor_web_test")
load("//packages/bazel:index.bzl", "protractor_web_test_suite")
load("@build_bazel_rules_typescript//:defs.bzl", "ts_devserver")
ts_library(
@ -46,7 +46,7 @@ ts_devserver(
],
)
protractor_web_test(
protractor_web_test_suite(
name = "perf",
configuration = "//:protractor-perf.conf.js",
data = [
@ -60,8 +60,6 @@ protractor_web_test(
on_prepare = ":protractor.on-prepare.js",
server = ":devserver",
tags = [
"fixme-ivy-aot",
"fixme-ivy-jit",
"ivy-only",
],
deps = [

View File

@ -1,7 +1,7 @@
package(default_visibility = ["//visibility:public"])
load("//tools:defaults.bzl", "ng_module", "ng_rollup_bundle")
load("//packages/bazel:index.bzl", "protractor_web_test")
load("//packages/bazel:index.bzl", "protractor_web_test_suite")
load("@build_bazel_rules_typescript//:defs.bzl", "ts_devserver")
ng_module(
@ -46,7 +46,7 @@ ts_devserver(
tags = ["ivy-only"],
)
protractor_web_test(
protractor_web_test_suite(
name = "perf",
configuration = "//:protractor-perf.conf.js",
data = [
@ -56,8 +56,6 @@ protractor_web_test(
on_prepare = ":protractor.on_prepare.js",
server = ":devserver",
tags = [
"fixme-ivy-aot",
"fixme-ivy-jit",
"ivy-only",
],
deps = [

View File

@ -1,6 +1,6 @@
{
"name": "angular-srcs",
"version": "7.0.3",
"version": "7.0.4",
"private": true,
"branchPattern": "2.0.*",
"description": "Angular - a web framework for modern web apps",
@ -30,6 +30,7 @@
"test-fixme-ivy-jit": "bazel test --define=compile=jit --build_tag_filters=-no-ivy-jit --test_tag_filters=-no-ivy-jit",
"test-ivy-aot": "bazel test --define=compile=aot --build_tag_filters=-no-ivy-aot,-fixme-ivy-aot --test_tag_filters=-no-ivy-aot,-fixme-ivy-aot",
"test-fixme-ivy-aot": "bazel test --define=compile=aot --build_tag_filters=-no-ivy-aot --test_tag_filters=-no-ivy-aot",
"list-fixme-ivy-targets": "bazel query --output=label 'attr(\"tags\", \"\\[.*fixme-ivy.*\\]\", //...) except kind(\"sh_binary\", //...) except kind(\"devmode_js_sources\", //...)' | sort",
"bazel": "bazel"
},
"// 1": "dependencies are used locally and by bazel",

View File

@ -5,9 +5,6 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
const path = require('path');
const DEBUG = false;
const configPath = 'TMPL_config';
@ -32,15 +29,44 @@ function setConf(conf, name, value, msg) {
conf[name] = value;
}
function mergeCapabilities(conf, capabilities) {
if (conf.capabilities) {
if (conf.capabilities.browserName === capabilities.browserName) {
// there are capabilities to merge
if (capabilities.browserName === 'chrome') {
conf.capabilities.chromeOptions = conf.capabilities.chromeOptions || {};
conf.capabilities.chromeOptions.binary = capabilities.chromeOptions.binary;
conf.capabilities.chromeOptions.args = conf.capabilities.chromeOptions.args || [];
conf.capabilities.chromeOptions.args.push(...capabilities.chromeOptions.args);
console.warn(
`Your protractor configuration specifies capabilities for browser '${conf.capabilities.browserName}'
which will be merged with capabilities provided by Bazel resulting in:`,
JSON.stringify(conf.capabilities, null, 2));
} else {
// TODO(gmagolan): implement firefox support for protractor
throw new Error(
`Unexpected browserName ${capabilities.browserName} for capabilities merging`);
}
} else {
console.warn(
`Your protractor configuration specifies capabilities for browser '${conf.capabilities.browserName}' which will be overwritten by Bazel`);
conf.capabilities = capabilities;
}
} else {
conf.capabilities = capabilities;
}
}
let conf = {};
// Import the user's base protractor configuration if specified
if (configPath) {
const baseConf = require(configPath);
if (!baseConf.config) {
throw new Error('Invalid base protractor configration. Expected config to be exported.');
throw new Error('Invalid base protractor configuration. Expected config to be exported.');
}
conf = baseConf.config;
if (DEBUG) console.info(`Base protractor configuration: ${JSON.stringify(conf, null, 2)}`);
}
// Import the user's on prepare function if specified
@ -109,8 +135,8 @@ if (process.env['WEB_TEST_METADATA']) {
}
setConf(conf, 'directConnect', true, 'is set to true for chrome');
setConf(conf, 'chromeDriver', chromeDriver, 'is determined by the browsers attribute');
setConf(
conf, 'capabilities', {
mergeCapabilities(
conf, {
browserName: 'chrome',
chromeOptions: {
binary: chromeBin,
@ -131,7 +157,7 @@ if (process.env['WEB_TEST_METADATA']) {
// }
// setConf(conf, 'seleniumAddress', process.env.WEB_TEST_HTTP_SERVER.trim() + "/wd/hub", 'is
// configured by Bazel for firefox browser')
// setConf(conf, 'capabilities', {
// mergeCapabilities(conf, {
// browserName: "firefox",
// 'moz:firefoxOptions': {
// binary: firefoxBin,

View File

@ -70,7 +70,7 @@ export function createTsConfig(options: TsConfigOptions) {
'tsickleExternsPath': '',
// we don't copy the node_modules into our tmp dir, so we should look in
// the original workspace directory for it
'nodeModulesPrefix': '../angular/external/ngdeps/node_modules',
'nodeModulesPrefix': '../ngdeps/node_modules',
},
'files': options.files,
'angularCompilerOptions': {

View File

@ -19,6 +19,7 @@
"magic-string": "^0.25.0",
"shelljs": "^0.8.1",
"source-map": "^0.6.1",
"tslib": "^1.9.0",
"yargs": "9.0.1"
},
"peerDependencies": {

View File

@ -974,10 +974,8 @@ function normalizeSeparators(path: string): string {
* TODO(tbosch): talk to the TypeScript team to expose their logic for calculating the `rootDir`
* if none was specified.
*
* Note: This function works on normalized paths from typescript.
*
* @param outDir
* @param outSrcMappings
* Note: This function works on normalized paths from typescript but should always return
* POSIX normalized paths for output paths.
*/
export function createSrcToOutPathMapper(
outDir: string | undefined, sampleSrcFileName: string | undefined,
@ -986,7 +984,6 @@ export function createSrcToOutPathMapper(
resolve: typeof path.resolve,
relative: typeof path.relative
} = path): (srcFileName: string) => string {
let srcToOutPath: (srcFileName: string) => string;
if (outDir) {
let path: {} = {}; // Ensure we error if we use `path` instead of `host`.
if (sampleSrcFileName == null || sampleOutFileName == null) {
@ -1006,11 +1003,18 @@ export function createSrcToOutPathMapper(
srcDirParts[srcDirParts.length - 1 - i] === outDirParts[outDirParts.length - 1 - i])
i++;
const rootDir = srcDirParts.slice(0, srcDirParts.length - i).join('/');
srcToOutPath = (srcFileName) => host.resolve(outDir, host.relative(rootDir, srcFileName));
return (srcFileName) => {
// Note: Before we return the mapped output path, we need to normalize the path delimiters
// because the output path is usually passed to TypeScript which sometimes only expects
// posix normalized paths (e.g. if a custom compiler host is used)
return normalizeSeparators(host.resolve(outDir, host.relative(rootDir, srcFileName)));
};
} else {
srcToOutPath = (srcFileName) => srcFileName;
// Note: Before we return the output path, we need to normalize the path delimiters because
// the output path is usually passed to TypeScript which only passes around posix
// normalized paths (e.g. if a custom compiler host is used)
return (srcFileName) => normalizeSeparators(srcFileName);
}
return srcToOutPath;
}
export function i18nExtract(

View File

@ -136,7 +136,7 @@ export function setupBazelTo(basePath: string) {
}
// Link typescript
const typescriptSource = path.join(sources, 'angular/external/ngdeps/node_modules/typescript');
const typescriptSource = path.join(sources, 'ngdeps/node_modules/typescript');
const typescriptDest = path.join(nodeModulesPath, 'typescript');
if (fs.existsSync(typescriptSource)) {
fs.symlinkSync(typescriptSource, typescriptDest);

View File

@ -604,13 +604,13 @@ describe('ng program', () => {
it('should work on windows with normalized paths', () => {
const mapper =
createSrcToOutPathMapper('c:/tmp/out', 'c:/tmp/a/x.ts', 'c:/tmp/out/a/x.js', path.win32);
expect(mapper('c:/tmp/b/y.js')).toBe('c:\\tmp\\out\\b\\y.js');
expect(mapper('c:/tmp/b/y.js')).toBe('c:/tmp/out/b/y.js');
});
it('should work on windows with non-normalized paths', () => {
const mapper = createSrcToOutPathMapper(
'c:\\tmp\\out', 'c:\\tmp\\a\\x.ts', 'c:\\tmp\\out\\a\\x.js', path.win32);
expect(mapper('c:\\tmp\\b\\y.js')).toBe('c:\\tmp\\out\\b\\y.js');
expect(mapper('c:\\tmp\\b\\y.js')).toBe('c:/tmp/out/b/y.js');
});
});

View File

@ -134,6 +134,7 @@ export interface Directive {
* id: string;
*
* ```
*
*/
inputs?: string[];
@ -168,6 +169,7 @@ export interface Directive {
* class MainComponent {
* }
* ```
*
*/
outputs?: string[];
@ -199,6 +201,7 @@ export interface Directive {
* class MainComponent {
* }
* ```
*
*/
exportAs?: string;
@ -434,6 +437,67 @@ export interface ComponentDecorator {
*
* ```
*
* ### Preserving whitespace
*
* Removing whitespace can greatly reduce AOT-generated code size and speed up view creation.
* As of Angular 6, the default for `preserveWhitespaces` is false (whitespace is removed).
* To change the default setting for all components in your application, set
* the `preserveWhitespaces` option of the AOT compiler.
*
* By default, the AOT compiler removes whitespace characters as follows:
* * Trims all whitespaces at the beginning and the end of a template.
* * Removes whitespace-only text nodes. For example,
*
* ```
* <button>Action 1</button> <button>Action 2</button>
* ```
*
* becomes:
*
* ```
* <button>Action 1</button><button>Action 2</button>
* ```
*
* * Replaces a series of whitespace characters in text nodes with a single space.
* For example, `<span>\n some text\n</span>` becomes `<span> some text </span>`.
* * Does NOT alter text nodes inside HTML tags such as `<pre>` or `<textarea>`,
* where whitespace characters are significant.
*
* Note that these transformations can influence DOM nodes layout, although impact
* should be minimal.
*
* You can override the default behavior to preserve whitespace characters
* in certain fragments of a template. For example, you can exclude an entire
* DOM sub-tree by using the `ngPreserveWhitespaces` attribute:
*
* ```html
* <div ngPreserveWhitespaces>
* whitespaces are preserved here
* <span> and here </span>
* </div>
* ```
*
* You can force a single space to be preserved in a text node by using `&ngsp;`,
* which is replaced with a space character by Angular's template
* compiler:
*
* ```html
* <a>Spaces</a>&ngsp;<a>between</a>&ngsp;<a>links.</a>
* <!-->compiled to be equivalent to:</>
* <a>Spaces</a> <a>between</a> <a>links.</a>
* ```
*
* Note that sequences of `&ngsp;` are still collapsed to just one space character when
* the `preserveWhitespaces` option is set to `false`.
*
* ```html
* <a>before</a>&ngsp;&ngsp;&ngsp;<a>after</a>
* <!-->compiled to be equivalent to:</>
* <a>Spaces</a> <a>between</a> <a>links.</a>
* ```
*
* To preserve sequences of whitespace characters, use the
* `ngPreserveWhitespaces` attribute.
*
* @Annotation
*/
@ -551,89 +615,6 @@ export interface Component extends Directive {
/**
* Component decorator and metadata.
*
* @usageNotes
*
* ### Using animations
*
* The following snippet shows an animation trigger in a component's
* metadata. The trigger is attached to an element in the component's
* template, using "@_trigger_name_", and a state expression that is evaluated
* at run time to determine whether the animation should start.
*
* ```typescript
* @Component({
* selector: 'animation-cmp',
* templateUrl: 'animation-cmp.html',
* animations: [
* trigger('myTriggerName', [
* state('on', style({ opacity: 1 }),
* state('off', style({ opacity: 0 }),
* transition('on => off', [
* animate("1s")
* ])
* ])
* ]
* })
* ```
*
* ```html
* <!-- animation-cmp.html -->
* <div @myTriggerName="expression">...</div>
* ```
*
* ### Preserving whitespace
*
* Removing whitespace can greatly reduce AOT-generated code size, and speed up view creation.
* As of Angular 6, default for `preserveWhitespaces` is false (whitespace is removed).
* To change the default setting for all components in your application, set
* the `preserveWhitespaces` option of the AOT compiler.
*
* Current implementation removes whitespace characters as follows:
* - Trims all whitespaces at the beginning and the end of a template.
* - Removes whitespace-only text nodes. For example,
* `<button>Action 1</button> <button>Action 2</button>` becomes
* `<button>Action 1</button><button>Action 2</button>`.
* - Replaces a series of whitespace characters in text nodes with a single space.
* For example, `<span>\n some text\n</span>` becomes `<span> some text </span>`.
* - Does NOT alter text nodes inside HTML tags such as `<pre>` or `<textarea>`,
* where whitespace characters are significant.
*
* Note that these transformations can influence DOM nodes layout, although impact
* should be minimal.
*
* You can override the default behavior to preserve whitespace characters
* in certain fragments of a template. For example, you can exclude an entire
* DOM sub-tree by using the `ngPreserveWhitespaces` attribute:
*
* ```html
* <div ngPreserveWhitespaces>
* whitespaces are preserved here
* <span> and here </span>
* </div>
* ```
*
* You can force a single space to be preserved in a text node by using `&ngsp;`,
* which is replaced with a space character by Angular's template
* compiler:
*
* ```html
* <a>Spaces</a>&ngsp;<a>between</a>&ngsp;<a>links.</a>
* <!-->compiled to be equivalent to:</>
* <a>Spaces</a> <a>between</a> <a>links.</a>
* ```
*
* Note that sequences of `&ngsp;` are still collapsed to just one space character when
* the `preserveWhitespaces` option is set to `false`.
*
* ```html
* <a>before</a>&ngsp;&ngsp;&ngsp;<a>after</a>
* <!-->compiled to be equivalent to:</>
* <a>Spaces</a> <a>between</a> <a>links.</a>
* ```
*
* To preserve sequences of whitespace characters, use the
* `ngPreserveWhitespaces` attribute.
*
* @Annotation
* @publicApi
*/
@ -766,6 +747,7 @@ export interface Input {
*
* class App {}
* ```
*
*/
bindingPropertyName?: string;
}
@ -885,6 +867,7 @@ export interface HostBindingDecorator {
* prop;
* }
* ```
*
*/
(hostPropertyName?: string): any;
new (hostPropertyName?: string): any;

View File

@ -15,7 +15,6 @@ class ChildDirective {
@Directive({selector: 'someDir'})
class SomeDir implements AfterContentInit {
// TODO(issue/24571): remove '!'.
@ContentChild(ChildDirective) contentChild !: ChildDirective;
ngAfterContentInit() {

View File

@ -23,9 +23,7 @@ export class Pane {
`
})
export class Tab {
// TODO(issue/24571): remove '!'.
@ContentChildren(Pane) topLevelPanes !: QueryList<Pane>;
// TODO(issue/24571): remove '!'.
@ContentChildren(Pane, {descendants: true}) arbitraryNestedPanes !: QueryList<Pane>;
get serializedPanes(): string {

View File

@ -17,17 +17,26 @@ export const CHECKBOX_VALUE_ACCESSOR: any = {
};
/**
* The accessor for writing a value and listening to changes on a checkbox input element.
* @description
* A `ControlValueAccessor` for writing a value and listening to changes on a checkbox input
* element.
*
* @usageNotes
* ### Example
*
* ```
* <input type="checkbox" name="rememberLogin" ngModel>
* ### Using a checkbox with a reactive form.
*
* The following example shows how to use a checkbox with a reactive form.
*
* ```ts
* const rememberLoginControl = new FormControl();
* ```
*
* ```
* <input type="checkbox" [formControl]="rememberLoginControl">
* ```
*
* @ngModule FormsModule
* @ngModule ReactiveFormsModule
* @ngModule FormsModule
* @publicApi
*/
@Directive({
@ -37,17 +46,50 @@ export const CHECKBOX_VALUE_ACCESSOR: any = {
providers: [CHECKBOX_VALUE_ACCESSOR]
})
export class CheckboxControlValueAccessor implements ControlValueAccessor {
/**
* @description
* The registered callback function called when a change event occurs on the input element.
*/
onChange = (_: any) => {};
/**
* @description
* The registered callback function called when a blur event occurs on the input element.
*/
onTouched = () => {};
constructor(private _renderer: Renderer2, private _elementRef: ElementRef) {}
/**
* Sets the "checked" property on the input element.
*
* @param value The checked value
*/
writeValue(value: any): void {
this._renderer.setProperty(this._elementRef.nativeElement, 'checked', value);
}
/**
* @description
* Registers a function called when the control value changes.
*
* @param fn The callback function
*/
registerOnChange(fn: (_: any) => {}): void { this.onChange = fn; }
/**
* @description
* Registers a function called when the control is touched.
*
* @param fn The callback function
*/
registerOnTouched(fn: () => {}): void { this.onTouched = fn; }
/**
* Sets the "disabled" property on the input element.
*
* @param isDisabled The disabled value
*/
setDisabledState(isDisabled: boolean): void {
this._renderer.setProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
}

View File

@ -32,18 +32,28 @@ function _isAndroid(): boolean {
export const COMPOSITION_BUFFER_MODE = new InjectionToken<boolean>('CompositionEventMode');
/**
* The default accessor for writing a value and listening to changes that is used by the
* `NgModel`, `FormControlDirective`, and `FormControlName` directives.
* @description
* The default `ControlValueAccessor` for writing a value and listening to changes on input
* elements. The accessor is used by the `FormControlDirective`, `FormControlName`, and
* `NgModel` directives.
*
* @usageNotes
* ### Example
*
* ```
* <input type="text" name="searchQuery" ngModel>
* ### Using the default value accessor
*
* The following example shows how to use an input element that activates the default value accessor
* (in this case, a text field).
*
* ```ts
* const firstNameControl = new FormControl();
* ```
*
* ```
* <input type="text" [formControl]="firstNameControl">
* ```
*
* @ngModule FormsModule
* @ngModule ReactiveFormsModule
* @ngModule FormsModule
* @publicApi
*/
@Directive({
@ -61,7 +71,16 @@ export const COMPOSITION_BUFFER_MODE = new InjectionToken<boolean>('CompositionE
providers: [DEFAULT_VALUE_ACCESSOR]
})
export class DefaultValueAccessor implements ControlValueAccessor {
/**
* @description
* The registered callback function called when an input event occurs on the input element.
*/
onChange = (_: any) => {};
/**
* @description
* The registered callback function called when a blur event occurs on the input element.
*/
onTouched = () => {};
/** Whether the user is creating a composition string (IME events). */
@ -75,14 +94,37 @@ export class DefaultValueAccessor implements ControlValueAccessor {
}
}
/**
* Sets the "value" property on the input element.
*
* @param value The checked value
*/
writeValue(value: any): void {
const normalizedValue = value == null ? '' : value;
this._renderer.setProperty(this._elementRef.nativeElement, 'value', normalizedValue);
}
/**
* @description
* Registers a function called when the control value changes.
*
* @param fn The callback function
*/
registerOnChange(fn: (_: any) => void): void { this.onChange = fn; }
/**
* @description
* Registers a function called when the control is touched.
*
* @param fn The callback function
*/
registerOnTouched(fn: () => void): void { this.onTouched = fn; }
/**
* Sets the "disabled" property on the input element.
*
* @param isDisabled The disabled value
*/
setDisabledState(isDisabled: boolean): void {
this._renderer.setProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
}

View File

@ -17,18 +17,27 @@ export const NUMBER_VALUE_ACCESSOR: any = {
};
/**
* The accessor for writing a number value and listening to changes that is used by the
* `NgModel`, `FormControlDirective`, and `FormControlName` directives.
* @description
* The `ControlValueAccessor` for writing a number value and listening to number input changes.
* The value accessor is used by the `FormControlDirective`, `FormControlName`, and `NgModel`
* directives.
*
* @usageNotes
* ### Example
*
* ```
* <input type="number" [(ngModel)]="age">
* ### Using a number input with a reactive form.
*
* The following example shows how to use a number input with a reactive form.
*
* ```ts
* const totalCountControl = new FormControl();
* ```
*
* ```
* <input type="number" [formControl]="totalCountControl">
* ```
*
* @ngModule FormsModule
* @ngModule ReactiveFormsModule
* @ngModule FormsModule
*/
@Directive({
selector:
@ -41,22 +50,55 @@ export const NUMBER_VALUE_ACCESSOR: any = {
providers: [NUMBER_VALUE_ACCESSOR]
})
export class NumberValueAccessor implements ControlValueAccessor {
/**
* @description
* The registered callback function called when a change or input event occurs on the input
* element.
*/
onChange = (_: any) => {};
/**
* @description
* The registered callback function called when a blur event occurs on the input element.
*/
onTouched = () => {};
constructor(private _renderer: Renderer2, private _elementRef: ElementRef) {}
/**
* Sets the "value" property on the input element.
*
* @param value The checked value
*/
writeValue(value: number): void {
// The value needs to be normalized for IE9, otherwise it is set to 'null' when null
const normalizedValue = value == null ? '' : value;
this._renderer.setProperty(this._elementRef.nativeElement, 'value', normalizedValue);
}
/**
* @description
* Registers a function called when the control value changes.
*
* @param fn The callback function
*/
registerOnChange(fn: (_: number|null) => void): void {
this.onChange = (value) => { fn(value == '' ? null : parseFloat(value)); };
}
/**
* @description
* Registers a function called when the control is touched.
*
* @param fn The callback function
*/
registerOnTouched(fn: () => void): void { this.onTouched = fn; }
/**
* Sets the "disabled" property on the input element.
*
* @param isDisabled The disabled value
*/
setDisabledState(isDisabled: boolean): void {
this._renderer.setProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
}

View File

@ -18,16 +18,25 @@ export const RADIO_VALUE_ACCESSOR: any = {
};
/**
* Internal class used by Angular to uncheck radio buttons with the matching name.
* @description
* Class used by Angular to track radio buttons. For internal use only.
*/
@Injectable()
export class RadioControlRegistry {
private _accessors: any[] = [];
/**
* @description
* Adds a control to the internal registry. For internal use only.
*/
add(control: NgControl, accessor: RadioControlValueAccessor) {
this._accessors.push([control, accessor]);
}
/**
* @description
* Removes a control from the internal registry. For internal use only.
*/
remove(accessor: RadioControlValueAccessor) {
for (let i = this._accessors.length - 1; i >= 0; --i) {
if (this._accessors[i][1] === accessor) {
@ -37,6 +46,10 @@ export class RadioControlRegistry {
}
}
/**
* @description
* Selects a radio button. For internal use only.
*/
select(accessor: RadioControlValueAccessor) {
this._accessors.forEach((c) => {
if (this._isSameGroup(c, accessor) && c[1] !== accessor) {
@ -56,32 +69,22 @@ export class RadioControlRegistry {
/**
* @description
*
* Writes radio control values and listens to radio control changes.
*
* Used by `NgModel`, `FormControlDirective`, and `FormControlName`
* to keep the view synced with the `FormControl` model.
*
* If you have imported the `FormsModule` or the `ReactiveFormsModule`, this
* value accessor will be active on any radio control that has a form directive. You do
* **not** need to add a special selector to activate it.
* The `ControlValueAccessor` for writing radio control values and listening to radio control
* changes. The value accessor is used by the `FormControlDirective`, `FormControlName`, and
* `NgModel` directives.
*
* @usageNotes
* ### How to use radio buttons with form directives
*
* To use radio buttons in a template-driven form, you'll want to ensure that radio buttons
* in the same group have the same `name` attribute. Radio buttons with different `name`
* attributes do not affect each other.
* ### Using radio buttons with reactive form directives
*
* {@example forms/ts/radioButtons/radio_button_example.ts region='TemplateDriven'}
*
* When using radio buttons in a reactive form, radio buttons in the same group should have the
* same `formControlName`. You can also add a `name` attribute, but it's optional.
* The follow example shows how to use radio buttons in a reactive form. When using radio buttons in
* a reactive form, radio buttons in the same group should have the same `formControlName`.
* Providing a `name` attribute is optional.
*
* {@example forms/ts/reactiveRadioButtons/reactive_radio_button_example.ts region='Reactive'}
*
* @ngModule FormsModule
* @ngModule ReactiveFormsModule
* @ngModule FormsModule
* @publicApi
*/
@Directive({
@ -101,32 +104,81 @@ export class RadioControlValueAccessor implements ControlValueAccessor,
/** @internal */
// TODO(issue/24571): remove '!'.
_fn !: Function;
/**
* @description
* The registered callback function called when a change event occurs on the input element.
*/
onChange = () => {};
/**
* @description
* The registered callback function called when a blur event occurs on the input element.
*/
onTouched = () => {};
/**
* @description
* Tracks the name of the radio input element.
*/
// TODO(issue/24571): remove '!'.
@Input() name !: string;
/**
* @description
* Tracks the name of the `FormControl` bound to the directive. The name corresponds
* to a key in the parent `FormGroup` or `FormArray`.
*/
// TODO(issue/24571): remove '!'.
@Input() formControlName !: string;
/**
* @description
* Tracks the value of the radio input element
*/
@Input() value: any;
constructor(
private _renderer: Renderer2, private _elementRef: ElementRef,
private _registry: RadioControlRegistry, private _injector: Injector) {}
/**
* @description
* A lifecycle method called when the directive is initialized. For internal use only.
*
* @param changes A object of key/value pairs for the set of changed inputs.
*/
ngOnInit(): void {
this._control = this._injector.get(NgControl);
this._checkName();
this._registry.add(this._control, this);
}
/**
* @description
* Lifecycle method called before the directive's instance is destroyed. For internal use only.
*
* @param changes A object of key/value pairs for the set of changed inputs.
*/
ngOnDestroy(): void { this._registry.remove(this); }
/**
* @description
* Sets the "checked" property value on the radio input element.
*
* @param value The checked value
*/
writeValue(value: any): void {
this._state = value === this.value;
this._renderer.setProperty(this._elementRef.nativeElement, 'checked', this._state);
}
/**
* @description
* Registers a function called when the control value changes.
*
* @param fn The callback function
*/
registerOnChange(fn: (_: any) => {}): void {
this._fn = fn;
this.onChange = () => {
@ -135,10 +187,26 @@ export class RadioControlValueAccessor implements ControlValueAccessor,
};
}
/**
* Sets the "value" on the radio input element and unchecks it.
*
* @param value
*/
fireUncheck(value: any): void { this.writeValue(value); }
/**
* @description
* Registers a function called when the control is touched.
*
* @param fn The callback function
*/
registerOnTouched(fn: () => {}): void { this.onTouched = fn; }
/**
* Sets the "disabled" property on the input element.
*
* @param isDisabled The disabled value
*/
setDisabledState(isDisabled: boolean): void {
this._renderer.setProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
}

View File

@ -17,18 +17,27 @@ export const RANGE_VALUE_ACCESSOR: StaticProvider = {
};
/**
* The accessor for writing a range value and listening to changes that is used by the
* `NgModel`, `FormControlDirective`, and `FormControlName` directives.
* @description
* The `ControlValueAccessor` for writing a range value and listening to range input changes.
* The value accessor is used by the `FormControlDirective`, `FormControlName`, and `NgModel`
* directives.
*
* @usageNotes
* ### Example
*
* ```
* <input type="range" [(ngModel)]="age" >
* ### Using a range input with a reactive form
*
* The following example shows how to use a range input with a reactive form.
*
* ```ts
* const ageControl = new FormControl();
* ```
*
* ```
* <input type="range" [formControl]="ageControl">
* ```
*
* @ngModule FormsModule
* @ngModule ReactiveFormsModule
* @ngModule FormsModule
*/
@Directive({
selector:
@ -41,21 +50,53 @@ export const RANGE_VALUE_ACCESSOR: StaticProvider = {
providers: [RANGE_VALUE_ACCESSOR]
})
export class RangeValueAccessor implements ControlValueAccessor {
/**
* @description
* The registered callback function called when a change or input event occurs on the input
* element.
*/
onChange = (_: any) => {};
/**
* @description
* The registered callback function called when a blur event occurs on the input element.
*/
onTouched = () => {};
constructor(private _renderer: Renderer2, private _elementRef: ElementRef) {}
/**
* Sets the "value" property on the input element.
*
* @param value The checked value
*/
writeValue(value: any): void {
this._renderer.setProperty(this._elementRef.nativeElement, 'value', parseFloat(value));
}
/**
* @description
* Registers a function called when the control value changes.
*
* @param fn The callback function
*/
registerOnChange(fn: (_: number|null) => void): void {
this.onChange = (value) => { fn(value == '' ? null : parseFloat(value)); };
}
/**
* @description
* Registers a function called when the control is touched.
*
* @param fn The callback function
*/
registerOnTouched(fn: () => void): void { this.onTouched = fn; }
/**
* Sets the "disabled" property on the range input element.
*
* @param isDisabled The disabled value
*/
setDisabledState(isDisabled: boolean): void {
this._renderer.setProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
}

View File

@ -28,35 +28,26 @@ function _extractId(valueString: string): string {
/**
* @description
*
* Writes values and listens to changes on a select element.
*
* Used by `NgModel`, `FormControlDirective`, and `FormControlName`
* to keep the view synced with the `FormControl` model.
*
* If you have imported the `FormsModule` or the `ReactiveFormsModule`, this
* value accessor will be active on any select control that has a form directive. You do
* **not** need to add a special selector to activate it.
* The `ControlValueAccessor` for writing select control values and listening to select control
* changes. The value accessor is used by the `FormControlDirective`, `FormControlName`, and
* `NgModel` directives.
*
* @usageNotes
* ### How to use select controls with form directives
*
* ### Using select controls in a reactive form
*
* The following examples show how to use a select control in a reactive form.
*
* {@example forms/ts/reactiveSelectControl/reactive_select_control_example.ts region='Component'}
*
* ### Using select controls in a template-driven form
*
* To use a select in a template-driven form, simply add an `ngModel` and a `name`
* attribute to the main `<select>` tag.
*
* If your option values are simple strings, you can bind to the normal `value` property
* on the option. If your option values happen to be objects (and you'd like to save the
* selection in your form as an object), use `ngValue` instead:
*
* {@example forms/ts/selectControl/select_control_example.ts region='Component'}
*
* In reactive forms, you'll also want to add your form directive (`formControlName` or
* `formControl`) on the main `<select>` tag. Like in the former example, you have the
* choice of binding to the `value` or `ngValue` property on the select's options.
*
* {@example forms/ts/reactiveSelectControl/reactive_select_control_example.ts region='Component'}
*
* ### Caveat: Option selection
* ### Customizing option selection
*
* Angular uses object identity to select option. It's possible for the identities of items
* to change while the data does not. This can happen, for example, if the items are produced
@ -67,10 +58,12 @@ function _extractId(valueString: string): string {
* `compareWith` takes a **function** which has two arguments: `option1` and `option2`.
* If `compareWith` is given, Angular selects option by the return value of the function.
*
* ### Syntax
* ```ts
* const selectedCountriesControl = new FormControl();
* ```
*
* ```
* <select [compareWith]="compareFn" [(ngModel)]="selectedCountries">
* <select [compareWith]="compareFn" [formControl]="selectedCountriesControl">
* <option *ngFor="let country of countries" [ngValue]="country">
* {{country.name}}
* </option>
@ -81,13 +74,13 @@ function _extractId(valueString: string): string {
* }
* ```
*
* Note: We listen to the 'change' event because 'input' events aren't fired
* **Note:** We listen to the 'change' event because 'input' events aren't fired
* for selects in Firefox and IE:
* https://bugzilla.mozilla.org/show_bug.cgi?id=1024350
* https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/4660045/
*
* @ngModule FormsModule
* @ngModule ReactiveFormsModule
* @ngModule FormsModule
* @publicApi
*/
@Directive({
@ -103,9 +96,23 @@ export class SelectControlValueAccessor implements ControlValueAccessor {
/** @internal */
_idCounter: number = 0;
/**
* @description
* The registered callback function called when a change event occurs on the input element.
*/
onChange = (_: any) => {};
/**
* @description
* The registered callback function called when a blur event occurs on the input element.
*/
onTouched = () => {};
/**
* @description
* Tracks the option comparison algorithm for tracking identities when
* checking for changes.
*/
@Input()
set compareWith(fn: (o1: any, o2: any) => boolean) {
if (typeof fn !== 'function') {
@ -118,6 +125,12 @@ export class SelectControlValueAccessor implements ControlValueAccessor {
constructor(private _renderer: Renderer2, private _elementRef: ElementRef) {}
/**
* Sets the "value" property on the input element. The "selectedIndex"
* property is also set if an ID is provided on the option element.
*
* @param value The checked value
*/
writeValue(value: any): void {
this.value = value;
const id: string|null = this._getOptionId(value);
@ -128,14 +141,32 @@ export class SelectControlValueAccessor implements ControlValueAccessor {
this._renderer.setProperty(this._elementRef.nativeElement, 'value', valueString);
}
/**
* @description
* Registers a function called when the control value changes.
*
* @param fn The callback function
*/
registerOnChange(fn: (value: any) => any): void {
this.onChange = (valueString: string) => {
this.value = this._getOptionValue(valueString);
fn(this.value);
};
}
/**
* @description
* Registers a function called when the control is touched.
*
* @param fn The callback function
*/
registerOnTouched(fn: () => any): void { this.onTouched = fn; }
/**
* Sets the "disabled" property on the select input element.
*
* @param isDisabled The disabled value
*/
setDisabledState(isDisabled: boolean): void {
this._renderer.setProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
}
@ -160,17 +191,20 @@ export class SelectControlValueAccessor implements ControlValueAccessor {
/**
* @description
*
* Marks `<option>` as dynamic, so Angular can be notified when options change.
*
* See docs for `SelectControlValueAccessor` for usage examples.
* @see `SelectControlValueAccessor`
*
* @ngModule FormsModule
* @ngModule ReactiveFormsModule
* @ngModule FormsModule
* @publicApi
*/
@Directive({selector: 'option'})
export class NgSelectOption implements OnDestroy {
/**
* @description
* ID of the option element
*/
// TODO(issue/24571): remove '!'.
id !: string;
@ -180,6 +214,11 @@ export class NgSelectOption implements OnDestroy {
if (this._select) this.id = this._select._registerOption();
}
/**
* @description
* Tracks the value bound to the option element. Unlike the value binding,
* ngValue supports binding to objects.
*/
@Input('ngValue')
set ngValue(value: any) {
if (this._select == null) return;
@ -188,6 +227,11 @@ export class NgSelectOption implements OnDestroy {
this._select.writeValue(this._select.value);
}
/**
* @description
* Tracks simple string values bound to the option element.
* For objects, use the `ngValue` input binding.
*/
@Input('value')
set value(value: any) {
this._setElementValue(value);
@ -199,6 +243,10 @@ export class NgSelectOption implements OnDestroy {
this._renderer.setProperty(this._element.nativeElement, 'value', value);
}
/**
* @description
* Lifecycle method called before the directive's instance is destroyed. For internal use only.
*/
ngOnDestroy(): void {
if (this._select) {
this._select._optionMap.delete(this.id);

View File

@ -41,33 +41,35 @@ abstract class HTMLCollection {
}
/**
* The accessor for writing a value and listening to changes on a select element.
* @description
* The `ControlValueAccessor` for writing multi-select control values and listening to multi-select control
* changes. The value accessor is used by the `FormControlDirective`, `FormControlName`, and `NgModel`
* directives.
*
* @see `SelectControlValueAccessor`
*
* @usageNotes
* ### Caveat: Options selection
*
* Angular uses object identity to select options. It's possible for the identities of items
* to change while the data does not. This can happen, for example, if the items are produced
* from an RPC to the server, and that RPC is re-run. Even if the data hasn't changed, the
* second response will produce objects with different identities.
*
* To customize the default option comparison algorithm, `<select multiple>` supports `compareWith`
* input. `compareWith` takes a **function** which has two arguments: `option1` and `option2`.
* If `compareWith` is given, Angular selects options by the return value of the function.
*
* ### Syntax
*
* ### Using a multi-select control
*
* The follow example shows you how to use a multi-select control with a reactive form.
*
* ```ts
* const countryControl = new FormControl();
* ```
*
* ```
* <select multiple [compareWith]="compareFn" [(ngModel)]="selectedCountries">
* <option *ngFor="let country of countries" [ngValue]="country">
* {{country.name}}
* </option>
* <select multiple name="countries" [formControl]="countryControl">
* <option *ngFor="let country of countries" [ngValue]="country">
* {{ country.name }}
* </option>
* </select>
*
* compareFn(c1: Country, c2: Country): boolean {
* return c1 && c2 ? c1.id === c2.id : c1 === c2;
* }
* ```
*
* ### Customizing option selection
*
* To customize the default option comparison algorithm, `<select>` supports `compareWith` input.
* See the `SelectControlValueAccessor` for usage.
*
* @ngModule ReactiveFormsModule
* @ngModule FormsModule
@ -80,15 +82,34 @@ abstract class HTMLCollection {
providers: [SELECT_MULTIPLE_VALUE_ACCESSOR]
})
export class SelectMultipleControlValueAccessor implements ControlValueAccessor {
/**
* @description
* The current value
*/
value: any;
/** @internal */
_optionMap: Map<string, NgSelectMultipleOption> = new Map<string, NgSelectMultipleOption>();
/** @internal */
_idCounter: number = 0;
/**
* @description
* The registered callback function called when a change event occurs on the input element.
*/
onChange = (_: any) => {};
/**
* @description
* The registered callback function called when a blur event occurs on the input element.
*/
onTouched = () => {};
/**
* @description
* Tracks the option comparison algorithm for tracking identities when
* checking for changes.
*/
@Input()
set compareWith(fn: (o1: any, o2: any) => boolean) {
if (typeof fn !== 'function') {
@ -101,6 +122,13 @@ export class SelectMultipleControlValueAccessor implements ControlValueAccessor
constructor(private _renderer: Renderer2, private _elementRef: ElementRef) {}
/**
* @description
* Sets the "value" property on one or of more
* of the select's options.
*
* @param value The value
*/
writeValue(value: any): void {
this.value = value;
let optionSelectedStateSetter: (opt: NgSelectMultipleOption, o: any) => void;
@ -114,6 +142,13 @@ export class SelectMultipleControlValueAccessor implements ControlValueAccessor
this._optionMap.forEach(optionSelectedStateSetter);
}
/**
* @description
* Registers a function called when the control value changes
* and writes an array of the selected options.
*
* @param fn The callback function
*/
registerOnChange(fn: (value: any) => any): void {
this.onChange = (_: any) => {
const selected: Array<any> = [];
@ -140,8 +175,20 @@ export class SelectMultipleControlValueAccessor implements ControlValueAccessor
fn(selected);
};
}
/**
* @description
* Registers a function called when the control is touched.
*
* @param fn The callback function
*/
registerOnTouched(fn: () => any): void { this.onTouched = fn; }
/**
* Sets the "disabled" property on the select input element.
*
* @param isDisabled The disabled value
*/
setDisabledState(isDisabled: boolean): void {
this._renderer.setProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
}
@ -169,18 +216,14 @@ export class SelectMultipleControlValueAccessor implements ControlValueAccessor
}
/**
* @description
* Marks `<option>` as dynamic, so Angular can be notified when options change.
*
* @usageNotes
* ### Example
* @see `SelectMultipleControlValueAccessor`
*
* ```
* <select multiple name="city" ngModel>
* <option *ngFor="let c of cities" [value]="c"></option>
* </select>
* ```
* @ngModule FormsModule
* @ngModule ReactiveFormsModule
* @ngModule FormsModule
* @publicApi
*/
@Directive({selector: 'option'})
export class NgSelectMultipleOption implements OnDestroy {
@ -197,6 +240,11 @@ export class NgSelectMultipleOption implements OnDestroy {
}
}
/**
* @description
* Tracks the value bound to the option element. Unlike the value binding,
* ngValue supports binding to objects.
*/
@Input('ngValue')
set ngValue(value: any) {
if (this._select == null) return;
@ -205,6 +253,11 @@ export class NgSelectMultipleOption implements OnDestroy {
this._select.writeValue(this._select.value);
}
/**
* @description
* Tracks simple string values bound to the option element.
* For objects, use the `ngValue` input binding.
*/
@Input('value')
set value(value: any) {
if (this._select) {
@ -226,6 +279,10 @@ export class NgSelectMultipleOption implements OnDestroy {
this._renderer.setProperty(this._element.nativeElement, 'selected', selected);
}
/**
* @description
* Lifecycle method called before the directive's instance is destroyed. For internal use only.
*/
ngOnDestroy(): void {
if (this._select) {
this._select._optionMap.delete(this.id);

View File

@ -33,7 +33,9 @@ export type NavigationTrigger = 'imperative' | 'popstate' | 'hashchange';
* ```
* class MyService {
* constructor(public router: Router, logger: Logger) {
* router.events.filter(e => e instanceof RouterEvent).subscribe(e => {
* router.events.pipe(
* filter(e => e instanceof RouterEvent)
* ).subscribe(e => {
* logger.log(e.id, e.url);
* });
* }

View File

@ -17,13 +17,13 @@ import {UrlTree} from '../url_tree';
export function recognize(
rootComponentType: Type<any>| null, config: Route[], serializer: (url: UrlTree) => string,
paramsInheritanceStrategy: 'emptyOnly' |
'always'): MonoTypeOperatorFunction<NavigationTransition> {
paramsInheritanceStrategy: 'emptyOnly' | 'always', relativeLinkResolution: 'legacy' |
'corrected'): MonoTypeOperatorFunction<NavigationTransition> {
return function(source: Observable<NavigationTransition>) {
return source.pipe(mergeMap(
t => recognizeFn(
rootComponentType, config, t.urlAfterRedirects, serializer(t.urlAfterRedirects),
paramsInheritanceStrategy)
paramsInheritanceStrategy, relativeLinkResolution)
.pipe(map(targetSnapshot => ({...t, targetSnapshot})))));
};
}
}

View File

@ -413,7 +413,7 @@ export class Router {
// Recognize
recognize(
this.rootComponentType, this.config, (url) => this.serializeUrl(url),
this.paramsInheritanceStrategy),
this.paramsInheritanceStrategy, this.relativeLinkResolution),
// Fire RoutesRecognized
tap(t => {

View File

@ -76,6 +76,41 @@ describe('Integration', () => {
]);
})));
describe('relativeLinkResolution', () => {
beforeEach(inject([Router], (router: Router) => {
router.resetConfig([{
path: 'foo',
children: [{path: 'bar', children: [{path: '', component: RelativeLinkCmp}]}]
}]);
}));
it('should not ignore empty paths in legacy mode',
fakeAsync(inject([Router], (router: Router) => {
router.relativeLinkResolution = 'legacy';
const fixture = createRoot(router, RootCmp);
router.navigateByUrl('/foo/bar');
advance(fixture);
const link = fixture.nativeElement.querySelector('a');
expect(link.getAttribute('href')).toEqual('/foo/bar/simple');
})));
it('should ignore empty paths in corrected mode',
fakeAsync(inject([Router], (router: Router) => {
router.relativeLinkResolution = 'corrected';
const fixture = createRoot(router, RootCmp);
router.navigateByUrl('/foo/bar');
advance(fixture);
const link = fixture.nativeElement.querySelector('a');
expect(link.getAttribute('href')).toEqual('/foo/simple');
})));
});
it('should set the restoredState to null when executing imperative navigations',
fakeAsync(inject([Router], (router: Router) => {
router.resetConfig([
@ -3977,6 +4012,57 @@ describe('Integration', () => {
]);
})));
});
describe('relativeLinkResolution', () => {
@Component({selector: 'link-cmp', template: `<a [routerLink]="['../simple']">link</a>`})
class RelativeLinkCmp {
}
@NgModule({
declarations: [RelativeLinkCmp],
imports: [RouterModule.forChild([
{path: 'foo/bar', children: [{path: '', component: RelativeLinkCmp}]},
])]
})
class LazyLoadedModule {
}
it('should not ignore empty path when in legacy mode',
fakeAsync(inject(
[Router, NgModuleFactoryLoader],
(router: Router, loader: SpyNgModuleFactoryLoader) => {
router.relativeLinkResolution = 'legacy';
loader.stubbedModules = {expected: LazyLoadedModule};
const fixture = createRoot(router, RootCmp);
router.resetConfig([{path: 'lazy', loadChildren: 'expected'}]);
router.navigateByUrl('/lazy/foo/bar');
advance(fixture);
const link = fixture.nativeElement.querySelector('a');
expect(link.getAttribute('href')).toEqual('/lazy/foo/bar/simple');
})));
it('should ignore empty path when in corrected mode',
fakeAsync(inject(
[Router, NgModuleFactoryLoader],
(router: Router, loader: SpyNgModuleFactoryLoader) => {
router.relativeLinkResolution = 'corrected';
loader.stubbedModules = {expected: LazyLoadedModule};
const fixture = createRoot(router, RootCmp);
router.resetConfig([{path: 'lazy', loadChildren: 'expected'}]);
router.navigateByUrl('/lazy/foo/bar');
advance(fixture);
const link = fixture.nativeElement.querySelector('a');
expect(link.getAttribute('href')).toEqual('/lazy/foo/simple');
})));
});
});
describe('Custom Route Reuse Strategy', () => {

View File

@ -191,7 +191,7 @@ function resolveBazelFilePath(fileName: string): string {
// are not available in the working directory. In order to resolve the real path for the
// runfile, we need to use `require.resolve` which handles runfiles properly on Windows.
if (process.env['BAZEL_TARGET']) {
return path.posix.relative(process.cwd(), require.resolve(fileName));
return path.relative(process.cwd(), require.resolve(fileName));
}
return fileName;

View File

@ -381,7 +381,7 @@ const memberDeclarationOrder: {[key: number]: number} = {
};
function stripEmptyLines(text: string): string {
return text.split('\n').filter(x => !!x.length).join('\n');
return text.split(/\r?\n/).filter(x => !!x.length).join('\n');
}
/**