Compare commits

...

52 Commits

Author SHA1 Message Date
cdda60a430 release: cut the v10.0.6 release 2020-07-28 14:38:28 -07:00
7570356bfa build: fix broken build (#38274)
```
export const __core_private_testing_placeholder__ = '';
```
This API should be removed. But doing so seems to break `google3` and
so it requires a bit of investigation. A work around is to mark it as
`@codeGenApi` for now and investigate later.

PR Close #38274
2020-07-28 12:31:00 -07:00
c4a97d822e Revert "ci: roll back phased review conditions" (#38259)
This reverts commit ca8eafc2983f983803cd03e4a08bf732672712dd.

PR Close #38259
2020-07-28 11:26:29 -07:00
fc4dfc5eb1 ci: only check active groups for the no-groups-above-this-* checks (#38259)
Since PullApprove starts all inactive groups as a pending state, to properly
assess if any groups we care about are pending we must only check the active
groups.  We additionally do this with rejected because the intention of the
reusable checks is around checking active rules only.

PR Close #38259
2020-07-28 11:26:28 -07:00
25d95dae6d docs(elements): update api doc for custom elements (#38252)
by adding cross-references to Angular Elements Overview guide.

PR Close #38252
2020-07-28 11:19:04 -07:00
1c4fcce2a1 fix(core): Attribute decorator attributeName is mandatory (#38131)
`Attribute` decorator has defined `attributeName` as optional but actually its
 mandatory and compiler throws an error if `attributeName` is undefined. Made
`attributeName` mandatory in the `Attribute` decorator to reflect this functionality

Fixes #32658

PR Close #38131
2020-07-28 11:17:25 -07:00
Ahn
6e73faaed7 docs(core): correct SomeService to SomeComponent (#38264)
PR Close #38264
2020-07-28 11:10:59 -07:00
41c9910613 docs: update api reference doc for router-link-active directive (#38247)
Edits and organizes the usage information for the directive.

PR Close #38247
2020-07-28 11:09:45 -07:00
aaddef213d ci: roll back phased review conditions (#38257)
It was determined that the list of 'pending' groups also included inactive groups.
That means that the 'no-groups-above-this-pending' would generally fail because
there's almost always some inactive group above it.

This commit removes the conditions for phased review while we
investigate if the inactive groups can be excluded.

PR Close #38257
2020-07-28 10:02:15 -07:00
02f3aee1db docs: Refactor module-types.md to make it easier to understand (#38206)
Project DOCS-736 to rewrite headings to focus on user tasks,
verify that the content is up-to-date and complete, and
add relevant links to other NgModule topics to improve readability.
Also addresses one of many issues in GitHub issue 21531.

PR Close #38206
2020-07-28 10:01:38 -07:00
c27ba96093 ci: add more owners for limited categories (#38170)
* Add alxhub, atscott, and AndrewKushnir to code owners
* Add atscott & AndrewKushnir to public-api and size-tracking

Follow-up to #37994

PR Close #38170
2020-07-28 10:01:05 -07:00
c5a474cb54 docs: Refactor ngmodule-vs-jsmodule.md to make it easier to understand (#38148)
Project DOCS-734 to rewrite headings to focus on user tasks,
verify that the content is up-to-date and complete, and
add relevant links to other NgModule topics to improve readability.
Also addresses one of many issues in GitHub issue 21531.

PR Close #38148
2020-07-28 10:00:29 -07:00
d5264f5645 fix(core): unify the signature between ngZone and noopZone (#37581)
Now we have two implementations of Zone in Angular, one is NgZone, the other is NoopZone.
They should have the same signatures, includes
1. properties
2. methods

In this PR, unify the signatures of the two implementations, and remove the unnecessary cast.

PR Close #37581
2020-07-28 09:59:49 -07:00
0cd4b87021 Revert "refactor(core): remove unused export (#38224)"
This reverts commit c6c8e15813.
2020-07-28 09:56:24 -07:00
b1e7775a8a fix(compiler-cli): Add support for string literal class members (#38226)
The current implementation of the TypeScriptReflectionHost does not account for members that
are string literals, i.e. `class A { 'string-literal-prop': string; }`

PR Close #38226
2020-07-27 15:26:27 -07:00
87f5feff11 docs: update api reference doc for router link directive (#38181)
Edits and organizes the usage information for the directive.

PR Close #38181
2020-07-27 15:25:45 -07:00
c3ddc3d6b1 refactor(platform-browser): specify return type of parseEventName (#38089)
This commit refactors the argument of the `parseEventName` function
to use an object with named properties instead of using an object indexer.

PR Close #38089
2020-07-27 15:25:00 -07:00
cec39a7d16 test: update ts-api-guardian's strip_export_pattern to exclude Ivy instructions (#38224)
Previously the instructions were included in the golden files to monitor the frequency and rate of
the instruction API changes for the purpose of understanding the stability of this API (as it was
considered for becoming a public API and deployed to npm via generated code).

This experiment has confirmed that the instruction API is not stable enough to be used as public
API. We've since also came up with an alternative plan to compile libraries with the Ivy compiler
for npm deployment and this plan does not rely on making Ivy instructions public.

For these reasons, I'm removing the instructions from the golden files as it's no longer important
to track them.

The are three instructions that are still being included: `ɵɵdefineInjectable`, `ɵɵinject`, and
`ɵɵInjectableDef`.

These instructions are already generated by the VE compiler to support tree-shakable providers, and
code depending on these instructions is already deployed to npm. For this reason we need to treat
them as public api.

This change also reduces the code review overhead, because changes to public api golden files now
require multiple approvals.

PR Close #38224
2020-07-27 15:23:28 -07:00
c6c8e15813 refactor(core): remove unused export (#38224)
This export used to be here to turn this file into an ES Module - this is no longer needed
because the file contains imports.

PR Close #38224
2020-07-27 15:10:24 -07:00
752fd14fe5 refactor: correct @publicApi and @codeGenApi markers in various files (#38224)
The markers were previously incorrectly assigned. I noticed the issues when reviewing
the golden files and this change corrects them.

PR Close #38224
2020-07-27 15:10:18 -07:00
776067cd43 fix(zone.js): zone patch rxjs should return null _unsubscribe correctly. (#37091)
Close #31684.

In some rxjs operator, such as `retryWhen`, rxjs internally will set
`Subscription._unsubscribe` method to null, and the current zone.js monkey patch
didn't handle this case correctly, even rxjs set _unsubscribe to null, zone.js
still return a function by finding the prototype chain.

This PR fix this issue and the following test will pass.

```
const errorGenerator = () => {
  return throwError(new Error('error emit'));
};

const genericRetryStrategy = (finalizer: () => void) => (attempts: Observable<any>) =>
    attempts.pipe(
      mergeMap((error, i) => {
        const retryAttempt = i + 1;
        if (retryAttempt > 3) {
          return throwError(error);
        }
        return timer(retryAttempt * 1);
      }),
      finalize(() => finalizer()));

errorGenerator()
  .pipe(
    retryWhen(genericRetryStrategy(() => {
      expect(log.length).toBe(3);
      done();
    })),
    catchError(error => of(error)))
  .subscribe()
```

PR Close #37091
2020-07-27 12:11:27 -07:00
e87a46be21 docs: add template ref var to glossary (#36743)
There is not an entry in the glossary for template
reference variable. To clarify for site visitors,
we are adding one here.

PR Close #36743
2020-07-27 12:01:47 -07:00
89a7ff3ada docs: fix breaking URL for RxJS marble testing (#38209)
When checking this URL for the `RxJS marble testing` Ive found it pointing to `Page not found`

PR Close #38209
2020-07-27 11:12:08 -07:00
3d6e50dc02 docs: clarify the description of pipes (#37950)
This commit clarifies some of the language regarding pipes in the pipes guide.
This commit also specifies the term transforming rather than formatting.

PR Close #37950
2020-07-27 10:23:21 -07:00
264950bbf2 fix(compiler): share identical stylesheets between components in the same file (#38213)
Prior to this commit, duplicated styles defined in multiple components in the same file were not
shared between components, thus causing extra payload size. This commit updates compiler logic to
use `ConstantPool` for the styles (while generating the `styles` array on component def), which
enables styles sharing when needed (when duplicates styles are present).

Resolves #38204.

PR Close #38213
2020-07-27 10:11:56 -07:00
84c5be0b5b refactor(compiler): allow strings with certain length to be included into ConstantPool (#38213)
Prior to this commit, the `ConstantPool` ignored all primitive values. It turned out that it's
beneficial to include strings above certain length to the pool as well. This commit updates the
`ConstantPool` logic to allow such strings to be shared across multiple instances if needed.
For instance, this is helpful for component styles that might be reused across multiple components
in the same file.

PR Close #38213
2020-07-27 10:09:48 -07:00
eda8f2f8b9 refactor(compiler): separate compilation and transform phases (#38213)
This commit splits the transformation into 2 separate steps: Ivy compilation and actual transformation
of corresponding TS nodes. This is needed to have all `o.Expression`s generated before any TS transforms
happen. This allows `ConstantPool` to properly identify expressions that can be shared across multiple
components declared in the same file.

Resolves #38203.

PR Close #38213
2020-07-27 10:09:33 -07:00
cc52945d00 docs: add ng-add save option (#38198)
PR Close #38198
2020-07-27 09:52:15 -07:00
07f184a69d refactor(router): extract Router config utils to a separate file (#38229)
This commit refactors Router package to move config utils to a separate file for better
organization and to resolve the problem with circular dependency issue.

Resolves #38212.

PR Close #38229
2020-07-27 09:49:14 -07:00
a123ef58b1 fix(dev-infra): Ensure conditions with groups do not fail verification (#37798)
There are a few changes in this PR to ensure conditions that are based
on groups (i.e. `- groups.pending.length == 0`) do not fail the verify
task:

* Remove the warning when a condition is encountered that depends on the
`groups` state. The warning will otherwise be printed once for every
file that triggers the execution of the condition (400,000+ times)
* Add an `unverifiable` flag to `GroupCondition` interface and set it to
true when an error is encountered due to attempting to get the state of
`groups` in a condition
* Ignore any unverifiable conditions when gathering unmatched
conditions. These should not be considered `unmatched` for verification
purposes.
* Print the unverifiable conditions by group in the results

Sample output:
```

┌──────────────────────────────────────────────────────────────────────────────┐
│                         PullApprove results by group                         │
└──────────────────────────────────────────────────────────────────────────────┘
Groups skipped (4 groups)
Matched conditions by Group (37 groups)
Unmatched conditions by Group (0 groups)
Unverifiable conditions by Group (3 groups)
  [public-api]
    len(groups.pending.exclude("required-minimum-review")...
    len(groups.rejected.exclude("required-minimum-review")...
  [size-tracking]
    len(groups.pending.exclude("required-minimum-review")...
    len(groups.rejected.exclude("required-minimum-review")...
  [circular-dependencies]
    len(groups.pending.exclude("required-minimum-review")...
    len(groups.rejected.exclude("required-minimum-review")...

```

PR Close #37798
2020-07-24 18:02:49 -07:00
024126dde4 feat(dev-infra): add phased review to groups requiring final sign-off after initial review (#37798)
The size-tracking, public-api, and circular-dependencies groups can get a lot of
PRs to review. In addition, the members of these groups do not always
have the necessary context to fully review the PR in question. This
change ensures that the owners in the groups where the changes are being
made have approve the changes (ie, the aren't pending or rejected)
before requesting final sign-off from these three critical groups.

PR Close #37798
2020-07-24 18:02:42 -07:00
4275c34818 refactor(dev-infra): create anchors/aliases for excluded always active groups (#37798)
global-approvers, global-docs-approvers, and required-minimum-review groups are always active. It's useful
to have aliases for getting groups that are active/pending/rejected while excluding these few.

PR Close #37798
2020-07-24 18:02:19 -07:00
c4e6f585c5 fix(zone.js): patch nodejs EventEmtter.prototype.off (#37863)
Close #35473

zone.js nodejs patch should also patch `EventEmitter.prototype.off` as `removeListener`.
So `off` can correctly remove the listeners added by `EventEmitter.prototype.addListener`

PR Close #37863
2020-07-24 15:45:01 -07:00
7467fd36b9 fix(zone.js): clearTimeout/clearInterval should call on object global (#37858)
Close #37333

`clearTimeout` is patched by `zone.js`, and it finally calls the native delegate of `clearTimeout`,
the current implemention only call `clearNative(id)`, but it should call on object `global` like
`clearNative.call(global, id)`. Otherwise in some env, it will throw error
`clearTimeout called on an object that does not implement interface Window`

PR Close #37858
2020-07-24 15:22:48 -07:00
aca01985fd docs: Fix link by removing a space (#38214)
PR Close #38214
2020-07-24 09:53:06 -07:00
eb5e14e6e0 docs(core): Fix incorrectly rendered code example in structural directives guide (#38207)
The code example was missing a close brace and also incorrectly rendered
the template div as an actual div in the page DOM.

PR Close #38207
2020-07-24 09:52:31 -07:00
b8af10902f docs: fixed that class attribute is not closed (#38219)
PR Close #38219
2020-07-24 08:15:44 -07:00
f411c9e5b9 build(docs-infra): simplify ExampleZipper by removing PackageJsonCustomizer (#38192)
Previously, `ExampleZipper` (the tool used for creating ZIP archives
from our docs examples) used the `PackageJsonCustomizer` to generate
`package.json` files for each example type. This had the following
drawbacks:
- The generated files had to be kept up-to-date with the corresponding
  boilerplate files in `aio/tools/examples/shared/boilerplate/` and
  there was no easy way to find out when the files got out-of-sync.
- The `PackageJsonCustomizer` logic was non-trivial and difficult to
  reason about.
- The same information was duplicated in the boilerplate files and the
  customizer configuration files.

This setup was useful when we used a single `package.json` file for all
docs examples. Now, however, each example type can have its own
boilerplate `package.json` file, including scripts and dependencies
relevant to the example type. Therefore, it is no longer necessary to
generate `package.json` files for ZIP archives.

This commit eliminates the drawbacks mentioned above and simplifies the
`ExampleZipper` tool by removing `PackageJsonCustomizer` and re-using
the boilerplate `package.json` files for ZIP archives.

The changes in this commit also fix some ZIP archives that were
previously broken (for example due to missing dependencies).

PR Close #38192
2020-07-23 11:08:12 -07:00
7f455e6eec fix(docs-infra): correctly display copy button in IE11 (#38186)
Fix button top portion was clipped in IE11 by setting overflow to visible

Fixes #37816

PR Close #38186
2020-07-23 11:07:29 -07:00
e36caafa52 build(docs-infra): upgrade cli command docs sources to a404d2a86 (#38183)
Updating [angular#10.0.x](https://github.com/angular/angular/tree/10.0.x) from
[cli-builds#10.0.x](https://github.com/angular/cli-builds/tree/10.0.x).

##
Relevant changes in
[commit range](14af4e07c...a404d2a86):

**Modified**
- help/update.json

PR Close #38183
2020-07-23 11:06:55 -07:00
5e89d98876 refactor(dev-infra): Add support for groups in the conditions evaluator (#38164)
Conditions can refer to the groups array that is a list of the preceding groups.
This commit adds support to the verification for those conditions.

This commit also adds some tests to the parsing and condition matching
to ensure everything works as expected.

PR Close #38164
2020-07-23 11:05:43 -07:00
200dbd4860 docs: add Kevin Kreuzer to GDE page (#37929)
This commit adds Kevin Kreuzer to the Angular GDE page along with a biography, his contributions, and a photograph.

PR Close #37929
2020-07-23 11:03:58 -07:00
c90952884a docs: update dynamic-component loading guide (#36959)
The 'entryComponents' array is no longer a special case for dynamic component loading because of the Ivy compiler.

PR Close #36959
2020-07-23 11:03:02 -07:00
7c2d8fc672 docs: remove duplicate https:// (#38199)
This doc contained a duplicate `http://` before the domain name leading to an invalid link.
This commit fixes this issue.
PR Close #38199
2020-07-23 10:54:45 -07:00
a50a688aaf docs: update api reference for router outlet directive (#38166)
Incorporate more specific information about multiple outlets and how to target them, with link to tutorial example.

PR Close #38166
2020-07-22 20:50:13 -07:00
6ec7297e43 build(docs-infra): remove boilerplate file listings in example-boilerplate.js (#38173)
To avoid unnecessary code duplication in docs examples, we have some
boilerplate files for various example types (in
`aio/tools/examples/shared/boilerplate/`). These files are copied to
each example project in `aio/content/examples/` (according to the
example's type, as specified in its `example-config.json` file).

Previously, the `example-boilerplate.js`, which is responsible for
copying the boilerplate files, had lists for files to be copied for each
project type and only copied the listed files from the boilerplate
directory to the example directory. This approach had some drawbacks:
- Files need to be updated in two separate locations: in the boilerplate
  directory that includes the files and the file list in
  `example-boilerplate.js`.
- It is easy to add a file in the boilerplate directory but forget to
  add it in `example-boilerplate.js` and not realize that it is not
  being included in the example project (including the generated
  StackBlitz project and ZIP archive).

This commit changes the approach by removing the boilerplate file
listings from `example-boilerplate.js` and copying all files from a
boilerplate directory to example directories. This addresses the above
drawbacks and simplifies the `example-boilerplate.js` script.

I have verified that the resulting code example doc regions as well as
the generated StackBlitz projects and ZIP archives are identical to the
ones generated before this commit.

PR Close #38173
2020-07-22 10:15:10 -07:00
f264cd1cb8 fix(docs-infra): include .gitignore file in CLI-based docs examples (#38173)
Previously, the `.gitignore` file that is part of the boilerplate files
for CLI-based docs examples (located in
`aio/tools/examples/shared/boilerplate/cli/`) was not added to the
example projects, because it was not included in the boilerplate file
list in `example-boilerplate.js`.

This commit fixes it by adding the `.gitignore` file to the list. This
ensures that docs examples more closely match CLI-generated projects.

PR Close #38173
2020-07-22 10:15:10 -07:00
fc17bddcde fix(docs-infra): correctly add polyfills.ts file as boilerplate for i18n docs examples (#38173)
Docs examples of type `i18n` need a slightly modified version of
`polyfills.ts` that imports `@angular/localize/init`. Previously, this
file was not included in `i18n` example projects for two reasons:

- While the file was included in the `i18n` boilerplate files (at
  `aio/tools/examples/shared/boilerplate/i18n/`), it was not included in
  the boilerplate file list in `example-boilerplate.js`.
- The file was in the wrong location: It was located at the project root
  instead of inside the `src/` directory.

This commit addresses the above issues (i.e. adds the file to the
boilerplate file list for `i18n` projects and moves the file inside the
`src/` directory).

PR Close #38173
2020-07-22 10:15:10 -07:00
0765626761 build(docs-infra): remove obsolete systemjs.config.web[.build].js files from docs examples (#38173)
There were some `systemjs.config.web[.build].js` files in the `systemjs`
boilerplate directory that are not used any more. In the past, these
files were used in the Plunker-based live examples, but we no longer use
Plunker for live examples.

This commit removes these obsolete files.

PR Close #38173
2020-07-22 10:15:10 -07:00
f146b34042 build(docs-infra): remove obsolete typings.d.ts files from angular.io and docs examples (#38173)
There were two `typings.d.ts` files with SystemJS module definitions in
`aio/src/` and `aio/tools/examples/shared/boilerplate/cli/`. These are
remnants from old CLI versions that used SystemJS and are no longer
needed. For docs examples specifically, these files were never copied
over to example projects and thus not included in StackBlitz projects
and ZIP archives.

This commit removes these obsolete files.

PR Close #38173
2020-07-22 10:15:10 -07:00
f899d6ea44 docs: fix typo in ng_control.ts (#38157)
PR Close #38157
2020-07-22 10:14:24 -07:00
c18d7a1469 docs: fix typo from singular to plural spelling (#36586)
This commit fixes the spelling of the singular form
of the word function to the plural spelling in
packages/core/src/application_init.ts

PR Close #36586
2020-07-22 10:12:45 -07:00
100 changed files with 1547 additions and 1713 deletions

View File

@ -67,6 +67,25 @@ version: 3
# Meta field that goes unused by PullApprove to allow for defining aliases to be
# used throughout the config.
meta:
# The following groups have no file based conditions and will be initially `active` on all PRs
# - `global-approvers`
# - `global-docs-approvers`
# - `required-minimum-review`
#
# By checking the number of active/pending/rejected groups when these are excluded, we can determine
# if any other groups are matched.
#
# Note: Because all inactive groups start as pending, we are only checking pending and rejected active groups.
#
# Also note that the ordering of groups matters in this file. The only groups visible to the current
# one are those that appear above it.
no-groups-above-this-pending: &no-groups-above-this-pending
len(groups.active.pending.exclude("required-minimum-review").exclude("global-approvers").exclude("global-docs-approvers")) == 0
no-groups-above-this-rejected: &no-groups-above-this-rejected
len(groups.active.rejected.exclude("required-minimum-review").exclude("global-approvers").exclude("global-docs-approvers")) == 0
no-groups-above-this-active: &no-groups-above-this-active
len(groups.active.exclude("required-minimum-review").exclude("global-approvers").exclude("global-docs-approvers")) == 0
can-be-global-approved: &can-be-global-approved "\"global-approvers\" not in groups.approved"
can-be-global-docs-approved: &can-be-global-docs-approved "\"global-docs-approvers\" not in groups.approved"
defaults: &defaults
@ -1123,6 +1142,8 @@ groups:
public-api:
<<: *defaults
conditions:
- *no-groups-above-this-pending
- *no-groups-above-this-rejected
- *can-be-global-approved
- >
contains_any_globs(files, [
@ -1136,14 +1157,16 @@ groups:
])
reviewers:
users:
- AndrewKushnir
- IgorMinar
- alxhub
- atscott
- jelbourn
- petebacondarwin
- pkozlowski-opensource
reviews:
request: -1 # request reviews from everyone
required: 3 # require at least 3 approvals
request: 4 # Request reviews from four people
required: 3 # Require that three people approve
reviewed_for: required
@ -1153,6 +1176,8 @@ groups:
size-tracking:
<<: *defaults
conditions:
- *no-groups-above-this-pending
- *no-groups-above-this-rejected
- *can-be-global-approved
- >
contains_any_globs(files, [
@ -1160,14 +1185,16 @@ groups:
])
reviewers:
users:
- AndrewKushnir
- IgorMinar
- alxhub
- atscott
- jelbourn
- petebacondarwin
- pkozlowski-opensource
reviews:
request: -1 # request reviews from everyone
required: 2 # require at least 2 approvals
request: 4 # Request reviews from four people
required: 2 # Require that two people approve
reviewed_for: required
@ -1177,6 +1204,8 @@ groups:
circular-dependencies:
<<: *defaults
conditions:
- *no-groups-above-this-pending
- *no-groups-above-this-rejected
- *can-be-global-approved
- >
contains_any_globs(files, [
@ -1184,9 +1213,11 @@ groups:
])
reviewers:
users:
- AndrewKushnir
- IgorMinar
- alxhub
- atscott
- jelbourn
- josephperrott
- petebacondarwin
- pkozlowski-opensource
@ -1208,7 +1239,10 @@ groups:
])
reviewers:
users:
- AndrewKushnir
- IgorMinar
- alxhub
- atscott
- jelbourn
- josephperrott
- mhevery
@ -1238,13 +1272,10 @@ groups:
# `global-approvers` can still approve PRs that match this `fallback` rule,
# but that should be an exception and not an expectation.
conditions:
- *no-groups-above-this-active
# When any of the `global-*` groups is approved, they cause other groups to deactivate.
# In those cases, the condition above would evaluate to `true` while in reality, only a global
# approval has been provided. To ensure we don't activate the fallback group in such cases,
# ensure that no explicit global approval has been provided.
- *can-be-global-approved
# The following groups have no conditions and will be `active` on all PRs
# - `global-approvers`
# - `global-docs-approvers`
#
# Since this means the minimum number of active groups a PR can have is 2, this
# `fallback` group should be matched anytime the number of active groups is at or
# below this minimum. This work as a protection to ensure that pullapprove does
# not incidently mark a PR as passing without meeting the review criteria.
- len(groups.active) <= 2
- *can-be-global-docs-approved

View File

@ -1,3 +1,16 @@
<a name="10.0.6"></a>
## 10.0.6 (2020-07-28)
### Bug Fixes
* **compiler:** share identical stylesheets between components in the same file ([#38213](https://github.com/angular/angular/issues/38213)) ([264950b](https://github.com/angular/angular/commit/264950b)), closes [#38204](https://github.com/angular/angular/issues/38204)
* **compiler-cli:** Add support for string literal class members ([#38226](https://github.com/angular/angular/issues/38226)) ([b1e7775](https://github.com/angular/angular/commit/b1e7775))
* **core:** `Attribute` decorator `attributeName` is mandatory ([#38131](https://github.com/angular/angular/issues/38131)) ([1c4fcce](https://github.com/angular/angular/commit/1c4fcce)), closes [#32658](https://github.com/angular/angular/issues/32658)
* **core:** unify the signature between ngZone and noopZone ([#37581](https://github.com/angular/angular/issues/37581)) ([d5264f5](https://github.com/angular/angular/commit/d5264f5))
<a name="10.0.5"></a>
## 10.0.5 (2020-07-22)

View File

@ -18,6 +18,7 @@
**/src/karma.conf.js
**/.angular-cli.json
**/.editorconfig
**/.gitignore
**/angular.json
**/tsconfig.json
**/bs-config.e2e.json

View File

@ -16,5 +16,12 @@
"@angular/core": "^7.2.0"
},
// #docregion collection
"schematics": "./schematics/collection.json"
}
"schematics": "./schematics/collection.json",
// #enddocregion collection
// #docregion ng-add
"ng-add": {
"save": "devDependencies"
}
// #enddocregion ng-add
// #docregion collection
}

View File

@ -131,22 +131,6 @@ The `createComponent()` method returns a reference to the loaded component.
Use that reference to interact with the component by assigning to its properties or calling its methods.
{@a selector-references}
#### Selector references
Generally, the Angular compiler generates a `ComponentFactory`
for any component referenced in a template. However, there are
no selector references in the templates for
dynamically loaded components since they load at runtime.
To ensure that the compiler still generates a factory,
add dynamically loaded components to the `NgModule`'s `entryComponents` array:
<code-example path="dynamic-component-loader/src/app/app.module.ts" region="entry-components" header="src/app/app.module.ts (entry components)"></code-example>
{@a common-interface}

View File

@ -939,6 +939,19 @@ A TypeScript-like syntax that Angular evaluates within a [data binding](#data-bi
Read about how to write template expressions in the [template expressions](guide/interpolation#template-expressions) section of the [Interpolation](guide/interpolation) guide.
{@a template-reference-variable}
## template reference variable
A variable defined in a template that references an instance associated with an element, such as a directive instance, component instance, template as in `TemplateRef`, or DOM element.
After declaring a template reference variable on an element in a template,
you can access values from that variable elsewhere within the same template.
The following example defines a template reference variable named `#phone`.
<code-example path="template-reference-variables/src/app/app.component.html" region="ref-var" header="src/app/app.component.html"></code-example>
For more information, see the [Template reference variable](guide/template-reference-variables) guide.
{@a token}
## token

View File

@ -170,6 +170,6 @@ If an idempotent expression returns a string or a number, it returns the same st
<div class="alert is-helpful">
There is one exception to this behavior that applies to `*ngFor`. `*ngFor` has `trackBy` functionality that can deal with referential inequality of objects when iterating over them. See [*ngFor with `trackBy`](guide/built-in-directives #ngfor-with-trackby) for details.
There is one exception to this behavior that applies to `*ngFor`. `*ngFor` has `trackBy` functionality that can deal with referential inequality of objects when iterating over them. See [*ngFor with `trackBy`](guide/built-in-directives#ngfor-with-trackby) for details.
</div>

View File

@ -569,7 +569,7 @@ which can only be reached by querying for them via the property decorated with
{@a no-unidirectional-flow-worries}
<div class="alert is-helpful>
<div class="alert is-helpful">
<header>No need to wait for content updates</header>

View File

@ -1,116 +1,41 @@
# Types of feature modules
# Guidelines for creating NgModules
There are five general categories of feature modules which
tend to fall into the following groups:
This topic provides a conceptual overview of the different categories of [NgModules](guide/glossary#ngmodule "Definition of NgModule") you can create in order to organize your code in a modular structure.
These categories are not cast in stone—they are suggestions.
You may want to create NgModules for other purposes, or combine the characteristics of some of these categories.
* Domain feature modules.
* Routed feature modules.
* Routing modules.
* Service feature modules.
* Widget feature modules.
NgModules are a great way to organize an app and keep code related to a specific functionality or feature separate from other code.
Use NgModules to consolidate [components](guide/glossary#component "Definition of component"), [directives](guide/glossary#directive "Definition of directive"), and [pipes](guide/glossary#pipe "Definition of pipe)") into cohesive blocks of functionality.
Focus each block on a feature or business domain, a workflow or navigation flow, a common collection of utilities, or one or more [providers](guide/glossary#provider "Definition of provider") for [services](guide/glossary#service "Definition of service").
While the following guidelines describe the use of each type and their
typical characteristics, in real world apps, you may see hybrids.
For more about NgModules, see [Organizing your app with NgModules](guide/ngmodules "Organizing your app with NgModules").
<table>
<div class="alert is-helpful">
<tr>
<th style="vertical-align: top">
Feature Module
</th>
For the example app used in NgModules-related topics, see the <live-example name="ngmodules"></live-example>.
<th style="vertical-align: top">
Guidelines
</th>
</tr>
</div>
<tr>
<td>Domain</td>
<td>
Domain feature modules deliver a user experience dedicated to a particular application domain like editing a customer or placing an order.
## Summary of NgModule categories
They typically have a top component that acts as the feature root and private, supporting sub-components descend from it.
All apps start by [bootstrapping a root NgModule](guide/bootstrapping "Launching an app with a root NgModule").
You can organize your other NgModules any way you wish.
Domain feature modules consist mostly of declarations. Only the top component is exported.
This topic provides some guidelines for the following general categories of NgModules:
Domain feature modules rarely have providers. When they do, the lifetime of the provided services should be the same as the lifetime of the module.
* [Domain](#domain): A domain NgModule is organized around a feature, business domain, or user experience.
* [Routed](#routed): The top component of the NgModule acts as the destination of a [router](guide/glossary#router "Definition of router") navigation route.
* [Routing](#routing): A routing NgModule provides the routing configuration for another NgModule.
* [Service](#service): A service NgModule provides utility services such as data access and messaging.
* [Widget](#widget): A widget NgModule makes a component, directive, or pipe available to other NgModules.
* [Shared](#shared): A shared NgModule makes a set of components, directives, and pipes available to other NgModules.
Domain feature modules are typically imported exactly once by a larger feature module.
They might be imported by the root `AppModule` of a small application that lacks routing.
</td>
</tr>
<tr>
<td>Routed</td>
<td>
Routed feature modules are domain feature modules whose top components are the targets of router navigation routes.
All lazy-loaded modules are routed feature modules by definition.
Routed feature modules dont export anything because their components never appear in the template of an external component.
A lazy-loaded routed feature module should not be imported by any module. Doing so would trigger an eager load, defeating the purpose of lazy loading.That means you wont see them mentioned among the `AppModule` imports. An eager loaded routed feature module must be imported by another module so that the compiler learns about its components.
Routed feature modules rarely have providers for reasons explained in [Lazy Loading Feature Modules](/guide/lazy-loading-ngmodules). When they do, the lifetime of the provided services should be the same as the lifetime of the module. Don't provide application-wide singleton services in a routed feature module or in a module that the routed module imports.
</td>
</tr>
<tr>
<td>Routing</td>
<td>
A routing module provides routing configuration for another module and separates routing concerns from its companion module.
A routing module typically does the following:
<ul>
<li>Defines routes.</li>
<li>Adds router configuration to the module's imports.</li>
<li>Adds guard and resolver service providers to the module's providers.</li>
<li>The name of the routing module should parallel the name of its companion module, using the suffix "Routing". For example, <code>FooModule</code> in <code>foo.module.ts</code> has a routing module named <code>FooRoutingModule</code> in <code>foo-routing.module.ts</code>. If the companion module is the root <code>AppModule</code>, the <code>AppRoutingModule</code> adds router configuration to its imports with <code>RouterModule.forRoot(routes)</code>. All other routing modules are children that import <code>RouterModule.forChild(routes)</code>.</li>
<li>A routing module re-exports the <code>RouterModule</code> as a convenience so that components of the companion module have access to router directives such as <code>RouterLink</code> and <code>RouterOutlet</code>.</li>
<li>A routing module does not have its own declarations. Components, directives, and pipes are the responsibility of the feature module, not the routing module.</li>
</ul>
A routing module should only be imported by its companion module.
</td>
</tr>
<tr>
<td>Service</td>
<td>
Service modules provide utility services such as data access and messaging. Ideally, they consist entirely of providers and have no declarations. Angular's `HttpClientModule` is a good example of a service module.
The root `AppModule` is the only module that should import service modules.
</td>
</tr>
<tr>
<td>Widget</td>
<td>
A widget module makes components, directives, and pipes available to external modules. Many third-party UI component libraries are widget modules.
A widget module should consist entirely of declarations, most of them exported.
A widget module should rarely have providers.
Import widget modules in any module whose component templates need the widgets.
</td>
</tr>
</table>
The following table summarizes the key characteristics of each feature module group.
The following table summarizes the key characteristics of each category.
<table>
<tr>
<th style="vertical-align: top">
Feature Module
NgModule
</th>
<th style="vertical-align: top">
@ -135,7 +60,7 @@ The following table summarizes the key characteristics of each feature module gr
<td>Yes</td>
<td>Rare</td>
<td>Top component</td>
<td>Feature, AppModule</td>
<td>Another domain, AppModule</td>
</tr>
<tr>
@ -151,7 +76,7 @@ The following table summarizes the key characteristics of each feature module gr
<td>No</td>
<td>Yes (Guards)</td>
<td>RouterModule</td>
<td>Feature (for routing)</td>
<td>Another domain (for routing)</td>
</tr>
<tr>
@ -167,14 +92,137 @@ The following table summarizes the key characteristics of each feature module gr
<td>Yes</td>
<td>Rare</td>
<td>Yes</td>
<td>Feature</td>
<td>Another domain</td>
</tr>
<tr>
<td>Shared</td>
<td>Yes</td>
<td>No</td>
<td>Yes</td>
<td>Another domain</td>
</tr>
</table>
<hr />
{@a domain}
## More on NgModules
## Domain NgModules
Use a domain NgModule to deliver a user experience dedicated to a particular feature or app domain, such as editing a customer or placing an order.
One example is `ContactModule` in the <live-example name="ngmodules"></live-example>.
A domain NgModule organizes the code related to a certain function, containing all of the components, routing, and templates that make up the function.
Your top component in the domain NgModule acts as the feature or domain's root, and is the only component you export.
Private supporting subcomponents descend from it.
Import a domain NgModule exactly once into another NgModule, such as a domain NgModule, or into the root NgModule (`AppModule`) of an app that contains only a few NgModules.
Domain NgModules consist mostly of declarations.
You rarely include providers.
If you do, the lifetime of the provided services should be the same as the lifetime of the NgModule.
<div class="alert is-helpful">
For more information about lifecycles, see [Hooking into the component lifecycle](guide/lifecycle-hooks "Hooking into the component lifecycle").
</div>
{@a routed}
## Routed NgModules
Use a routed NgModule for all [lazy-loaded NgModules](guide/lazy-loading-ngmodules "Lazy-loading an NgModule").
Use the top component of the NgModule as the destination of a router navigation route.
Routed NgModules dont export anything because their components never appear in the template of an external component.
Don't import a lazy-loaded routed NgModule into another NgModule, as this would trigger an eager load, defeating the purpose of lazy loading.
Routed NgModules rarely have providers because you load a routed NgModule only when needed (such as for routing).
Services listed in the NgModules' `provider` array would not be available because the root injector wouldnt know about the lazy-loaded NgModule.
If you include providers, the lifetime of the provided services should be the same as the lifetime of the NgModule.
Don't provide app-wide [singleton services](guide/singleton-services) in a routed NgModule or in an NgModule that the routed NgModule imports.
<div class="alert is-helpful">
For more information about providers and lazy-loaded routed NgModules, see [Limiting provider scope](guide/providers#limiting-provider-scope-by-lazy-loading-modules "Providing dependencies: Limiting provider scope").
</div>
{@a routing}
## Routing NgModules
Use a routing NgModule to provide the routing configuration for a domain NgModule, thereby separating routing concerns from its companion domain NgModule.
One example is `ContactRoutingModule` in the <live-example name="ngmodules"></live-example>, which provides the routing for its companion domain NgModule `ContactModule`.
<div class="alert is-helpful">
For an overview and details about routing, see [In-app navigation: routing to views](guide/router "In-app navigation: routing to views").
</div>
Use a routing NgModule to do the following tasks:
* Define routes.
* Add router configuration to the NgModule's import.
* Add guard and resolver service providers to the NgModule's providers.
The name of the routing NgModule should parallel the name of its companion NgModule, using the suffix `Routing`.
For example, <code>ContactModule</code> in <code>contact.module.ts</code> has a routing NgModule named <code>ContactRoutingModule</code> in <code>contact-routing.module.ts</code>.
Import a routing NgModule only into its companion NgModule.
If the companion NgModule is the root <code>AppModule</code>, the <code>AppRoutingModule</code> adds router configuration to its imports with <code>RouterModule.forRoot(routes)</code>.
All other routing NgModules are children that import <code>RouterModule.forChild(routes)</code>.
In your routing NgModule, re-export the <code>RouterModule</code> as a convenience so that components of the companion NgModule have access to router directives such as <code>RouterLink</code> and <code>RouterOutlet</code>.
Don't use declarations in a routing NgModule.
Components, directives, and pipes are the responsibility of the companion domain NgModule, not the routing NgModule.
{@a service}
## Service NgModules
Use a service NgModule to provide a utility service such as data access or messaging.
Ideal service NgModules consist entirely of providers and have no declarations.
Angular's `HttpClientModule` is a good example of a service NgModule.
Use only the root `AppModule` to import service NgModules.
{@a widget}
## Widget NgModules
Use a widget NgModule to make a component, directive, or pipe available to external NgModules.
Import widget NgModules into any NgModules that need the widgets in their templates.
Many third-party UI component libraries are provided as widget NgModules.
A widget NgModule should consist entirely of declarations, most of them exported.
It would rarely have providers.
{@a shared}
## Shared NgModules
Put commonly used directives, pipes, and components into one NgModule, typically named `SharedModule`, and then import just that NgModule wherever you need it in other parts of your app.
You can import the shared NgModule in your domain NgModules, including [lazy-loaded NgModules](guide/lazy-loading-ngmodules "Lazy-loading an NgModule").
One example is `SharedModule` in the <live-example name="ngmodules"></live-example>, which provides the `AwesomePipe` custom pipe and `HighlightDirective` directive.
Shared NgModules should not include providers, nor should any of its imported or re-exported NgModules include providers.
To learn how to use shared modules to organize and streamline your code, see [Sharing NgModules in an app](guide/sharing-ngmodules "Sharing NgModules in an app").
## Next steps
You may also be interested in the following:
* [Lazy Loading Modules with the Angular Router](guide/lazy-loading-ngmodules).
* [Providers](guide/providers).
* For more about NgModules, see [Organizing your app with NgModules](guide/ngmodules "Organizing your app with NgModules").
* To learn more about the root NgModule, see [Launching an app with a root NgModule](guide/bootstrapping "Launching an app with a root NgModule").
* To learn about frequently used Angular NgModules and how to import them into your app, see [Frequently-used modules](guide/frequent-ngmodules "Frequently-used modules").
* For a complete description of the NgModule metadata properties, see [Using the NgModule metadata](guide/ngmodule-api "Using the NgModule metadata").
If you want to manage NgModule loading and the use of dependencies and services, see the following:
* To learn about loading NgModules eagerly when the app starts, or lazy-loading NgModules asynchronously by the router, see [Lazy-loading feature modules](guide/lazy-loading-ngmodules).
* To understand how to provide a service or other dependency for your app, see [Providing Dependencies for an NgModule](guide/providers "Providing Dependencies for an NgModule").
* To learn how to create a singleton service to use in NgModules, see [Making a service a singleton](guide/singleton-services "Making a service a singleton").

View File

@ -1,72 +1,82 @@
# JavaScript modules vs. NgModules
JavaScript and Angular use modules to organize code, and
though they organize it differently, Angular apps rely on both.
JavaScript modules and NgModules can help you modularize your code, but they are very different.
Angular apps rely on both kinds of modules.
## JavaScript modules: Files containing code
## JavaScript modules
A [JavaScript module](https://javascript.info/modules "JavaScript.Info - Modules") is an individual file with JavaScript code, usually containing a class or a library of functions for a specific purpose within your app.
JavaScript modules let you spread your work across multiple files.
In JavaScript, modules are individual files with JavaScript code in them. To make whats in them available, you write an export statement, usually after the relevant code, like this:
<div class="alert is-helpful">
To learn more about JavaScript modules, see [ES6 In Depth: Modules](https://hacks.mozilla.org/2015/08/es6-in-depth-modules/).
For the module specification, see the [6th Edition of the ECMAScript standard](http://www.ecma-international.org/ecma-262/6.0/#sec-modules).
</div>
To make the code in a JavaScript module available to other modules, use an `export` statement at the end of the relevant code in the module, such as the following:
```typescript
export class AppComponent { ... }
```
Then, when you need that files code in another file, you import it like this:
When you need that modules code in another module, use an `import` statement as follows:
```typescript
import { AppComponent } from './app.component';
```
JavaScript modules help you namespace, preventing accidental global variables.
Each module has its own top-level scope.
In other words, top-level variables and functions in a module are not seen in other scripts or modules.
Each module provides a namespace for identifiers to prevent them from clashing with identifiers in other modules.
With multiple modules, you can prevent accidental global variables by creating a single global namespace and adding sub-modules to it.
For more information on JavaScript modules, see [JavaScript/ECMAScript modules](https://hacks.mozilla.org/2015/08/es6-in-depth-modules/).
The Angular framework itself is loaded as a set of JavaScript modules.
## NgModules
## NgModules: Classes with metadata for compiling
<!-- KW-- perMisko: let's discuss. This does not answer the question why it is different. Also, last sentence is confusing.-->
NgModules are classes decorated with `@NgModule`. The `@NgModule` decorators `imports` array tells Angular what other NgModules the current module needs. The modules in the `imports` array are different than JavaScript modules because they are NgModules rather than regular JavaScript modules. Classes with an `@NgModule` decorator are by convention kept in their own files, but what makes them an `NgModule` isnt being in their own file, like JavaScript modules; its the presence of `@NgModule` and its metadata.
An [NgModule](guide/glossary#ngmodule "Definition of NgModule") is a class marked by the `@NgModule` decorator with a metadata object that describes how that particular part of the app fits together with the other parts.
NgModules are specific to Angular.
While classes with an `@NgModule` decorator are by convention kept in their own files, they differ from JavaScript modules because they include this metadata.
The `AppModule` generated from the [Angular CLI](cli) demonstrates both kinds of modules in action:
The `@NgModule` metadata plays an important role in guiding the Angular compilation process that converts the app code you write into highly performant JavaScript code.
The metadata describes how to compile a component's template and how to create an [injector](guide/glossary#injector "Definition of injector") at runtime.
It identifies the NgModule's [components](guide/glossary#component "Definition of component"), [directives](guide/glossary#directive "Definition of directive"), and [pipes](guide/glossary#pipe "Definition of pipe)"),
and makes some of them public through the `exports` property so that external components can use them.
You can also use an NgModule to add [providers](guide/glossary#provider "Definition of provider") for [services](guide/glossary#service "Definition of a service"), so that the services are available elsewhere in your app.
```typescript
/* These are JavaScript import statements. Angular doesnt know anything about these. */
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
Rather than defining all member classes in one giant file as a JavaScript module, declare which components, directives, and pipes belong to the NgModule in the `@NgModule.declarations` list.
These classes are called [declarables](guide/glossary#declarable "Definition of a declarable").
An NgModule can export only the declarable classes it owns or imports from other NgModules.
It doesn't declare or export any other kind of class.
Declarables are the only classes that matter to the Angular compilation process.
import { AppComponent } from './app.component';
For a complete description of the NgModule metadata properties, see [Using the NgModule metadata](guide/ngmodule-api "Using the NgModule metadata").
/* The @NgModule decorator lets Angular know that this is an NgModule. */
@NgModule({
declarations: [
AppComponent
],
imports: [ /* These are NgModule imports. */
BrowserModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
```
## An example that uses both
The root NgModule `AppModule` generated by the [Angular CLI](cli) for a new app project demonstrates how you use both kinds of modules:
The NgModule classes differ from JavaScript module in the following key ways:
<code-example path="ngmodules/src/app/app.module.1.ts" header="src/app/app.module.ts (default AppModule)"></code-example>
* An NgModule bounds [declarable classes](guide/ngmodule-faq#q-declarable) only.
Declarables are the only classes that matter to the [Angular compiler](guide/ngmodule-faq#q-angular-compiler).
* Instead of defining all member classes in one giant file as in a JavaScript module,
you list the module's classes in the `@NgModule.declarations` list.
* An NgModule can only export the [declarable classes](guide/ngmodule-faq#q-declarable)
it owns or imports from other modules. It doesn't declare or export any other kind of class.
* Unlike JavaScript modules, an NgModule can extend the _entire_ application with services
by adding providers to the `@NgModule.providers` list.
The root NgModule starts with `import` statements to import JavaScript modules.
It then configures the `@NgModule` with the following arrays:
<hr />
* `declarations`: The components, directives, and pipes that belong to the NgModule.
A new app project's root NgModule has only one component, called `AppComponent`.
## More on NgModules
* `imports`: Other NgModules you are using, so that you can use their declarables.
The newly generated root NgModule imports [`BrowserModule`](api/platform-browser/BrowserModule "BrowserModule NgModule") in order to use browser-specific services such as [DOM](https://www.w3.org/TR/DOM-Level-2-Core/introduction.html "Definition of Document Object Model") rendering, sanitization, and location.
For more information on NgModules, see:
* [Bootstrapping](guide/bootstrapping).
* [Frequently used modules](guide/frequent-ngmodules).
* [Providers](guide/providers).
* `providers`: Providers of services that components in other NgModules can use.
There are no providers in a newly generated root NgModule.
* `bootstrap`: The [entry component](guide/entry-components "Specifying an entry component") that Angular creates and inserts into the `index.html` host web page, thereby bootstrapping the app.
This entry component, `AppComponent`, appears in both the `declarations` and the `bootstrap` arrays.
## Next steps
* For more about NgModules, see [Organizing your app with NgModules](guide/ngmodules "Organizing your app with NgModules").
* To learn more about the root NgModule, see [Launching an app with a root NgModule](guide/bootstrapping "Launching an app with a root NgModule").
* To learn about frequently used Angular NgModules and how to import them into your app, see [Frequently-used modules](guide/frequent-ngmodules "Frequently-used modules").

View File

@ -1,7 +1,7 @@
# Transforming Data Using Pipes
Use [pipes](guide/glossary#pipe "Definition of a pipe") to transform and format strings, currency amounts, dates, and other display data.
Pipes are simple functions you can use in [template expressions](/guide/glossary#template-expression "Definition of template expression") to accept an input value and return a transformed value.
Use [pipes](guide/glossary#pipe "Definition of a pipe") to transform strings, currency amounts, dates, and other data for display.
Pipes are simple functions you can use in [template expressions](/guide/glossary#template-expression "Definition of template expression") to accept an input value and return a transformed value. Pipes are useful because you can use them throughout your application, while only declaring each pipe once.
For example, you would use a pipe to show a date as **April 15, 1988** rather than the raw string format.
<div class="alert is-helpful">
@ -63,7 +63,7 @@ function.
{@a parameterizing-a-pipe}
## Formatting data with parameters and chained pipes
## Transforming data with parameters and chained pipes
Use optional parameters to fine-tune a pipe's output.
For example, you can use the [`CurrencyPipe`](api/common/CurrencyPipe "API reference") with a country code such as EUR as a parameter.

View File

@ -57,6 +57,20 @@ The task uses the user's preferred package manager to add the library to the pro
In this example, the function receives the current `Tree` and returns it without any modifications.
If you need to, you can do additional setup when your package is installed, such as generating files, updating configuration, or any other initial setup your library requires.
### Define dependency type
Use the `save` option of `ng-add` to configure if the library should be added to the `dependencies`, the `devDepedencies`, or not saved at all in the project's `package.json` configuration file.
<code-example header="projects/my-lib/package.json (ng-add Reference)" path="schematics-for-libraries/projects/my-lib/package.json" region="ng-add">
</code-example>
Possible values are:
* `false` - Don't add the package to package.json
* `true` - Add the package to the dependencies
* `"dependencies"` - Add the package to the dependencies
* `"devDependencies"` - Add the package to the devDependencies
## Building your schematics
To bundle your schematics together with your library, you must configure the library to build the schematics separately, then add them to the bundle.

View File

@ -879,12 +879,14 @@ export type LoadingState<T> = Loaded<T> | Loading;
export class IfLoadedDirective<T> {
@Input('ifLoaded') set state(state: LoadingState<T>) {}
static ngTemplateGuard_state<T>(dir: IfLoadedDirective<T>, expr: LoadingState<T>): expr is Loaded<T> { return true; };
}
export interface Person {
name: string;
}
@Component({
template: `<div *ifLoaded="state">{{ state.data }}</div>`,
template: `&lt;div *ifLoaded="state">{{ state.data }}&lt;/div>`,
})
export class AppComponent {
state: LoadingState<Person>;

View File

@ -19,6 +19,13 @@ Here, a `<button>` further down the template refers to the `phone` variable.
<code-example path="template-reference-variables/src/app/app.component.html" region="ref-phone" header="src/app/app.component.html"></code-example>
Angular assigns each template reference variable a value based on where you declare the variable:
* If you declare the variable on a component, the variable refers to the component instance.
* If you declare the variable on a standard HTML tag, the variable refers to the element.
* If you declare the variable on an `<ng-template>` element, the variable refers to a `TemplateRef` instance, which represents the template.
* If the variable specifies a name on the right-hand side, such as `#var="ngModel"`, the variable refers to the directive or component on the element with a matching `exportAs` name.
<h3 class="no-toc">How a reference variable gets its value</h3>
In most cases, Angular sets the reference variable's value to the element on which it is declared.

View File

@ -779,7 +779,7 @@ which reports router activity, is a _hot_ observable.
RxJS marble testing is a rich subject, beyond the scope of this guide.
Learn about it on the web, starting with the
[official documentation](https://github.com/ReactiveX/rxjs/blob/master/doc/writing-marble-tests.md).
[official documentation](https://rxjs.dev/guide/testing/marble-testing).
<hr>

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -840,5 +840,13 @@
"bio": "Emma is a Developer Advocate at Google. She is passionate about good user experiences and design.",
"groups": ["Angular"],
"lead": "mgechev"
},
"kreuzercode": {
"name": "Kevin Kreuzer",
"picture": "kevin-kreuzer.jpg",
"twitter": "kreuzercode",
"website": "kreuzercode.com",
"bio": "Kevin is a passionate freelance front-end engineer and Google Developer Expert based in Switzerland. He is a JavaScript enthusiast and fascinated by Angular. Kevin always tries to learn new things, expand his knowledge, and share it with others in the form of blog posts, workshops, podcasts, or presentations. He is a writer for various publications and the most active writer on Angular in-depth in 2019. Contributing to multiple projects and maintaining 7 npm packages, Kevin is also a big believer in open source. Furthermore, Kevin is a big football fan. Since his childhood, he has supported Real Madrid, which you might notice in a lot of his blog posts and tutorials.",
"groups": ["GDE"]
}
}

View File

@ -23,7 +23,7 @@
"build-local-with-viewengine": "yarn ~~build",
"prebuild-local-with-viewengine-ci": "node scripts/switch-to-viewengine && yarn setup-local-ci",
"build-local-with-viewengine-ci": "yarn ~~build --progress=false",
"extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js 14af4e07c",
"extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js a404d2a86",
"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",

View File

@ -108,6 +108,7 @@ aio-code pre {
top: -7px;
right: -19px;
padding: 0;
overflow: visible; // This is required for the button to be displayed correctly in IE11.
color: $blue-grey-200;
background-color: transparent;

View File

@ -1,5 +0,0 @@
/* SystemJS module definition */
declare var module: NodeModule;
interface NodeModule {
id: string;
}

View File

@ -14,16 +14,3 @@ There, select all the packages that are updated on the new Angular release.
**2)** Changes to the tsconfig.json? There are several files in `/aio/tools/examples/shared/boilerplate/*/tsconfig.json` (based on the example type).
**3)** The files `/aio/tools/examples/shared/boilerplate/systemjs/src/systemjs.config.web[.build].js` contains the configuration for plunkers. They have some hardcoded versions that could be updated.
>N.B.: Plunkers have been replaced by Stackblitz and (almost) all examples have be replaced by CLI/WebPack-based examples that do not use SystemJS.
The upgrade examples may still rely on SystemJS.
---
> NOTE(gkalpak):
> There are some `package.json` files in `/aio/tools/examples/shared/boilerplate/*`.
> AFAICT, they are copied over to the examples (based on the example type), but they are neither
> used for installing dependencies (which come from `/aio/tools/examples/shared/package.json`) nor
> used in zips (since they are overwritten by `/aio/tools/example-zipper/customizer`).
> For all stackblitz live-examples, `/aio/tools/examples/shared/boilerplate/cli/package.json` seems
> to be used.

View File

@ -23,33 +23,18 @@ to flag an example as something to stackblitz or zip. For example:
The zipper will use this information for creating new zips.
## Three kinds of examples
## Two kinds of examples
The majority of examples in AIO use `CLI`, with some additionally using `Webpack` and upgrade usiing `SystemJS`. This
tool is able to differentiate between them.
There are mainly two kinds of AIO docs examples: The ones based on the Angular CLI and the ones based on SystemJS.
The majority of the examples are CLI-based with only some of the `ngUpgrade` examples using SystemJS.
The boilerplate uses a `package.json` that contains packages and scripts to run any kind of example.
Using that `package.json` in the zips would confuse the users.
Some of the CLI-based examples require small tweaks to the default layout/configuration (for example, to add support for Angular elements, i18n, universal, etc.).
These example types have separate boilerplate directories with the files that are different from the default `cli` boilerplate.
Thanks to the `package.json` customizer, we can create a new `package.json` on the fly that would
only contain the packages and scripts needed to run that example.
There are appropriate `package.json` files for each type of example in the boilerplate directories.
If there is no special `package.json` file for an example type, the one from the `cli` boilerplate directory will be used instead.
The `exampleZipper.js` won't include any `System.js` related files for `CLI` or `Webpack` projects.
### The package.json customizer
Given a `type`, this tool will populate a `package.json` file customized for that type.
Here you find a:
* **base.json** - All the common scripts and packages
* **cli.json** - Extra scripts and packages for the CLI
* **universal.json** - Extra scripts and packages for universal
* **i18n.json** - Extra scripts and packages for i18n
* **systemjs.json** - All the System.js related packages but it also contains the remainder scripts
that are not in the other files.
The tool will also give some standard names to the scripts.
The `exampleZipper.js` won't include any `System.js` related files for CLI-based projects.
## The zipper.json

View File

@ -1,34 +0,0 @@
{
"scripts": [
{ "name": "lint" }
],
"dependencies": [
"@angular/animations",
"@angular/common",
"@angular/compiler",
"@angular/core",
"@angular/forms",
"@angular/platform-browser",
"@angular/platform-browser-dynamic",
"@angular/router",
"@angular/upgrade",
"angular-in-memory-web-api",
"rxjs",
"zone.js"
],
"devDependencies": [
"@angular/compiler-cli",
"@types/jasmine",
"@types/node",
"jasmine-core",
"karma",
"karma-chrome-launcher",
"karma-cli",
"karma-jasmine",
"karma-jasmine-html-reporter",
"lodash",
"protractor",
"tslint",
"typescript"
]
}

View File

@ -1,23 +0,0 @@
{
"scripts": [
{ "name": "ng", "command": "ng" },
{ "name": "build", "command": "ng build" },
{ "name": "start", "command": "ng serve" },
{ "name": "test", "command": "ng test" },
{ "name": "lint", "command": "ng lint" },
{ "name": "e2e", "command": "ng e2e" }
],
"dependencies": [
"angular",
"angular-route"
],
"devDependencies": [
"@angular/cli",
"@types/angular",
"@types/angular-route",
"@types/jasminewd2",
"jasmine-spec-reporter",
"karma-coverage-istanbul-reporter",
"ts-node"
]
}

View File

@ -1,19 +0,0 @@
{
"scripts": [
{ "name": "ng", "command": "ng" },
{ "name": "build", "command": "ng build" },
{ "name": "start", "command": "ng serve" },
{ "name": "test", "command": "ng test" },
{ "name": "lint", "command": "ng lint" },
{ "name": "e2e", "command": "ng e2e" }
],
"dependencies": [],
"devDependencies": [
"@angular-devkit/build-angular",
"@angular/cli",
"@types/jasminewd2",
"jasmine-spec-reporter",
"karma-coverage-istanbul-reporter",
"ts-node"
]
}

View File

@ -1,22 +0,0 @@
{
"scripts": [
{ "name": "ng", "command": "ng" },
{ "name": "build", "command": "ng build" },
{ "name": "start", "command": "ng serve" },
{ "name": "test", "command": "ng test" },
{ "name": "lint", "command": "ng lint" },
{ "name": "e2e", "command": "ng e2e" }
],
"dependencies": [
"@angular/elements",
"@webcomponents/custom-elements"
],
"devDependencies": [
"@angular-devkit/build-angular",
"@angular/cli",
"@types/jasminewd2",
"jasmine-spec-reporter",
"karma-coverage-istanbul-reporter",
"ts-node"
]
}

View File

@ -1,19 +0,0 @@
{
"scripts": [
{ "name": "ng", "command": "ng" },
{ "name": "build", "command": "ng build" },
{ "name": "start", "command": "ng serve" },
{ "name": "test", "command": "ng test" },
{ "name": "lint", "command": "ng lint" },
{ "name": "e2e", "command": "ng e2e" }
],
"dependencies": [],
"devDependencies": [
"@angular-devkit/build-angular",
"@angular/cli",
"@types/jasminewd2",
"jasmine-spec-reporter",
"karma-coverage-istanbul-reporter",
"ts-node"
]
}

View File

@ -1,21 +0,0 @@
{
"scripts": [
{ "name": "start", "command": "ng serve" },
{ "name": "start:fr", "command": "ng serve --configuration=fr" },
{ "name": "build", "command": "ng build" },
{ "name": "build:fr", "command": "ng build --configuration=production-fr" },
{ "name": "test", "command": "ng test" },
{ "name": "lint", "command": "ng lint" },
{ "name": "e2e", "command": "ng e2e" },
{ "name": "extract", "command": "ng xi18n --output-path=locale" }
],
"dependencies": [],
"devDependencies": [
"@angular-devkit/build-angular",
"@angular/cli",
"@types/jasminewd2",
"jasmine-spec-reporter",
"karma-coverage-istanbul-reporter",
"ts-node"
]
}

View File

@ -1,19 +0,0 @@
{
"name": "angular-io-example",
"version": "1.0.0",
"private": true,
"description": "Example project from an angular.io guide.",
"scripts": {
},
"keywords": [],
"author": "",
"license": "MIT",
"dependencies": {
},
"devDependencies": {
},
"repository": {}
}

View File

@ -1,63 +0,0 @@
'use strict';
const path = require('canonical-path');
const fs = require('fs');
const examplesPath = path.resolve(__dirname, '../../../examples');
const packageFolder = path.resolve(__dirname);
class PackageJsonCustomizer {
constructor() {
this.dependenciesPackageJson = this.readJson(path.join(examplesPath, '/shared/package.json'));
this.scriptsPackageJson = this.readJson(path.join(examplesPath, '/shared/boilerplate/systemjs/package.json'));
this.basePackageJson = this.readJson(`${packageFolder}/base.json`);
this.templatePackageJson = this.readJson(`${packageFolder}/package.json`, false);
}
generate(type = 'systemjs') {
let packageJson = JSON.parse(this.templatePackageJson);
let rules = require(`${packageFolder}/${type}.json`);
this._mergeJSON(rules, this.basePackageJson);
rules.scripts.forEach((r) => {
const scriptName = r.name;
const script = this.scriptsPackageJson.scripts[scriptName];
const finalName = r.rename ? r.rename : r.name;
const finalScript = r.command ? r.command : script;
packageJson.scripts[finalName] = finalScript;
});
rules.dependencies.forEach((name) => {
const version = this.dependenciesPackageJson.dependencies[name];
packageJson.dependencies[name] = version;
});
rules.devDependencies.forEach((name) => {
const version = this.dependenciesPackageJson.devDependencies[name];
packageJson.devDependencies[name] = version;
});
return JSON.stringify(packageJson, null, 2);
}
_mergeJSON(json1, json2) {
var result = json1;
for (var prop in json2)
{
if (json2.hasOwnProperty(prop))
{
result[prop] = (result[prop].concat(json2[prop])).sort();
}
}
return result;
}
readJson(jsonFile, parse = true) {
const contents = fs.readFileSync(jsonFile, 'utf8');
return parse ? JSON.parse(contents) : contents;
}
}
module.exports = PackageJsonCustomizer;

View File

@ -1,24 +0,0 @@
{
"scripts": [
{ "name": "ng", "command": "ng" },
{ "name": "build", "command": "ng build" },
{ "name": "build:lib", "command": "ng build my-lib" },
{ "name": "start", "command": "ng serve" },
{ "name": "test", "command": "ng test" },
{ "name": "lint", "command": "ng lint" },
{ "name": "e2e", "command": "ng e2e" }
],
"dependencies": [],
"devDependencies": [
"@angular-devkit/build-angular",
"@angular-devkit/build-ng-packagr",
"@angular/cli",
"@types/jasminewd2",
"jasmine-spec-reporter",
"karma-coverage-istanbul-reporter",
"ng-packagr",
"tsickle",
"tslib",
"ts-node"
]
}

View File

@ -1,60 +0,0 @@
{
"scripts": [
{ "name": "build" },
{ "name": "build:watch" },
{ "name": "serve" },
{ "name": "prestart" },
{ "name": "start" },
{ "name": "pretest" },
{ "name": "test" },
{ "name": "pretest:once" },
{ "name": "test:once" },
{ "name": "build:upgrade" },
{ "name": "serve:upgrade" },
{ "name": "build:aot" },
{ "name": "serve:aot" },
{ "name": "copy-dist-files" }
],
"dependencies": [
"@angular/animations",
"@angular/common",
"@angular/compiler",
"@angular/core",
"@angular/forms",
"@angular/platform-browser",
"@angular/platform-browser-dynamic",
"@angular/router",
"@angular/upgrade",
"core-js",
"rxjs",
"systemjs",
"tslib",
"zone.js"
],
"devDependencies": [
"@angular/compiler-cli",
"@types/angular",
"@types/angular-animate",
"@types/angular-mocks",
"@types/angular-resource",
"@types/angular-route",
"@types/jasmine",
"@types/jasminewd2",
"@types/node",
"concurrently",
"http-server",
"jasmine-core",
"karma",
"karma-chrome-launcher",
"karma-jasmine",
"karma-jasmine-html-reporter",
"lite-server",
"protractor",
"rollup",
"rollup-plugin-commonjs",
"rollup-plugin-node-resolve",
"rollup-plugin-uglify",
"tslint",
"typescript"
]
}

View File

@ -1,20 +0,0 @@
{
"scripts": [
{ "name": "ng", "command": "ng" },
{ "name": "build", "command": "ng build" },
{ "name": "start", "command": "ng serve" },
{ "name": "test", "command": "ng test" },
{ "name": "lint", "command": "ng lint" },
{ "name": "e2e", "command": "ng e2e" }
],
"dependencies": [],
"devDependencies": [
"@angular-devkit/build-angular",
"@angular/cli",
"@types/jasminewd2",
"jasmine-marbles",
"jasmine-spec-reporter",
"karma-coverage-istanbul-reporter",
"ts-node"
]
}

View File

@ -1,29 +0,0 @@
{
"scripts": [
{ "name": "ng", "command": "ng" },
{ "name": "build", "command": "ng build" },
{ "name": "start", "command": "ng serve" },
{ "name": "test", "command": "ng test" },
{ "name": "lint", "command": "ng lint" },
{ "name": "e2e", "command": "ng e2e" },
{ "name": "dev:ssr", "command": "ng run angular.io-example:serve-ssr" },
{ "name": "build:ssr", "command": "ng build --prod && ng run angular.io-example:server:production" },
{ "name": "serve:ssr", "command": "node dist/server/main.js" },
{ "name": "prerender", "command": "ng run angular.io-example:prerender" }
],
"dependencies": [
"@angular/platform-server",
"@nguniversal/express-engine",
"express"
],
"devDependencies": [
"@angular-devkit/build-angular",
"@angular/cli",
"@nguniversal/builders",
"@types/express",
"@types/jasminewd2",
"jasmine-spec-reporter",
"karma-coverage-istanbul-reporter",
"ts-node"
]
}

View File

@ -6,7 +6,6 @@ const archiver = require('archiver');
const fs = require('fs-extra');
const globby = require('globby');
const PackageJsonCustomizer = require('./customizer/package-json/packageJsonCustomizer');
const regionExtractor = require('../transforms/examples-package/services/region-parser');
const EXAMPLE_CONFIG_NAME = 'example-config.json';
@ -17,7 +16,6 @@ class ExampleZipper {
this.examplesSystemjsConfig = path.join(__dirname, '../examples/shared/boilerplate/systemjs/src/systemjs.config.js');
this.examplesSystemjsLoaderConfig = path.join(__dirname, '../examples/shared/boilerplate/systemjs/src/systemjs-angular-loader.js');
this.exampleTsconfig = path.join(__dirname, '../examples/shared/boilerplate/systemjs/src/tsconfig.json');
this.customizer = new PackageJsonCustomizer();
let gpathStackblitz = path.join(sourceDirName, '**/*stackblitz.json');
let gpathZipper = path.join(sourceDirName, '**/zipper.json');
@ -91,6 +89,7 @@ class ExampleZipper {
'bs-config.json',
'karma.conf.js',
'karma-test-shim.js',
'package.json',
'tsconfig.*',
'tslint.*',
'e2e/protractor.conf.js',
@ -98,11 +97,8 @@ class ExampleZipper {
'src/favicon.ico',
'src/polyfills.ts',
'src/test.ts',
'src/typings.d.ts',
'src/environments/**/*',
'src/testing/**/*',
// Only ignore root package.json
'!package.json'
];
var alwaysExcludes = [
'!**/bs-config.e2e.json',
@ -168,8 +164,6 @@ class ExampleZipper {
zip.append(output, { name: relativePath } );
});
// we need the package.json from _examples root, not the _boilerplate one
zip.append(this.customizer.generate(exampleType), { name: 'package.json' });
// also a systemjs config
if (exampleType === 'systemjs') {
zip.append(fs.readFileSync(this.examplesSystemjsConfig, 'utf8'), { name: 'src/systemjs.config.js' });

View File

@ -6,65 +6,13 @@ const yargs = require('yargs');
const SHARED_PATH = path.resolve(__dirname, 'shared');
const SHARED_NODE_MODULES_PATH = path.resolve(SHARED_PATH, 'node_modules');
const BOILERPLATE_BASE_PATH = path.resolve(SHARED_PATH, 'boilerplate');
const BOILERPLATE_COMMON_BASE_PATH = path.resolve(BOILERPLATE_BASE_PATH, 'common');
const BOILERPLATE_CLI_PATH = path.resolve(BOILERPLATE_BASE_PATH, 'cli');
const BOILERPLATE_COMMON_PATH = path.resolve(BOILERPLATE_BASE_PATH, 'common');
const BOILERPLATE_VIEWENGINE_PATH = path.resolve(BOILERPLATE_BASE_PATH, 'viewengine');
const EXAMPLES_BASE_PATH = path.resolve(__dirname, '../../content/examples');
const BOILERPLATE_PATHS = {
cli: [
'src/environments/environment.prod.ts', 'src/environments/environment.ts',
'src/assets/.gitkeep', 'browserslist', 'src/favicon.ico', 'karma.conf.js',
'src/polyfills.ts', 'src/test.ts', 'tsconfig.app.json', 'tsconfig.spec.json',
'tslint.json', 'e2e/src/app.po.ts', 'e2e/protractor-puppeteer.conf.js',
'e2e/protractor.conf.js', 'e2e/tsconfig.json', '.editorconfig', 'angular.json', 'package.json',
'tsconfig.json', 'tslint.json'
],
systemjs: [
'src/systemjs-angular-loader.js', 'src/systemjs.config.js', 'src/tsconfig.json',
'bs-config.json', 'bs-config.e2e.json', 'package.json', 'tslint.json'
],
common: ['src/styles.css']
};
// All paths in this tool are relative to the current boilerplate folder, i.e boilerplate/i18n
// This maps the CLI files that exists in a parent folder
const cliRelativePath = BOILERPLATE_PATHS.cli.map(file => `../cli/${file}`);
BOILERPLATE_PATHS.elements = [...cliRelativePath, 'package.json', 'src/polyfills.ts'];
BOILERPLATE_PATHS.i18n = [...cliRelativePath, 'angular.json', 'package.json'];
BOILERPLATE_PATHS['service-worker'] = [...cliRelativePath, 'angular.json', 'package.json'];
BOILERPLATE_PATHS.testing = [
...cliRelativePath,
'angular.json',
'tsconfig.app.json',
'tsconfig.spec.json'
];
BOILERPLATE_PATHS.universal = [...cliRelativePath, 'angular.json', 'package.json'];
BOILERPLATE_PATHS['getting-started'] = [
...cliRelativePath,
'src/styles.css'
];
BOILERPLATE_PATHS.schematics = [
...cliRelativePath,
'angular.json'
];
BOILERPLATE_PATHS['cli-ajs'] = [
...cliRelativePath,
'package.json'
];
BOILERPLATE_PATHS.viewengine = {
systemjs: ['rollup-config.js', 'tsconfig-aot.json'],
cli: ['tsconfig.json']
};
const EXAMPLE_CONFIG_FILENAME = 'example-config.json';
class ExampleBoilerPlate {
@ -96,24 +44,26 @@ class ExampleBoilerPlate {
const boilerPlateType = exampleConfig.projectType || 'cli';
const boilerPlateBasePath = path.resolve(BOILERPLATE_BASE_PATH, boilerPlateType);
// Copy the boilerplate specific files
BOILERPLATE_PATHS[boilerPlateType].forEach(
filePath => this.copyFile(boilerPlateBasePath, exampleFolder, filePath));
// All example types other than `cli` and `systemjs` are based on `cli`. Copy over the `cli`
// boilerplate files first.
// (Some of these files might be later overwritten by type-specific files.)
if (boilerPlateType !== 'cli' && boilerPlateType !== 'systemjs') {
this.copyDirectoryContents(BOILERPLATE_CLI_PATH, exampleFolder);
}
// Copy the boilerplate common files
const useCommonBoilerplate = exampleConfig.useCommonBoilerplate !== false;
// Copy the type-specific boilerplate files.
this.copyDirectoryContents(boilerPlateBasePath, exampleFolder);
if (useCommonBoilerplate) {
BOILERPLATE_PATHS.common.forEach(filePath => this.copyFile(BOILERPLATE_COMMON_BASE_PATH, exampleFolder, filePath));
// Copy the common boilerplate files (unless explicitly not used).
if (exampleConfig.useCommonBoilerplate !== false) {
this.copyDirectoryContents(BOILERPLATE_COMMON_PATH, exampleFolder);
}
// Copy ViewEngine (pre-Ivy) specific files
if (viewengine) {
const veBoilerPlateType = boilerPlateType === 'systemjs' ? 'systemjs' : 'cli';
const veBoilerPlateBasePath =
path.resolve(BOILERPLATE_BASE_PATH, 'viewengine', veBoilerPlateType);
BOILERPLATE_PATHS.viewengine[veBoilerPlateType].forEach(
filePath => this.copyFile(veBoilerPlateBasePath, exampleFolder, filePath));
const veBoilerPlateBasePath = path.resolve(BOILERPLATE_VIEWENGINE_PATH, veBoilerPlateType);
this.copyDirectoryContents(veBoilerPlateBasePath, exampleFolder);
}
});
}
@ -137,22 +87,28 @@ class ExampleBoilerPlate {
return glob.sync(pattern, {ignore: [ignorePattern]}).map(file => path.dirname(file));
}
copyFile(sourceFolder, destinationFolder, filePath) {
const sourcePath = path.resolve(sourceFolder, filePath);
// normalize path if needed
filePath = this.normalizePath(filePath);
const destinationPath = path.resolve(destinationFolder, filePath);
fs.copySync(sourcePath, destinationPath, {overwrite: true});
fs.chmodSync(destinationPath, 444);
}
loadJsonFile(filePath) { return fs.readJsonSync(filePath, {throws: false}) || {}; }
normalizePath(filePath) {
// transform for example ../cli/src/tsconfig.app.json to src/tsconfig.app.json
return filePath.replace(/\.{2}\/\w+\//, '');
copyDirectoryContents(srcDir, dstDir) {
shelljs.ls('-Al', srcDir).forEach(stat => {
const srcPath = path.resolve(srcDir, stat.name);
const dstPath = path.resolve(dstDir, stat.name);
if (stat.isDirectory()) {
// `srcPath` is a directory: Recursively copy it to `dstDir`.
shelljs.mkdir('-p', dstPath);
return this.copyDirectoryContents(srcPath, dstPath);
} else {
// `srcPath` is a file: Copy it to `dstDir`.
// (Also make the file non-writable to avoid accidental editing of boilerplate files).
if (shelljs.test('-f', dstPath)) {
// If the file already exists, ensure it is writable (so it can be overwritten).
shelljs.chmod(666, dstPath);
}
shelljs.cp(srcPath, dstDir);
shelljs.chmod(444, dstPath);
}
});
}
}

View File

@ -9,24 +9,13 @@ describe('example-boilerplate tool', () => {
describe('add', () => {
const sharedDir = path.resolve(__dirname, 'shared');
const sharedNodeModulesDir = path.resolve(sharedDir, 'node_modules');
const BPFiles = {
cli: 20,
i18n: 2,
universal: 2,
systemjs: 7,
common: 1,
viewengine: {
cli: 1,
systemjs: 2,
},
};
const exampleFolders = ['a/b', 'c/d'];
beforeEach(() => {
spyOn(fs, 'ensureSymlinkSync');
spyOn(fs, 'existsSync').and.returnValue(true);
spyOn(shelljs, 'exec');
spyOn(exampleBoilerPlate, 'copyFile');
spyOn(exampleBoilerPlate, 'copyDirectoryContents');
spyOn(exampleBoilerPlate, 'getFoldersContaining').and.returnValue(exampleFolders);
spyOn(exampleBoilerPlate, 'loadJsonFile').and.returnValue({});
});
@ -61,58 +50,81 @@ describe('example-boilerplate tool', () => {
it('should copy all the source boilerplate files for systemjs', () => {
const boilerplateDir = path.resolve(sharedDir, 'boilerplate');
exampleBoilerPlate.loadJsonFile.and.callFake(filePath => filePath.indexOf('a/b') !== -1 ? { projectType: 'systemjs' } : {});
exampleBoilerPlate.loadJsonFile.and.returnValue({ projectType: 'systemjs' });
exampleBoilerPlate.add();
expect(exampleBoilerPlate.copyFile).toHaveBeenCalledTimes(
(BPFiles.cli) +
(BPFiles.systemjs) +
(BPFiles.common * exampleFolders.length)
);
// for example
expect(exampleBoilerPlate.copyFile).toHaveBeenCalledWith(`${boilerplateDir}/systemjs`, 'a/b', 'package.json');
expect(exampleBoilerPlate.copyFile).toHaveBeenCalledWith(`${boilerplateDir}/common`, 'a/b', 'src/styles.css');
expect(exampleBoilerPlate.copyDirectoryContents).toHaveBeenCalledTimes(4);
expect(exampleBoilerPlate.copyDirectoryContents.calls.allArgs()).toEqual([
[`${boilerplateDir}/systemjs`, 'a/b'],
[`${boilerplateDir}/common`, 'a/b'],
[`${boilerplateDir}/systemjs`, 'c/d'],
[`${boilerplateDir}/common`, 'c/d'],
]);
});
it('should copy all the source boilerplate files for cli', () => {
const boilerplateDir = path.resolve(sharedDir, 'boilerplate');
exampleBoilerPlate.loadJsonFile.and.returnValue({ projectType: 'cli' });
exampleBoilerPlate.add();
expect(exampleBoilerPlate.copyFile).toHaveBeenCalledTimes(
(BPFiles.cli * exampleFolders.length) +
(BPFiles.common * exampleFolders.length)
);
// for example
expect(exampleBoilerPlate.copyFile).toHaveBeenCalledWith(`${boilerplateDir}/cli`, 'a/b', 'package.json');
expect(exampleBoilerPlate.copyFile).toHaveBeenCalledWith(`${boilerplateDir}/common`, 'c/d', 'src/styles.css');
expect(exampleBoilerPlate.copyDirectoryContents).toHaveBeenCalledTimes(4);
expect(exampleBoilerPlate.copyDirectoryContents.calls.allArgs()).toEqual([
[`${boilerplateDir}/cli`, 'a/b'],
[`${boilerplateDir}/common`, 'a/b'],
[`${boilerplateDir}/cli`, 'c/d'],
[`${boilerplateDir}/common`, 'c/d'],
]);
});
it('should copy all the source boilerplate files for i18n', () => {
it('should default to `cli` if `projectType` is not specified', () => {
const boilerplateDir = path.resolve(sharedDir, 'boilerplate');
exampleBoilerPlate.loadJsonFile.and.callFake(filePath => filePath.indexOf('a/b') !== -1 ? { projectType: 'i18n' } : {})
exampleBoilerPlate.loadJsonFile.and.returnValue({});
exampleBoilerPlate.add();
expect(exampleBoilerPlate.copyFile).toHaveBeenCalledTimes(
(BPFiles.cli + BPFiles.i18n) +
(BPFiles.cli) +
(BPFiles.common * exampleFolders.length)
);
// for example
expect(exampleBoilerPlate.copyFile).toHaveBeenCalledWith(`${boilerplateDir}/i18n`, 'a/b', '../cli/angular.json');
expect(exampleBoilerPlate.copyFile).toHaveBeenCalledWith(`${boilerplateDir}/i18n`, 'a/b', 'package.json');
expect(exampleBoilerPlate.copyFile).toHaveBeenCalledWith(`${boilerplateDir}/common`, 'c/d', 'src/styles.css');
expect(exampleBoilerPlate.copyDirectoryContents).toHaveBeenCalledTimes(4);
expect(exampleBoilerPlate.copyDirectoryContents.calls.allArgs()).toEqual([
[`${boilerplateDir}/cli`, 'a/b'],
[`${boilerplateDir}/common`, 'a/b'],
[`${boilerplateDir}/cli`, 'c/d'],
[`${boilerplateDir}/common`, 'c/d'],
]);
});
it('should copy all the source boilerplate files for universal', () => {
it('should copy all the source boilerplate files for i18n (on top of the cli ones)', () => {
const boilerplateDir = path.resolve(sharedDir, 'boilerplate');
exampleBoilerPlate.loadJsonFile.and.callFake(filePath => filePath.indexOf('a/b') !== -1 ? { projectType: 'universal' } : {})
exampleBoilerPlate.loadJsonFile.and.returnValue({ projectType: 'i18n' });
exampleBoilerPlate.add();
expect(exampleBoilerPlate.copyFile).toHaveBeenCalledTimes(
(BPFiles.cli + BPFiles.universal) +
(BPFiles.cli) +
(BPFiles.common * exampleFolders.length)
);
// for example
expect(exampleBoilerPlate.copyFile).toHaveBeenCalledWith(`${boilerplateDir}/universal`, 'a/b', '../cli/tslint.json');
expect(exampleBoilerPlate.copyFile).toHaveBeenCalledWith(`${boilerplateDir}/universal`, 'a/b', 'angular.json');
expect(exampleBoilerPlate.copyFile).toHaveBeenCalledWith(`${boilerplateDir}/common`, 'c/d', 'src/styles.css');
expect(exampleBoilerPlate.copyDirectoryContents).toHaveBeenCalledTimes(6);
expect(exampleBoilerPlate.copyDirectoryContents.calls.allArgs()).toEqual([
[`${boilerplateDir}/cli`, 'a/b'],
[`${boilerplateDir}/i18n`, 'a/b'],
[`${boilerplateDir}/common`, 'a/b'],
[`${boilerplateDir}/cli`, 'c/d'],
[`${boilerplateDir}/i18n`, 'c/d'],
[`${boilerplateDir}/common`, 'c/d'],
]);
});
it('should copy all the source boilerplate files for universal (on top of the cli ones)', () => {
const boilerplateDir = path.resolve(sharedDir, 'boilerplate');
exampleBoilerPlate.loadJsonFile.and.returnValue({ projectType: 'universal' });
exampleBoilerPlate.add();
expect(exampleBoilerPlate.copyDirectoryContents).toHaveBeenCalledTimes(6);
expect(exampleBoilerPlate.copyDirectoryContents.calls.allArgs()).toEqual([
[`${boilerplateDir}/cli`, 'a/b'],
[`${boilerplateDir}/universal`, 'a/b'],
[`${boilerplateDir}/common`, 'a/b'],
[`${boilerplateDir}/cli`, 'c/d'],
[`${boilerplateDir}/universal`, 'c/d'],
[`${boilerplateDir}/common`, 'c/d'],
]);
});
it('should try to load the example config file', () => {
@ -130,27 +142,55 @@ describe('example-boilerplate tool', () => {
it('should copy all the source boilerplate files for systemjs', () => {
const boilerplateDir = path.resolve(sharedDir, 'boilerplate');
exampleBoilerPlate.loadJsonFile.and.callFake(filePath => filePath.indexOf('a/b') !== -1 ? { projectType: 'systemjs' } : {});
exampleBoilerPlate.loadJsonFile.and.returnValue({ projectType: 'systemjs' });
exampleBoilerPlate.add(true);
expect(exampleBoilerPlate.copyFile).toHaveBeenCalledTimes(
(BPFiles.cli + BPFiles.viewengine.cli) +
(BPFiles.systemjs + BPFiles.viewengine.systemjs) +
(BPFiles.common * exampleFolders.length)
);
// for example
expect(exampleBoilerPlate.copyFile).toHaveBeenCalledWith(`${boilerplateDir}/viewengine/systemjs`, 'a/b', 'tsconfig-aot.json');
expect(exampleBoilerPlate.copyDirectoryContents).toHaveBeenCalledTimes(6);
expect(exampleBoilerPlate.copyDirectoryContents.calls.allArgs()).toEqual([
[`${boilerplateDir}/systemjs`, 'a/b'],
[`${boilerplateDir}/common`, 'a/b'],
[`${boilerplateDir}/viewengine/systemjs`, 'a/b'],
[`${boilerplateDir}/systemjs`, 'c/d'],
[`${boilerplateDir}/common`, 'c/d'],
[`${boilerplateDir}/viewengine/systemjs`, 'c/d'],
]);
});
it('should copy all the source boilerplate files for cli', () => {
const boilerplateDir = path.resolve(sharedDir, 'boilerplate');
exampleBoilerPlate.loadJsonFile.and.returnValue({ projectType: 'cli' });
exampleBoilerPlate.add(true);
expect(exampleBoilerPlate.copyFile).toHaveBeenCalledTimes(
(BPFiles.cli * exampleFolders.length) +
(BPFiles.viewengine.cli * exampleFolders.length) +
(BPFiles.common * exampleFolders.length)
);
// for example
expect(exampleBoilerPlate.copyFile).toHaveBeenCalledWith(`${boilerplateDir}/viewengine/cli`, 'a/b', 'tsconfig.json');
expect(exampleBoilerPlate.copyDirectoryContents).toHaveBeenCalledTimes(6);
expect(exampleBoilerPlate.copyDirectoryContents.calls.allArgs()).toEqual([
[`${boilerplateDir}/cli`, 'a/b'],
[`${boilerplateDir}/common`, 'a/b'],
[`${boilerplateDir}/viewengine/cli`, 'a/b'],
[`${boilerplateDir}/cli`, 'c/d'],
[`${boilerplateDir}/common`, 'c/d'],
[`${boilerplateDir}/viewengine/cli`, 'c/d'],
]);
});
it('should copy all the source boilerplate files for elements', () => {
const boilerplateDir = path.resolve(sharedDir, 'boilerplate');
exampleBoilerPlate.loadJsonFile.and.returnValue({ projectType: 'elements' });
exampleBoilerPlate.add(true);
expect(exampleBoilerPlate.copyDirectoryContents).toHaveBeenCalledTimes(8);
expect(exampleBoilerPlate.copyDirectoryContents.calls.allArgs()).toEqual([
[`${boilerplateDir}/cli`, 'a/b'],
[`${boilerplateDir}/elements`, 'a/b'],
[`${boilerplateDir}/common`, 'a/b'],
[`${boilerplateDir}/viewengine/cli`, 'a/b'],
[`${boilerplateDir}/cli`, 'c/d'],
[`${boilerplateDir}/elements`, 'c/d'],
[`${boilerplateDir}/common`, 'c/d'],
[`${boilerplateDir}/viewengine/cli`, 'c/d'],
]);
});
});
});
@ -172,16 +212,110 @@ describe('example-boilerplate tool', () => {
});
});
describe('copyFile', () => {
it('should use copySync and chmodSync', () => {
spyOn(fs, 'copySync');
spyOn(fs, 'chmodSync');
exampleBoilerPlate.copyFile('source/folder', 'destination/folder', 'some/file/path');
expect(fs.copySync).toHaveBeenCalledWith(
path.resolve('source/folder/some/file/path'),
path.resolve('destination/folder/some/file/path'),
{ overwrite: true });
expect(fs.chmodSync).toHaveBeenCalledWith(path.resolve('destination/folder/some/file/path'), 444);
describe('copyDirectoryContents', () => {
const spyFnFor = fnName => (...args) => { callLog.push(`${fnName}(${args.join(', ')})`); };
let callLog;
beforeEach(() => {
callLog = [];
spyOn(shelljs, 'chmod').and.callFake(spyFnFor('chmod'));
spyOn(shelljs, 'cp').and.callFake(spyFnFor('cp'));
spyOn(shelljs, 'mkdir').and.callFake(spyFnFor('mkdir'));
spyOn(shelljs, 'test').and.callFake(spyFnFor('test'));
});
it('should list all contents of a directory', () => {
const lsSpy = spyOn(shelljs, 'ls').and.returnValue([]);
exampleBoilerPlate.copyDirectoryContents('source/dir', 'destination/dir');
expect(lsSpy).toHaveBeenCalledWith('-Al', 'source/dir');
});
it('should use copy files and make them read-only', () => {
spyOn(shelljs, 'ls').and.returnValue([
{name: 'file-1.txt', isDirectory: () => false},
{name: 'file-2.txt', isDirectory: () => false},
]);
exampleBoilerPlate.copyDirectoryContents('source/dir', 'destination/dir');
expect(callLog).toEqual([
`test(-f, ${path.resolve('destination/dir/file-1.txt')})`,
`cp(${path.resolve('source/dir/file-1.txt')}, destination/dir)`,
`chmod(444, ${path.resolve('destination/dir/file-1.txt')})`,
`test(-f, ${path.resolve('destination/dir/file-2.txt')})`,
`cp(${path.resolve('source/dir/file-2.txt')}, destination/dir)`,
`chmod(444, ${path.resolve('destination/dir/file-2.txt')})`,
]);
});
it('should make existing files in destination writable before overwriting', () => {
spyOn(shelljs, 'ls').and.returnValue([
{name: 'new-file.txt', isDirectory: () => false},
{name: 'existing-file.txt', isDirectory: () => false},
]);
shelljs.test.and.callFake((_, filePath) => filePath.endsWith('existing-file.txt'));
exampleBoilerPlate.copyDirectoryContents('source/dir', 'destination/dir');
expect(callLog).toEqual([
`cp(${path.resolve('source/dir/new-file.txt')}, destination/dir)`,
`chmod(444, ${path.resolve('destination/dir/new-file.txt')})`,
`chmod(666, ${path.resolve('destination/dir/existing-file.txt')})`,
`cp(${path.resolve('source/dir/existing-file.txt')}, destination/dir)`,
`chmod(444, ${path.resolve('destination/dir/existing-file.txt')})`,
]);
});
it('should recursively copy sub-directories', () => {
spyOn(shelljs, 'ls')
.withArgs('-Al', 'source/dir').and.returnValue([
{name: 'file-1.txt', isDirectory: () => false},
{name: 'sub-dir-1', isDirectory: () => true},
{name: 'file-2.txt', isDirectory: () => false},
])
.withArgs('-Al', path.resolve('source/dir/sub-dir-1')).and.returnValue([
{name: 'file-3.txt', isDirectory: () => false},
{name: 'sub-dir-2', isDirectory: () => true},
])
.withArgs('-Al', path.resolve('source/dir/sub-dir-1/sub-dir-2')).and.returnValue([
{name: 'file-4.txt', isDirectory: () => false},
]);
exampleBoilerPlate.copyDirectoryContents('source/dir', 'destination/dir');
expect(callLog).toEqual([
// Copy `file-1.txt`.
`test(-f, ${path.resolve('destination/dir/file-1.txt')})`,
`cp(${path.resolve('source/dir/file-1.txt')}, destination/dir)`,
`chmod(444, ${path.resolve('destination/dir/file-1.txt')})`,
// Create `sub-dir-1` and recursively copy its contents.
`mkdir(-p, ${path.resolve('destination/dir/sub-dir-1')})`,
// Copy `sub-dir-1/file-3.txt`.
`test(-f, ${path.resolve('destination/dir/sub-dir-1/file-3.txt')})`,
'cp(' +
`${path.resolve('source/dir/sub-dir-1/file-3.txt')}, ` +
`${path.resolve('destination/dir/sub-dir-1')})`,
`chmod(444, ${path.resolve('destination/dir/sub-dir-1/file-3.txt')})`,
// Create `sub-dir-1/sub-dir-2` and recursively copy its contents.
`mkdir(-p, ${path.resolve('destination/dir/sub-dir-1/sub-dir-2')})`,
// Copy `sub-dir-1/sub-dir-2/file-4.txt`.
`test(-f, ${path.resolve('destination/dir/sub-dir-1/sub-dir-2/file-4.txt')})`,
'cp(' +
`${path.resolve('source/dir/sub-dir-1/sub-dir-2/file-4.txt')}, ` +
`${path.resolve('destination/dir/sub-dir-1/sub-dir-2')})`,
`chmod(444, ${path.resolve('destination/dir/sub-dir-1/sub-dir-2/file-4.txt')})`,
// Copy `file-2.txt`.
`test(-f, ${path.resolve('destination/dir/file-2.txt')})`,
`cp(${path.resolve('source/dir/file-2.txt')}, destination/dir)`,
`chmod(444, ${path.resolve('destination/dir/file-2.txt')})`,
]);
});
});

View File

@ -1,6 +1,7 @@
{
"name": "angular.io-example",
"version": "0.0.0",
"description": "Example project from an angular.io guide.",
"license": "MIT",
"scripts": {
"ng": "ng",

View File

@ -1,6 +1,7 @@
{
"name": "angular.io-example",
"version": "0.0.0",
"description": "Example project from an angular.io guide.",
"license": "MIT",
"scripts": {
"ng": "ng",

View File

@ -1,5 +0,0 @@
/* SystemJS module definition */
declare var module: NodeModule;
interface NodeModule {
id: string;
}

View File

@ -1,6 +1,7 @@
{
"name": "angular.io-example",
"version": "0.0.0",
"description": "Example project from an angular.io guide.",
"license": "MIT",
"scripts": {
"ng": "ng",

View File

@ -1,6 +1,7 @@
{
"name": "angular.io-example",
"version": "0.0.0",
"description": "Example project from an angular.io guide.",
"license": "MIT",
"scripts": {
"ng": "ng",

View File

@ -1,6 +1,7 @@
{
"name": "angular.io-example",
"version": "0.0.0",
"description": "Example project from an angular.io guide.",
"license": "MIT",
"scripts": {
"ng": "ng",

View File

@ -1,8 +1,8 @@
{
"name": "angular-examples",
"version": "1.0.0",
"private": true,
"description": "Example package.json, only contains needed scripts for examples. See _examples/package.json for master package.json.",
"name": "angular.io-example",
"version": "0.0.0",
"description": "Example project from an angular.io guide.",
"license": "MIT",
"scripts": {
"build": "tsc -p src/",
"build:watch": "tsc -p src/ -w",
@ -25,26 +25,25 @@
"serve:aot": "lite-server -c bs-config.aot.json",
"copy-dist-files": "node ./copy-dist-files.js"
},
"keywords": [],
"author": "",
"license": "MIT",
"private": true,
"dependencies": {
"@angular/animations": "~9.0.3",
"@angular/common": "~9.0.3",
"@angular/compiler": "~9.0.3",
"@angular/core": "~9.0.3",
"@angular/forms": "~9.0.3",
"@angular/platform-browser": "~9.0.3",
"@angular/platform-browser-dynamic": "~9.0.3",
"@angular/router": "~9.0.3",
"@angular/upgrade": "~9.0.3",
"@angular/animations": "~9.1.4",
"@angular/common": "~9.1.4",
"@angular/compiler": "~9.1.4",
"@angular/core": "~9.1.4",
"@angular/forms": "~9.1.4",
"@angular/platform-browser": "~9.1.4",
"@angular/platform-browser-dynamic": "~9.1.4",
"@angular/router": "~9.1.4",
"@angular/upgrade": "~9.1.4",
"core-js": "^2.5.4",
"rxjs": "~6.5.4",
"tslib": "^1.10.0",
"zone.js": "~0.10.3"
},
"devDependencies": {
"@angular/compiler-cli": "~9.0.3",
"@angular/compiler-cli": "~9.1.4",
"@angular/language-service": "~9.1.4",
"@types/angular": "1.6.47",
"@types/angular-animate": "1.5.10",
"@types/angular-mocks": "1.6.0",
@ -68,6 +67,5 @@
"rollup-plugin-terser": "^5.3.0",
"tslint": "~5.18.0",
"typescript": "~3.7.5"
},
"repository": {}
}
}

View File

@ -1,101 +0,0 @@
/*
Copyright Google LLC. All Rights Reserved.
Use of this source code is governed by an MIT-style license that
can be found in the LICENSE file at http://angular.io/license
*/
/**
* WEB VERSION FOR CURRENT ANGULAR BUILD
* (based on systemjs.config.js in angular.io)
* System configuration for Angular samples
* Adjust as necessary for your application needs.
*
* UNTESTED !
*/
(function (global) {
System.config({
// DEMO ONLY! REAL CODE SHOULD NOT TRANSPILE IN THE BROWSER
transpiler: 'ts',
typescriptOptions: {
// Copy of compiler options in standard tsconfig.json
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": ["es2015", "dom"],
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true
},
meta: {
'typescript': {
"exports": "ts"
}
},
paths: {
// paths serve as alias
'npm:': 'https://unpkg.com/',
'ng:': 'https://cdn.rawgit.com/angular/'
},
// map tells the System loader where to look for things
map: {
// our app is within the app folder
'app': 'app',
// angular bundles
'@angular/animations': 'ng:animations-builds/master/bundles/animations.umd.js',
'@angular/animations/browser': 'ng:animations-builds/master/bundles/animations-browser.umd.js',
'@angular/core': 'ng:core-builds/master/bundles/core.umd.js',
'@angular/common': 'ng:common-builds/master/bundles/common.umd.js',
'@angular/common/http': 'ng:common-builds/master/bundles/common-http.umd.js',
'@angular/compiler': 'ng:compiler-builds/master/bundles/compiler.umd.js',
'@angular/platform-browser': 'ng:platform-browser-builds/master/bundles/platform-browser.umd.js',
'@angular/platform-browser/animations': 'ng:animations-builds/master/bundles/platform-browser-animations.umd.js',
'@angular/platform-browser-dynamic': 'ng:platform-browser-dynamic-builds/master/bundles/platform-browser-dynamic.umd.js',
'@angular/router': 'ng:router-builds/master/bundles/router.umd.js',
'@angular/router/upgrade': 'ng:router-builds/master/bundles/router-upgrade.umd.js',
'@angular/forms': 'ng:forms-builds/master/bundles/forms.umd.js',
'@angular/upgrade': 'ng:upgrade-builds/master/bundles/upgrade.umd.js',
'@angular/upgrade/static': 'ng:upgrade-builds/master/bundles/upgrade-static.umd.js',
// angular testing umd bundles (overwrite the shim mappings)
'@angular/core/testing': 'ng:core-builds/master/bundles/core-testing.umd.js',
'@angular/common/testing': 'ng:common-builds/master/bundles/common-testing.umd.js',
'@angular/common/http/testing': 'ng:common-builds/master/bundles/common-http-testing.umd.js',
'@angular/compiler/testing': 'ng:compiler-builds/master/bundles/compiler-testing.umd.js',
'@angular/platform-browser/testing': 'ng:platform-browser-builds/master/bundles/platform-browser-testing.umd.js',
'@angular/platform-browser-dynamic/testing': 'ng:platform-browser-dynamic-builds/master/bundles/platform-browser-dynamic-testing.umd.js',
'@angular/router/testing': 'ng:router-builds/master/bundles/router-testing.umd.js',
'@angular/forms/testing': 'ng:forms-builds/master/bundles/forms-testing.umd.js',
// other libraries
'rxjs': 'npm:rxjs@5.5.2',
'rxjs/operators': 'npm:rxjs@5.5.2/operators/index.js',
'tslib': 'npm:tslib/tslib.js',
'angular-in-memory-web-api': 'npm:angular-in-memory-web-api@0.4/bundles/in-memory-web-api.umd.js',
'ts': 'npm:plugin-typescript@5.2.7/lib/plugin.js',
'typescript': 'npm:typescript@2.4.2/lib/typescript.js',
},
// packages tells the System loader how to load when no filename and/or no extension
packages: {
app: {
main: './main.ts',
defaultExtension: 'ts',
meta: {
'./*.ts': {
loader: 'systemjs-angular-loader.js'
}
}
},
'rxjs/ajax': {main: 'index.js', defaultExtension: 'js' },
'rxjs/operators': {main: 'index.js', defaultExtension: 'js' },
'rxjs/testing': {main: 'index.js', defaultExtension: 'js' },
'rxjs/websocket': {main: 'index.js', defaultExtension: 'js' },
'rxjs': { main: 'index.js', defaultExtension: 'js' },
}
});
})(this);

View File

@ -1,87 +0,0 @@
/*
Copyright Google LLC. All Rights Reserved.
Use of this source code is governed by an MIT-style license that
can be found in the LICENSE file at http://angular.io/license
*/
/**
* WEB ANGULAR VERSION
* (based on systemjs.config.js in angular.io)
* System configuration for Angular samples
* Adjust as necessary for your application needs.
*/
(function (global) {
System.config({
// DEMO ONLY! REAL CODE SHOULD NOT TRANSPILE IN THE BROWSER
transpiler: 'ts',
typescriptOptions: {
// Copy of compiler options in standard tsconfig.json
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": ["es2015", "dom"],
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true
},
meta: {
'typescript': {
"exports": "ts"
}
},
paths: {
// paths serve as alias
'npm:': 'https://unpkg.com/'
},
// map tells the System loader where to look for things
map: {
// our app is within the app folder
'app': 'app',
// angular bundles
'@angular/animations': 'npm:@angular/animations/bundles/animations.umd.js',
'@angular/animations/browser': 'npm:@angular/animations/bundles/animations-browser.umd.js',
'@angular/core': 'npm:@angular/core/bundles/core.umd.js',
'@angular/common': 'npm:@angular/common/bundles/common.umd.js',
'@angular/common/http': 'npm:@angular/common/bundles/common-http.umd.js',
'@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
'@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
'@angular/platform-browser/animations': 'npm:@angular/platform-browser/bundles/platform-browser-animations.umd.js',
'@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
'@angular/router': 'npm:@angular/router/bundles/router.umd.js',
'@angular/router/upgrade': 'npm:@angular/router/bundles/router-upgrade.umd.js',
'@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
'@angular/upgrade': 'npm:@angular/upgrade/bundles/upgrade.umd.js',
'@angular/upgrade/static': 'npm:@angular/upgrade/bundles/upgrade-static.umd.js',
// other libraries
'rxjs': 'npm:rxjs@5.5.2',
'rxjs/operators': 'npm:rxjs@5.5.2/operators/index.js',
'tslib': 'npm:tslib/tslib.js',
'angular-in-memory-web-api': 'npm:angular-in-memory-web-api@0.4/bundles/in-memory-web-api.umd.js',
'ts': 'npm:plugin-typescript@5.2.7/lib/plugin.js',
'typescript': 'npm:typescript@2.4.2/lib/typescript.js',
},
// packages tells the System loader how to load when no filename and/or no extension
packages: {
app: {
main: './main.ts',
defaultExtension: 'ts',
meta: {
'./*.ts': {
loader: 'systemjs-angular-loader.js'
}
}
},
'rxjs/ajax': {main: 'index.js', defaultExtension: 'js' },
'rxjs/operators': {main: 'index.js', defaultExtension: 'js' },
'rxjs/testing': {main: 'index.js', defaultExtension: 'js' },
'rxjs/websocket': {main: 'index.js', defaultExtension: 'js' },
'rxjs': { main: 'index.js', defaultExtension: 'js' },
}
});
})(this);

View File

@ -1,6 +1,7 @@
{
"name": "angular.io-example",
"version": "0.0.0",
"description": "Example project from an angular.io guide.",
"license": "MIT",
"scripts": {
"ng": "ng",

View File

@ -1,4 +1,5 @@
load("@npm_bazel_typescript//:index.bzl", "ts_library")
load("//tools:defaults.bzl", "jasmine_node_test")
ts_library(
name = "pullapprove",
@ -8,6 +9,8 @@ ts_library(
"group.ts",
"logging.ts",
"parse-yaml.ts",
"pullapprove_arrays.ts",
"utils.ts",
"verify.ts",
],
module_name = "@angular/dev-infra-private/pullapprove",
@ -25,3 +28,24 @@ ts_library(
"@npm//yargs",
],
)
ts_library(
name = "pullapprove_test_lib",
testonly = True,
srcs = glob(
["*.spec.ts"],
),
visibility = ["//visibility:private"],
deps = [
":pullapprove",
"@npm//@types/jasmine",
"@npm//@types/node",
"@npm//typescript",
],
)
jasmine_node_test(
name = "pullapprove_test",
srcs = [":pullapprove_test_lib"],
visibility = ["//visibility:private"],
)

View File

@ -6,10 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/
import {IMinimatch, Minimatch} from 'minimatch';
/** Map that holds patterns and their corresponding Minimatch globs. */
const patternCache = new Map<string, IMinimatch>();
import {PullApproveGroup} from './group';
import {PullApproveGroupArray, PullApproveStringArray} from './pullapprove_arrays';
import {getOrCreateGlob} from './utils';
/**
* Context that is provided to conditions. Conditions can use various helpers
@ -18,30 +17,34 @@ const patternCache = new Map<string, IMinimatch>();
*/
const conditionContext = {
'len': (value: any[]) => value.length,
'contains_any_globs': (files: PullApproveArray, patterns: string[]) => {
'contains_any_globs': (files: PullApproveStringArray, patterns: string[]) => {
// Note: Do not always create globs for the same pattern again. This method
// could be called for each source file. Creating glob's is expensive.
return files.some(f => patterns.some(pattern => getOrCreateGlob(pattern).match(f)));
}
},
};
/**
* Converts a given condition to a function that accepts a set of files. The returned
* function can be called to check if the set of files matches the condition.
*/
export function convertConditionToFunction(expr: string): (files: string[]) => boolean {
// Creates a dynamic function with the specified expression. The first parameter will
// be `files` as that corresponds to the supported `files` variable that can be accessed
// in PullApprove condition expressions. The followed parameters correspond to other
// context variables provided by PullApprove for conditions.
const evaluateFn = new Function('files', ...Object.keys(conditionContext), `
export function convertConditionToFunction(expr: string): (
files: string[], groups: PullApproveGroup[]) => boolean {
// Creates a dynamic function with the specified expression.
// The first parameter will be `files` as that corresponds to the supported `files` variable that
// can be accessed in PullApprove condition expressions. The second parameter is the list of
// PullApproveGroups that are accessible in the condition expressions. The followed parameters
// correspond to other context variables provided by PullApprove for conditions.
const evaluateFn = new Function('files', 'groups', ...Object.keys(conditionContext), `
return (${transformExpressionToJs(expr)});
`);
// Create a function that calls the dynamically constructed function which mimics
// the condition expression that is usually evaluated with Python in PullApprove.
return files => {
const result = evaluateFn(new PullApproveArray(...files), ...Object.values(conditionContext));
return (files, groups) => {
const result = evaluateFn(
new PullApproveStringArray(...files), new PullApproveGroupArray(...groups),
...Object.values(conditionContext));
// If an array is returned, we consider the condition as active if the array is not
// empty. This matches PullApprove's condition evaluation that is based on Python.
if (Array.isArray(result)) {
@ -59,41 +62,3 @@ export function convertConditionToFunction(expr: string): (files: string[]) => b
function transformExpressionToJs(expression: string): string {
return expression.replace(/not\s+/g, '!');
}
/**
* Superset of a native array. The superset provides methods which mimic the
* list data structure used in PullApprove for files in conditions.
*/
class PullApproveArray extends Array<string> {
constructor(...elements: string[]) {
super(...elements);
// Set the prototype explicitly because in ES5, the prototype is accidentally
// lost due to a limitation in down-leveling.
// https://github.com/Microsoft/TypeScript/wiki/FAQ#why-doesnt-extending-built-ins-like-error-array-and-map-work.
Object.setPrototypeOf(this, PullApproveArray.prototype);
}
/** Returns a new array which only includes files that match the given pattern. */
include(pattern: string): PullApproveArray {
return new PullApproveArray(...this.filter(s => getOrCreateGlob(pattern).match(s)));
}
/** Returns a new array which only includes files that did not match the given pattern. */
exclude(pattern: string): PullApproveArray {
return new PullApproveArray(...this.filter(s => !getOrCreateGlob(pattern).match(s)));
}
}
/**
* Gets a glob for the given pattern. The cached glob will be returned
* if available. Otherwise a new glob will be created and cached.
*/
function getOrCreateGlob(pattern: string) {
if (patternCache.has(pattern)) {
return patternCache.get(pattern)!;
}
const glob = new Minimatch(pattern, {dot: true});
patternCache.set(pattern, glob);
return glob;
}

View File

@ -9,12 +9,14 @@
import {error} from '../utils/console';
import {convertConditionToFunction} from './condition_evaluator';
import {PullApproveGroupConfig} from './parse-yaml';
import {PullApproveGroupStateDependencyError} from './pullapprove_arrays';
/** A condition for a group. */
interface GroupCondition {
expression: string;
checkFn: (files: string[]) => boolean;
checkFn: (files: string[], groups: PullApproveGroup[]) => boolean;
matchedFiles: Set<string>;
unverifiable: boolean;
}
/** Result of testing files against the group. */
@ -24,6 +26,7 @@ export interface PullApproveGroupResult {
matchedCount: number;
unmatchedConditions: GroupCondition[];
unmatchedCount: number;
unverifiableConditions: GroupCondition[];
}
// Regular expression that matches conditions for the global approval.
@ -39,7 +42,9 @@ export class PullApproveGroup {
/** List of conditions for the group. */
conditions: GroupCondition[] = [];
constructor(public groupName: string, config: PullApproveGroupConfig) {
constructor(
public groupName: string, config: PullApproveGroupConfig,
readonly precedingGroups: PullApproveGroup[] = []) {
this._captureConditions(config);
}
@ -58,6 +63,7 @@ export class PullApproveGroup {
expression,
checkFn: convertConditionToFunction(expression),
matchedFiles: new Set(),
unverifiable: false,
});
} catch (e) {
error(`Could not parse condition in group: ${this.groupName}`);
@ -76,33 +82,47 @@ export class PullApproveGroup {
* the pull approve group's conditions.
*/
testFile(filePath: string): boolean {
return this.conditions.every(({matchedFiles, checkFn, expression}) => {
return this.conditions.every((condition) => {
const {matchedFiles, checkFn, expression} = condition;
try {
const matchesFile = checkFn([filePath]);
const matchesFile = checkFn([filePath], this.precedingGroups);
if (matchesFile) {
matchedFiles.add(filePath);
}
return matchesFile;
} catch (e) {
const errMessage = `Condition could not be evaluated: \n\n` +
`From the [${this.groupName}] group:\n` +
` - ${expression}` +
`\n\n${e.message} ${e.stack}\n\n`;
error(errMessage);
process.exit(1);
// In the case of a condition that depends on the state of groups we want to
// ignore that the verification can't accurately evaluate the condition and then
// continue processing. Other types of errors fail the verification, as conditions
// should otherwise be able to execute without throwing.
if (e instanceof PullApproveGroupStateDependencyError) {
condition.unverifiable = true;
// Return true so that `this.conditions.every` can continue evaluating.
return true;
} else {
const errMessage = `Condition could not be evaluated: \n\n` +
`From the [${this.groupName}] group:\n` +
` - ${expression}` +
`\n\n${e.message} ${e.stack}\n\n`;
error(errMessage);
process.exit(1);
}
}
});
}
/** Retrieve the results for the Group, all matched and unmatched conditions. */
getResults(): PullApproveGroupResult {
const matchedConditions = this.conditions.filter(c => !!c.matchedFiles.size);
const unmatchedConditions = this.conditions.filter(c => !c.matchedFiles.size);
const matchedConditions = this.conditions.filter(c => c.matchedFiles.size > 0);
const unmatchedConditions =
this.conditions.filter(c => c.matchedFiles.size === 0 && !c.unverifiable);
const unverifiableConditions = this.conditions.filter(c => c.unverifiable);
return {
matchedConditions,
matchedCount: matchedConditions.length,
unmatchedConditions,
unmatchedCount: unmatchedConditions.length,
unverifiableConditions,
groupName: this.groupName,
};
}

View File

@ -9,14 +9,23 @@
import {info} from '../utils/console';
import {PullApproveGroupResult} from './group';
type ConditionGrouping = keyof Pick<
PullApproveGroupResult, 'matchedConditions'|'unmatchedConditions'|'unverifiableConditions'>;
/** Create logs for each pullapprove group result. */
export function logGroup(group: PullApproveGroupResult, matched = true, printMessageFn = info) {
const conditions = matched ? group.matchedConditions : group.unmatchedConditions;
export function logGroup(
group: PullApproveGroupResult, conditionsToPrint: ConditionGrouping, printMessageFn = info) {
const conditions = group[conditionsToPrint];
printMessageFn.group(`[${group.groupName}]`);
if (conditions.length) {
conditions.forEach(matcher => {
const count = matcher.matchedFiles.size;
printMessageFn(`${count} ${count === 1 ? 'match' : 'matches'} - ${matcher.expression}`);
conditions.forEach(groupCondition => {
const count = groupCondition.matchedFiles.size;
if (conditionsToPrint === 'unverifiableConditions') {
printMessageFn(`${groupCondition.expression}`);
} else {
printMessageFn(
`${count} ${count === 1 ? 'match' : 'matches'} - ${groupCondition.expression}`);
}
});
printMessageFn.groupEnd();
}

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {parse as parseYaml} from 'yaml';
import {PullApproveGroup} from './group';
export interface PullApproveGroupConfig {
conditions?: string[];
@ -33,3 +34,12 @@ export interface PullApproveConfig {
export function parsePullApproveYaml(rawYaml: string): PullApproveConfig {
return parseYaml(rawYaml, {merge: true}) as PullApproveConfig;
}
/** Parses all of the groups defined in the pullapprove yaml. */
export function getGroupsFromYaml(pullApproveYamlRaw: string): PullApproveGroup[] {
/** JSON representation of the pullapprove yaml file. */
const pullApprove = parsePullApproveYaml(pullApproveYamlRaw);
return Object.entries(pullApprove.groups).reduce((groups, [groupName, group]) => {
return groups.concat(new PullApproveGroup(groupName, group, groups));
}, [] as PullApproveGroup[]);
}

View File

@ -0,0 +1,89 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {PullApproveGroup} from './group';
import {getOrCreateGlob} from './utils';
export class PullApproveGroupStateDependencyError extends Error {
constructor(message?: string) {
super(message);
// Set the prototype explicitly because in ES5, the prototype is accidentally
// lost due to a limitation in down-leveling.
// https://github.com/Microsoft/TypeScript/wiki/FAQ#why-doesnt-extending-built-ins-like-error-array-and-map-work.
Object.setPrototypeOf(this, PullApproveGroupStateDependencyError.prototype);
// Error names are displayed in their stack but can't be set in the constructor.
this.name = PullApproveGroupStateDependencyError.name;
}
}
/**
* Superset of a native array. The superset provides methods which mimic the
* list data structure used in PullApprove for files in conditions.
*/
export class PullApproveStringArray extends Array<string> {
constructor(...elements: string[]) {
super(...elements);
// Set the prototype explicitly because in ES5, the prototype is accidentally
// lost due to a limitation in down-leveling.
// https://github.com/Microsoft/TypeScript/wiki/FAQ#why-doesnt-extending-built-ins-like-error-array-and-map-work.
Object.setPrototypeOf(this, PullApproveStringArray.prototype);
}
/** Returns a new array which only includes files that match the given pattern. */
include(pattern: string): PullApproveStringArray {
return new PullApproveStringArray(...this.filter(s => getOrCreateGlob(pattern).match(s)));
}
/** Returns a new array which only includes files that did not match the given pattern. */
exclude(pattern: string): PullApproveStringArray {
return new PullApproveStringArray(...this.filter(s => !getOrCreateGlob(pattern).match(s)));
}
}
/**
* Superset of a native array. The superset provides methods which mimic the
* list data structure used in PullApprove for groups in conditions.
*/
export class PullApproveGroupArray extends Array<PullApproveGroup> {
constructor(...elements: PullApproveGroup[]) {
super(...elements);
// Set the prototype explicitly because in ES5, the prototype is accidentally
// lost due to a limitation in down-leveling.
// https://github.com/Microsoft/TypeScript/wiki/FAQ#why-doesnt-extending-built-ins-like-error-array-and-map-work.
Object.setPrototypeOf(this, PullApproveGroupArray.prototype);
}
include(pattern: string): PullApproveGroupArray {
return new PullApproveGroupArray(...this.filter(s => s.groupName.match(pattern)));
}
/** Returns a new array which only includes files that did not match the given pattern. */
exclude(pattern: string): PullApproveGroupArray {
return new PullApproveGroupArray(...this.filter(s => s.groupName.match(pattern)));
}
get pending() {
throw new PullApproveGroupStateDependencyError();
}
get active() {
throw new PullApproveGroupStateDependencyError();
}
get inactive() {
throw new PullApproveGroupStateDependencyError();
}
get rejected() {
throw new PullApproveGroupStateDependencyError();
}
get names() {
return this.map(g => g.groupName);
}
}

View File

@ -0,0 +1,24 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {IMinimatch, Minimatch} from 'minimatch';
/** Map that holds patterns and their corresponding Minimatch globs. */
const patternCache = new Map<string, IMinimatch>();
/**
* Gets a glob for the given pattern. The cached glob will be returned
* if available. Otherwise a new glob will be created and cached.
*/
export function getOrCreateGlob(pattern: string) {
if (patternCache.has(pattern)) {
return patternCache.get(pattern)!;
}
const glob = new Minimatch(pattern, {dot: true});
patternCache.set(pattern, glob);
return glob;
}

View File

@ -0,0 +1,88 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {PullApproveGroup} from './group';
import {getGroupsFromYaml} from './parse-yaml';
describe('group parsing', () => {
it('gets group name', () => {
const groupName = 'fw-migrations';
const groups = getGroupsFromYaml(`
groups:
${groupName}:
type: optional
`);
expect(groups[0].groupName).toBe(groupName);
});
it('gets correct number of groups', () => {
const groups = getGroupsFromYaml(`
groups:
fw-migrations:
type: optional
fw-core:
type: optional
`);
expect(groups.length).toBe(2);
});
it('gets preceding groups', () => {
const groups = getGroupsFromYaml(`
groups:
fw-migrations:
type: optional
fw-core:
type: optional
dev-infra:
type: optional
`);
const fwMigrations = getGroupByName(groups, 'fw-migrations')!;
const fwCore = getGroupByName(groups, 'fw-core')!;
const devInfra = getGroupByName(groups, 'dev-infra')!;
expect(getGroupNames(fwMigrations.precedingGroups)).toEqual([]);
expect(getGroupNames(fwCore.precedingGroups)).toEqual([fwMigrations.groupName]);
expect(getGroupNames(devInfra.precedingGroups)).toEqual([
fwMigrations.groupName, fwCore.groupName
]);
});
it('matches file conditions', () => {
const groups = getGroupsFromYaml(`
groups:
fw-core:
conditions:
- contains_any_globs(files, ['packages/core/**'])
`);
const fwCore = getGroupByName(groups, 'fw-core')!;
expect(fwCore.testFile('packages/core/test.ts')).toBe(true);
expect(fwCore.testFile('some/other/location/test.ts')).toBe(false);
});
it('allows conditions based on groups', () => {
const groups = getGroupsFromYaml(`
groups:
fw-migrations:
conditions:
- len(groups) > 0
fw-core:
conditions:
- len(groups.active) > 0
`);
const fwMigrations = getGroupByName(groups, 'fw-migrations')!;
expect(() => fwMigrations.testFile('any')).not.toThrow();
const fwCore = getGroupByName(groups, 'fw-core')!;
expect(() => fwCore.testFile('any')).not.toThrow();
});
});
function getGroupByName(groups: PullApproveGroup[], name: string): PullApproveGroup|undefined {
return groups.find(g => g.groupName === name);
}
function getGroupNames(groups: PullApproveGroup[]) {
return groups.map(g => g.groupName);
}

View File

@ -11,10 +11,8 @@ import {resolve} from 'path';
import {getRepoBaseDir} from '../utils/config';
import {debug, info} from '../utils/console';
import {allFiles} from '../utils/repo-files';
import {PullApproveGroup} from './group';
import {logGroup, logHeader} from './logging';
import {parsePullApproveYaml} from './parse-yaml';
import {getGroupsFromYaml} from './parse-yaml';
export function verify() {
/** Full path to PullApprove config file */
@ -23,12 +21,8 @@ export function verify() {
const REPO_FILES = allFiles();
/** The pull approve config file. */
const pullApproveYamlRaw = readFileSync(PULL_APPROVE_YAML_PATH, 'utf8');
/** JSON representation of the pullapprove yaml file. */
const pullApprove = parsePullApproveYaml(pullApproveYamlRaw);
/** All of the groups defined in the pullapprove yaml. */
const groups = Object.entries(pullApprove.groups).map(([groupName, group]) => {
return new PullApproveGroup(groupName, group);
});
const groups = getGroupsFromYaml(pullApproveYamlRaw);
/**
* PullApprove groups without conditions. These are skipped in the verification
* as those would always be active and cause zero unmatched files.
@ -90,11 +84,16 @@ export function verify() {
info.groupEnd();
const matchedGroups = resultsByGroup.filter(group => !group.unmatchedCount);
info.group(`Matched conditions by Group (${matchedGroups.length} groups)`);
matchedGroups.forEach(group => logGroup(group, true, debug));
matchedGroups.forEach(group => logGroup(group, 'matchedConditions', debug));
info.groupEnd();
const unmatchedGroups = resultsByGroup.filter(group => group.unmatchedCount);
info.group(`Unmatched conditions by Group (${unmatchedGroups.length} groups)`);
unmatchedGroups.forEach(group => logGroup(group, false));
unmatchedGroups.forEach(group => logGroup(group, 'unmatchedConditions'));
info.groupEnd();
const unverifiableConditionsInGroups =
resultsByGroup.filter(group => group.unverifiableConditions.length > 0);
info.group(`Unverifiable conditions by Group (${unverifiableConditionsInGroups.length} groups)`);
unverifiableConditionsInGroups.forEach(group => logGroup(group, 'unverifiableConditions'));
info.groupEnd();
// Provide correct exit code based on verification success.

View File

@ -16,6 +16,7 @@ ts_library(
"@npm//@types/node",
"@npm//@types/shelljs",
"@npm//chalk",
"@npm//inquirer",
"@npm//shelljs",
"@npm//tslib",
"@npm//typed-graphqlify",

View File

@ -175,7 +175,7 @@ If a PR is missing the `PR target: *` label, or if the label is set to "TBD" whe
Before a PR can be merged it must be approved by the appropriate reviewer(s).
To ensure that the right people review each change, we set review requests using [PullApprove](https://https://docs.pullapprove.com/) (via `.pullapprove`) and require that each PR has at least one approval from an appropriate code owner.
To ensure that the right people review each change, we set review requests using [PullApprove](https://docs.pullapprove.com/) (via `.pullapprove`) and require that each PR has at least one approval from an appropriate code owner.
If the PR author is a code owner themselves, the approval can come from _any_ repo collaborator (person with write access).
In any case, the reviewer should actually look through the code and provide feedback if necessary.

View File

@ -53,7 +53,7 @@ export declare function asNativeElements(debugEls: DebugElement[]): any;
export declare function assertPlatform(requiredToken: any): PlatformRef;
export declare interface Attribute {
attributeName?: string;
attributeName: string;
}
export declare const Attribute: AttributeDecorator;
@ -671,192 +671,18 @@ export declare interface OutputDecorator {
new (bindingPropertyName?: string): any;
}
export declare function ɵɵadvance(delta: number): void;
export declare function ɵɵattribute(name: string, value: any, sanitizer?: SanitizerFn | null, namespace?: string): typeof ɵɵattribute;
export declare function ɵɵattributeInterpolate1(attrName: string, prefix: string, v0: any, suffix: string, sanitizer?: SanitizerFn, namespace?: string): typeof ɵɵattributeInterpolate1;
export declare function ɵɵattributeInterpolate2(attrName: string, prefix: string, v0: any, i0: string, v1: any, suffix: string, sanitizer?: SanitizerFn, namespace?: string): typeof ɵɵattributeInterpolate2;
export declare function ɵɵattributeInterpolate3(attrName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, suffix: string, sanitizer?: SanitizerFn, namespace?: string): typeof ɵɵattributeInterpolate3;
export declare function ɵɵattributeInterpolate4(attrName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, suffix: string, sanitizer?: SanitizerFn, namespace?: string): typeof ɵɵattributeInterpolate4;
export declare function ɵɵattributeInterpolate5(attrName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, suffix: string, sanitizer?: SanitizerFn, namespace?: string): typeof ɵɵattributeInterpolate5;
export declare function ɵɵattributeInterpolate6(attrName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, suffix: string, sanitizer?: SanitizerFn, namespace?: string): typeof ɵɵattributeInterpolate6;
export declare function ɵɵattributeInterpolate7(attrName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, suffix: string, sanitizer?: SanitizerFn, namespace?: string): typeof ɵɵattributeInterpolate7;
export declare function ɵɵattributeInterpolate8(attrName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, i6: string, v7: any, suffix: string, sanitizer?: SanitizerFn, namespace?: string): typeof ɵɵattributeInterpolate8;
export declare function ɵɵattributeInterpolateV(attrName: string, values: any[], sanitizer?: SanitizerFn, namespace?: string): typeof ɵɵattributeInterpolateV;
export declare function ɵɵclassMap(classes: {
[className: string]: boolean | undefined | null;
} | string | undefined | null): void;
export declare function ɵɵclassMapInterpolate1(prefix: string, v0: any, suffix: string): void;
export declare function ɵɵclassMapInterpolate2(prefix: string, v0: any, i0: string, v1: any, suffix: string): void;
export declare function ɵɵclassMapInterpolate3(prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, suffix: string): void;
export declare function ɵɵclassMapInterpolate4(prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, suffix: string): void;
export declare function ɵɵclassMapInterpolate5(prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, suffix: string): void;
export declare function ɵɵclassMapInterpolate6(prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, suffix: string): void;
export declare function ɵɵclassMapInterpolate7(prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, suffix: string): void;
export declare function ɵɵclassMapInterpolate8(prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, i6: string, v7: any, suffix: string): void;
export declare function ɵɵclassMapInterpolateV(values: any[]): void;
export declare function ɵɵclassProp(className: string, value: boolean | undefined | null): typeof ɵɵclassProp;
export declare type ɵɵComponentDefWithMeta<T, Selector extends String, ExportAs extends string[], InputMap extends {
[key: string]: string;
}, OutputMap extends {
[key: string]: string;
}, QueryFields extends string[], NgContentSelectors extends string[]> = ɵComponentDef<T>;
export declare function ɵɵcontentQuery<T>(directiveIndex: number, predicate: Type<any> | string[], descend: boolean, read?: any): void;
export declare function ɵɵCopyDefinitionFeature(definition: ɵDirectiveDef<any> | ɵComponentDef<any>): void;
export declare function ɵɵdefineComponent<T>(componentDefinition: {
type: Type<T>;
selectors?: ɵCssSelectorList;
decls: number;
vars: number;
inputs?: {
[P in keyof T]?: string | [string, string];
};
outputs?: {
[P in keyof T]?: string;
};
hostBindings?: HostBindingsFunction<T>;
hostVars?: number;
hostAttrs?: TAttributes;
contentQueries?: ContentQueriesFunction<T>;
exportAs?: string[];
template: ComponentTemplate<T>;
consts?: TConstants;
ngContentSelectors?: string[];
viewQuery?: ViewQueriesFunction<T> | null;
features?: ComponentDefFeature[];
encapsulation?: ViewEncapsulation;
data?: {
[kind: string]: any;
};
styles?: string[];
changeDetection?: ChangeDetectionStrategy;
directives?: DirectiveTypesOrFactory | null;
pipes?: PipeTypesOrFactory | null;
schemas?: SchemaMetadata[] | null;
}): never;
export declare const ɵɵdefineDirective: <T>(directiveDefinition: {
type: Type<T>;
selectors?: ɵCssSelectorList | undefined;
inputs?: { [P in keyof T]?: string | [string, string] | undefined; } | undefined;
outputs?: { [P_1 in keyof T]?: string | undefined; } | undefined;
features?: DirectiveDefFeature[] | undefined;
hostBindings?: HostBindingsFunction<T> | undefined;
hostVars?: number | undefined;
hostAttrs?: TAttributes | undefined;
contentQueries?: ContentQueriesFunction<T> | undefined;
viewQuery?: ViewQueriesFunction<T> | null | undefined;
exportAs?: string[] | undefined;
}) => never;
/** @codeGenApi */
export declare function ɵɵdefineInjectable<T>(opts: {
token: unknown;
providedIn?: Type<any> | 'root' | 'platform' | 'any' | null;
factory: () => T;
}): never;
export declare function ɵɵdefineInjector(options: {
factory: () => any;
providers?: any[];
imports?: any[];
}): never;
export declare function ɵɵdefineNgModule<T>(def: {
type: T;
bootstrap?: Type<any>[] | (() => Type<any>[]);
declarations?: Type<any>[] | (() => Type<any>[]);
imports?: Type<any>[] | (() => Type<any>[]);
exports?: Type<any>[] | (() => Type<any>[]);
schemas?: SchemaMetadata[] | null;
id?: string | null;
}): never;
export declare function ɵɵdefinePipe<T>(pipeDef: {
name: string;
type: Type<T>;
pure?: boolean;
}): never;
export declare type ɵɵDirectiveDefWithMeta<T, Selector extends string, ExportAs extends string[], InputMap extends {
[key: string]: string;
}, OutputMap extends {
[key: string]: string;
}, QueryFields extends string[]> = ɵDirectiveDef<T>;
export declare function ɵɵdirectiveInject<T>(token: Type<T> | InjectionToken<T>): T;
export declare function ɵɵdirectiveInject<T>(token: Type<T> | InjectionToken<T>, flags: InjectFlags): T;
export declare function ɵɵdisableBindings(): void;
export declare function ɵɵelement(index: number, name: string, attrsIndex?: number | null, localRefsIndex?: number): void;
export declare function ɵɵelementContainer(index: number, attrsIndex?: number | null, localRefsIndex?: number): void;
export declare function ɵɵelementContainerEnd(): void;
export declare function ɵɵelementContainerStart(index: number, attrsIndex?: number | null, localRefsIndex?: number): void;
export declare function ɵɵelementEnd(): void;
export declare function ɵɵelementStart(index: number, name: string, attrsIndex?: number | null, localRefsIndex?: number): void;
export declare function ɵɵenableBindings(): void;
export declare type ɵɵFactoryDef<T, CtorDependencies extends CtorDependency[]> = () => T;
export declare function ɵɵgetCurrentView(): OpaqueViewState;
export declare function ɵɵgetFactoryOf<T>(type: Type<any>): FactoryFn<T> | null;
export declare function ɵɵgetInheritedFactory<T>(type: Type<any>): (type: Type<T>) => T;
export declare function ɵɵhostProperty<T>(propName: string, value: T, sanitizer?: SanitizerFn | null): typeof ɵɵhostProperty;
export declare function ɵɵi18n(index: number, message: string, subTemplateIndex?: number): void;
export declare function ɵɵi18nApply(index: number): void;
export declare function ɵɵi18nAttributes(index: number, values: string[]): void;
export declare function ɵɵi18nEnd(): void;
export declare function ɵɵi18nExp<T>(value: T): typeof ɵɵi18nExp;
export declare function ɵɵi18nPostprocess(message: string, replacements?: {
[key: string]: (string | string[]);
}): string;
export declare function ɵɵi18nStart(index: number, message: string, subTemplateIndex?: number): void;
export declare function ɵɵInheritDefinitionFeature(definition: ɵDirectiveDef<any> | ɵComponentDef<any>): void;
/** @codeGenApi */
export declare function ɵɵinject<T>(token: Type<T> | InjectionToken<T>): T;
export declare function ɵɵinject<T>(token: Type<T> | InjectionToken<T>, flags?: InjectFlags): T | null;
/** @codeGenApi */
export declare interface ɵɵInjectableDef<T> {
factory: (t?: Type<any>) => T;
providedIn: InjectorType<any> | 'root' | 'platform' | 'any' | null;
@ -864,226 +690,12 @@ export declare interface ɵɵInjectableDef<T> {
value: T | undefined;
}
/** @codeGenApi */
export declare function ɵɵinjectAttribute(attrNameToInject: string): string | null;
export declare interface ɵɵInjectorDef<T> {
factory: () => T;
imports: (InjectorType<any> | InjectorTypeWithProviders<any>)[];
providers: (Type<any> | ValueProvider | ExistingProvider | FactoryProvider | ConstructorProvider | StaticClassProvider | ClassProvider | any[])[];
}
/** @codeGenApi */
export declare function ɵɵinjectPipeChangeDetectorRef(flags?: InjectFlags): ChangeDetectorRef | null;
export declare function ɵɵinvalidFactory(): never;
export declare function ɵɵinvalidFactoryDep(index: number): never;
export declare function ɵɵlistener(eventName: string, listenerFn: (e?: any) => any, useCapture?: boolean, eventTargetResolver?: GlobalTargetResolver): typeof ɵɵlistener;
export declare function ɵɵloadQuery<T>(): QueryList<T>;
export declare function ɵɵnamespaceHTML(): void;
export declare function ɵɵnamespaceMathML(): void;
export declare function ɵɵnamespaceSVG(): void;
export declare function ɵɵnextContext<T = any>(level?: number): T;
export declare type ɵɵNgModuleDefWithMeta<T, Declarations, Imports, Exports> = ɵNgModuleDef<T>;
export declare function ɵɵNgOnChangesFeature<T>(): DirectiveDefFeature;
export declare function ɵɵpipe(index: number, pipeName: string): any;
export declare function ɵɵpipeBind1(index: number, slotOffset: number, v1: any): any;
export declare function ɵɵpipeBind2(index: number, slotOffset: number, v1: any, v2: any): any;
export declare function ɵɵpipeBind3(index: number, slotOffset: number, v1: any, v2: any, v3: any): any;
export declare function ɵɵpipeBind4(index: number, slotOffset: number, v1: any, v2: any, v3: any, v4: any): any;
export declare function ɵɵpipeBindV(index: number, slotOffset: number, values: [any, ...any[]]): any;
export declare type ɵɵPipeDefWithMeta<T, Name extends string> = ɵPipeDef<T>;
export declare function ɵɵprojection(nodeIndex: number, selectorIndex?: number, attrs?: TAttributes): void;
export declare function ɵɵprojectionDef(projectionSlots?: ProjectionSlots): void;
export declare function ɵɵproperty<T>(propName: string, value: T, sanitizer?: SanitizerFn | null): typeof ɵɵproperty;
export declare function ɵɵpropertyInterpolate(propName: string, v0: any, sanitizer?: SanitizerFn): typeof ɵɵpropertyInterpolate;
export declare function ɵɵpropertyInterpolate1(propName: string, prefix: string, v0: any, suffix: string, sanitizer?: SanitizerFn): typeof ɵɵpropertyInterpolate1;
export declare function ɵɵpropertyInterpolate2(propName: string, prefix: string, v0: any, i0: string, v1: any, suffix: string, sanitizer?: SanitizerFn): typeof ɵɵpropertyInterpolate2;
export declare function ɵɵpropertyInterpolate3(propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, suffix: string, sanitizer?: SanitizerFn): typeof ɵɵpropertyInterpolate3;
export declare function ɵɵpropertyInterpolate4(propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, suffix: string, sanitizer?: SanitizerFn): typeof ɵɵpropertyInterpolate4;
export declare function ɵɵpropertyInterpolate5(propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, suffix: string, sanitizer?: SanitizerFn): typeof ɵɵpropertyInterpolate5;
export declare function ɵɵpropertyInterpolate6(propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, suffix: string, sanitizer?: SanitizerFn): typeof ɵɵpropertyInterpolate6;
export declare function ɵɵpropertyInterpolate7(propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, suffix: string, sanitizer?: SanitizerFn): typeof ɵɵpropertyInterpolate7;
export declare function ɵɵpropertyInterpolate8(propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, i6: string, v7: any, suffix: string, sanitizer?: SanitizerFn): typeof ɵɵpropertyInterpolate8;
export declare function ɵɵpropertyInterpolateV(propName: string, values: any[], sanitizer?: SanitizerFn): typeof ɵɵpropertyInterpolateV;
export declare function ɵɵProvidersFeature<T>(providers: Provider[], viewProviders?: Provider[]): (definition: ɵDirectiveDef<T>) => void;
export declare function ɵɵpureFunction0<T>(slotOffset: number, pureFn: () => T, thisArg?: any): T;
export declare function ɵɵpureFunction1(slotOffset: number, pureFn: (v: any) => any, exp: any, thisArg?: any): any;
export declare function ɵɵpureFunction2(slotOffset: number, pureFn: (v1: any, v2: any) => any, exp1: any, exp2: any, thisArg?: any): any;
export declare function ɵɵpureFunction3(slotOffset: number, pureFn: (v1: any, v2: any, v3: any) => any, exp1: any, exp2: any, exp3: any, thisArg?: any): any;
export declare function ɵɵpureFunction4(slotOffset: number, pureFn: (v1: any, v2: any, v3: any, v4: any) => any, exp1: any, exp2: any, exp3: any, exp4: any, thisArg?: any): any;
export declare function ɵɵpureFunction5(slotOffset: number, pureFn: (v1: any, v2: any, v3: any, v4: any, v5: any) => any, exp1: any, exp2: any, exp3: any, exp4: any, exp5: any, thisArg?: any): any;
export declare function ɵɵpureFunction6(slotOffset: number, pureFn: (v1: any, v2: any, v3: any, v4: any, v5: any, v6: any) => any, exp1: any, exp2: any, exp3: any, exp4: any, exp5: any, exp6: any, thisArg?: any): any;
export declare function ɵɵpureFunction7(slotOffset: number, pureFn: (v1: any, v2: any, v3: any, v4: any, v5: any, v6: any, v7: any) => any, exp1: any, exp2: any, exp3: any, exp4: any, exp5: any, exp6: any, exp7: any, thisArg?: any): any;
export declare function ɵɵpureFunction8(slotOffset: number, pureFn: (v1: any, v2: any, v3: any, v4: any, v5: any, v6: any, v7: any, v8: any) => any, exp1: any, exp2: any, exp3: any, exp4: any, exp5: any, exp6: any, exp7: any, exp8: any, thisArg?: any): any;
export declare function ɵɵpureFunctionV(slotOffset: number, pureFn: (...v: any[]) => any, exps: any[], thisArg?: any): any;
export declare function ɵɵqueryRefresh(queryList: QueryList<any>): boolean;
export declare function ɵɵreference<T>(index: number): T;
export declare function ɵɵresolveBody(element: RElement & {
ownerDocument: Document;
}): {
name: string;
target: HTMLElement;
};
export declare function ɵɵresolveDocument(element: RElement & {
ownerDocument: Document;
}): {
name: string;
target: Document;
};
export declare function ɵɵresolveWindow(element: RElement & {
ownerDocument: Document;
}): {
name: string;
target: (Window & typeof globalThis) | null;
};
export declare function ɵɵrestoreView(viewToRestore: OpaqueViewState): void;
export declare function ɵɵsanitizeHtml(unsafeHtml: any): string;
export declare function ɵɵsanitizeResourceUrl(unsafeResourceUrl: any): string;
export declare function ɵɵsanitizeScript(unsafeScript: any): string;
export declare function ɵɵsanitizeStyle(unsafeStyle: any): string;
export declare function ɵɵsanitizeUrl(unsafeUrl: any): string;
export declare function ɵɵsanitizeUrlOrResourceUrl(unsafeUrl: any, tag: string, prop: string): any;
/** @deprecated */
export declare function ɵɵselect(index: number): void;
export declare function ɵɵsetComponentScope(type: ɵComponentType<any>, directives: Type<any>[], pipes: Type<any>[]): void;
export declare function ɵɵsetNgModuleScope(type: any, scope: {
declarations?: Type<any>[] | (() => Type<any>[]);
imports?: Type<any>[] | (() => Type<any>[]);
exports?: Type<any>[] | (() => Type<any>[]);
}): void;
export declare function ɵɵstaticContentQuery<T>(directiveIndex: number, predicate: Type<any> | string[], descend: boolean, read?: any): void;
export declare function ɵɵstaticViewQuery<T>(predicate: Type<any> | string[], descend: boolean, read?: any): void;
export declare function ɵɵstyleMap(styles: {
[styleName: string]: any;
} | string | undefined | null): void;
export declare function ɵɵstyleMapInterpolate1(prefix: string, v0: any, suffix: string): void;
export declare function ɵɵstyleMapInterpolate2(prefix: string, v0: any, i0: string, v1: any, suffix: string): void;
export declare function ɵɵstyleMapInterpolate3(prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, suffix: string): void;
export declare function ɵɵstyleMapInterpolate4(prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, suffix: string): void;
export declare function ɵɵstyleMapInterpolate5(prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, suffix: string): void;
export declare function ɵɵstyleMapInterpolate6(prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, suffix: string): void;
export declare function ɵɵstyleMapInterpolate7(prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, suffix: string): void;
export declare function ɵɵstyleMapInterpolate8(prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, i6: string, v7: any, suffix: string): void;
export declare function ɵɵstyleMapInterpolateV(values: any[]): void;
export declare function ɵɵstyleProp(prop: string, value: string | number | ɵSafeValue | undefined | null, suffix?: string | null): typeof ɵɵstyleProp;
export declare function ɵɵstylePropInterpolate1(prop: string, prefix: string, v0: any, suffix: string, valueSuffix?: string | null): typeof ɵɵstylePropInterpolate1;
export declare function ɵɵstylePropInterpolate2(prop: string, prefix: string, v0: any, i0: string, v1: any, suffix: string, valueSuffix?: string | null): typeof ɵɵstylePropInterpolate2;
export declare function ɵɵstylePropInterpolate3(prop: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, suffix: string, valueSuffix?: string | null): typeof ɵɵstylePropInterpolate3;
export declare function ɵɵstylePropInterpolate4(prop: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, suffix: string, valueSuffix?: string | null): typeof ɵɵstylePropInterpolate4;
export declare function ɵɵstylePropInterpolate5(prop: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, suffix: string, valueSuffix?: string | null): typeof ɵɵstylePropInterpolate5;
export declare function ɵɵstylePropInterpolate6(prop: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, suffix: string, valueSuffix?: string | null): typeof ɵɵstylePropInterpolate6;
export declare function ɵɵstylePropInterpolate7(prop: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, suffix: string, valueSuffix?: string | null): typeof ɵɵstylePropInterpolate7;
export declare function ɵɵstylePropInterpolate8(prop: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, i6: string, v7: any, suffix: string, valueSuffix?: string | null): typeof ɵɵstylePropInterpolate8;
export declare function ɵɵstylePropInterpolateV(prop: string, values: any[], valueSuffix?: string | null): typeof ɵɵstylePropInterpolateV;
export declare function ɵɵsyntheticHostListener(eventName: string, listenerFn: (e?: any) => any, useCapture?: boolean, eventTargetResolver?: GlobalTargetResolver): typeof ɵɵsyntheticHostListener;
export declare function ɵɵsyntheticHostProperty<T>(propName: string, value: T | ɵNO_CHANGE, sanitizer?: SanitizerFn | null): typeof ɵɵsyntheticHostProperty;
export declare function ɵɵtemplate(index: number, templateFn: ComponentTemplate<any> | null, decls: number, vars: number, tagName?: string | null, attrsIndex?: number | null, localRefsIndex?: number | null, localRefExtractor?: LocalRefExtractor): void;
export declare function ɵɵtemplateRefExtractor(tNode: TNode, currentView: ɵangular_packages_core_core_bp): TemplateRef<unknown> | null;
export declare function ɵɵtext(index: number, value?: string): void;
export declare function ɵɵtextInterpolate(v0: any): typeof ɵɵtextInterpolate;
export declare function ɵɵtextInterpolate1(prefix: string, v0: any, suffix: string): typeof ɵɵtextInterpolate1;
export declare function ɵɵtextInterpolate2(prefix: string, v0: any, i0: string, v1: any, suffix: string): typeof ɵɵtextInterpolate2;
export declare function ɵɵtextInterpolate3(prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, suffix: string): typeof ɵɵtextInterpolate3;
export declare function ɵɵtextInterpolate4(prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, suffix: string): typeof ɵɵtextInterpolate4;
export declare function ɵɵtextInterpolate5(prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, suffix: string): typeof ɵɵtextInterpolate5;
export declare function ɵɵtextInterpolate6(prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, suffix: string): typeof ɵɵtextInterpolate6;
export declare function ɵɵtextInterpolate7(prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, suffix: string): typeof ɵɵtextInterpolate7;
export declare function ɵɵtextInterpolate8(prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, i6: string, v7: any, suffix: string): typeof ɵɵtextInterpolate8;
export declare function ɵɵtextInterpolateV(values: any[]): typeof ɵɵtextInterpolateV;
export declare function ɵɵviewQuery<T>(predicate: Type<any> | string[], descend: boolean, read?: any): void;
export declare const PACKAGE_ROOT_URL: InjectionToken<string>;
export declare interface Pipe {

View File

@ -1,3 +1,6 @@
/** @codeGenApi */
export declare const __core_private_testing_placeholder__ = "";
export declare function async(fn: Function): (done: any) => any;
export declare class ComponentFixture<T> {

View File

@ -12,7 +12,7 @@
"master": {
"uncompressed": {
"runtime-es2015": 2987,
"main-es2015": 450301,
"main-es2015": 448184,
"polyfills-es2015": 52630
}
}

View File

@ -1,6 +1,6 @@
{
"name": "angular-srcs",
"version": "10.0.5",
"version": "10.0.6",
"private": true,
"description": "Angular - a web framework for modern web apps",
"homepage": "https://github.com/angular/angular",

View File

@ -152,13 +152,13 @@ export interface ClassMember {
name: string;
/**
* TypeScript `ts.Identifier` representing the name of the member, or `null` if no such node
* is present.
* TypeScript `ts.Identifier` or `ts.StringLiteral` representing the name of the member, or `null`
* if no such node is present.
*
* The `nameNode` is useful in writing references to this member that will be correctly source-
* mapped back to the original file.
*/
nameNode: ts.Identifier|null;
nameNode: ts.Identifier|ts.StringLiteral|null;
/**
* TypeScript `ts.Expression` which represents the value of the member.

View File

@ -363,7 +363,7 @@ export class TypeScriptReflectionHost implements ReflectionHost {
let kind: ClassMemberKind|null = null;
let value: ts.Expression|null = null;
let name: string|null = null;
let nameNode: ts.Identifier|null = null;
let nameNode: ts.Identifier|ts.StringLiteral|null = null;
if (ts.isPropertyDeclaration(node)) {
kind = ClassMemberKind.Property;
@ -385,6 +385,9 @@ export class TypeScriptReflectionHost implements ReflectionHost {
} else if (ts.isIdentifier(node.name)) {
name = node.name.text;
nameNode = node.name;
} else if (ts.isStringLiteral(node.name)) {
name = node.name.text;
nameNode = node.name;
} else {
return null;
}

View File

@ -9,7 +9,7 @@ import * as ts from 'typescript';
import {absoluteFrom, getSourceFileOrError} from '../../file_system';
import {runInEachFileSystem} from '../../file_system/testing';
import {getDeclaration, makeProgram} from '../../testing';
import {CtorParameter} from '../src/host';
import {ClassMember, ClassMemberKind, CtorParameter} from '../src/host';
import {TypeScriptReflectionHost} from '../src/typescript';
import {isNamedClassDeclaration} from '../src/util';
@ -450,6 +450,95 @@ runInEachFileSystem(() => {
]);
});
});
describe('getMembersOfClass()', () => {
it('should get string literal members of class', () => {
const {program} = makeProgram([{
name: _('/entry.ts'),
contents: `
class Foo {
'string-literal-property-member' = 'my value';
}
`
}]);
const members = getMembers(program);
expect(members.length).toBe(1);
expectMember(members[0], 'string-literal-property-member', ClassMemberKind.Property);
});
it('should retrieve method members', () => {
const {program} = makeProgram([{
name: _('/entry.ts'),
contents: `
class Foo {
myMethod(): void {
}
}
`
}]);
const members = getMembers(program);
expect(members.length).toBe(1);
expectMember(members[0], 'myMethod', ClassMemberKind.Method);
});
it('should retrieve constructor as member', () => {
const {program} = makeProgram([{
name: _('/entry.ts'),
contents: `
class Foo {
constructor() {}
}
`
}]);
const members = getMembers(program);
expect(members.length).toBe(1);
expectMember(members[0], 'constructor', ClassMemberKind.Constructor);
});
it('should retrieve decorators of member', () => {
const {program} = makeProgram([{
name: _('/entry.ts'),
contents: `
declare var Input;
class Foo {
@Input()
prop: string;
}
`
}]);
const members = getMembers(program);
expect(members.length).toBe(1);
expect(members[0].decorators).not.toBeNull();
expect(members[0].decorators![0].name).toBe('Input');
});
it('identifies static members', () => {
const {program} = makeProgram([{
name: _('/entry.ts'),
contents: `
class Foo {
static staticMember = '';
}
`
}]);
const members = getMembers(program);
expect(members.length).toBe(1);
expect(members[0].isStatic).toBeTrue();
});
function getMembers(program: ts.Program) {
const clazz = getDeclaration(program, _('/entry.ts'), 'Foo', isNamedClassDeclaration);
const checker = program.getTypeChecker();
const host = new TypeScriptReflectionHost(checker);
return host.getMembersOfClass(clazz);
}
function expectMember(member: ClassMember, name: string, kind: ClassMemberKind) {
expect(member.name).toEqual(name);
expect(member.kind).toEqual(kind);
}
});
});
function expectParameter(

View File

@ -14,6 +14,7 @@ import {Decorator, ReflectionHost} from '../../reflection';
import {ImportManager, translateExpression, translateStatement} from '../../translator';
import {visit, VisitListEntryResult, Visitor} from '../../util/src/visitor';
import {CompileResult} from './api';
import {TraitCompiler} from './compilation';
import {addImports} from './utils';
@ -43,12 +44,15 @@ export function ivyTransformFactory(
};
}
class IvyVisitor extends Visitor {
constructor(
private compilation: TraitCompiler, private reflector: ReflectionHost,
private importManager: ImportManager, private defaultImportRecorder: DefaultImportRecorder,
private isClosureCompilerEnabled: boolean, private isCore: boolean,
private constantPool: ConstantPool) {
/**
* Visits all classes, performs Ivy compilation where Angular decorators are present and collects
* result in a Map that associates a ts.ClassDeclaration with Ivy compilation results. This visitor
* does NOT perform any TS transformations.
*/
class IvyCompilationVisitor extends Visitor {
public classCompilationMap = new Map<ts.ClassDeclaration, CompileResult[]>();
constructor(private compilation: TraitCompiler, private constantPool: ConstantPool) {
super();
}
@ -56,55 +60,79 @@ class IvyVisitor extends Visitor {
VisitListEntryResult<ts.Statement, ts.ClassDeclaration> {
// Determine if this class has an Ivy field that needs to be added, and compile the field
// to an expression if so.
const res = this.compilation.compile(node, this.constantPool);
const result = this.compilation.compile(node, this.constantPool);
if (result !== null) {
this.classCompilationMap.set(node, result);
}
return {node};
}
}
if (res !== null) {
// There is at least one field to add.
const statements: ts.Statement[] = [];
const members = [...node.members];
/**
* Visits all classes and performs transformation of corresponding TS nodes based on the Ivy
* compilation results (provided as an argument).
*/
class IvyTransformationVisitor extends Visitor {
constructor(
private compilation: TraitCompiler,
private classCompilationMap: Map<ts.ClassDeclaration, CompileResult[]>,
private reflector: ReflectionHost, private importManager: ImportManager,
private defaultImportRecorder: DefaultImportRecorder,
private isClosureCompilerEnabled: boolean, private isCore: boolean) {
super();
}
res.forEach(field => {
// Translate the initializer for the field into TS nodes.
const exprNode = translateExpression(
field.initializer, this.importManager, this.defaultImportRecorder,
ts.ScriptTarget.ES2015);
// Create a static property declaration for the new field.
const property = ts.createProperty(
undefined, [ts.createToken(ts.SyntaxKind.StaticKeyword)], field.name, undefined,
undefined, exprNode);
if (this.isClosureCompilerEnabled) {
// Closure compiler transforms the form `Service.ɵprov = X` into `Service$ɵprov = X`. To
// prevent this transformation, such assignments need to be annotated with @nocollapse.
// Note that tsickle is typically responsible for adding such annotations, however it
// doesn't yet handle synthetic fields added during other transformations.
ts.addSyntheticLeadingComment(
property, ts.SyntaxKind.MultiLineCommentTrivia, '* @nocollapse ',
/* hasTrailingNewLine */ false);
}
field.statements
.map(
stmt => translateStatement(
stmt, this.importManager, this.defaultImportRecorder, ts.ScriptTarget.ES2015))
.forEach(stmt => statements.push(stmt));
members.push(property);
});
// Replace the class declaration with an updated version.
node = ts.updateClassDeclaration(
node,
// Remove the decorator which triggered this compilation, leaving the others alone.
maybeFilterDecorator(node.decorators, this.compilation.decoratorsFor(node)),
node.modifiers, node.name, node.typeParameters, node.heritageClauses || [],
// Map over the class members and remove any Angular decorators from them.
members.map(member => this._stripAngularDecorators(member)));
return {node, after: statements};
visitClassDeclaration(node: ts.ClassDeclaration):
VisitListEntryResult<ts.Statement, ts.ClassDeclaration> {
// If this class is not registered in the map, it means that it doesn't have Angular decorators,
// thus no further processing is required.
if (!this.classCompilationMap.has(node)) {
return {node};
}
return {node};
// There is at least one field to add.
const statements: ts.Statement[] = [];
const members = [...node.members];
for (const field of this.classCompilationMap.get(node)!) {
// Translate the initializer for the field into TS nodes.
const exprNode = translateExpression(
field.initializer, this.importManager, this.defaultImportRecorder,
ts.ScriptTarget.ES2015);
// Create a static property declaration for the new field.
const property = ts.createProperty(
undefined, [ts.createToken(ts.SyntaxKind.StaticKeyword)], field.name, undefined,
undefined, exprNode);
if (this.isClosureCompilerEnabled) {
// Closure compiler transforms the form `Service.ɵprov = X` into `Service$ɵprov = X`. To
// prevent this transformation, such assignments need to be annotated with @nocollapse.
// Note that tsickle is typically responsible for adding such annotations, however it
// doesn't yet handle synthetic fields added during other transformations.
ts.addSyntheticLeadingComment(
property, ts.SyntaxKind.MultiLineCommentTrivia, '* @nocollapse ',
/* hasTrailingNewLine */ false);
}
field.statements
.map(
stmt => translateStatement(
stmt, this.importManager, this.defaultImportRecorder, ts.ScriptTarget.ES2015))
.forEach(stmt => statements.push(stmt));
members.push(property);
}
// Replace the class declaration with an updated version.
node = ts.updateClassDeclaration(
node,
// Remove the decorator which triggered this compilation, leaving the others alone.
maybeFilterDecorator(node.decorators, this.compilation.decoratorsFor(node)), node.modifiers,
node.name, node.typeParameters, node.heritageClauses || [],
// Map over the class members and remove any Angular decorators from them.
members.map(member => this._stripAngularDecorators(member)));
return {node, after: statements};
}
/**
@ -224,11 +252,26 @@ function transformIvySourceFile(
const constantPool = new ConstantPool();
const importManager = new ImportManager(importRewriter);
// Recursively scan through the AST and perform any updates requested by the IvyCompilation.
const visitor = new IvyVisitor(
compilation, reflector, importManager, defaultImportRecorder, isClosureCompilerEnabled,
isCore, constantPool);
let sf = visit(file, visitor, context);
// The transformation process consists of 2 steps:
//
// 1. Visit all classes, perform compilation and collect the results.
// 2. Perform actual transformation of required TS nodes using compilation results from the first
// step.
//
// This is needed to have all `o.Expression`s generated before any TS transforms happen. This
// allows `ConstantPool` to properly identify expressions that can be shared across multiple
// components declared in the same file.
// Step 1. Go though all classes in AST, perform compilation and collect the results.
const compilationVisitor = new IvyCompilationVisitor(compilation, constantPool);
visit(file, compilationVisitor, context);
// Step 2. Scan through the AST again and perform transformations based on Ivy compilation
// results obtained at Step 1.
const transformationVisitor = new IvyTransformationVisitor(
compilation, compilationVisitor.classCompilationMap, reflector, importManager,
defaultImportRecorder, isClosureCompilerEnabled, isCore);
let sf = visit(file, transformationVisitor, context);
// Generate the constant statements first, as they may involve adding additional imports
// to the ImportManager.

View File

@ -6613,6 +6613,68 @@ export const Foo = Foo__PRE_R3__;
const jsContents = env.getContents('test.js');
expect(jsContents).toContain('styles: ["h1[_ngcontent-%COMP%] {font-size: larger}"]');
});
it('should share same styles declared in different components in the same file', () => {
env.write('test.ts', `
import {Component} from '@angular/core';
@Component({
selector: 'comp-a',
template: 'Comp A',
styles: [
'span { font-size: larger; }',
'div { background: url(/some-very-very-long-path.png); }',
'img { background: url(/a/some-very-very-long-path.png); }'
]
})
export class CompA {}
@Component({
selector: 'comp-b',
template: 'Comp B',
styles: [
'span { font-size: larger; }',
'div { background: url(/some-very-very-long-path.png); }',
'img { background: url(/b/some-very-very-long-path.png); }'
]
})
export class CompB {}
`);
env.driveMain();
const jsContents = env.getContents('test.js');
// Verify that long styles present in both components are extracted to a separate var.
expect(jsContents)
.toContain(
'_c0 = "div[_ngcontent-%COMP%] { background: url(/some-very-very-long-path.png); }";');
expect(jsContents)
.toContain(
'styles: [' +
// This style is present in both components, but was not extracted into a separate
// var since it doesn't reach length threshold (50 chars) in `ConstantPool`.
'"span[_ngcontent-%COMP%] { font-size: larger; }", ' +
// Style that is present in both components, but reaches length threshold -
// extracted to a separate var.
'_c0, ' +
// Style that is unique to this component, but that reaches length threshold -
// remains a string in the `styles` array.
'"img[_ngcontent-%COMP%] { background: url(/a/some-very-very-long-path.png); }"]');
expect(jsContents)
.toContain(
'styles: [' +
// This style is present in both components, but was not extracted into a separate
// var since it doesn't reach length threshold (50 chars) in `ConstantPool`.
'"span[_ngcontent-%COMP%] { font-size: larger; }", ' +
// Style that is present in both components, but reaches length threshold -
// extracted to a separate var.
'_c0, ' +
// Style that is unique to this component, but that reaches length threshold -
// remains a string in the `styles` array.
'"img[_ngcontent-%COMP%] { background: url(/b/some-very-very-long-path.png); }"]');
});
});
describe('non-exported classes', () => {

View File

@ -36,6 +36,13 @@ export const enum DefinitionKind {
*/
const KEY_CONTEXT = {};
/**
* Generally all primitive values are excluded from the `ConstantPool`, but there is an exclusion
* for strings that reach a certain length threshold. This constant defines the length threshold for
* strings.
*/
const POOL_INCLUSION_LENGTH_THRESHOLD_FOR_STRINGS = 50;
/**
* A node that is a place-holder that allows the node to be replaced when the actual
* node is known.
@ -96,7 +103,8 @@ export class ConstantPool {
private nextNameIndex = 0;
getConstLiteral(literal: o.Expression, forceShared?: boolean): o.Expression {
if (literal instanceof o.LiteralExpr || literal instanceof FixupExpression) {
if ((literal instanceof o.LiteralExpr && !isLongStringExpr(literal)) ||
literal instanceof FixupExpression) {
// Do no put simple literals into the constant pool or try to produce a constant for a
// reference to a constant.
return literal;
@ -305,3 +313,8 @@ function invalid<T>(this: o.ExpressionVisitor, arg: o.Expression|o.Statement): n
function isVariable(e: o.Expression): e is o.ReadVarExpr {
return e instanceof o.ReadVarExpr;
}
function isLongStringExpr(expr: o.LiteralExpr): boolean {
return typeof expr.value === 'string' &&
expr.value.length >= POOL_INCLUSION_LENGTH_THRESHOLD_FOR_STRINGS;
}

View File

@ -22,10 +22,10 @@ export const createInjectionToken = makeMetadataFactory<object>(
'InjectionToken', (desc: string) => ({_desc: desc, ɵprov: undefined}));
export interface Attribute {
attributeName?: string;
attributeName: string;
}
export const createAttribute =
makeMetadataFactory<Attribute>('Attribute', (attributeName?: string) => ({attributeName}));
makeMetadataFactory<Attribute>('Attribute', (attributeName: string) => ({attributeName}));
export interface Query {
descendants: boolean;

View File

@ -231,7 +231,7 @@ export function compileComponentFromMetadata(
const styleValues = meta.encapsulation == core.ViewEncapsulation.Emulated ?
compileStyles(meta.styles, CONTENT_ATTR, HOST_ATTR) :
meta.styles;
const strings = styleValues.map(str => o.literal(str));
const strings = styleValues.map(str => constantPool.getConstLiteral(o.literal(str)));
definitionMap.set('styles', o.literalArr(strings));
} else if (meta.encapsulation === core.ViewEncapsulation.Emulated) {
// If there is no style, don't generate css selectors on elements

View File

@ -15,7 +15,7 @@ import {Inject, Injectable, InjectionToken, Optional} from './di';
* A [DI token](guide/glossary#di-token "DI token definition") that you can use to provide
* one or more initialization functions.
*
* The provided function are injected at application startup and executed during
* The provided functions are injected at application startup and executed during
* app initialization. If any of these functions returns a Promise, initialization
* does not complete until the Promise is resolved.
*

View File

@ -109,6 +109,7 @@ export function injectInjectorOnly<T>(
*
* @see inject
* @codeGenApi
* @publicApi This instruction has been emitted by ViewEngine for some time and is deployed to npm.
*/
export function ɵɵinject<T>(token: Type<T>|InjectionToken<T>): T;
export function ɵɵinject<T>(token: Type<T>|InjectionToken<T>, flags?: InjectFlags): T|null;

View File

@ -22,9 +22,10 @@ import {ClassProvider, ConstructorProvider, ExistingProvider, FactoryProvider, S
* `InjectorDef`, `NgModule`, or a special scope (e.g. `'root'`). A value of `null` indicates
* that the injectable does not belong to any scope.
*
* NOTE: This is a private type and should not be exported
*
* @publicApi
* @codeGenApi
* @publicApi The ViewEngine compiler emits code with this type for injectables. This code is
* deployed to npm, and should be treated as public api.
*/
export interface ɵɵInjectableDef<T> {
/**
@ -65,7 +66,7 @@ export interface ɵɵInjectableDef<T> {
*
* NOTE: This is a private type and should not be exported
*
* @publicApi
* @codeGenApi
*/
export interface ɵɵInjectorDef<T> {
factory: () => T;
@ -137,6 +138,7 @@ export interface InjectorTypeWithProviders<T> {
* The factory can call `inject` to access the `Injector` and request injection of dependencies.
*
* @codeGenApi
* @publicApi This instruction has been emitted by ViewEngine for some time and is deployed to npm.
*/
export function ɵɵdefineInjectable<T>(opts: {
token: unknown,
@ -175,7 +177,7 @@ export const defineInjectable = ɵɵdefineInjectable;
* whose providers will also be added to the injector. Locally provided types will override
* providers from imports.
*
* @publicApi
* @codeGenApi
*/
export function ɵɵdefineInjector(options: {factory: () => any, providers?: any[], imports?: any[]}):
never {

View File

@ -271,7 +271,7 @@ export interface Attribute {
/**
* The name of the attribute whose value can be injected.
*/
attributeName?: string;
attributeName: string;
}
/**

View File

@ -327,7 +327,7 @@ export interface ViewChildDecorator {
* * A template reference variable as a string (e.g. query `<my-component #cmp></my-component>`
* with `@ViewChild('cmp')`)
* * Any provider defined in the child component tree of the current component (e.g.
* `@ViewChild(SomeService) someService: SomeService`)
* `@ViewChild(SomeComponent) someComponent: SomeComponent`)
* * Any provider defined through a string token (e.g. `@ViewChild('someToken') someTokenVal:
* any`)
* * A `TemplateRef` (e.g. query `<ng-template></ng-template>` with `@ViewChild(TemplateRef)

View File

@ -32,7 +32,7 @@ import {_sanitizeUrl as _sanitizeUrl} from './url_sanitizer';
* @returns `html` string which is safe to display to user, because all of the dangerous javascript
* and urls have been removed.
*
* @publicApi
* @codeGenApi
*/
export function ɵɵsanitizeHtml(unsafeHtml: any): string {
const sanitizer = getSanitizer();
@ -54,7 +54,7 @@ export function ɵɵsanitizeHtml(unsafeHtml: any): string {
* @param unsafeStyle untrusted `style`, typically from the user.
* @returns `style` string which is safe to bind to the `style` properties.
*
* @publicApi
* @codeGenApi
*/
export function ɵɵsanitizeStyle(unsafeStyle: any): string {
const sanitizer = getSanitizer();
@ -81,7 +81,7 @@ export function ɵɵsanitizeStyle(unsafeStyle: any): string {
* @returns `url` string which is safe to bind to the `src` properties such as `<img src>`, because
* all of the dangerous javascript has been removed.
*
* @publicApi
* @codeGenApi
*/
export function ɵɵsanitizeUrl(unsafeUrl: any): string {
const sanitizer = getSanitizer();
@ -103,7 +103,7 @@ export function ɵɵsanitizeUrl(unsafeUrl: any): string {
* @returns `url` string which is safe to bind to the `src` properties such as `<img src>`, because
* only trusted `url`s have been allowed to pass.
*
* @publicApi
* @codeGenApi
*/
export function ɵɵsanitizeResourceUrl(unsafeResourceUrl: any): string {
const sanitizer = getSanitizer();
@ -126,7 +126,7 @@ export function ɵɵsanitizeResourceUrl(unsafeResourceUrl: any): string {
* @returns `url` string which is safe to bind to the `<script>` element such as `<img src>`,
* because only trusted `scripts` have been allowed to pass.
*
* @publicApi
* @codeGenApi
*/
export function ɵɵsanitizeScript(unsafeScript: any): string {
const sanitizer = getSanitizer();
@ -169,7 +169,7 @@ export function getUrlSanitizer(tag: string, prop: string) {
* @param prop name of the property that contains the value.
* @returns `url` string which is safe to bind.
*
* @publicApi
* @codeGenApi
*/
export function ɵɵsanitizeUrlOrResourceUrl(unsafeUrl: any, tag: string, prop: string): any {
return getUrlSanitizer(tag, prop)(unsafeUrl);

View File

@ -177,7 +177,7 @@ export class NgZone {
* If a synchronous error happens it will be rethrown and not reported via `onError`.
*/
run<T>(fn: (...args: any[]) => T, applyThis?: any, applyArgs?: any[]): T {
return (this as any as NgZonePrivate)._inner.run(fn, applyThis, applyArgs) as T;
return (this as any as NgZonePrivate)._inner.run(fn, applyThis, applyArgs);
}
/**
@ -196,7 +196,7 @@ export class NgZone {
const zone = (this as any as NgZonePrivate)._inner;
const task = zone.scheduleEventTask('NgZoneEvent: ' + name, fn, EMPTY_PAYLOAD, noop, noop);
try {
return zone.runTask(task, applyThis, applyArgs) as T;
return zone.runTask(task, applyThis, applyArgs);
} finally {
zone.cancelTask(task);
}
@ -207,7 +207,7 @@ export class NgZone {
* rethrown.
*/
runGuarded<T>(fn: (...args: any[]) => T, applyThis?: any, applyArgs?: any[]): T {
return (this as any as NgZonePrivate)._inner.runGuarded(fn, applyThis, applyArgs) as T;
return (this as any as NgZonePrivate)._inner.runGuarded(fn, applyThis, applyArgs);
}
/**
@ -224,7 +224,7 @@ export class NgZone {
* Use {@link #run} to reenter the Angular zone and do work that updates the application model.
*/
runOutsideAngular<T>(fn: (...args: any[]) => T): T {
return (this as any as NgZonePrivate)._outer.run(fn) as T;
return (this as any as NgZonePrivate)._outer.run(fn);
}
}
@ -388,19 +388,19 @@ export class NoopNgZone implements NgZone {
readonly onStable: EventEmitter<any> = new EventEmitter();
readonly onError: EventEmitter<any> = new EventEmitter();
run(fn: (...args: any[]) => any, applyThis?: any, applyArgs?: any): any {
run<T>(fn: (...args: any[]) => T, applyThis?: any, applyArgs?: any): T {
return fn.apply(applyThis, applyArgs);
}
runGuarded(fn: (...args: any[]) => any, applyThis?: any, applyArgs?: any): any {
runGuarded<T>(fn: (...args: any[]) => any, applyThis?: any, applyArgs?: any): T {
return fn.apply(applyThis, applyArgs);
}
runOutsideAngular(fn: (...args: any[]) => any): any {
runOutsideAngular<T>(fn: (...args: any[]) => T): T {
return fn();
}
runTask(fn: (...args: any[]) => any, applyThis?: any, applyArgs?: any, name?: string): any {
runTask<T>(fn: (...args: any[]) => T, applyThis?: any, applyArgs?: any, name?: string): T {
return fn.apply(applyThis, applyArgs);
}
}

View File

@ -27,6 +27,13 @@ if (_global.beforeEach) {
});
}
// TODO(juliemr): remove this, only used because we need to export something to have compilation
// work.
/**
* This API should be removed. But doing so seems to break `google3` and so it requires a bit of
* investigation.
*
* A work around is to mark it as `@codeGenApi` for now and investigate later.
*
* @codeGenApi
*/
// TODO(iminar): Remove this code in a safe way.
export const __core_private_testing_placeholder__ = '';

View File

@ -18,6 +18,8 @@ import {createCustomEvent, getComponentInputs, getDefaultAttributeToPropertyInpu
* that can be used for custom element registration. Implemented and returned
* by the {@link createCustomElement createCustomElement() function}.
*
* @see [Angular Elements Overview](guide/elements "Turning Angular components into custom elements")
*
* @publicApi
*/
export interface NgElementConstructor<P> {
@ -115,6 +117,8 @@ export interface NgElementConfig {
* static property to affect all newly created instances, or as a constructor argument for
* one-off creations.
*
* @see [Angular Elements Overview](guide/elements "Turning Angular components into custom elements")
*
* @param component The component to transform.
* @param config A configuration that provides initialization information to the created class.
* @returns The custom-element construction class, which can be registered with

View File

@ -18,7 +18,7 @@ function unimplemented(): any {
/**
* @description
* A base class that all control `FormControl`-based directives extend. It binds a `FormControl`
* A base class that all `FormControl`-based directives extend. It binds a `FormControl`
* object to a DOM element.
*
* @publicApi

View File

@ -111,7 +111,7 @@ export class KeyEventsPlugin extends EventManagerPlugin {
});
}
static parseEventName(eventName: string): {[key: string]: string}|null {
static parseEventName(eventName: string): {fullKey: string, domEventName: string}|null {
const parts: string[] = eventName.toLowerCase().split('.');
const domEventName = parts.shift();
@ -136,10 +136,7 @@ export class KeyEventsPlugin extends EventManagerPlugin {
return null;
}
const result: {[k: string]: string} = {};
result['domEventName'] = domEventName;
result['fullKey'] = fullKey;
return result;
return {domEventName, fullKey};
}
static getEventFullKey(event: KeyboardEvent): string {

View File

@ -9,9 +9,7 @@
import {NgModuleFactory, NgModuleRef, Type} from '@angular/core';
import {Observable} from 'rxjs';
import {EmptyOutletComponent} from './components/empty_outlet';
import {ActivatedRouteSnapshot} from './router_state';
import {PRIMARY_OUTLET} from './shared';
import {UrlSegment, UrlSegmentGroup} from './url_tree';
@ -490,107 +488,3 @@ export interface Route {
export class LoadedRouterConfig {
constructor(public routes: Route[], public module: NgModuleRef<any>) {}
}
export function validateConfig(config: Routes, parentPath: string = ''): void {
// forEach doesn't iterate undefined values
for (let i = 0; i < config.length; i++) {
const route: Route = config[i];
const fullPath: string = getFullPath(parentPath, route);
validateNode(route, fullPath);
}
}
function validateNode(route: Route, fullPath: string): void {
if (!route) {
throw new Error(`
Invalid configuration of route '${fullPath}': Encountered undefined route.
The reason might be an extra comma.
Example:
const routes: Routes = [
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },
{ path: 'dashboard', component: DashboardComponent },, << two commas
{ path: 'detail/:id', component: HeroDetailComponent }
];
`);
}
if (Array.isArray(route)) {
throw new Error(`Invalid configuration of route '${fullPath}': Array cannot be specified`);
}
if (!route.component && !route.children && !route.loadChildren &&
(route.outlet && route.outlet !== PRIMARY_OUTLET)) {
throw new Error(`Invalid configuration of route '${
fullPath}': a componentless route without children or loadChildren cannot have a named outlet set`);
}
if (route.redirectTo && route.children) {
throw new Error(`Invalid configuration of route '${
fullPath}': redirectTo and children cannot be used together`);
}
if (route.redirectTo && route.loadChildren) {
throw new Error(`Invalid configuration of route '${
fullPath}': redirectTo and loadChildren cannot be used together`);
}
if (route.children && route.loadChildren) {
throw new Error(`Invalid configuration of route '${
fullPath}': children and loadChildren cannot be used together`);
}
if (route.redirectTo && route.component) {
throw new Error(`Invalid configuration of route '${
fullPath}': redirectTo and component cannot be used together`);
}
if (route.path && route.matcher) {
throw new Error(
`Invalid configuration of route '${fullPath}': path and matcher cannot be used together`);
}
if (route.redirectTo === void 0 && !route.component && !route.children && !route.loadChildren) {
throw new Error(`Invalid configuration of route '${
fullPath}'. One of the following must be provided: component, redirectTo, children or loadChildren`);
}
if (route.path === void 0 && route.matcher === void 0) {
throw new Error(`Invalid configuration of route '${
fullPath}': routes must have either a path or a matcher specified`);
}
if (typeof route.path === 'string' && route.path.charAt(0) === '/') {
throw new Error(`Invalid configuration of route '${fullPath}': path cannot start with a slash`);
}
if (route.path === '' && route.redirectTo !== void 0 && route.pathMatch === void 0) {
const exp =
`The default value of 'pathMatch' is 'prefix', but often the intent is to use 'full'.`;
throw new Error(`Invalid configuration of route '{path: "${fullPath}", redirectTo: "${
route.redirectTo}"}': please provide 'pathMatch'. ${exp}`);
}
if (route.pathMatch !== void 0 && route.pathMatch !== 'full' && route.pathMatch !== 'prefix') {
throw new Error(`Invalid configuration of route '${
fullPath}': pathMatch can only be set to 'prefix' or 'full'`);
}
if (route.children) {
validateConfig(route.children, fullPath);
}
}
function getFullPath(parentPath: string, currentRoute: Route): string {
if (!currentRoute) {
return parentPath;
}
if (!parentPath && !currentRoute.path) {
return '';
} else if (parentPath && !currentRoute.path) {
return `${parentPath}/`;
} else if (!parentPath && currentRoute.path) {
return currentRoute.path;
} else {
return `${parentPath}/${currentRoute.path}`;
}
}
/**
* Makes a copy of the config and adds any default required properties.
*/
export function standardizeConfig(r: Route): Route {
const children = r.children && r.children.map(standardizeConfig);
const c = children ? {...r, children} : {...r};
if (!c.component && (children || c.loadChildren) && (c.outlet && c.outlet !== PRIMARY_OUTLET)) {
c.component = EmptyOutletComponent;
}
return c;
}

View File

@ -20,56 +20,63 @@ import {UrlTree} from '../url_tree';
/**
* @description
*
* Lets you link to specific routes in your app.
* When applied to an element in a template, makes that element a link
* that initiates navigation to a route. Navigation opens one or more routed components
* in one or more `<router-outlet>` locations on the page.
*
* Consider the following route configuration:
* `[{ path: 'user/:name', component: UserCmp }]`.
* When linking to this `user/:name` route, you use the `RouterLink` directive.
*
* If the link is static, you can use the directive as follows:
* Given a route configuration `[{ path: 'user/:name', component: UserCmp }]`,
* the following creates a static link to the route:
* `<a routerLink="/user/bob">link to user component</a>`
*
* If you use dynamic values to generate the link, you can pass an array of path
* segments, followed by the params for each segment.
* You can use dynamic values to generate the link.
* For a dynamic link, pass an array of path segments,
* followed by the params for each segment.
* For example, `['/team', teamId, 'user', userName, {details: true}]`
* generates a link to `/team/11/user/bob;details=true`.
*
* For instance `['/team', teamId, 'user', userName, {details: true}]`
* means that we want to generate a link to `/team/11/user/bob;details=true`.
* Multiple static segments can be merged into one term and combined with dynamic segements.
* For example, `['/team/11/user', userName, {details: true}]`
*
* Multiple static segments can be merged into one
* (e.g., `['/team/11/user', userName, {details: true}]`).
* The input that you provide to the link is treated as a delta to the current URL.
* For instance, suppose the current URL is `/user/(box//aux:team)`.
* The link `<a [routerLink]="['/user/jim']">Jim</a>` creates the URL
* `/user/(jim//aux:team)`.
* See {@link Router#createUrlTree createUrlTree} for more information.
*
* The first segment name can be prepended with `/`, `./`, or `../`:
* * If the first segment begins with `/`, the router will look up the route from the root of the
* @usageNotes
*
* You can use absolute or relative paths in a link, set query parameters,
* control how parameters are handled, and keep a history of navigation states.
*
* ### Relative link paths
*
* The first segment name can be prepended with `/`, `./`, or `../`.
* * If the first segment begins with `/`, the router looks up the route from the root of the
* app.
* * If the first segment begins with `./`, or doesn't begin with a slash, the router will
* instead look in the children of the current activated route.
* * And if the first segment begins with `../`, the router will go up one level.
* * If the first segment begins with `./`, or doesn't begin with a slash, the router
* looks in the children of the current activated route.
* * If the first segment begins with `../`, the router goes up one level in the route tree.
*
* You can set query params and fragment as follows:
* ### Setting and handling query params and fragments
*
* The following link adds a query parameter and a fragment to the generated URL:
*
* ```
* <a [routerLink]="['/user/bob']" [queryParams]="{debug: true}" fragment="education">
* link to user component
* </a>
* ```
* RouterLink will use these to generate this link: `/user/bob?debug=true#education`.
* By default, the directive constructs the new URL using the given query parameters.
* The example generates the link: `/user/bob?debug=true#education`.
*
* (Deprecated in v4.0.0 use `queryParamsHandling` instead) You can also tell the
* directive to preserve the current query params and fragment:
* You can instruct the directive to handle query parameters differently
* by specifying the `queryParamsHandling` option in the link.
* Allowed values are:
*
* ```
* <a [routerLink]="['/user/bob']" preserveQueryParams preserveFragment>
* link to user component
* </a>
* ```
* - `'merge'`: Merge the given `queryParams` into the current query params.
* - `'preserve'`: Preserve the current query params.
*
* You can tell the directive how to handle queryParams. Available options are:
* - `'merge'`: merge the queryParams into the current queryParams
* - `'preserve'`: preserve the current queryParams
* - default/`''`: use the queryParams only
*
* Same options for {@link NavigationExtras#queryParamsHandling
* NavigationExtras#queryParamsHandling}.
* For example:
*
* ```
* <a [routerLink]="['/user/bob']" [queryParams]="{debug: true}" queryParamsHandling="merge">
@ -77,9 +84,13 @@ import {UrlTree} from '../url_tree';
* </a>
* ```
*
* You can provide a `state` value to be persisted to the browser's History.state
* property (See https://developer.mozilla.org/en-US/docs/Web/API/History#Properties). It's
* used as follows:
* See {@link NavigationExtras.queryParamsHandling NavigationExtras#queryParamsHandling}.
*
* ### Preserving navigation history
*
* You can provide a `state` value to be persisted to the browser's
* [`History.state` property](https://developer.mozilla.org/en-US/docs/Web/API/History#Properties).
* For example:
*
* ```
* <a [routerLink]="['/user/bob']" [state]="{tracingId: 123}">
@ -87,8 +98,9 @@ import {UrlTree} from '../url_tree';
* </a>
* ```
*
* And later the value can be read from the router through `router.getCurrentNavigation`.
* For example, to capture the `tracingId` above during the `NavigationStart` event:
* Use {@link Router.getCurrentNavigation() Router#getCurrentNavigation} to retrieve a saved
* navigation-state value. For example, to capture the `tracingId` during the `NavigationStart`
* event:
*
* ```
* // Get NavigationStart events
@ -98,15 +110,6 @@ import {UrlTree} from '../url_tree';
* });
* ```
*
* The router link directive always treats the provided input as a delta to the current url.
*
* For instance, if the current url is `/user/(box//aux:team)`.
*
* Then the following link `<a [routerLink]="['/user/jim']">Jim</a>` will generate the link
* `/user/(jim//aux:team)`.
*
* See {@link Router#createUrlTree createUrlTree} for more information.
*
* @ngModule RouterModule
*
* @publicApi

View File

@ -19,44 +19,49 @@ import {RouterLink, RouterLinkWithHref} from './router_link';
*
* @description
*
* Lets you add a CSS class to an element when the link's route becomes active.
* Tracks whether the linked route of an element is currently active, and allows you
* to specify one or more CSS classes to add to the element when the linked route
* is active.
*
* This directive lets you add a CSS class to an element when the link's route
* becomes active.
*
* Consider the following example:
* Use this directive to create a visual distinction for elements associated with an active route.
* For example, the following code highlights the word "Bob" when the the router
* activates the associated route:
*
* ```
* <a routerLink="/user/bob" routerLinkActive="active-link">Bob</a>
* ```
*
* When the url is either '/user' or '/user/bob', the active-link class will
* be added to the `a` tag. If the url changes, the class will be removed.
* Whenever the URL is either '/user' or '/user/bob', the "active-link" class is
* added to the anchor tag. If the URL changes, the class is removed.
*
* You can set more than one class, as follows:
* You can set more than one class using a space-separated string or an array.
* For example:
*
* ```
* <a routerLink="/user/bob" routerLinkActive="class1 class2">Bob</a>
* <a routerLink="/user/bob" [routerLinkActive]="['class1', 'class2']">Bob</a>
* ```
*
* You can configure RouterLinkActive by passing `exact: true`. This will add the classes
* only when the url matches the link exactly.
* To add the classes only when the URL matches the link exactly, add the option `exact: true`:
*
* ```
* <a routerLink="/user/bob" routerLinkActive="active-link" [routerLinkActiveOptions]="{exact:
* true}">Bob</a>
* ```
*
* You can assign the RouterLinkActive instance to a template variable and directly check
* the `isActive` status.
* To directly check the `isActive` status of the link, assign the `RouterLinkActive`
* instance to a template variable.
* For example, the following checks the status without assigning any CSS classes:
*
* ```
* <a routerLink="/user/bob" routerLinkActive #rla="routerLinkActive">
* Bob {{ rla.isActive ? '(already open)' : ''}}
* </a>
* ```
*
* Finally, you can apply the RouterLinkActive directive to an ancestor of a RouterLink.
* You can apply the `RouterLinkActive` directive to an ancestor of linked elements.
* For example, the following sets the active-link class on the `<div>` parent tag
* when the URL is either '/user/jim' or '/user/bob'.
*
* ```
* <div routerLinkActive="active-link" [routerLinkActiveOptions]="{exact: true}">
@ -65,9 +70,6 @@ import {RouterLink, RouterLinkWithHref} from './router_link';
* </div>
* ```
*
* This will set the active-link class on the div tag if the url is either '/user/jim' or
* '/user/bob'.
*
* @ngModule RouterModule
*
* @publicApi

View File

@ -27,6 +27,21 @@ import {PRIMARY_OUTLET} from '../shared';
* <router-outlet name='right'></router-outlet>
* ```
*
* Named outlets can be the targets of secondary routes.
* The `Route` object for a secondary route has an `outlet` property to identify the target outlet:
*
* `{path: <base-path>, component: <component>, outlet: <target_outlet_name>}`
*
* Using named outlets and secondary routes, you can target multiple outlets in
* the same `RouterLink` directive.
*
* The router keeps track of separate branches in a navigation tree for each named outlet and
* generates a representation of that tree in the URL.
* The URL for a secondary route uses the following syntax to specify both the primary and secondary
* routes at the same time:
*
* `http://base-path/primary-route-path(outlet-name:route-path)`
*
* A router outlet emits an activate event when a new component is instantiated,
* and a deactivate event when a component is destroyed.
*
@ -35,6 +50,11 @@ import {PRIMARY_OUTLET} from '../shared';
* (activate)='onActivate($event)'
* (deactivate)='onDeactivate($event)'></router-outlet>
* ```
*
* @see [Routing tutorial](guide/router-tutorial-toh#named-outlets "Example of a named
* outlet and secondary route configuration").
* @see `RouterLink`
* @see `Route`
* @ngModule RouterModule
*
* @publicApi

View File

@ -11,7 +11,7 @@ import {Compiler, Injectable, Injector, isDevMode, NgModuleFactoryLoader, NgModu
import {BehaviorSubject, EMPTY, Observable, of, Subject, Subscription} from 'rxjs';
import {catchError, filter, finalize, map, switchMap, tap} from 'rxjs/operators';
import {QueryParamsHandling, Route, Routes, standardizeConfig, validateConfig} from './config';
import {QueryParamsHandling, Route, Routes} from './config';
import {createRouterState} from './create_router_state';
import {createUrlTree} from './create_url_tree';
import {Event, GuardsCheckEnd, GuardsCheckStart, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, NavigationTrigger, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, RoutesRecognized} from './events';
@ -28,6 +28,7 @@ import {ActivatedRoute, createEmptyState, RouterState, RouterStateSnapshot} from
import {isNavigationCancelingError, navigationCancelingError, Params} from './shared';
import {DefaultUrlHandlingStrategy, UrlHandlingStrategy} from './url_handling_strategy';
import {containsTree, createEmptyUrlTree, UrlSerializer, UrlTree} from './url_tree';
import {standardizeConfig, validateConfig} from './utils/config';
import {Checks, getAllRouteGuards} from './utils/preactivation';
import {isUrlTree} from './utils/type_guards';

View File

@ -10,8 +10,9 @@ import {Compiler, InjectionToken, Injector, NgModuleFactory, NgModuleFactoryLoad
import {from, Observable, of} from 'rxjs';
import {map, mergeMap} from 'rxjs/operators';
import {LoadChildren, LoadedRouterConfig, Route, standardizeConfig} from './config';
import {LoadChildren, LoadedRouterConfig, Route} from './config';
import {flatten, wrapIntoObservable} from './utils/collection';
import {standardizeConfig} from './utils/config';
/**
* The [DI token](guide/glossary/#di-token) for a router configuration.

View File

@ -0,0 +1,115 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {EmptyOutletComponent} from '../components/empty_outlet';
import {Route, Routes} from '../config';
import {PRIMARY_OUTLET} from '../shared';
export function validateConfig(config: Routes, parentPath: string = ''): void {
// forEach doesn't iterate undefined values
for (let i = 0; i < config.length; i++) {
const route: Route = config[i];
const fullPath: string = getFullPath(parentPath, route);
validateNode(route, fullPath);
}
}
function validateNode(route: Route, fullPath: string): void {
if (!route) {
throw new Error(`
Invalid configuration of route '${fullPath}': Encountered undefined route.
The reason might be an extra comma.
Example:
const routes: Routes = [
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },
{ path: 'dashboard', component: DashboardComponent },, << two commas
{ path: 'detail/:id', component: HeroDetailComponent }
];
`);
}
if (Array.isArray(route)) {
throw new Error(`Invalid configuration of route '${fullPath}': Array cannot be specified`);
}
if (!route.component && !route.children && !route.loadChildren &&
(route.outlet && route.outlet !== PRIMARY_OUTLET)) {
throw new Error(`Invalid configuration of route '${
fullPath}': a componentless route without children or loadChildren cannot have a named outlet set`);
}
if (route.redirectTo && route.children) {
throw new Error(`Invalid configuration of route '${
fullPath}': redirectTo and children cannot be used together`);
}
if (route.redirectTo && route.loadChildren) {
throw new Error(`Invalid configuration of route '${
fullPath}': redirectTo and loadChildren cannot be used together`);
}
if (route.children && route.loadChildren) {
throw new Error(`Invalid configuration of route '${
fullPath}': children and loadChildren cannot be used together`);
}
if (route.redirectTo && route.component) {
throw new Error(`Invalid configuration of route '${
fullPath}': redirectTo and component cannot be used together`);
}
if (route.path && route.matcher) {
throw new Error(
`Invalid configuration of route '${fullPath}': path and matcher cannot be used together`);
}
if (route.redirectTo === void 0 && !route.component && !route.children && !route.loadChildren) {
throw new Error(`Invalid configuration of route '${
fullPath}'. One of the following must be provided: component, redirectTo, children or loadChildren`);
}
if (route.path === void 0 && route.matcher === void 0) {
throw new Error(`Invalid configuration of route '${
fullPath}': routes must have either a path or a matcher specified`);
}
if (typeof route.path === 'string' && route.path.charAt(0) === '/') {
throw new Error(`Invalid configuration of route '${fullPath}': path cannot start with a slash`);
}
if (route.path === '' && route.redirectTo !== void 0 && route.pathMatch === void 0) {
const exp =
`The default value of 'pathMatch' is 'prefix', but often the intent is to use 'full'.`;
throw new Error(`Invalid configuration of route '{path: "${fullPath}", redirectTo: "${
route.redirectTo}"}': please provide 'pathMatch'. ${exp}`);
}
if (route.pathMatch !== void 0 && route.pathMatch !== 'full' && route.pathMatch !== 'prefix') {
throw new Error(`Invalid configuration of route '${
fullPath}': pathMatch can only be set to 'prefix' or 'full'`);
}
if (route.children) {
validateConfig(route.children, fullPath);
}
}
function getFullPath(parentPath: string, currentRoute: Route): string {
if (!currentRoute) {
return parentPath;
}
if (!parentPath && !currentRoute.path) {
return '';
} else if (parentPath && !currentRoute.path) {
return `${parentPath}/`;
} else if (!parentPath && currentRoute.path) {
return currentRoute.path;
} else {
return `${parentPath}/${currentRoute.path}`;
}
}
/**
* Makes a copy of the config and adds any default required properties.
*/
export function standardizeConfig(r: Route): Route {
const children = r.children && r.children.map(standardizeConfig);
const c = children ? {...r, children} : {...r};
if (!c.component && (children || c.loadChildren) && (c.outlet && c.outlet !== PRIMARY_OUTLET)) {
c.component = EmptyOutletComponent;
}
return c;
}

View File

@ -6,8 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import {validateConfig} from '../src/config';
import {PRIMARY_OUTLET} from '../src/shared';
import {validateConfig} from '../src/utils/config';
describe('config', () => {
describe('validateConfig', () => {

View File

@ -55,7 +55,7 @@ export function patchTimer(window: any, setName: string, cancelName: string, nam
}
function clearTask(task: Task) {
return clearNative!((<TimerOptions>task.data).handleId);
return clearNative!.call(window, (<TimerOptions>task.data).handleId);
}
setNative =

View File

@ -16,6 +16,7 @@ Zone.__load_patch('EventEmitter', (global: any) => {
const EE_REMOVE_ALL_LISTENER = 'removeAllListeners';
const EE_LISTENERS = 'listeners';
const EE_ON = 'on';
const EE_OFF = 'off';
const compareTaskCallbackVsDelegate = function(task: any, delegate: any) {
// same callback, same capture, same event name, just return
@ -47,6 +48,7 @@ Zone.__load_patch('EventEmitter', (global: any) => {
});
if (result && result[0]) {
obj[EE_ON] = obj[EE_ADD_LISTENER];
obj[EE_OFF] = obj[EE_REMOVE_LISTENER];
}
}

View File

@ -115,7 +115,7 @@ type ZoneSubscriberContext = {
_zoneUnsubscribe: {value: null, writable: true, configurable: true},
_unsubscribe: {
get: function(this: Subscription) {
if ((this as any)._zoneUnsubscribe) {
if ((this as any)._zoneUnsubscribe || (this as any)._zoneUnsubscribeCleared) {
return (this as any)._zoneUnsubscribe;
}
const proto = Object.getPrototypeOf(this);
@ -125,7 +125,13 @@ type ZoneSubscriberContext = {
(this as any)._zone = Zone.current;
if (!unsubscribe) {
(this as any)._zoneUnsubscribe = unsubscribe;
// In some operator such as `retryWhen`, the _unsubscribe
// method will be set to null, so we need to set another flag
// to tell that we should return null instead of finding
// in the prototype chain.
(this as any)._zoneUnsubscribeCleared = true;
} else {
(this as any)._zoneUnsubscribeCleared = false;
(this as any)._zoneUnsubscribe = function() {
if (this._zone && this._zone !== Zone.current) {
return this._zone.run(unsubscribe, this, arguments);

View File

@ -223,9 +223,9 @@ interface Zone {
* @param task to run
* @param applyThis
* @param applyArgs
* @returns {*}
* @returns {any} Value from the `task.callback` function.
*/
runTask(task: Task, applyThis?: any, applyArgs?: any): any;
runTask<T>(task: Task, applyThis?: any, applyArgs?: any): T;
/**
* Schedule a MicroTask.

View File

@ -6,7 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/
import {patchTimer} from '../../lib/common/timers';
import {isNode, zoneSymbol} from '../../lib/common/utils';
declare const global: any;
const wtfMock = global.wtfMock;
@ -56,6 +58,25 @@ describe('setTimeout', function() {
});
});
it('should call native clearTimeout with the correct context', function() {
// since clearTimeout has been patched already, we can not test `clearTimeout` directly
// we will fake another API patch to test
let context: any = null;
const fakeGlobal = {
setTimeout: function() {
return 1;
},
clearTimeout: function(id: number) {
context = this;
}
};
patchTimer(fakeGlobal, 'set', 'clear', 'Timeout')
const cancelId = fakeGlobal.setTimeout();
const m = fakeGlobal.clearTimeout;
m.call({}, cancelId);
expect(context).toBe(fakeGlobal);
});
it('should allow cancelation of fns registered with setTimeout after invocation', function(done) {
const testZone = Zone.current.fork((Zone as any)['wtfZoneSpec']).fork({name: 'TestZone'});
testZone.run(() => {

View File

@ -66,6 +66,18 @@ describe('nodejs EventEmitter', () => {
emitter.emit('test2', 'test value');
});
});
it('should remove listeners by calling off properly', () => {
zoneA.run(() => {
emitter.on('test', shouldNotRun);
emitter.on('test2', shouldNotRun);
emitter.off('test', shouldNotRun);
});
zoneB.run(() => {
emitter.off('test2', shouldNotRun);
emitter.emit('test', 'test value');
emitter.emit('test2', 'test value');
});
});
it('remove listener should return event emitter', () => {
zoneA.run(() => {
emitter.on('test', shouldNotRun);

View File

@ -0,0 +1,41 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Observable, of, throwError, timer} from 'rxjs';
import {catchError, finalize, mergeMap, retryWhen} from 'rxjs/operators';
describe('retryWhen', () => {
let log: any[];
const genericRetryStrategy = (finalizer: () => void) => (attempts: Observable<any>) =>
attempts.pipe(
mergeMap((error, i) => {
const retryAttempt = i + 1;
if (retryAttempt > 3) {
return throwError(error);
}
log.push(error);
return timer(retryAttempt * 1);
}),
finalize(() => finalizer()));
const errorGenerator = () => {
return throwError(new Error('error emit'));
};
beforeEach(() => {
log = [];
});
it('should retry max 3 times',
(done: DoneFn) => {errorGenerator()
.pipe(
retryWhen(genericRetryStrategy(() => {
expect(log.length).toBe(3);
done();
})),
catchError(error => of(error)))
.subscribe()});
});

View File

@ -27,6 +27,7 @@ import './rxjs.merge.spec';
import './rxjs.never.spec';
import './rxjs.of.spec';
import './rxjs.range.spec';
import './rxjs.retry.spec';
import './rxjs.throw.spec';
import './rxjs.timer.spec';
import './rxjs.zip.spec';

View File

@ -24,7 +24,7 @@ def ts_api_guardian_test(
golden,
actual,
data = [],
strip_export_pattern = ["^__", "^ɵ[^ɵ]"],
strip_export_pattern = [],
allow_module_identifiers = COMMON_MODULE_IDENTIFIERS,
use_angular_tag_rules = True,
**kwargs):
@ -49,8 +49,9 @@ def ts_api_guardian_test(
]
for i in strip_export_pattern:
# The below replacement is needed because under Windows '^' needs to be escaped twice
args += ["--stripExportPattern", i.replace("^", "^^^^")]
# Quote the regexp before passing it via the command line.
quoted_pattern = "\"%s\"" % i
args += ["--stripExportPattern", quoted_pattern]
for i in allow_module_identifiers:
args += ["--allowModuleIdentifiers", i]
@ -82,7 +83,7 @@ def ts_api_guardian_test_npm_package(
goldenDir,
actualDir,
data = [],
strip_export_pattern = ["^__", "^ɵ[^ɵ]"],
strip_export_pattern = ["^ɵ(?!ɵdefineInjectable|ɵinject|ɵInjectableDef)"],
allow_module_identifiers = COMMON_MODULE_IDENTIFIERS,
use_angular_tag_rules = True,
**kwargs):
@ -109,8 +110,9 @@ def ts_api_guardian_test_npm_package(
]
for i in strip_export_pattern:
# The below replacement is needed because under Windows '^' needs to be escaped twice
args += ["--stripExportPattern", i.replace("^", "^^^^")]
# Quote the regexp before passing it via the command line.
quoted_pattern = "\"%s\"" % i
args += ["--stripExportPattern", quoted_pattern]
for i in allow_module_identifiers:
args += ["--allowModuleIdentifiers", i]

View File

@ -47,7 +47,7 @@ export function startCli() {
options.exportTags = {
requireAtLeastOne: ['publicApi', 'codeGenApi'],
banned: ['experimental'],
toCopy: ['deprecated']
toCopy: ['deprecated', 'codeGenApi']
};
options.memberTags = {
requireAtLeastOne: [],