Compare commits

..

26 Commits
7.0.2 ... 7.0.3

Author SHA1 Message Date
926367150a release: cut the v7.0.3 release 2018-11-07 11:56:04 -08:00
14ff340e93 docs(forms): update reactive form directives API reference (#26823)
PR Close #26823
2018-11-05 15:11:42 -08:00
c8e750745a refactor(compiler): typo (#25496)
PR Close #25496
2018-11-05 12:53:04 -08:00
6b8f2da93e fix: missing semi-colon and empty line (#26945)
PR Close #26945
2018-11-05 11:37:56 -08:00
352219eafb docs: new GitHub Issue templates (#26918)
These templates take advatange of github's feature that allows
us to define multiple templates and which the UI presents to the
user and lets them choose the most appropriate template.

The goal of this is to tailor the templates to the templates to
various use-cases and bug categories and provide better guidance to
developers filing issues which should result in more efficient
processing of the issue backlog.

PR Close #26918
2018-11-05 09:51:15 -08:00
e8a8770eee style(compiler): typo fix (#26934)
PR Close #26934
2018-11-05 09:49:23 -08:00
f536261639 docs: minor edits and corrections to cli intro (#26654)
PR Close #26654
2018-11-02 13:22:10 -07:00
2d06693560 docs: edit file structure page (#26552)
PR Close #26552
2018-11-02 11:25:12 -07:00
315d95c747 fix(upgrade): make typings compatible with older AngularJS typings (#26880)
Make `angular1` typings compatible with older versions of AngularJS
typings from `@types/angular`.

Closes #26420

PR Close #26880
2018-11-02 11:24:06 -07:00
5c5b62cc86 ci: remove redundant start-xvfb step (#26869)
Since 8fc4ae51f, the jobs that need Xvfb use `*-browser` CircleCI docker
image flavors (e.g. `circleci/node:10.12-browsers`), which automatically
start Xvfb.

PR Close #26869
2018-11-02 10:37:36 -07:00
479f773bb3 ci: make integration_test job logs less verbose (#26869)
The build progress logs accounted for ~80% of the total log size, which
makes it harder to get to the interesting lines, such as error messages.

Used suggestion from [here][1].

[1]: https://github.com/angular/angular-cli/issues/11412#issuecomment-412021539

PR Close #26869
2018-11-02 10:37:36 -07:00
629c83588d build: upgrade @angular/cli in cli-hello-world integration test (#26869)
PR Close #26869
2018-11-02 10:37:36 -07:00
d96ba0ffd9 ci: fix and re-enable payload size checks for integration tests (#26869)
See #22810, #23376 and #23515 for more context.

Fixes #23376
Closes #23515

PR Close #26869
2018-11-02 10:37:36 -07:00
30700035b3 test: remove checks for non-existent directories in integration tests (#26869)
PR Close #26869
2018-11-02 10:37:36 -07:00
8c6d6b85db test: make elements integration tests less flaky (#26869)
PR Close #26869
2018-11-02 10:37:36 -07:00
0c10b57749 build(docs-infra): upgrade cli command docs sources to b50950b97 (#26915)
[Changed files](4faa81e25...b50950b97):

        - help/add.json

- help/build.json

- help/config.json

- help/doc.json

- help/e2e.json

- help/eject.json

- help/generate.json

- help/get.json

- help/help.json

- help/lint.json

- help/make-this-awesome.json

- help/new.json

- help/run.json

- help/serve.json

- help/set.json

- help/test.json

- help/update.json

- help/version.json

- help/xi18n.json

PR Close #26915
2018-11-02 10:35:55 -07:00
fe1c0d7a0c feat(docs-infra): add getting started widgets (#26059)
PR Close #26059
2018-11-02 10:34:54 -07:00
dc053859ee fix(router): remove type bludgeoning of context and outlet when running CanDeactivate (#26496)
Fixes #18253

PR Close #26496
2018-11-01 16:04:02 -07:00
4d532df52b fix(bazel): unknown replay compiler error in windows (#26711)
In Windows the compiler path ends with `.exe` thus it will never match and throw  `Unknown replay compiler`

PR Close #26711
2018-11-01 15:16:06 -07:00
5eb540cf34 docs: Webcomponents activated as of firefox 63 (#26889)
PR Close #26889
2018-11-01 14:30:22 -07:00
7697650edc fix(docs-infra): update overload rendering (#24976)
Based on the review here:
https://github.com/angular/angular/pull/24976#issuecomment-415535125

PR Close #24976
2018-11-01 14:17:12 -07:00
4517b8e618 build(docs-infra): display long overload parameter types as object (#24976)
In some overloads, the parameter type can be a large anonymous
object type.
This change displays such types as `object`. It is then up to the
documentation author to put more information about the type in the
method usage notes.

PR Close #24976
2018-11-01 14:17:12 -07:00
3abc4c9c54 build(docs-infra): fix individual API overload templates (#24976)
* Make individual overloads collapsible
* Show only the first overload expanded, rest collapsed
* Text changes to 'collapse all' once 'show all' is clicked
* Fix chevron/carrot rotation animation when overloads / overload item is expanded or collapsed

PR Close #24976
2018-11-01 14:17:12 -07:00
1d6333891c build(docs-infra): add method overload index (#24976)
PR Close #24976
2018-11-01 14:17:12 -07:00
4d4e85b708 build(docs-infra): improve API overload templates (#24976)
PR Close #24976
2018-11-01 14:17:12 -07:00
28104828d5 build: update canonical-path dependency (#26719)
This new version (1.0.0) provides a typings file!

PR Close #26719
2018-11-01 13:49:11 -07:00
75 changed files with 3947 additions and 2931 deletions

View File

@ -33,24 +33,18 @@ var_6: &job_defaults
docker:
- image: *default_docker_image
var_7: &start-xvfb
run:
name: Running X virtual framebuffer
command: Xvfb :99 -screen 0 1280x1024x24
background: true
# After checkout, rebase on top of master.
# Similar to travis behavior, but not quite the same.
# See https://discuss.circleci.com/t/1662
var_8: &post_checkout
var_7: &post_checkout
post: git pull --ff-only origin "refs/pull/${CI_PULL_REQUEST//*pull\//}/merge"
var_9: &yarn_install
var_8: &yarn_install
run:
name: Running Yarn install
command: yarn install --frozen-lockfile --non-interactive
var_10: &setup_circleci_bazel_config
var_9: &setup_circleci_bazel_config
run:
name: Setting up CircleCI bazel configuration
command: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
@ -167,7 +161,6 @@ jobs:
- restore_cache:
key: *cache_key
- *define_env_vars
- *start-xvfb
# Build aio
- run: yarn --cwd aio build --progress=false
# Lint the code
@ -196,7 +189,6 @@ jobs:
- restore_cache:
key: *cache_key
- *define_env_vars
- *start-xvfb
# Deploy angular.io to production (if necessary)
- run: setPublicVar CI_STABLE_BRANCH "$(npm info @angular/core dist-tags.latest | sed -r 's/^\s*([0-9]+\.[0-9]+)\.[0-9]+.*$/\1.x/')"
- run: yarn --cwd aio deploy-production
@ -214,7 +206,6 @@ jobs:
- attach_workspace:
at: dist
- *define_env_vars
- *start-xvfb
# Build aio (with local Angular packages)
- run: yarn --cwd aio build-local --progress=false
# Run PWA-score tests
@ -255,7 +246,6 @@ jobs:
- attach_workspace:
at: dist
- *define_env_vars
- *start-xvfb
# Install root
- *yarn_install
# Install aio
@ -276,7 +266,6 @@ jobs:
- attach_workspace:
at: dist
- *define_env_vars
- *start-xvfb
# Install root
- *yarn_install
# Install aio
@ -315,7 +304,6 @@ jobs:
- restore_cache:
key: *cache_key
- *define_env_vars
- *start-xvfb
- run: yarn install --cwd aio --frozen-lockfile --non-interactive
- run:
name: Wait for preview and run tests
@ -375,7 +363,6 @@ jobs:
- attach_workspace:
at: dist
- *define_env_vars
- *start-xvfb
- run: ./integration/run_tests.sh
# This job updates the content of repos like github.com/angular/core-builds
@ -419,7 +406,6 @@ jobs:
- restore_cache:
key: *cache_key
- *define_env_vars
- *start-xvfb
- run:
name: Run tests against the deployed apps
command: ./aio/scripts/test-production.sh $CI_AIO_MIN_PWA_SCORE

View File

@ -1,59 +1,10 @@
<!--
PLEASE HELP US PROCESS GITHUB ISSUES FASTER BY PROVIDING THE FOLLOWING INFORMATION.
🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑
ISSUES MISSING IMPORTANT INFORMATION MAY BE CLOSED WITHOUT INVESTIGATION.
-->
Please help us process issues more efficiently by filing an
issue using one of the following templates:
## I'm submitting a...
<!-- Check one of the following options with "x" -->
<pre><code>
[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report <!-- Please search GitHub for a similar issue or PR before submitting -->
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question
[ ] Other... Please describe:
</code></pre>
https://github.com/angular/angular/issues/new/choose
## Current behavior
<!-- Describe how the issue manifests. -->
Thank you!
## Expected behavior
<!-- Describe what the desired behavior would be. -->
## Minimal reproduction of the problem with instructions
<!--
For bug reports please provide the *STEPS TO REPRODUCE* and if possible a *MINIMAL DEMO* of the problem via
https://stackblitz.com or similar (you can use this template as a starting point: https://stackblitz.com/fork/angular-gitter).
-->
## What is the motivation / use case for changing the behavior?
<!-- Describe the motivation or the concrete use case. -->
## Environment
<pre><code>
Angular version: X.Y.Z
<!-- Check whether this is still an issue in the most recent Angular version -->
Browser:
- [ ] Chrome (desktop) version XX
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [ ] Firefox version XX
- [ ] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX
For Tooling issues:
- Node version: XX <!-- run `node --version` -->
- Platform: <!-- Mac, Linux, Windows -->
Others:
<!-- Anything else relevant? Operating system version, IDE, package manager, HTTP server, ... -->
</code></pre>
🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑

63
.github/ISSUE_TEMPLATE/1-bug-report.md vendored Normal file
View File

@ -0,0 +1,63 @@
---
name: "\U0001F41EBug report"
about: Report a bug in the Angular Framework
---
<!--🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅
Oh hi there! 😄
To expedite issue processing please search open and closed issues before submitting a new one.
Existing issues often contain information about workarounds, resolution, or progress updates.
🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅-->
# 🐞 bug report
### Affected Package
<!-- Can you pin-point one or more @angular/* packages as the source of the bug? -->
<!-- ✍edit: --> The issue is caused by package @angular/....
### Is this a regression?
<!-- Did this behavior use to work in the previous version? -->
<!-- ✍️--> Yes, the previous version in which this bug was not present was: ....
### Description
<!-- ✍️--> A clear and concise description of the problem...
## 🔬 Minimal Reproduction
<!--
Please create and share minimal reproduction of the issue starting with this template: https://stackblitz.com/fork/angular-issue-repro2
-->
<!-- ✍️--> https://stackblitz.com/...
<!--
If StackBlitz is not suitable for reproduction of your issue, please create a minimal GitHub repository with the reproduction of the issue. Share the link to the repo below along with step-by-step instructions to reproduce the problem, as well as expected and actual behavior.
-->
## 🔥 Exception or Error
<pre><code>
<!-- If the issue is accompanied by an exception or an error, please share it below: -->
<!-- ✍️-->
</code></pre>
## 🌍 Your Environment
**Angular Version:**
<pre><code>
<!-- run `ng version` and paste output below -->
<!-- ✍️-->
</code></pre>
**Anything else relevant?**
<!-- ✍Is this a browser specific issue? If so, please specify the browser and version. -->
<!-- ✍Do any of these matter: operating system, IDE, package manager, HTTP server, ...? If so, please mention it below. -->

View File

@ -0,0 +1,32 @@
---
name: "\U0001F680Feature request"
about: Suggest a feature for Angular Framework
---
<!--🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅
Oh hi there! 😄
To expedite issue processing please search open and closed issues before submitting a new one.
Existing issues often contain information about workarounds, resolution, or progress updates.
🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅-->
# 🚀 feature request
### Releavant Package
<!-- Can you pin-point one or more @angular/* packages the are relevant for this feature request? -->
<!-- ✍edit: --> This feature request is for @angular/....
### Description
<!-- ✍️--> A clear and concise description of the problem or missing capability...
### Describe the solution you'd like
<!-- ✍️--> If you have a solution in mind, please describe it.
### Describe alternatives you've considered
<!-- ✍️--> Have you considered any alternative solutions or workarounds?

55
.github/ISSUE_TEMPLATE/3-docs-bug.md vendored Normal file
View File

@ -0,0 +1,55 @@
---
name: "📚 Docs or angular.io issue report"
about: Report an issue in Angular's documentation or angular.io application
---
<!--🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅
Oh hi there! 😄
To expedite issue processing please search open and closed issues before submitting a new one.
Existing issues often contain information about workarounds, resolution, or progress updates.
🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅-->
# 📚 Docs or angular.io bug report
### Description
<!-- ✍edit:--> A clear and concise description of the problem...
## 🔬 Minimal Reproduction
### What's the affected URL?**
<!-- ✍edit:--> https://angular.io/...
### Reproduction Steps**
<!-- If applicable please list the steps to take to reproduce the issue -->
<!-- ✍edit:-->
### Expected vs Actual Behavior**
<!-- If applicable please describe the difference between the expected and actual behavior after following the repro steps. -->
<!-- ✍edit:-->
## 📷Screenshot
<!-- Often a screenshot can help to capture the issue better than a long description. -->
<!-- ✍upload a screenshot:-->
## 🔥 Exception or Error
<pre><code>
<!-- If the issue is accompanied by an exception or an error, please share it below: -->
<!-- ✍️-->
</code></pre>
## 🌍 Your Environment
### Browser info
<!-- ✍Is this a browser specific issue? If so, please specify the device, browser, and version. -->
### Anything else relevant?
<!-- ✍Please provide additional info if necessary. -->

View File

@ -0,0 +1,11 @@
---
name: ⚠️ Security issue disclosure
about: Report a security issue in Angular Framework, Material, or CLI
---
🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑
Please read https://angular.io/guide/security#report-issues on how to disclose security related issues.
🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑

View File

@ -0,0 +1,16 @@
---
name: "❓Support request"
about: Questions and requests for support
---
🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑
Please do not file questions or support requests on the GitHub issues tracker.
You can get your questions answered using other communication channels. Please see:
https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question
Thank you!
🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑

13
.github/ISSUE_TEMPLATE/6-angular-cli.md vendored Normal file
View File

@ -0,0 +1,13 @@
---
name: "\U0001F6E0Angular CLI"
about: Issues and feature requests for Angular CLI
---
🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑
Please file any Angular CLI issues at: https://github.com/angular/angular-cli/issues/new
For the time being, we keep Angular CLI issues in a separate repository.
🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑

View File

@ -0,0 +1,13 @@
---
name: "\U0001F48EAngular Material"
about: Issues and feature requests for Angular Material
---
🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑
Please file any Angular Material issues at: https://github.com/angular/material2/issues/new
For the time being, we keep Angular Material issues in a separate repository.
🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑

View File

@ -1,3 +1,15 @@
<a name="7.0.3"></a>
## [7.0.3](https://github.com/angular/angular/compare/7.0.2...7.0.3) (2018-11-07)
### Bug Fixes
* **bazel:** unknown replay compiler error in windows ([#26711](https://github.com/angular/angular/issues/26711)) ([4d532df](https://github.com/angular/angular/commit/4d532df))
* **router:** remove type bludgeoning of context and outlet when running CanDeactivate ([#26496](https://github.com/angular/angular/issues/26496)) ([dc05385](https://github.com/angular/angular/commit/dc05385)), closes [#18253](https://github.com/angular/angular/issues/18253)
* **upgrade:** make typings compatible with older AngularJS typings ([#26880](https://github.com/angular/angular/issues/26880)) ([315d95c](https://github.com/angular/angular/commit/315d95c)), closes [#26420](https://github.com/angular/angular/issues/26420)
<a name="7.0.2"></a>
## [7.0.2](https://github.com/angular/angular/compare/7.0.1...7.0.2) (2018-10-31)

View File

@ -33,7 +33,8 @@ cd my-first-project
ng serve
</code-example>
In your browser, open http://localhost:4200/ to see the new app run.
In your browser, open http://localhost:4200/ to see the new app run.
When you use the [ng serve](cli/serve) command to build an app and serve it locally, the server automatically rebuilds the app and reloads the page when you change any of the source files.
## Workspaces and project files
@ -51,12 +52,13 @@ Commands such as [add](cli/add) and [generate](cli/generate), which create or op
* See more about the [Workspace file structure](guide/file-structure).
When you use the [ng serve](cli/serve) command to build an app and serve it locally, the server automatically rebuilds the app and reloads the page when you change any of the source files.
### Workspace and project configuration
A single workspace configuration file, `angular.json`, is created at the top level of the workspace.
This is where you can set workspace-wide defaults, and specify configurations to use when the CLI builds a project for different targets.
This is where you can set per-project defaults for CLI command options, and specify configurations to use when the CLI builds a project for different targets.
The [ng config](cli/config) command lets you set and retrieve configuration values from the command line, or you can edit the `angular.json` file directly.
The [ng config](cli/config) command lets you set and retrieve configuration values from the command line, or you can edit the `angular.json` file directly.
Note that option names in the configuration file must use [camelCase](guide/glossary#case-types), while option names supplied to commands can use either camelCase or dash-case.
* See the [complete schema](https://github.com/angular/angular-cli/wiki/angular-workspace) for `angular.json`.
<!-- * Learn more about *configuration options for Angular(links to new guide or topics TBD)*. -->

View File

@ -107,7 +107,7 @@ The recently-developed [custom elements](https://developer.mozilla.org/en-US/doc
</tr>
<tr>
<td>Firefox</td>
<td> Set the <code>dom.webcomponents.enabled</code> and <code>dom.webcomponents.customelements.enabled</code> preferences to true. Planned to be enabled by default in version 63.</td>
<td>Supported natively as of version 63. In older versions: Set the <code>dom.webcomponents.enabled</code> and <code>dom.webcomponents.customelements.enabled</code> preferences to true.</td>
</tr>
<tr>
<td>Edge</td>

View File

@ -20,16 +20,15 @@ The top level of the workspace contains a number of workspace-wide configuration
| :--------------------- | :------------------------------------------|
| `.editorconfig` | Configuration for code editors. See [EditorConfig](https://editorconfig.org/). |
| `.gitignore` | Specifies intentionally untracked files that [Git](https://git-scm.com/) should ignore. |
| `angular.json` | CLI configuration for all projects in the workspace, including configuration options for build, serve, and test tools that the CLI uses, such as [Karma](https://karma-runner.github.io/) and [Protractor](http://www.protractortest.org/). |
| `angular.json` | CLI configuration defaults for all projects in the workspace, including configuration options for build, serve, and test tools that the CLI uses, such as [TSLint](https://palantir.github.io/tslint/), [Karma](https://karma-runner.github.io/), and [Protractor](http://www.protractortest.org/). For details, see [Angular Workspace Configuration](guide/workspace-config). |
| `node_modules` | Provides [npm packages](guide/npm-packages) to the entire workspace. |
| `package.json` | Lists package dependencies. See [npm documentation](https://docs.npmjs.com/files/package.json) for the specific format and contents of this file.|
| `tsconfig.app.json` | Default [TypeScript](https://www.typescriptlang.org/) configuration for apps in the workspace. |
| `tsconfig.spec.json` | Default TypeScript configuration for e2e test apps in the workspace. |
| `tslint.json` | Default [TSLint](https://palantir.github.io/tslint/) configuration for apps in the workspace. |
| `README.md` | Introductory documentation. |
| `package.json` | Configures [npm package dependencies](guide/npm-packages) that are available to all projects in the workspace. See [npm documentation](https://docs.npmjs.com/files/package.json) for the specific format and contents of this file. |
| `package-lock.json` | Provides version information for all packages installed into `node_modules` by the npm client. See [npm documentation](https://docs.npmjs.com/files/package-lock.json) for details. If you use the yarn client, this file will be [yarn.lock](https://yarnpkg.com/lang/en/docs/yarn-lock/) instead. |
| `tsconfig.json` | Default [TypeScript](https://www.typescriptlang.org/) configuration for apps in the workspace, including TypeScript and Angular template compiler options. See [TypeScript Configuration](guide/typescript-configuration). |
| `tslint.json` | Default [TSLint](https://palantir.github.io/tslint/) configuration for apps in the workspace. |
| `README.md` | Introductory documentation. |
All projects within a workspace share this configuration context.
All projects within a workspace share a [CLI configuration context](guide/workspace-config).
Project-specific [TypeScript](https://www.typescriptlang.org/) configuration files inherit from the workspace-wide `tsconfig.*.json`, and app-specific [TSLint](https://palantir.github.io/tslint/) configuration files inherit from the workspace-wide `tslint.json`.
### Default app project files
@ -40,9 +39,9 @@ This initial app is the *default app* for CLI commands (unless you change the de
A newly generated app contains the source files for a root module, with a root component and template.
When the workspace file structure is in place, you can use the `ng generate` command on the command line to add functionality and data to the initial app.
<div class="alert is-helpful>
<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://angular.console.com), or manipulate files directly in the app's source folder and configuration files.
</div>
@ -104,18 +103,6 @@ my-app/
Inside the `src/` folder, the `app/` folder contains your app's logic and data. Angular components, templates, and styles go here. An `assets/` subfolder contains images and anything else your app needs. Files at the top level of `src/` support testing and running your app.
<code-example language="none" linenums="false">
src/
app/
app.component.css
app.component.html
app.component.spec.ts
app.component.ts
app.module.ts
assets/...
...
</code-example>
| APP SOURCE FILES | PURPOSE |
| :-------------------------- | :------------------------------------------|
| `app/app.component.ts` | Defines the logic for the app's root component, named `AppComponent`. The view associated with this root component becomes the root of the [view hierarchy](guide/glossary#view-hierarchy) as you add components and services to your app. |

View File

@ -120,3 +120,20 @@ Try this <live-example></live-example>.
<live-example embedded name="testy" stackblitz="super-stackblitz"></live-example>
<p>More text follows ...</p>
<p>Getting Started Widgets</p>
<p>Interpolation</p>
<aio-gs-interpolation></aio-gs-interpolation>
<p>Property Binding</p>
<aio-gs-property-binding></aio-gs-property-binding>
<p>Event Binding</p>
<aio-gs-event-binding></aio-gs-event-binding>
<p>NgIf</p>
<aio-gs-ng-if></aio-gs-ng-if>
<p>NgFor</p>
<aio-gs-ng-for></aio-gs-ng-for>

View File

@ -18,7 +18,7 @@
"build-for": "yarn ~~build --configuration",
"prebuild-local": "yarn setup-local",
"build-local": "yarn ~~build --configuration=stable",
"extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js 4faa81e25",
"extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js b50950b97",
"lint": "yarn check-env && yarn docs-lint && ng lint && yarn example-lint && yarn tools-lint",
"test": "yarn check-env && ng test",
"pree2e": "yarn check-env && yarn update-webdriver",
@ -104,7 +104,7 @@
"@types/jasminewd2": "^2.0.4",
"@types/node": "~6.0.60",
"archiver": "^1.3.0",
"canonical-path": "^0.0.2",
"canonical-path": "1.0.0",
"chalk": "^2.1.0",
"cjson": "^0.5.0",
"codelyzer": "~4.2.1",

View File

@ -2,8 +2,8 @@
"aio": {
"master": {
"uncompressed": {
"runtime": 3173,
"main": 494475,
"runtime": 3881,
"main": 499953,
"polyfills": 53926,
"prettify": 14917
}

View File

@ -48,6 +48,26 @@ export const ELEMENT_MODULE_PATHS_AS_ROUTES = [
selector: 'live-example',
loadChildren: './live-example/live-example.module#LiveExampleModule'
},
{
selector: 'aio-gs-interpolation',
loadChildren: './getting-started/interpolation/interpolation.module#InterpolationModule'
},
{
selector: 'aio-gs-property-binding',
loadChildren: './getting-started/property-binding/property-binding.module#PropertyBindingModule'
},
{
selector: 'aio-gs-event-binding',
loadChildren: './getting-started/event-binding/event-binding.module#EventBindingModule'
},
{
selector: 'aio-gs-ng-if',
loadChildren: './getting-started/ng-if/ng-if.module#NgIfModule'
},
{
selector: 'aio-gs-ng-for',
loadChildren: './getting-started/ng-for/ng-for.module#NgForModule'
},
];
/**

View File

@ -0,0 +1,39 @@
import { Component } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ContainerComponent } from './container.component';
@Component({
template: `
<aio-gs-container>
<ng-container class="template">Template</ng-container>
<ng-container class="data">Data</ng-container>
<ng-container class="result">Result</ng-container>
</aio-gs-container>
`
})
export class TestComponent {}
describe('Getting Started Container Component', () => {
let fixture: ComponentFixture<ContainerComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ ContainerComponent, TestComponent ]
});
fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
});
it('should project the content into the appropriate areas', () => {
const compiled = fixture.debugElement.nativeElement;
const pre = compiled.querySelector('pre');
const code = compiled.querySelector('code');
const tabledata = compiled.querySelectorAll('td');
expect(pre.textContent).toContain('Template');
expect(code.textContent).toContain('Data');
expect(tabledata[2].textContent).toContain('Result');
});
});

View File

@ -0,0 +1,80 @@
import { Component } from '@angular/core';
@Component({
selector: 'aio-gs-container',
template: `
<table>
<thead>
<tr>
<th>Template</th>
<th>Data</th>
<th>Result</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<pre><ng-content select=".template"></ng-content></pre>
</td>
<td>
<code><ng-content select=".data"></ng-content></code>
</td>
<td><ng-content select=".result"></ng-content></td>
</tr>
</tbody>
</table>
`,
styles: [
`
pre {
margin: 0;
}
code {
display: flex;
align-items: center;
}
@media only screen and (max-width: 760px),
(min-device-width: 768px) and (max-device-width: 1024px) {
/* Force table to not be like tables anymore */
table, thead, tbody, th, td, tr {
display: block;
}
/* Hide table headers (but not display: none;, for accessibility) */
thead tr {
position: absolute;
top: -9999px;
left: -9999px;
}
tr { border: 1px solid #ccc; }
td {
/* Behave like a "row" */
border: none;
border-bottom: 1px solid #eee;
position: relative;
padding-top: 10%;
}
td:before {
/* Now like a table header */
position: absolute;
/* Top/left values mimic padding */
top: 6px;
left: 6px;
width: 45%;
padding-right: 10px;
}
/* Label the data */
td:nth-of-type(1):before { content: "Template"; }
td:nth-of-type(2):before { content: "Data"; }
td:nth-of-type(3):before { content: "Result"; }
}
`
]
})
export class ContainerComponent { }

View File

@ -0,0 +1,10 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ContainerComponent } from './container.component';
@NgModule({
imports: [ CommonModule ],
declarations: [ ContainerComponent ],
exports: [ ContainerComponent ]
})
export class ContainerModule { }

View File

@ -0,0 +1,46 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ContainerModule } from '../container/container.module';
import { EventBindingComponent } from './event-binding.component';
import { createCustomEvent } from '../../../../testing/dom-utils';
describe('Getting Started Event Binding Component', () => {
let component: EventBindingComponent;
let fixture: ComponentFixture<EventBindingComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ ContainerModule ],
declarations: [ EventBindingComponent ]
});
fixture = TestBed.createComponent(EventBindingComponent);
component = fixture.componentInstance;
fixture.detectChanges();
spyOn(window, 'alert');
});
it('should update the name property on input change', () => {
const text = 'Hello Angular';
const compiled = fixture.debugElement.nativeElement;
const input: HTMLInputElement = compiled.querySelector('input');
input.value = text;
input.dispatchEvent(createCustomEvent(document, 'input', ''));
fixture.detectChanges();
expect(component.name).toBe(text);
});
it('should display an alert when the button is clicked', () => {
const compiled = fixture.debugElement.nativeElement;
const button: HTMLButtonElement = compiled.querySelector('button');
button.click();
expect(window.alert).toHaveBeenCalledWith('Hello, Angular!');
});
});

View File

@ -0,0 +1,30 @@
import { Component } from '@angular/core';
@Component({
selector: 'aio-gs-event-binding',
template: `
<aio-gs-container>
<ng-container class="template">&lt;button (click)="greet(name)"&gt;
Greet
&lt;/button&gt;</ng-container>
<ng-container class="data">
name = '<input #input (input)="name = input.value" [value]="name">';
</ng-container>
<ng-container class="result">
<button (click)="greet(name)">
Greet
</button>
</ng-container>
</aio-gs-container>
`,
preserveWhitespaces: true
})
export class EventBindingComponent {
name = 'Angular';
greet(name: string) {
window.alert(`Hello, ${name}!`);
}
}

View File

@ -0,0 +1,15 @@
import { NgModule, Type } from '@angular/core';
import { CommonModule } from '@angular/common';
import { WithCustomElementComponent } from '../../element-registry';
import { EventBindingComponent } from './event-binding.component';
import { ContainerModule } from '../container/container.module';
@NgModule({
imports: [ CommonModule, ContainerModule ],
declarations: [ EventBindingComponent ],
exports: [ EventBindingComponent ],
entryComponents: [ EventBindingComponent ]
})
export class EventBindingModule implements WithCustomElementComponent {
customElementComponent: Type<any> = EventBindingComponent;
}

View File

@ -0,0 +1,41 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { InterpolationComponent } from './interpolation.component';
import { ContainerModule } from '../container/container.module';
import { createCustomEvent } from '../../../../testing/dom-utils';
describe('Getting Started Interpolation Component', () => {
let component: InterpolationComponent;
let fixture: ComponentFixture<InterpolationComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ ContainerModule ],
declarations: [ InterpolationComponent ]
});
fixture = TestBed.createComponent(InterpolationComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should update the siteName property on input change', () => {
const text = 'Hello Angular';
const compiled = fixture.debugElement.nativeElement;
const input: HTMLInputElement = compiled.querySelector('input');
input.value = text;
input.dispatchEvent(createCustomEvent(document, 'input', ''));
fixture.detectChanges();
expect(component.siteName).toBe(text);
});
it('should display the siteName', () => {
const compiled = fixture.debugElement.nativeElement;
const header: HTMLHeadingElement = compiled.querySelector('h1');
expect(header.textContent).toContain('My Store');
});
});

View File

@ -0,0 +1,20 @@
import { Component } from '@angular/core';
@Component({
selector: 'aio-gs-interpolation',
template: `
<aio-gs-container>
<ng-container class="template">&lt;h1&gt;Welcome to {{'{'+'{'}}siteName{{'}'+'}'}}&lt;h1&gt;</ng-container>
<ng-container class="data">
siteName = '<input #input (input)="siteName = input.value" [value]="siteName">';
</ng-container>
<ng-container class="result"><h1>Welcome to {{ siteName }}</h1></ng-container>
</aio-gs-container>
`
})
export class InterpolationComponent {
siteName = 'My Store';
}

View File

@ -0,0 +1,15 @@
import { NgModule, Type } from '@angular/core';
import { CommonModule } from '@angular/common';
import { WithCustomElementComponent } from '../../element-registry';
import { InterpolationComponent } from './interpolation.component';
import { ContainerModule } from '../container/container.module';
@NgModule({
imports: [ CommonModule, ContainerModule ],
declarations: [ InterpolationComponent ],
exports: [ InterpolationComponent ],
entryComponents: [ InterpolationComponent ]
})
export class InterpolationModule implements WithCustomElementComponent {
customElementComponent: Type<any> = InterpolationComponent;
}

View File

@ -0,0 +1,42 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NgForComponent } from './ng-for.component';
import { ContainerModule } from '../container/container.module';
import { ProductService } from '../product.service';
describe('Getting Started NgFor Component', () => {
let component: NgForComponent;
let fixture: ComponentFixture<NgForComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ ContainerModule ],
declarations: [ NgForComponent ],
providers: [ ProductService ]
});
fixture = TestBed.createComponent(NgForComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should display the products', () => {
const compiled = fixture.debugElement.nativeElement;
const spans = compiled.querySelectorAll('span');
expect(spans[0]!.textContent).toContain('Shoes');
expect(spans[1]!.textContent).toContain('Phones');
});
it('should display an error message if provided products JSON is invalid', () => {
fixture.detectChanges();
component.productsData$.next('bad');
fixture.detectChanges();
component.parseError$.subscribe(error => {
expect(error).toBeTruthy();
})
});
});

View File

@ -0,0 +1,46 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
import { ProductService } from '../product.service';
@Component({
selector: 'aio-gs-ng-for',
template: `
<aio-gs-container>
<ng-container class="template">&lt;span *ngFor="let product of products">
{{'{'+'{'}}product{{'}'+'}'}}
&lt;/span&gt;</ng-container>
<ng-container class="data">
products = <input #input (input)="productsData$.next(input.value)" [value]="productsData$ | async">;
<div *ngIf="parseError$ | async" class="material-icons" matTooltip="The provided JSON is invalid">error_outline</div>
</ng-container>
<ng-container class="result">
<span *ngFor="let product of products$ | async">{{product}}</span>
</ng-container>
</aio-gs-container>
`,
styles: [`
span::after {
content: ' ';
}
`],
preserveWhitespaces: true
})
export class NgForComponent implements OnInit, OnDestroy {
productsData$ = this.productService.productsData$;
products$ = this.productService.products$;
parseError$ = this.productService.parseError$;
productsSub: Subscription;
constructor(private productService: ProductService) {}
ngOnInit() {
this.productsSub = this.productService.init().subscribe();
}
ngOnDestroy() {
this.productsSub.unsubscribe();
}
}

View File

@ -0,0 +1,18 @@
import { NgModule, Type } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatTooltipModule } from '@angular/material/tooltip';
import { WithCustomElementComponent } from '../../element-registry';
import { NgForComponent } from './ng-for.component';
import { ContainerModule } from '../container/container.module';
import { ProductService } from '../product.service';
@NgModule({
imports: [ CommonModule, ContainerModule, MatTooltipModule ],
declarations: [ NgForComponent ],
exports: [ NgForComponent ],
entryComponents: [ NgForComponent ],
providers: [ ProductService ]
})
export class NgForModule implements WithCustomElementComponent {
customElementComponent: Type<any> = NgForComponent;
}

View File

@ -0,0 +1,51 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NgIfComponent } from './ng-if.component';
import { ContainerModule } from '../container/container.module';
import { ProductService } from '../product.service';
describe('Getting Started NgIf Component', () => {
let component: NgIfComponent;
let fixture: ComponentFixture<NgIfComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ ContainerModule ],
declarations: [ NgIfComponent ],
providers: [ ProductService ]
});
fixture = TestBed.createComponent(NgIfComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should display the message if products are listed', () => {
const compiled = fixture.debugElement.nativeElement;
const paragraph = compiled.querySelector('p');
expect(paragraph.textContent).toContain('available');
});
it('should not display the message if products list is empty', () => {
component.products$.next([]);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
const paragraph = compiled.querySelector('p');
expect(paragraph).toBeFalsy();
});
it('should display an error message if provided products JSON is invalid', () => {
fixture.detectChanges();
component.productsData$.next('bad');
fixture.detectChanges();
component.parseError$.subscribe(error => {
expect(error).toBeTruthy();
})
});
});

View File

@ -0,0 +1,43 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
import { ProductService } from '../product.service';
@Component({
selector: 'aio-gs-ng-if',
template: `
<aio-gs-container>
<ng-container class="template">&lt;p *ngIf="products.length > 0"&gt;
We still have products available.
&lt;/p&gt;</ng-container>
<ng-container class="data">
products = <input #input (input)="productsData$.next(input.value)" [value]="productsData$ | async">;
<div *ngIf="parseError$ | async" class="material-icons" matTooltip="The provided JSON is invalid">error_outline</div>
</ng-container>
<ng-container class="result">
<p *ngIf="(products$ | async)?.length > 0">
We still have products available.
</p>
</ng-container>
</aio-gs-container>
`,
preserveWhitespaces: true
})
export class NgIfComponent implements OnInit, OnDestroy {
productsData$ = this.productService.productsData$;
products$ = this.productService.products$;
parseError$ = this.productService.parseError$;
productsSub: Subscription;
constructor(private productService: ProductService) {}
ngOnInit() {
this.productsSub = this.productService.init().subscribe();
}
ngOnDestroy() {
this.productsSub.unsubscribe();
}
}

View File

@ -0,0 +1,18 @@
import { NgModule, Type } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatTooltipModule } from '@angular/material/tooltip';
import { WithCustomElementComponent } from '../../element-registry';
import { NgIfComponent } from './ng-if.component';
import { ContainerModule } from '../container/container.module';
import { ProductService } from '../product.service';
@NgModule({
imports: [ CommonModule, ContainerModule, MatTooltipModule ],
declarations: [ NgIfComponent ],
exports: [ NgIfComponent ],
entryComponents: [ NgIfComponent ],
providers: [ ProductService ]
})
export class NgIfModule implements WithCustomElementComponent {
customElementComponent: Type<any> = NgIfComponent;
}

View File

@ -0,0 +1,33 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, Subject } from 'rxjs';
import { tap, debounceTime } from 'rxjs/operators';
export const PRODUCTS = ['Shoes', 'Phones'];
@Injectable()
export class ProductService {
productsData$ = new BehaviorSubject(JSON.stringify(PRODUCTS));
products$ = new BehaviorSubject<string[]>(PRODUCTS);
parseError$ = new Subject<boolean>();
init() {
return this.productsData$.pipe(
debounceTime(250),
tap(data => {
let parsed;
try {
parsed = JSON.parse(data);
} catch (e) {
parsed = null;
}
if (parsed && Array.isArray(parsed)) {
this.products$.next(parsed);
this.parseError$.next(false);
} else {
this.parseError$.next(true);
}
}));
}
}

View File

@ -0,0 +1,46 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PropertyBindingComponent } from './property-binding.component';
import { ContainerModule } from '../container/container.module';
import { createCustomEvent } from '../../../../testing/dom-utils';
describe('Getting Started Property Binding Component', () => {
let component: PropertyBindingComponent;
let fixture: ComponentFixture<PropertyBindingComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ ContainerModule ],
declarations: [ PropertyBindingComponent ]
});
fixture = TestBed.createComponent(PropertyBindingComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should update the image title on input change', () => {
const text = 'Hello Angular';
const compiled = fixture.debugElement.nativeElement;
const input: HTMLInputElement = compiled.querySelector('input');
input.value = text;
input.dispatchEvent(createCustomEvent(document, 'input', ''));
fixture.detectChanges();
expect(component.imageTitle).toBe(text);
});
it('should display the image title', () => {
const compiled = fixture.debugElement.nativeElement;
const image: HTMLImageElement = compiled.querySelector('img');
expect(image.getAttribute('title')).toContain('Angular Logo');
});
});

View File

@ -0,0 +1,21 @@
import { Component } from '@angular/core';
@Component({
selector: 'aio-gs-property-binding',
template: `
<aio-gs-container>
<ng-container class="template">&lt;img ... [title]="imageTitle"&gt;</ng-container>
<ng-container class="data">
imageTitle = '<input #input (input)="imageTitle = input.value" [value]="imageTitle">';
</ng-container>
<ng-container class="result">
<img src="/assets/images/logos/angular/angular.svg" width="37" height="40" [title]="imageTitle">
</ng-container>
</aio-gs-container>
`
})
export class PropertyBindingComponent {
imageTitle = 'Angular Logo';
}

View File

@ -0,0 +1,15 @@
import { NgModule, Type } from '@angular/core';
import { CommonModule } from '@angular/common';
import { WithCustomElementComponent } from '../../element-registry';
import { PropertyBindingComponent } from './property-binding.component';
import { ContainerModule } from '../container/container.module';
@NgModule({
imports: [ CommonModule, ContainerModule ],
declarations: [ PropertyBindingComponent ],
exports: [ PropertyBindingComponent ],
entryComponents: [ PropertyBindingComponent ]
})
export class PropertyBindingModule implements WithCustomElementComponent {
customElementComponent: Type<any> = PropertyBindingComponent;
}

View File

@ -123,7 +123,7 @@ td {
padding: 8px 30px;
letter-spacing: 0.30px;
p {
p:first-child, p:last-child {
margin: 0;
}
}

View File

@ -1,46 +0,0 @@
.api-body {
max-width: 1200px;
table {
margin: 12px 0 24px;
th {
text-transform: none;
font-size: 16px;
font-weight: bold;
}
tr {
border-bottom: 1px solid $lightgray;
}
td {
vertical-align: middle;
}
hr {
margin: 16px 0;
}
tr:last-child {
border-bottom: none;
}
&.item-table {
td {
padding: 32px;
}
}
&.list-table {
td {
padding: 16px 24px;
}
}
.short-description {
margin-left: 0;
}
}
}

View File

@ -2,7 +2,6 @@
LAYOUT STYLES
============================== */
@import 'api-pages';
@import 'content-layout';
@import 'doc-viewer';
@import 'footer';

View File

@ -1,13 +1,111 @@
.github-links {
float: right;
.material-icons {
border-radius: 4px;
padding: 4px;
font-size: 20px;
&:hover {
background-color: $mist;
.api-body {
max-width: 1200px;
table {
margin: 12px 0 24px;
th {
text-transform: none;
font-size: 16px;
font-weight: bold;
}
tr {
border-bottom: 1px solid $lightgray;
}
td {
vertical-align: middle;
}
// This is overriding a style here:
// https://github.com/angular/angular/blob/95993e1/aio/src/styles/2-modules/_table.scss#L58-L62
tbody > tr > td tr td:first-child {
@media screen and (max-width: 480px) {
background-color: inherit;
}
}
hr {
margin: 16px 0;
}
tr:last-child {
border-bottom: none;
}
&.item-table {
td {
padding: 32px;
}
}
&.list-table {
td {
padding: 16px 24px;
}
}
.short-description {
margin-left: 0;
}
&.parameters-table {
margin-top: 0;
font-size: 14px;
box-shadow: none;
tr {
@media screen and (max-width: 480px) {
display: flex;
flex-direction: column;
}
td:first-child {
font-weight: 600;
padding-left: 16px;
width: 20%;
}
}
td {
padding: 8px 8px 8px 0;
border: 0;
vertical-align: top;
}
}
}
.class-overview {
position: relative;
code-example {
clear: left;
}
}
.short-description {
margin: 6px 0 0 10px;
}
.properties-table {
font-size: 14px;
thead th {
&:nth-child(1) {
width: 20%;
}
&:nth-child(2) {
width: 20%;
}
}
}
}
.breadcrumb-container {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.api-header {
@ -18,6 +116,22 @@
flex-direction: column;
align-items: flex-start;
}
h1 {
margin-top: -4px;
}
}
.github-links {
float: right;
.material-icons {
border-radius: 4px;
padding: 4px;
font-size: 20px;
&:hover {
background-color: $mist;
}
}
}
.api-body {
@ -65,10 +179,10 @@
}
}
.api-heading {
padding: 5px 0;
font-size: 16px;
font-weight: bold;
font-size: 14px;
}
.parameters-table {
@ -80,21 +194,71 @@
}
details.overloads {
margin-left: -8px;
box-shadow: none;
.icon-action-header {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
a {
display: flex;
}
}
.detail-contents {
padding: 0;
border: 1px solid $lightgray;
border-radius: 2px;
box-shadow: none;
> *:not(hr) {
margin: 16px 24px;
}
}
summary {
height: inherit;
padding: 8px 12px;
padding: 0;
h4 {
margin: 0;
clear: left;
}
.show-all {
display: initial;
}
.collapse-all {
display: none;
}
}
&[open] > summary {
.show-all {
display: none;
}
.collapse-all {
display: initial;
}
}
}
details.overload {
box-shadow: none;
margin: 0;
}
.from-constructor, .read-only-property, .write-only-property {
font-size: 12px;
font-weight: 600;
letter-spacing: 0.5px;
font-style: italic;
color: $blue;
background-color: $lightgray;
border-radius: 4px;
padding: 4px 6px;
}
.ngmodule-list {
@ -120,10 +284,6 @@
}
}
}
.member-name {
font-weight: bold;
}
}
.deprecated-api-item {

View File

@ -25,14 +25,7 @@ summary {
display: none; // Remove the built in details marker in webkit
}
&::before {
content: '\E5CE'; // See https://material.io/icons/#ic_expand_less
font-family: 'Material Icons';
font-size: 24px;
-webkit-font-smoothing: antialiased;
@include rotate(0deg); // We will rotate 180 degrees when details is open
float: right;
}
overflow: hidden;
}
details {
@ -42,7 +35,11 @@ details {
padding: 16px 24px;
}
&[open] > summary::before {
@include rotate(180deg); // Rotate the icon
// Rotate the icon
summary i.material-icons.expand {
@include rotate(0deg);
}
&[open] > summary i.material-icons.expand {
@include rotate(180deg);
}
}

View File

@ -3,12 +3,3 @@ hr {
background: $lightgray;
height: 1px;
}
.hr-margin {
display: block;
height: 1px;
border: 0;
margin-top: 16px;
margin-bottom: 16px;
padding: 0;
}

View File

@ -3,8 +3,8 @@
============================== */
@import 'alert';
@import 'api-pages';
@import 'api-list';
@import 'api-pages';
@import 'buttons';
@import 'callout';
@import 'card';

View File

@ -0,0 +1,16 @@
/**
* Create a `CustomEvent` (even on browsers where `CustomEvent` is not a constructor).
*/
export function createCustomEvent(doc: Document, name: string, detail: any): CustomEvent {
const bubbles = false;
const cancelable = false;
// On IE9-11, `CustomEvent` is not a constructor.
if (typeof CustomEvent !== 'function') {
const event = doc.createEvent('CustomEvent');
event.initCustomEvent(name, bubbles, cancelable, detail);
return event;
}
return new CustomEvent(name, {bubbles, cancelable, detail});
}

View File

@ -57,7 +57,7 @@
"@types/jasminewd2": "^2.0.4",
"@types/jquery": "^3.3.4",
"@types/node": "~8.9.4",
"canonical-path": "0.0.2",
"canonical-path": "1.0.0",
"concurrently": "^3.0.0",
"http-server": "^0.9.0",
"jasmine-core": "~2.99.1",

View File

@ -1450,6 +1450,11 @@ canonical-path@0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/canonical-path/-/canonical-path-0.0.2.tgz#e31eb937a8c93ee2a01df1839794721902874574"
canonical-path@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/canonical-path/-/canonical-path-1.0.0.tgz#fcb470c23958def85081856be7a86e904f180d1d"
integrity sha512-feylzsbDxi1gPZ1IjystzIQZagYYLvfKrSuygUCgf7z6x790VEzze5QEkdSV1U58RA7Hi0+v6fv4K54atOzATg==
caseless@~0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"

View File

@ -2,19 +2,21 @@
{% set comma = joiner(',') %}
{% set breadcrumbDelimiter = joiner('&gt;') %}
<article>
{$ github.githubLinks(doc, versionInfo) $}
<div class="breadcrumb">
<script type="application/ld+json">
{
"@context": "http://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{%- for crumb in doc.breadCrumbs %}{$ comma() $}
{ "@type": "ListItem", "position": {$ loop.index $}, "item": { "@id": "https://angular.io/{$ crumb.path $}", "name": "{$ crumb.text $}" } }{% endfor %}
]
}
</script>
{% for crumb in doc.breadCrumbs %}{% if not loop.last %} {$ breadcrumbDelimiter() $} {% if crumb.path %}<a href="{$ crumb.path $}">{$ crumb.text $}</a>{% else %}{$ crumb.text $}{% endif %}{% endif %}{% endfor %}
<div class="breadcrumb-container">
<div class="breadcrumb">
<script type="application/ld+json">
{
"@context": "http://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{%- for crumb in doc.breadCrumbs %}{$ comma() $}
{ "@type": "ListItem", "position": {$ loop.index $}, "item": { "@id": "https://angular.io/{$ crumb.path $}", "name": "{$ crumb.text $}" } }{% endfor %}
]
}
</script>
{% for crumb in doc.breadCrumbs %}{% if not loop.last %} {$ breadcrumbDelimiter() $} {% if crumb.path %}<a href="{$ crumb.path $}">{$ crumb.text $}</a>{% else %}{$ crumb.text $}{% endif %}{% endif %}{% endfor %}
</div>
{$ github.githubLinks(doc, versionInfo) $}
</div>
{% block header %}
<header class="api-header">

View File

@ -40,40 +40,41 @@
{%- endmacro -%}
{%- macro renderOverloadInfo(overload, cssClass, method) -%}
<div class="overload-info">
{% if overload.shortDescription and (overload.shortDescription != method.shortDescription) %}
<div class="short-description">
{$ overload.shortDescription | marked $}
</div>{% endif %}
{% if overload.shortDescription and (overload.shortDescription != method.shortDescription) %}
<div class="short-description">
{$ overload.shortDescription | marked $}
</div>{% endif %}
<code-example language="ts" hideCopy="true" linenums="false" class="no-box api-heading">{$ renderMemberSyntax(overload) $}</code-example>
<code-example language="ts" hideCopy="true" linenums="false" class="no-box api-heading">{$ renderMemberSyntax(overload) $}</code-example>
{% if overload.deprecated !== undefined %}
<div class="deprecated">
{$ ('**Deprecated** ' + overload.deprecated) | marked $}
</div>{% endif %}
{% if overload.deprecated !== undefined %}
<div class="deprecated">
{$ ('**Deprecated** ' + overload.deprecated) | marked $}
</div>{% endif %}
<h6 class="no-anchor">Parameters</h6>
{$ params.renderParameters(overload.parameterDocs, cssClass + '-parameters', cssClass + '-parameter', true) $}
<h4 class="no-anchor">Parameters</h4>
{$ params.renderParameters(overload.parameterDocs, cssClass + '-parameters', cssClass + '-parameter') $}
{% if overload.type or overload.returns.type %}
<h4 class="no-anchor">Returns</h4>
{% marked %}`{$ (overload.type or overload.returns.type) $}`{% if overload.returns %}: {$ overload.returns.description $}{% endif %}{% endmarked %}
{% endif %}
{% if overload.type or overload.returns.type %}
<h6 class="no-anchor">Returns</h6>
{% marked %}`{$ (overload.type or overload.returns.type) $}`{% if overload.returns %}: {$ overload.returns.description $}{% endif %}{% endmarked %}
{% endif %}
{% if overload.throws.length %}
<h4 class="no-anchor">Throws</h4>
{% for error in overload.throws %}
{% marked %}`{$ (error.typeList or 'Error') $}` {$ error.description $}{% endmarked %}
{% endfor %}
{% endif %}
{% if overload.throws.length %}
<h6 class="no-anchor">Throws</h6>
{% for error in overload.throws %}
{% marked %}`{$ (error.typeList or 'Error') $}` {$ error.description $}{% endmarked %}
{% endfor %}
{% endif %}
{% if overload.description and (overload.description != method.description) -%}
<div class="description">
{$ overload.description | marked $}
{% if overload.description and (overload.description != method.description) -%}
<div class="description">
{$ overload.description | marked $}
</div>
{%- endif %}
</div>
{%- endif %}
{%- endmacro -%}
{%- macro renderMethodDetail(versionInfo, method, cssClass) -%}
@ -81,13 +82,13 @@
<table class="is-full-width method-table {$ cssClass $}">
{% if method.name !== 'constructor' %}<thead><tr><th>
<div class="with-github-links">
<h3>
{% if method.isCallMember %}<i>call signature</i>
{% elseif method.isNewMember %}<i>construct signature</i>
{% else %}{$ method.name $}()
{% endif %}
</h3>
{$ github.githubLinks(method, versionInfo) $}
<h3>
{% if method.isCallMember %}<i>call signature</i>
{% elseif method.isNewMember %}<i>construct signature</i>
{% else %}{$ method.name $}()
{% endif %}
</h3>
{$ github.githubLinks(method, versionInfo) $}
</div>
</th></tr></thead>{% endif %}
<tbody>
@ -121,16 +122,26 @@
<tr>
<td>
<details class="overloads">
<summary><h4 class="no-anchor">{$ method.overloads.length $} overloads...</h4></summary>
<summary>
<div class="icon-action-header">
<h4 class="no-anchor">{$ method.overloads.length $} overloads...</h4>
<a>
<span class="show-all">Show All</span>
<span class="collapse-all">Hide All</span>
<i class="material-icons expand">expand_more</i>
</a>
</div>
</summary>
<div class="detail-contents">
{% if method.isAbstract %}
{$ renderOverloadInfo(method, cssClass + '-overload', method) $}
<hr class="hr-margin fullwidth">
{% endif %}
{% for overload in method.overloads %}
{$ renderOverloadInfo(overload, cssClass + '-overload', method) $}
{% if not loop.last %}<hr class="hr-margin fullwidth">{% endif %}
{% endfor %}
{% if method.isAbstract %}
{$ renderOverloadInfo(method, cssClass + '-overload', method) $}
<hr class="hr-margin fullwidth">
{% endif %}
{% for overload in method.overloads %}
<h5 class="no-anchor">Overload #{$ loop.index $}</h5>
{$ renderOverloadInfo(overload, cssClass + '-overload', method) $}
{% if not loop.last %}<hr class="hr-margin">{% endif %}
{% endfor %}
</div>
</details>
</td>
@ -140,7 +151,7 @@
{% if method.description -%}
<tr>
<td class="description">
{$ method.description | marked({ h3: 'h4' }) $}
{$ method.description | marked({ h3: 'h5' }) $}
</td>
</tr>
{%- endif %}
@ -148,7 +159,8 @@
{% if method.usageNotes -%}
<tr>
<td class="usage-notes">
{$ method.usageNotes | marked({ h3: 'h4' }) $}
<h4 id="{$ method.anchor $}-usage-notes">Usage Notes</h4>
{$ method.usageNotes | marked({ h3: 'h5' }) $}
</td>
</tr>
{%- endif %}

View File

@ -17,12 +17,14 @@
<tbody>
{% for parameter in parameters %}
<tr class="{$ parameterClass $}">
<td class="param-name"><a id="{$ parameter.anchor $}"></a>{$ parameter.name $}</td>
{% if showType %}<td class="param-type"><code>{$ parameter.type $}</code></td>{% endif %}
<td class="param-name">
<a id="{$ parameter.anchor $}"></a>
<code>{$ parameter.name $}</code>
</td>
{% if showType %}<td class="param-type"><code>{% if r/^\{/.test(parameter.type) and r/\}$/.test(parameter.type) and parameter.type.length > 20 %}object{% else %}{$ parameter.type $}{% endif %}</code></td>{% endif %}
<td class="param-description">
{% marked %}
{% if (parameter.shortDescription | trim) or (parameter.description | trim) %}{$ parameter.shortDescription + '\n\n' + parameter.description $}
{% elseif not showType and parameter.type %}<p>Type: <code>{$ parameter.type $}</code>.</p>
{% endif %}
{% if parameter.isOptional or parameter.defaultValue !== undefined %}Optional. Default is `{$ parameter.defaultValue === undefined and 'undefined' or parameter.defaultValue $}`.{% endif %}
@ -34,4 +36,4 @@
{%- else -%}
<p>There are no parameters.</p>
{%- endif -%}
{%- endmacro -%}
{%- endmacro -%}

File diff suppressed because it is too large Load Diff

View File

@ -10,10 +10,11 @@ To test Angular CLI applications, we use the integration test `cli-hello-world`.
When a significant change is released in the CLI, the application should be updated with `ng update`:
```bash
$ cd integration
$ ng update
# ng build
# ng test
$ cd integration/cli-hello-world
$ yarn install
$ yarn ng update @angular/cli @angular-devkit/build-angular
# yarn build
# yarn test
# typescript version
```
@ -36,7 +37,7 @@ The API for each test is:
This means that the test should be started by test script, like
```
'scripts' { 'test': 'runProgramA && assertResultIsGood' }
"scripts": {"test": "runProgramA && assertResultIsGood"}
```
Note that the `package.json` file uses a special `file://../../dist` scheme

View File

@ -2,9 +2,9 @@
"cli-hello-world": {
"master": {
"uncompressed": {
"runtime": 1110,
"main": 151765,
"polyfills": 59462
"runtime": 1497,
"main": 181839,
"polyfills": 59608
}
}
},
@ -12,32 +12,8 @@
"master": {
"uncompressed": {
"bundle": "TODO(i): temporarily increase the payload size limit from 105779 - this is due to a closure issue related to ESM reexports that still needs to be investigated",
"bundle": 178101
}
}
},
"hello_world__render3__closure": {
"master": {
"uncompressed": {
"bundle": 8153
}
}
},
"hello_world__render3__rollup": {
"master": {
"uncompressed": {
"bundle": 10129
}
}
},
"hello_world__render3__cli": {
"master": {
"uncompressed": {
"inline": 1447,
"main": 40513,
"polyfills": 60105
"bundle": 177585
}
}
}
}

View File

@ -76,6 +76,13 @@
},
"production": {
"browserTarget": "cli-hello-world:build:production"
},
"ci": {
"progress": false
},
"ci-production": {
"browserTarget": "cli-hello-world:build:production",
"progress": false
}
}
},
@ -141,6 +148,12 @@
"configurations": {
"production": {
"devServerTarget": "cli-hello-world:serve:production"
},
"ci": {
"devServerTarget": "cli-hello-world:serve:ci"
},
"ci-production": {
"devServerTarget": "cli-hello-world:serve:ci-production"
}
}
},

View File

@ -4,12 +4,12 @@
"license": "MIT",
"scripts": {
"build": "ng build --prod --progress=false",
"e2e": "ng e2e",
"e2e": "ng e2e --webdriver-update=false",
"lint": "ng lint",
"ng": "ng",
"postinstall": "webdriver-manager update --gecko=false --standalone=false $CHROMEDRIVER_VERSION_ARG",
"start": "ng serve",
"test": "ng test --progress=false && ng e2e --webdriver-update=false && ng e2e --prod --webdriver-update=false"
"test": "ng test --progress=false --watch=false && yarn e2e --configuration=ci && yarn e2e --configuration=ci-production"
},
"private": true,
"dependencies": {
@ -27,8 +27,8 @@
"zone.js": "file:../../node_modules/zone.js"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.5.0",
"@angular/cli": "7.0.0-beta.4",
"@angular-devkit/build-angular": "~0.10.3",
"@angular/cli": "7.0.3",
"@angular/compiler-cli": "file:../../dist/packages-dist/compiler-cli",
"@angular/language-service": "file:../../dist/packages-dist/language-service",
"@types/jasmine": "~2.8.3",

File diff suppressed because it is too large Load Diff

View File

@ -1,20 +1,22 @@
import { browser, element, by } from 'protractor';
import { browser, element, ExpectedConditions as EC, by } from 'protractor';
browser.waitForAngularEnabled(false);
describe('Element E2E Tests', function () {
describe('Hello World Elements', () => {
it('should display: Hello world!', function () {
browser.get('hello-world.html');
const helloWorldEl = element(by.css('hello-world-el'));
const helloWorldEl = element(by.css('hello-world-el'));
beforeEach(() => browser.get('hello-world.html'));
it('should display "Hello World!"', function () {
expect(helloWorldEl.getText()).toEqual('Hello World!');
});
it('should display: Hello Foo! via name attribute', function () {
browser.get('hello-world.html');
const helloWorldEl = element(by.css('hello-world-el'));
it('should display "Hello Foo!" via name attribute', function () {
const input = element(by.css('input[type=text]'));
['f', 'o', 'o'].forEach((key) => input.sendKeys(key));
expect(helloWorldEl.getText()).toEqual('Hello foo!');
input.sendKeys('Foo');
// Make tests less flaky on CI by waiting up to 5s for the element text to be updated.
browser.wait(EC.textToBePresentInElement(helloWorldEl, 'Hello Foo!'), 5000);
});
});
});

View File

@ -19,5 +19,3 @@ export class AppModule {
}
ngDoBootstrap() {}
}
export {HelloWorldComponent};

View File

@ -3,11 +3,8 @@ import {AppModuleNgFactory} from './app.ngfactory';
platformBrowser().bootstrapModuleFactory(AppModuleNgFactory, {ngZone: 'noop'});
const input = document.querySelector('input');
const helloWorld = document.querySelector('hello-world-el');
const input = document.querySelector('input[type=text]');
if(input && helloWorld){
input.addEventListener('input', e => {
const newText = (e.target as any).value;
helloWorld.setAttribute('name', newText);
});
input.addEventListener('input', () => helloWorld.setAttribute('name', input.value));
}

View File

@ -15,7 +15,7 @@ if $CI; then
# We don't install this by default because it contains some broken Bazel setup
# and also it's a very big dependency that we never use except when publishing
# payload sizes on CI.
yarn add --silent -D firebase-tools@3.12.0
yarn add --silent -D firebase-tools@5.1.1
source ${basedir}/scripts/ci/payload-size.sh
# NB: we don't run build-packages-dist.sh because we expect that it was done
@ -47,20 +47,21 @@ for testDir in $(ls | grep -v node_modules) ; do
yarn install --cache-folder ../$cache
yarn test || exit 1
# Track payload size for cli-hello-world and hello_world__closure and the render3 tests
if [[ $testDir == cli-hello-world ]] || [[ $testDir == hello_world__closure ]] || [[ $testDir == hello_world__render3__closure ]] || [[ $testDir == hello_world__render3__rollup ]] || [[ $testDir == hello_world__render3__cli ]]; then
if [[ $testDir == cli-hello-world ]] || [[ $testDir == hello_world__render3__cli ]]; then
if $CI && ([[ $testDir == cli-hello-world ]] || [[ $testDir == hello_world__closure ]]); then
if [[ $testDir == cli-hello-world ]]; then
yarn build
fi
#if $CI; then
# trackPayloadSize "$testDir" "dist/*.js" true false "${basedir}/integration/_payload-limits.json"
#fi
trackPayloadSize "$testDir" "dist/*.js" true false "${basedir}/integration/_payload-limits.json"
fi
# remove the temporary node modules directory to keep the source folder clean.
rm -rf node_modules
)
done
#if $CI; then
# trackPayloadSize "umd" "../dist/packages-dist/*/bundles/*.umd.min.js" false false
#fi
if $CI; then
trackPayloadSize "umd" "../dist/packages-dist/*/bundles/*.umd.min.js" false false
fi

View File

@ -1,6 +1,6 @@
{
"name": "angular-srcs",
"version": "7.0.2",
"version": "7.0.3",
"private": true,
"branchPattern": "2.0.*",
"description": "Angular - a web framework for modern web apps",
@ -57,7 +57,7 @@
"angular-mocks-1.5": "npm:angular-mocks@1.5",
"angular-mocks-1.6": "npm:angular-mocks@1.6",
"base64-js": "1.2.1",
"canonical-path": "0.0.2",
"canonical-path": "1.0.0",
"chokidar": "1.7.0",
"convert-source-map": "^1.5.1",
"dependency-graph": "^0.7.2",

View File

@ -81,9 +81,11 @@ def _esm5_outputs_aspect(target, ctx):
)
replay_compiler = target.typescript.replay_params.compiler.path.split("/")[-1]
if replay_compiler == "tsc_wrapped":
# in windows replay_compiler path end with '.exe'
if replay_compiler.startswith("tsc_wrapped"):
compiler = ctx.executable._tsc_wrapped
elif replay_compiler == "ngc-wrapped":
elif replay_compiler.startswith("ngc-wrapped"):
compiler = ctx.executable._ngc_wrapped
else:
fail("Unknown replay compiler", target.typescript.replay_params.compiler.path)

View File

@ -12,7 +12,7 @@
"dependencies": {
"reflect-metadata": "^0.1.2",
"minimist": "^1.2.0",
"canonical-path": "0.0.2",
"canonical-path": "1.0.0",
"chokidar": "^1.4.2",
"convert-source-map": "^1.5.1",
"dependency-graph": "^0.7.2",

View File

@ -22,6 +22,7 @@ ts_library(
"@ngdeps//@types/shelljs",
"@ngdeps//@types/source-map",
"@ngdeps//@types/yargs",
"@ngdeps//canonical-path",
"@ngdeps//dependency-graph",
"@ngdeps//magic-string",
"@ngdeps//source-map",

View File

@ -1,14 +0,0 @@
declare module 'canonical-path' {
export function normalize(p: string): string;
export function join(...paths: any[]): string;
export function resolve(...pathSegments: any[]): string;
export function isAbsolute(p: string): boolean;
export function relative(from: string, to: string): string;
export function dirname(p: string): string;
export function basename(p: string, ext?: string): string;
export function extname(p: string): string;
export var sep: string;
export var delimiter: string;
export function parse(p: string): ParsedPath;
export function format(pP: ParsedPath): string;
}

View File

@ -15,6 +15,7 @@ ts_library(
"//packages/compiler-cli/src/ngtsc/transform",
"@ngdeps//@types/convert-source-map",
"@ngdeps//@types/mock-fs",
"@ngdeps//canonical-path",
"@ngdeps//magic-string",
"@ngdeps//typescript",
],

View File

@ -113,7 +113,7 @@ export function performWatchCompilation(host: PerformWatchHost):
let cachedOptions: ParsedConfiguration|undefined; // CompilerOptions cached from last compilation
let timerHandleForRecompilation: any; // Handle for 0.25s wait timer to trigger recompilation
const ingoreFilesForWatch = new Set<string>();
const ignoreFilesForWatch = new Set<string>();
const fileCache = new Map<string, CacheEntry>();
const firstCompileResult = doCompilation();
@ -162,7 +162,7 @@ export function performWatchCompilation(host: PerformWatchHost):
cachedCompilerHost.writeFile = function(
fileName: string, data: string, writeByteOrderMark: boolean,
onError?: (message: string) => void, sourceFiles: ReadonlyArray<ts.SourceFile> = []) {
ingoreFilesForWatch.add(path.normalize(fileName));
ignoreFilesForWatch.add(path.normalize(fileName));
return originalWriteFileCallback(fileName, data, writeByteOrderMark, onError, sourceFiles);
};
const originalFileExists = cachedCompilerHost.fileExists;
@ -191,7 +191,7 @@ export function performWatchCompilation(host: PerformWatchHost):
return ce.content !;
};
}
ingoreFilesForWatch.clear();
ignoreFilesForWatch.clear();
const oldProgram = cachedProgram;
// We clear out the `cachedProgram` here as a
// program can only be used as `oldProgram` 1x
@ -253,7 +253,7 @@ export function performWatchCompilation(host: PerformWatchHost):
fileCache.delete(path.normalize(fileName));
}
if (!ingoreFilesForWatch.has(path.normalize(fileName))) {
if (!ignoreFilesForWatch.has(path.normalize(fileName))) {
// Ignore the file if the file is one that was written by the compiler.
startTimerForRecompilation();
}

View File

@ -46,8 +46,8 @@ export class Token {
isString(): boolean { return this.type == TokenType.String; }
isOperator(operater: string): boolean {
return this.type == TokenType.Operator && this.strValue == operater;
isOperator(operator: string): boolean {
return this.type == TokenType.Operator && this.strValue == operator;
}
isIdentifier(): boolean { return this.type == TokenType.Identifier; }

View File

@ -30,38 +30,17 @@ export const formControlBinding: any = {
/**
* @description
*
* Syncs a standalone `FormControl` instance to a form control element.
*
* This directive ensures that any values written to the `FormControl`
* instance programmatically will be written to the DOM element (model -> view). Conversely,
* any values written to the DOM element through user input will be reflected in the
* `FormControl` instance (view -> model).
* * Syncs a standalone `FormControl` instance to a form control element.
*
* @see [Reactive Forms Guide](guide/reactive-forms)
* @see `FormControl`
* @see `AbstractControl`
*
* @usageNotes
* Use this directive if you'd like to create and manage a `FormControl` instance directly.
* Simply create a `FormControl`, save it to your component class, and pass it into the
* `FormControlDirective`.
*
* This directive is designed to be used as a standalone control. Unlike `FormControlName`,
* it does not require that your `FormControl` instance be part of any parent
* `FormGroup`, and it won't be registered to any `FormGroupDirective` that
* exists above it.
*
* **Get the value**: the `value` property is always synced and available on the
* `FormControl` instance. See a full list of available properties in
* `AbstractControl`.
*
* **Set the value**: You can pass in an initial value when instantiating the `FormControl`,
* or you can set it programmatically later using {@link AbstractControl#setValue setValue} or
* {@link AbstractControl#patchValue 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 valueChanges} event. You can also listen to
* {@link AbstractControl#statusChanges statusChanges} to be notified when the validation status is
* re-calculated.
*
* ### Example
* ### Registering a single form control
*
* The following examples shows how to register a standalone control and set its value.
*
* {@example forms/ts/simpleFormControl/simple_form_control_example.ts region='Component'}
*
@ -138,11 +117,23 @@ export const formControlBinding: any = {
@Directive({selector: '[formControl]', providers: [formControlBinding], exportAs: 'ngForm'})
export class FormControlDirective extends NgControl implements OnChanges {
/**
* @description
* Internal reference to the view model value.
*/
viewModel: any;
/**
* @description
* Tracks the `FormControl` instance bound to the directive.
*/
// TODO(issue/24571): remove '!'.
@Input('formControl') form !: FormControl;
/**
* @description
* Triggers a warning that this input should not be used with reactive forms.
*/
@Input('disabled')
set isDisabled(isDisabled: boolean) { ReactiveErrors.disabledAttrWarning(); }
@ -155,6 +146,7 @@ export class FormControlDirective extends NgControl implements OnChanges {
@Output('ngModelChange') update = new EventEmitter();
/**
* @description
* Static property used to track whether any ngModel warnings have been sent across
* all instances of FormControlDirective. Used to support warning config of "once".
*
@ -163,8 +155,9 @@ export class FormControlDirective extends NgControl implements OnChanges {
static _ngModelWarningSentOnce = false;
/**
* @description
* Instance property used to track whether an ngModel warning has been sent out for this
* particular FormControlDirective instance. Used to support warning config of "always".
* particular `FormControlDirective` instance. Used to support warning config of "always".
*
* @internal
*/
@ -181,6 +174,13 @@ export class FormControlDirective extends NgControl implements OnChanges {
this.valueAccessor = selectValueAccessor(this, valueAccessors);
}
/**
* @description
* A lifecycle method called when the directive's inputs change. For internal use
* only.
*
* @param changes A object of key/value pairs for the set of changed inputs.
*/
ngOnChanges(changes: SimpleChanges): void {
if (this._isControlChanged(changes)) {
setUpControl(this.form, this);
@ -197,16 +197,41 @@ export class FormControlDirective extends NgControl implements OnChanges {
}
}
/**
* @description
* Returns an array that represents the path from the top-level form to this control.
* Each index is the string name of the control on that level.
*/
get path(): string[] { return []; }
/**
* @description
* Synchronous validator function composed of all the synchronous validators
* registered with this directive.
*/
get validator(): ValidatorFn|null { return composeValidators(this._rawValidators); }
/**
* @description
* Async validator function composed of all the async validators registered with this
* directive.
*/
get asyncValidator(): AsyncValidatorFn|null {
return composeAsyncValidators(this._rawAsyncValidators);
}
/**
* @description
* The `FormControl` bound to this directive.
*/
get control(): FormControl { return this.form; }
/**
* @description
* Sets the new value for the view model and emits an `ngModelChange` event.
*
* @param newValue The new value for the view model.
*/
viewToModelUpdate(newValue: any): void {
this.viewModel = newValue;
this.update.emit(newValue);

View File

@ -29,42 +29,19 @@ export const controlNameBinding: any = {
/**
* @description
*
* Syncs a `FormControl` in an existing `FormGroup` to a form control
* element by name.
*
* This directive ensures that any values written to the `FormControl`
* instance programmatically will be written to the DOM element (model -> view). Conversely,
* any values written to the DOM element through user input will be reflected in the
* `FormControl` instance (view -> model).
*
* @see [Reactive Forms Guide](guide/reactive-forms)
* @see `FormControl`
* @see `AbstractControl`
*
* @usageNotes
* This directive is designed to be used with a parent `FormGroupDirective` (selector:
* `[formGroup]`).
*
* ### Register `FormControl` within a group
*
* It accepts the string name of the `FormControl` instance you want to
* link, and will look for a `FormControl` registered with that name in the
* closest `FormGroup` or `FormArray` above it.
*
* **Access the control**: You can access the `FormControl` associated with
* this directive by using the {@link AbstractControl#get get} method.
* Ex: `this.form.get('first');`
*
* **Get value**: the `value` property is always synced and available on the `FormControl`.
* See a full list of available properties in `AbstractControl`.
*
* **Set value**: You can set an initial value for the control when instantiating the
* `FormControl`, or you can set it programmatically later using
* {@link AbstractControl#setValue setValue} or {@link AbstractControl#patchValue 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 valueChanges} event. You can also listen to
* {@link AbstractControl#statusChanges statusChanges} to be notified when the validation status is
* re-calculated.
*
* ### Example
*
* In this example, we create form controls for first name and last name.
* The following example shows how to register multiple form controls within a form group
* and set their value.
*
* {@example forms/ts/simpleFormGroup/simple_form_group_example.ts region='Component'}
*
@ -150,14 +127,32 @@ export const controlNameBinding: any = {
@Directive({selector: '[formControlName]', providers: [controlNameBinding]})
export class FormControlName extends NgControl implements OnChanges, OnDestroy {
private _added = false;
/** @internal */
/**
* @description
* Internal reference to the view model value.
* @internal
*/
viewModel: any;
/**
* @description
* Tracks the `FormControl` instance bound to the directive.
*/
// TODO(issue/24571): remove '!'.
readonly control !: FormControl;
/**
* @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') name !: string;
/**
* @description
* Triggers a warning that this input should not be used with reactive forms.
*/
@Input('disabled')
set isDisabled(isDisabled: boolean) { ReactiveErrors.disabledAttrWarning(); }
@ -170,6 +165,7 @@ export class FormControlName extends NgControl implements OnChanges, OnDestroy {
@Output('ngModelChange') update = new EventEmitter();
/**
* @description
* Static property used to track whether any ngModel warnings have been sent across
* all instances of FormControlName. Used to support warning config of "once".
*
@ -178,6 +174,7 @@ export class FormControlName extends NgControl implements OnChanges, OnDestroy {
static _ngModelWarningSentOnce = false;
/**
* @description
* Instance property used to track whether an ngModel warning has been sent out for this
* particular FormControlName instance. Used to support warning config of "always".
*
@ -200,6 +197,12 @@ export class FormControlName extends NgControl implements OnChanges, OnDestroy {
this.valueAccessor = selectValueAccessor(this, valueAccessors);
}
/**
* @description
* A lifecycle method called when the directive's inputs change. For internal use only.
*
* @param changes A object of key/value pairs for the set of changed inputs.
*/
ngOnChanges(changes: SimpleChanges) {
if (!this._added) this._setUpControl();
if (isPropertyUpdated(changes, this.viewModel)) {
@ -209,23 +212,54 @@ export class FormControlName extends NgControl implements OnChanges, OnDestroy {
}
}
/**
* @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 {
if (this.formDirective) {
this.formDirective.removeControl(this);
}
}
/**
* @description
* Sets the new value for the view model and emits an `ngModelChange` event.
*
* @param newValue The new value for the view model.
*/
viewToModelUpdate(newValue: any): void {
this.viewModel = newValue;
this.update.emit(newValue);
}
/**
* @description
* Returns an array that represents the path from the top-level form to this control.
* Each index is the string name of the control on that level.
*/
get path(): string[] { return controlPath(this.name, this._parent !); }
/**
* @description
* The top-level directive for this group if present, otherwise null.
*/
get formDirective(): any { return this._parent ? this._parent.formDirective : null; }
/**
* @description
* Synchronous validator function composed of all the synchronous validators
* registered with this directive.
*/
get validator(): ValidatorFn|null { return composeValidators(this._rawValidators); }
/**
* @description
* Async validator function composed of all the async validators registered with this
* directive.
*/
get asyncValidator(): AsyncValidatorFn {
return composeAsyncValidators(this._rawAsyncValidators) !;
}

View File

@ -31,25 +31,14 @@ export const formDirectiveProvider: any = {
* `FormGroup` instance to match any child `FormControl`, `FormGroup`,
* and `FormArray` instances to child `FormControlName`, `FormGroupName`,
* and `FormArrayName` directives.
*
* @see [Reactive Forms Guide](guide/reactive-forms)
* @see `AbstractControl`
*
* @usageNotes
* **Set value**: You can set the form's initial value when instantiating the
* `FormGroup`, or you can set it programmatically later using the `FormGroup`'s
* {@link AbstractControl#setValue setValue} or {@link AbstractControl#patchValue patchValue}
* methods.
* ### Register Form Group
*
* **Listen to value**: If you want to listen to changes in the value of the form, you can subscribe
* to the `FormGroup`'s {@link AbstractControl#valueChanges valueChanges} event. You can also
* listen to its {@link AbstractControl#statusChanges 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
* triggered a form submission. The `ngSubmit` event will be emitted with the original form
* submission event.
*
* ### Example
*
* In this example, we create form controls for first name and last name.
* The following example registers a `FormGroup` with first name and last name controls,
* and listens for the *ngSubmit* event when the button is clicked.
*
* {@example forms/ts/simpleFormGroup/simple_form_group_example.ts region='Component'}
*
@ -64,13 +53,31 @@ export const formDirectiveProvider: any = {
})
export class FormGroupDirective extends ControlContainer implements Form,
OnChanges {
/**
* @description
* Reports whether the form submission has been triggered.
*/
public readonly submitted: boolean = false;
// TODO(issue/24571): remove '!'.
private _oldForm !: FormGroup;
/**
* @description
* Tracks the list of added `FormControlName` instances
*/
directives: FormControlName[] = [];
/**
* @description
* Tracks the `FormGroup` bound to this directive.
*/
@Input('formGroup') form: FormGroup = null !;
/**
* @description
* Emits an event when the form submission has been triggered.
*/
@Output() ngSubmit = new EventEmitter();
constructor(
@ -79,6 +86,12 @@ export class FormGroupDirective extends ControlContainer implements Form,
super();
}
/**
* @description
* A lifecycle method called when the directive's inputs change. For internal use only.
*
* @param changes A object of key/value pairs for the set of changed inputs.
*/
ngOnChanges(changes: SimpleChanges): void {
this._checkFormPresent();
if (changes.hasOwnProperty('form')) {
@ -88,12 +101,32 @@ export class FormGroupDirective extends ControlContainer implements Form,
}
}
/**
* @description
* Returns this directive's instance.
*/
get formDirective(): Form { return this; }
/**
* @description
* Returns the `FormGroup` bound to this directive.
*/
get control(): FormGroup { return this.form; }
/**
* @description
* Returns an array representing the path to this group. Because this directive
* always lives at the top level of a form, it always an empty array.
*/
get path(): string[] { return []; }
/**
* @description
* Method that sets up the control directive in this group, re-calculates its value
* and validity, and adds the instance to the internal list of directives.
*
* @param dir The `FormControlName` directive instance.
*/
addControl(dir: FormControlName): FormControl {
const ctrl: any = this.form.get(dir.path);
setUpControl(ctrl, dir);
@ -102,35 +135,92 @@ export class FormGroupDirective extends ControlContainer implements Form,
return ctrl;
}
/**
* @description
* Retrieves the `FormControl` instance from the provided `FormControlName` directive
*
* @param dir The `FormControlName` directive instance.
*/
getControl(dir: FormControlName): FormControl { return <FormControl>this.form.get(dir.path); }
/**
* @description
* Removes the `FormControlName` instance from the internal list of directives
*
* @param dir The `FormControlName` directive instance.
*/
removeControl(dir: FormControlName): void { removeDir<FormControlName>(this.directives, dir); }
/**
* Adds a new `FormGroupName` directive instance to the form.
*
* @param dir The `FormGroupName` directive instance.
*/
addFormGroup(dir: FormGroupName): void {
const ctrl: any = this.form.get(dir.path);
setUpFormContainer(ctrl, dir);
ctrl.updateValueAndValidity({emitEvent: false});
}
/**
* No-op method to remove the form group.
*
* @param dir The `FormGroupName` directive instance.
*/
removeFormGroup(dir: FormGroupName): void {}
/**
* @description
* Retrieves the `FormGroup` for a provided `FormGroupName` directive instance
*
* @param dir The `FormGroupName` directive instance.
*/
getFormGroup(dir: FormGroupName): FormGroup { return <FormGroup>this.form.get(dir.path); }
/**
* Adds a new `FormArrayName` directive instance to the form.
*
* @param dir The `FormArrayName` directive instance.
*/
addFormArray(dir: FormArrayName): void {
const ctrl: any = this.form.get(dir.path);
setUpFormContainer(ctrl, dir);
ctrl.updateValueAndValidity({emitEvent: false});
}
/**
* No-op method to remove the form array.
*
* @param dir The `FormArrayName` directive instance.
*/
removeFormArray(dir: FormArrayName): void {}
/**
* @description
* Retrieves the `FormArray` for a provided `FormArrayName` directive instance.
*
* @param dir The `FormArrayName` directive instance.
*/
getFormArray(dir: FormArrayName): FormArray { return <FormArray>this.form.get(dir.path); }
/**
* Sets the new value for the provided `FormControlName` directive.
*
* @param dir The `FormControlName` directive instance.
* @param value The new value for the directive's control.
*/
updateModel(dir: FormControlName, value: any): void {
const ctrl  = <FormControl>this.form.get(dir.path);
ctrl.setValue(value);
}
/**
* @description
* Method called with the "submit" event is triggered on the form.
* Triggers the `ngSubmit` emitter to emit the "submit" event as its payload.
*
* @param $event The "submit" event object
*/
onSubmit($event: Event): boolean {
(this as{submitted: boolean}).submitted = true;
syncPendingControls(this.form, this.directives);
@ -138,8 +228,18 @@ export class FormGroupDirective extends ControlContainer implements Form,
return false;
}
/**
* @description
* Method called when the "reset" event is triggered on the form.
*/
onReset(): void { this.resetForm(); }
/**
* @description
* Resets the form to an initial value and resets its submitted status.
*
* @param value The new value for the form.
*/
resetForm(value: any = undefined): void {
this.form.reset(value);
(this as{submitted: boolean}).submitted = false;

View File

@ -28,37 +28,42 @@ export const formGroupNameProvider: any = {
*
* Syncs a nested `FormGroup` to a DOM element.
*
* This directive can only be used with a parent `FormGroupDirective` (selector:
* `[formGroup]`).
* This directive can only be used with a parent `FormGroupDirective`.
*
* It accepts the string name of the nested `FormGroup` you want to link, and
* will look for a `FormGroup` registered with that name in the parent
* It accepts the string name of the nested `FormGroup` to link, and
* looks for a `FormGroup` registered with that name in the parent
* `FormGroup` instance you passed into `FormGroupDirective`.
*
* Nested form groups can come in handy when you want to validate a sub-group of a
* form separately from the rest or when you'd like to group the values of certain
* Use nested form groups to validate a sub-group of a
* form separately from the rest or to group the values of certain
* controls into their own nested object.
*
* @see [Reactive Forms Guide](guide/reactive-forms)
*
* @usageNotes
* **Access the group**: You can access the associated `FormGroup` using the
* {@link AbstractControl#get get} method. Ex: `this.form.get('name')`.
*
* ### Access the group by name
*
* The following example uses the {@link AbstractControl#get get} method to access the
* associated `FormGroup`
*
* You can also access individual controls within the group using dot syntax.
* Ex: `this.form.get('name.first')`
* ```ts
* this.form.get('name');
* ```
*
* ### Access individual controls in the group
*
* The following example uses the {@link AbstractControl#get get} method to access
* individual controls within the group using dot syntax.
*
* **Get the value**: the `value` property is always synced and available on the
* `FormGroup`. See a full list of available properties in `AbstractControl`.
* ```ts
* this.form.get('name.first');
* ```
*
* **Set the value**: You can set an initial value for each child control when instantiating
* the `FormGroup`, or you can set it programmatically later using
* {@link AbstractControl#setValue setValue} or {@link AbstractControl#patchValue 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 valueChanges} event. You can also listen to
* {@link AbstractControl#statusChanges statusChanges} to be notified when the validation status is
* re-calculated.
*
* ### Example
* ### Register a nested `FormGroup`.
*
* The following example registers a nested *name* `FormGroup` within an existing `FormGroup`,
* and provides methods to retrieve the nested `FormGroup` and individual controls.
*
* {@example forms/ts/nestedFormGroup/nested_form_group_example.ts region='Component'}
*
@ -67,6 +72,11 @@ export const formGroupNameProvider: any = {
*/
@Directive({selector: '[formGroupName]', providers: [formGroupNameProvider]})
export class FormGroupName extends AbstractFormGroupDirective implements OnInit, OnDestroy {
/**
* @description
* Tracks the name of the `FormGroup` bound to the directive. The name corresponds
* to a key in the parent `FormGroup` or `FormArray`.
*/
// TODO(issue/24571): remove '!'.
@Input('formGroupName') name !: string;
@ -104,32 +114,11 @@ export const formArrayNameProvider: any = {
* It accepts the string name of the nested `FormArray` you want to link, and
* will look for a `FormArray` registered with that name in the parent
* `FormGroup` instance you passed into `FormGroupDirective`.
*
* Nested form arrays can come in handy when you have a group of form controls but
* you're not sure how many there will be. Form arrays allow you to create new
* form controls dynamically.
*
* @see [Reactive Forms Guide](guide/reactive-forms)
* @see `AbstractControl`
*
* @usageNotes
* **Access the array**: You can access the associated `FormArray` using the
* {@link AbstractControl#get get} method on the parent `FormGroup`.
* Ex: `this.form.get('cities')`.
*
* **Get the value**: the `value` property is always synced and available on the
* `FormArray`. See a full list of available properties in `AbstractControl`.
*
* **Set the value**: You can set an initial value for each child control when instantiating
* the `FormArray`, or you can set the value programmatically later using the
* `FormArray`'s {@link AbstractControl#setValue setValue} or
* {@link AbstractControl#patchValue patchValue} methods.
*
* **Listen to value**: If you want to listen to changes in the value of the array, you can
* subscribe to the `FormArray`'s {@link AbstractControl#valueChanges valueChanges} event.
* You can also listen to its {@link AbstractControl#statusChanges statusChanges} event to be
* notified when the validation status is re-calculated.
*
* **Add new controls**: You can add new controls to the `FormArray` dynamically by calling
* its {@link FormArray#push push} method.
* Ex: `this.form.get('cities').push(new FormControl());`
*
* ### Example
*
@ -149,6 +138,11 @@ export class FormArrayName extends ControlContainer implements OnInit, OnDestroy
/** @internal */
_asyncValidators: any[];
/**
* @description
* Tracks the name of the `FormArray` bound to the directive. The name corresponds
* to a key in the parent `FormGroup` or `FormArray`.
*/
// TODO(issue/24571): remove '!'.
@Input('formArrayName') name !: string;
@ -162,27 +156,59 @@ export class FormArrayName extends ControlContainer implements OnInit, OnDestroy
this._asyncValidators = asyncValidators;
}
/**
* @description
* A lifecycle method called when the directive's inputs are initialized. For internal use only.
*
* @throws If the directive does not have a valid parent.
*/
ngOnInit(): void {
this._checkParentType();
this.formDirective !.addFormArray(this);
}
/**
* @description
* A lifecycle method called before the directive's instance is destroyed. For internal use only.
*/
ngOnDestroy(): void {
if (this.formDirective) {
this.formDirective.removeFormArray(this);
}
}
/**
* @description
* The `FormArray` bound to this directive.
*/
get control(): FormArray { return this.formDirective !.getFormArray(this); }
/**
* @description
* The top-level directive for this group if present, otherwise null.
*/
get formDirective(): FormGroupDirective|null {
return this._parent ? <FormGroupDirective>this._parent.formDirective : null;
}
/**
* @description
* Returns an array that represents the path from the top-level form to this control.
* Each index is the string name of the control on that level.
*/
get path(): string[] { return controlPath(this.name, this._parent); }
/**
* @description
* Synchronous validator function composed of all the synchronous validators registered with this
* directive.
*/
get validator(): ValidatorFn|null { return composeValidators(this._validators); }
/**
* @description
* Async validator function composed of all the async validators registered with this directive.
*/
get asyncValidator(): AsyncValidatorFn|null {
return composeAsyncValidators(this._asyncValidators);
}

View File

@ -121,8 +121,8 @@ function getRouteGuards(
}
if (shouldRun) {
const outlet = context !.outlet !;
checks.canDeactivateChecks.push(new CanDeactivate(outlet.component, curr));
const component = context && context.outlet && context.outlet.component || null;
checks.canDeactivateChecks.push(new CanDeactivate(component, curr));
}
} else {
if (curr) {

View File

@ -10,7 +10,12 @@ export type Ng1Token = string;
export type Ng1Expression = string | Function;
export interface IAnnotatedFunction extends Function { $inject?: ReadonlyArray<Ng1Token>; }
export interface IAnnotatedFunction extends Function {
// Older versions of `@types/angular` typings extend the global `Function` interface with
// `$inject?: string[]`, which is not compatible with `$inject?: ReadonlyArray<string>` (used in
// latest versions).
$inject?: Function extends{$inject?: string[]}? Ng1Token[]: ReadonlyArray<Ng1Token>;
}
export type IInjectable = (Ng1Token | Function)[] | IAnnotatedFunction;

View File

@ -24,8 +24,8 @@ if (msgFile) {
isValid = checkMsg(firstLine);
if (!isValid) {
console.error('\nCheck CONTRIBUTING.md at the root of the repo for more information.')
console.error('\nCheck CONTRIBUTING.md at the root of the repo for more information.');
}
}
process.exit(isValid ? 0 : 1);
process.exit(isValid ? 0 : 1);

View File

@ -1274,10 +1274,10 @@ camelcase@^4.1.0:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=
canonical-path@0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/canonical-path/-/canonical-path-0.0.2.tgz#e31eb937a8c93ee2a01df1839794721902874574"
integrity sha1-4x65N6jJPuKgHfGDl5RyGQKHRXQ=
canonical-path@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/canonical-path/-/canonical-path-1.0.0.tgz#fcb470c23958def85081856be7a86e904f180d1d"
integrity sha512-feylzsbDxi1gPZ1IjystzIQZagYYLvfKrSuygUCgf7z6x790VEzze5QEkdSV1U58RA7Hi0+v6fv4K54atOzATg==
caseless@~0.11.0:
version "0.11.0"