Compare commits
52 Commits
Author | SHA1 | Date | |
---|---|---|---|
cdda60a430 | |||
7570356bfa | |||
c4a97d822e | |||
fc4dfc5eb1 | |||
25d95dae6d | |||
1c4fcce2a1 | |||
6e73faaed7 | |||
41c9910613 | |||
aaddef213d | |||
02f3aee1db | |||
c27ba96093 | |||
c5a474cb54 | |||
d5264f5645 | |||
0cd4b87021 | |||
b1e7775a8a | |||
87f5feff11 | |||
c3ddc3d6b1 | |||
cec39a7d16 | |||
c6c8e15813 | |||
752fd14fe5 | |||
776067cd43 | |||
e87a46be21 | |||
89a7ff3ada | |||
3d6e50dc02 | |||
264950bbf2 | |||
84c5be0b5b | |||
eda8f2f8b9 | |||
cc52945d00 | |||
07f184a69d | |||
a123ef58b1 | |||
024126dde4 | |||
4275c34818 | |||
c4e6f585c5 | |||
7467fd36b9 | |||
aca01985fd | |||
eb5e14e6e0 | |||
b8af10902f | |||
f411c9e5b9 | |||
7f455e6eec | |||
e36caafa52 | |||
5e89d98876 | |||
200dbd4860 | |||
c90952884a | |||
7c2d8fc672 | |||
a50a688aaf | |||
6ec7297e43 | |||
f264cd1cb8 | |||
fc17bddcde | |||
0765626761 | |||
f146b34042 | |||
f899d6ea44 | |||
c18d7a1469 |
@ -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
|
||||
|
13
CHANGELOG.md
13
CHANGELOG.md
@ -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)
|
||||
|
||||
|
1
aio/content/examples/.gitignore
vendored
1
aio/content/examples/.gitignore
vendored
@ -18,6 +18,7 @@
|
||||
**/src/karma.conf.js
|
||||
**/.angular-cli.json
|
||||
**/.editorconfig
|
||||
**/.gitignore
|
||||
**/angular.json
|
||||
**/tsconfig.json
|
||||
**/bs-config.e2e.json
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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 don’t 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 won’t 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 don’t 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 wouldn’t 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").
|
||||
|
@ -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 what’s 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 file’s code in another file, you import it like this:
|
||||
When you need that module’s 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` decorator’s `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` isn’t being in their own file, like JavaScript modules; it’s 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 doesn’t 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").
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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: `<div *ifLoaded="state">{{ state.data }}</div>`,
|
||||
})
|
||||
export class AppComponent {
|
||||
state: LoadingState<Person>;
|
||||
|
@ -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.
|
||||
|
@ -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>
|
||||
|
||||
|
BIN
aio/content/images/bios/kevin-kreuzer.jpg
Normal file
BIN
aio/content/images/bios/kevin-kreuzer.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
@ -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"]
|
||||
}
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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;
|
||||
|
5
aio/src/typings.d.ts
vendored
5
aio/src/typings.d.ts
vendored
@ -1,5 +0,0 @@
|
||||
/* SystemJS module definition */
|
||||
declare var module: NodeModule;
|
||||
interface NodeModule {
|
||||
id: string;
|
||||
}
|
@ -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.
|
||||
|
@ -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
|
||||
|
||||
|
@ -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"
|
||||
]
|
||||
}
|
@ -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"
|
||||
]
|
||||
}
|
@ -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"
|
||||
]
|
||||
}
|
@ -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"
|
||||
]
|
||||
}
|
@ -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"
|
||||
]
|
||||
}
|
@ -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"
|
||||
]
|
||||
}
|
@ -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": {}
|
||||
}
|
@ -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;
|
@ -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"
|
||||
]
|
||||
}
|
@ -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"
|
||||
]
|
||||
}
|
@ -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"
|
||||
]
|
||||
}
|
@ -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"
|
||||
]
|
||||
}
|
@ -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' });
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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')})`,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -1,5 +0,0 @@
|
||||
/* SystemJS module definition */
|
||||
declare var module: NodeModule;
|
||||
interface NodeModule {
|
||||
id: string;
|
||||
}
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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": {}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
@ -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);
|
@ -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",
|
||||
|
@ -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"],
|
||||
)
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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[]);
|
||||
}
|
||||
|
89
dev-infra/pullapprove/pullapprove_arrays.ts
Normal file
89
dev-infra/pullapprove/pullapprove_arrays.ts
Normal 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);
|
||||
}
|
||||
}
|
24
dev-infra/pullapprove/utils.ts
Normal file
24
dev-infra/pullapprove/utils.ts
Normal 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;
|
||||
}
|
88
dev-infra/pullapprove/verify.spec.ts
Normal file
88
dev-infra/pullapprove/verify.spec.ts
Normal 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);
|
||||
}
|
@ -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.
|
||||
|
@ -16,6 +16,7 @@ ts_library(
|
||||
"@npm//@types/node",
|
||||
"@npm//@types/shelljs",
|
||||
"@npm//chalk",
|
||||
"@npm//inquirer",
|
||||
"@npm//shelljs",
|
||||
"@npm//tslib",
|
||||
"@npm//typed-graphqlify",
|
||||
|
@ -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.
|
||||
|
400
goldens/public-api/core/core.d.ts
vendored
400
goldens/public-api/core/core.d.ts
vendored
@ -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 {
|
||||
|
3
goldens/public-api/core/testing/testing.d.ts
vendored
3
goldens/public-api/core/testing/testing.d.ts
vendored
@ -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> {
|
||||
|
@ -12,7 +12,7 @@
|
||||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime-es2015": 2987,
|
||||
"main-es2015": 450301,
|
||||
"main-es2015": 448184,
|
||||
"polyfills-es2015": 52630
|
||||
}
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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.
|
||||
|
@ -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', () => {
|
||||
|
@ -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;
|
||||
}
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -271,7 +271,7 @@ export interface Attribute {
|
||||
/**
|
||||
* The name of the attribute whose value can be injected.
|
||||
*/
|
||||
attributeName?: string;
|
||||
attributeName: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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__ = '';
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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.
|
||||
|
115
packages/router/src/utils/config.ts
Normal file
115
packages/router/src/utils/config.ts
Normal 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;
|
||||
}
|
@ -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', () => {
|
||||
|
@ -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 =
|
||||
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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(() => {
|
||||
|
@ -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);
|
||||
|
41
packages/zone.js/test/rxjs/rxjs.retry.spec.ts
Normal file
41
packages/zone.js/test/rxjs/rxjs.retry.spec.ts
Normal 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()});
|
||||
});
|
@ -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';
|
||||
|
@ -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]
|
||||
|
@ -47,7 +47,7 @@ export function startCli() {
|
||||
options.exportTags = {
|
||||
requireAtLeastOne: ['publicApi', 'codeGenApi'],
|
||||
banned: ['experimental'],
|
||||
toCopy: ['deprecated']
|
||||
toCopy: ['deprecated', 'codeGenApi']
|
||||
};
|
||||
options.memberTags = {
|
||||
requireAtLeastOne: [],
|
||||
|
Reference in New Issue
Block a user