Compare commits

...

55 Commits

Author SHA1 Message Date
8d5ab908fc ci: add a change 2019-01-06 15:00:11 +00:00
1dd11c99cc ci: add linux buildkite tests 2019-01-06 14:53:27 +00:00
784453cf9d ci: use image based cache 2019-01-05 22:17:41 +00:00
51a0bd2e75 test(ivy): correct attribute marker in broken test (#27919)
Test incorrectly marks the attribute as a 'Class', when it should be 'SelectOnly'

PR Close #27919
2019-01-03 16:10:00 -08:00
1b0b36d143 fix(forms): match getError and hasError to get method signature (#20211)
Internally getError and hasError call the AbstractControl#get method which takes  `path: Array<string | number> | string` as input, since there are different ways to traverse the AbstractControl tree.
This change matches the method signitures of all methods that use this.

PR Close #20211
2019-01-03 10:16:06 -08:00
e268a0579a docs(forms): update desc for hasError and getError (#27861)
This commit adds docs for the changes made in #20211.

Closes #19734.

PR Close #27861
2019-01-03 10:15:23 -08:00
1f1e77b641 fix(ivy): don’t publish animation bindings as attributes (#27805)
Some of the animation tests have been failing because animation gets
triggered multiple times. The reason for this is that the compiler was
generating static attribute bindings in addition to dynamic bindings.
This created multiple writes to the animation render which failed the
tests.

PR Close #27805
2019-01-03 09:39:25 -08:00
880b4aabdb fix(docs-infra): upgrade to latest dgeni-packages to fix linking problem (#27864)
This new version of dgeni-packages gives the main (implemented)
overload of a method the correct id and aliases, which allow it to be
automatically linked.

See 398f35da30

Fixes #27820
Closes #27821

PR Close #27864
2019-01-03 09:38:06 -08:00
5fee444fea test(ivy): update test failure root cause (#27855)
Updates the root cause for a couple of the test failures.

PR Close #27855
2019-01-03 09:37:26 -08:00
1e6c9be86c fix(bazel): unable to launch protractor test on windows (#27850)
Due to an incorrect environment variable name, it's currently not possible to launch Protractor on Windows using the Bazel protractor rule.

PR Close #27850
2019-01-03 09:36:37 -08:00
a9790018df fix(ivy): resolve forwardRefs correctly in TestBed (JIT) (#27737)
Forward refs in some places (like imports/export/providers/viewProviders/queries) were not resolved before passing to compilation phase. Now we resolve missing refs before invoking compile function.

PR Close #27737
2019-01-03 09:34:15 -08:00
1dc95c41eb docs: fix typo in TOH http section (#26127)
PR Close #26127
2019-01-03 09:32:28 -08:00
a0a3648e7a docs: add more info on what is being cached (#27343)
PR Close #27343
2019-01-03 09:29:44 -08:00
7df9040c05 docs: add missing comma to environment config in build guide (#27476)
PR Close #27476
2019-01-03 09:29:17 -08:00
5638c1d507 docs: fix npm description in architecture modules guide (#27818)
PR Close #27818
2019-01-03 09:28:45 -08:00
755c8091af docs: improve description of bundle budgets (#27833)
PR Close #27833
2019-01-03 09:28:16 -08:00
59f64dd361 docs(service-worker): update service worker configuration doc (#27868)
PR Close #27868
2019-01-03 09:27:30 -08:00
27431e0e1e test(docs-infra): increase timeout for redirection to external URL (#27903)
Occasionally, external URLs take longer to load, which causes CI flakes.

PR Close #27903
2019-01-03 09:26:54 -08:00
b7d0ab7de3 test(ivy): enable more @angular/core tests (#27912)
PR Close #27912
2019-01-03 09:22:22 -08:00
460be795cf test(ivy): add root cause analysis for failing core tests (view injector integration) (#27912)
PR Close #27912
2019-01-03 09:22:22 -08:00
c4f7727408 docs: add api doc for viewport scroller (#27381)
PR Close #27381
2018-12-26 11:47:16 -08:00
d1de9ff313 build: update to latest rules_nodejs (#27764)
This includes a performance fix for module resolution in the common case under Ivy

PR Close #27764
2018-12-26 11:31:37 -08:00
707c6828b5 docs(elements): add "Firefox" for support of custom elements (#27789)
Firefox now supports custom elements: https://caniuse.com/#feat=custom-elementsv1

PR Close #27789
2018-12-26 11:27:45 -08:00
3f64e87ed1 test: check payload size for cli-hello-world-ivy (#27797)
Adds size bundle checking to the integration test `cli-hello-world-ivy`

PR Close #27797
2018-12-26 11:19:19 -08:00
62e45cef2d test: update cli-hello-world-ivy to cli@7.2.0-rc.0 (#27797)
Updates the app itself to reflect the result of using the  `experimentalIvy` flag on the CLI.
The result is similar to:

    npx @angular/cli@next new cli-hello-world-ivy --experimental-ivy --defaults

But replaces the current (cli `7.2.0-rc.0`) `renderComponent` bootstrap with the usual `platformBrowserDynamic` one.
It also keeps what the app did (display a pipe, tests it).

PR Close #27797
2018-12-26 11:19:19 -08:00
c34eee4e8e test(ivy): root cause analysis for @angular/platform-server (#27800)
PR Close #27800
2018-12-26 11:17:45 -08:00
9b91beed69 docs: fix typo explictly (#27798)
Closes #27796

PR Close #27798
2018-12-26 06:57:55 -05:00
eea2b0f288 revert: fix(router): ensure URL is updated after second redirect with UrlUpdateStrategy="eager" (#27523) 2018-12-25 22:14:07 -08:00
13eb57a59f fix(ivy): merge static style rendering across elements, directives and components (#27661)
PR Close #27661
2018-12-21 18:14:44 -05:00
0b3ae3d70c build: update to latest karma version (#27735)
Updates to the latest Karma version that includes karma-runner/karma@cc2eff2 and should be able to properly restart disconnected browsers. This was a long-term Karma bug and affected CI flakiness significantly.

PR Close #27735
2018-12-21 17:02:50 -05:00
4b67b0af3e fix(ivy): adding TestBed.overrideProvider support (#27693)
Prior to this change, provider overrides defined via TestBed.overrideProvider were not applied to Components/Directives. Now providers are taken into account while compiling Components/Directives (metadata is updated accordingly before being passed to compilation).

PR Close #27693
2018-12-21 16:39:34 -05:00
bba5e2632e docs(forms): clarify the pattern validator behavior (#27560)
PR Close #27560
2018-12-21 15:12:31 -05:00
c5ce4e62c6 docs: fix code example to ensure consistency in file (#26577)
PR Close #26577
2018-12-21 15:12:05 -05:00
509aa61619 fix(ivy): apply all overrides from TestBed, not the last one only (#27734)
In some cases in our tests we can define multiple overrides for a given class. As a result, only the last override is actually applied due to the fact that we store overrides in a Type<->Override map. This update changes the logic to keep all overrides defined in a given test for a Type (i.e. Type<->Override[] map) and applies them one by one at resolution phase. This behavior is more inline with the previous TestBed.

PR Close #27734
2018-12-21 15:09:00 -05:00
cdd737e28b test(ivy): mark tests using getDebugContext as obsolete in ivy (#27790)
PR Close #27790
2018-12-21 15:07:31 -05:00
5da55d6246 test(ivy): add root cause analysis for failing router tests (#27792)
PR Close #27792
2018-12-21 15:06:56 -05:00
50a91ba28c docs(upgrade): add gotchas/tips/example for multiple downgraded modules (#27217)
PR Close #27217
2018-12-20 16:20:41 -05:00
077a5fb04b test(upgrade): test injector tree traversal for downgraded components (#27217)
This commit adds tests that verify the current behavior wrt injector
tree traversal for downgraded components, so that it is easier to
contrast with changed behavior is future commits (should we decide
to actually change it).

PR Close #27217
2018-12-20 16:20:41 -05:00
bc0ee01d09 fix(upgrade): allow nesting components from different downgraded modules (#27217)
PR Close #27217
2018-12-20 16:20:41 -05:00
326b464d20 fix(upgrade): correctly handle nested downgraded components with downgradeModule() (#27217)
Previously, nested downgraded components would not be created/destroyed
inside the Angular zone (as they should) and they would not be wired up
correctly for change detection.

This commit ensures that ngUpgrade correctly detects whether this is an
ngUpgradeLite app (i.e. one using `downgradeModule()` instead of
`UpgradeModule`) and appropriately handles components, even if they are
nested inside other downgraded components.

Fixes #22581
Closes #22869
Closes #27083

PR Close #27217
2018-12-20 16:20:41 -05:00
8a7498e0ef refactor(upgrade): simplify special handling of ngUpgradeLite in downgradeComponent() (#27217)
PR Close #27217
2018-12-20 16:20:41 -05:00
07ada7f3d9 fix(router): ensure URL is updated after second redirect with UrlUpdateStrategy="eager" (#27523)
Navigating to a route such as `/users`, you may get redirected to `/login`. Previously, if you go then route to `/users` again the URL will end up showing `/users` after the second redirect. This only happened in `UrlUpdateStrategy="eager"`. This is now fixed so after the second redirect, the URL shows the correct page.

Fixes #27116

PR Close #27523
2018-12-20 16:18:52 -05:00
880e8a5cfc refactor: fix broken formatting 2018-12-20 09:16:09 -08:00
a0585c9a9a fix(ivy): correct content projection with nested templates (#27755)
Previously ivy code generation was emmiting the projectionDef instruction in
a template where the <ng-content> tag was found. This code generation logic was
incorrect since the ivy runtime expects the projectionDef instruction to be present
in the main template only.

This PR ammends the code generation logic so that the projectionDef instruction is
emmitedin the main template only.

PR Close #27755
2018-12-20 12:01:20 -05:00
a833b98fd0 fix(ivy): change detection strategy not being passed to compiler (#27753)
Fixes the defined change detection strategy not being passed to the compiler when a component is being compiled.

PR Close #27753
2018-12-20 12:00:58 -05:00
4b70a4e905 feat(ivy): support NgModule metadata from calls that do not return ModuleWithProviders types (#27326)
Normally functions that return `ModuleWithProvider` objects should parameterize
the return type to include the type of `NgModule` that is being returned. For
example `forRoot(): ModuleWithProviders<RouterModule>`.

But in some cases, especially those generated by nccc, these functions to not
explicitly declare `ModuleWithProviders` as their return type. Instead they
return a "intersection" type, one of whose members is a type literal that
declares the `NgModule` type returned. For example:
`forRoot(): CustomType&{ngModule:RouterModule}`.

This commit changes the `NgModuleDecoratorHandler` so that it can extract
the `NgModule` type from either kind of declaration.

PR Close #27326
2018-12-20 11:58:50 -05:00
f2a1c66031 feat(ivy): ngcc - add typings to ModuleWithProviders functions (#27326)
Exported functions or static method that return a `ModuleWithProviders`
compatible structure need to provide information about the referenced
`NgModule` type in their return type.

This allows ngtsc to be able to understand the type of `NgModule` that is
being returned from calls to the function, without having to dig into the
internals of the compiled library.

There are two ways to provide this information:

* Add a type parameter to the `ModuleWithProviders` return type. E.g.

```
static forRoot(): ModuleWithProviders<SomeNgModule>;
```

* Convert the return type to a union that includes a literal type. E.g.

```
static forRoot(): (SomeOtherType)&{ngModule:SomeNgModule};
```

This commit updates the rendering of typings files to include this type
information on all matching functions/methods.

PR Close #27326
2018-12-20 11:58:49 -05:00
cfb8c17511 feat(ivy): ngcc - map functions as well as classes from source to typings (#27326)
To support updating `ModuleWithProviders` calls,
we need to be able to map exported functions between
source and typings files, as well as classes.

PR Close #27326
2018-12-20 11:58:49 -05:00
99d0e27587 test(ivy): update root cause for core test (#27768)
PR Close #27768
2018-12-20 11:58:14 -05:00
b08f3acf09 test(bazel): Make sure CLI project created with Bazel works with original workflow (#27741)
Bazel bits added to a CLI project should not be destructive.
The project should still work under the original CLI workflow.

PR Close #27741
2018-12-19 18:24:25 -05:00
3ab25ab078 refactor: fix broken linting rules due to revert 2018-12-19 13:06:43 -08:00
0604527199 Revert "build: update gulp-clang-format dependency (#27712)" (#27759)
This reverts commit d766ad01db.

As discussed per chat, we want to temporarily revert that change because `gulp-clang-format` expects a more recent version of `clang-format` which comes with new style updates. In order to make sure that the formatting will be enforced in the meanwhile, we need to revert the update.

PR Close #27759
2018-12-19 15:13:36 -05:00
8f8572fd3e fix(ivy): @Host should behave as in View Engine (#27646)
PR Close #27646
2018-12-19 15:12:35 -05:00
e8f7241366 build: fix our copy of Array#find typing (#27742)
It should be nullable, matching the lib.es2015.d.ts from TypeScript

PR Close #27742
2018-12-19 15:11:52 -05:00
a20b2f72f2 fix(ivy): process creation mode deeply before running update mode (#27744)
Prior to this commit, we had two different modes for change detection
execution for Ivy, depending on whether you called `bootstrap()` or
`renderComponent()`. In the former case, we would complete creation
mode for all components in the tree before beginning update mode for
any component. In the latter case, we would run creation mode and
update mode together for each component individually.

Maintaining code to support these two different execution orders was
unnecessarily complex, so this commit aligns the two bootstrapping
mechanisms to execute in the same order. Now creation mode always
runs for all components before update mode begins.

This change also simplifies our rendering logic so that we use
`LView` flags as the source of truth for rendering mode instead of
`rf` function arguments. This fixed some related bugs (e.g. calling
`ViewRef.detectChanges` synchronously after the view's creation
would create view nodes twice, view queries would execute twice, etc).

PR Close #27744
2018-12-19 15:10:16 -05:00
175 changed files with 11646 additions and 6864 deletions

View File

@ -0,0 +1,14 @@
version: '3'
services:
windows-env:
build:
context: ../../
dockerfile: ./.buildkite/dockerfiles/windows-env.Dockerfile
windows-test:
build:
context: ../../
dockerfile: ./.buildkite/dockerfiles/windows-test.Dockerfile
linux-test:
build:
context: ../../
dockerfile: ./.buildkite/dockerfiles/linux-test.Dockerfile

View File

@ -0,0 +1,21 @@
FROM node:10.12
# Bazel Ubuntu pre-requisites.
# https://docs.bazel.build/versions/master/install-ubuntu.html
RUN apt-get update;
RUN apt-get -y install pkg-config zip g++ zlib1g-dev unzip python
# Chrome prerequisites.
# Based on https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md#running-puppeteer-in-docker
RUN apt-get install -y wget --no-install-recommends \
&& wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
&& sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
&& apt-get update \
&& apt-get install -y google-chrome-unstable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst ttf-freefont \
--no-install-recommends \
&& rm -rf /var/lib/apt/lists/* \
&& apt-get purge --auto-remove -y curl \
&& rm -rf /src/*.deb
# Work back from https://github.com/CircleCI-Public/circleci-dockerfiles/blob/master/node/images/10.12.0-jessie/Dockerfile to get Chrome working properly

View File

@ -0,0 +1,32 @@
FROM gcr.io/internal-200822/angular-linux:latest
USER root
# TODO: Delete the above once the bootstrap image is available.
WORKDIR /src
# Copy package.json and yarn.lock before the other files.
# This allows docker to cache these steps even if source files change.
COPY ./package.json /src/package.json
COPY ./yarn.lock /src/yarn.lock
COPY ./tools/yarn/check-yarn.js /src/tools/yarn/check-yarn.js
COPY ./tools/postinstall-patches.js /src/tools/postinstall-patches.js
RUN yarn install --frozen-lockfile --non-interactive --network-timeout 100000
# Setup files.
COPY ./ /src
COPY .circleci/bazel.rc /etc/bazel.bazelrc
# Workaround symlink when building image on Windows.
RUN rm /src/packages/upgrade/static/src
RUN ln -s ../src /src/packages/upgrade/static/src
# Run tests.
RUN yarn bazel test //tools/ts-api-guardian:all --noshow_progress
# RUN yarn bazel build //... --build_tag_filters=-ivy-only --test_tag_filters=-ivy-only,-local
# RUN yarn bazel build --define=compile=aot --build_tag_filters=-no-ivy-aot,-fixme-ivy-aot --test_tag_filters=-no-ivy-aot,-fixme-ivy-aot //...
# RUN yarn bazel test //... --build_tag_filters=-ivy-only --test_tag_filters=-ivy-only,-local
# RUN yarn test-ivy-aot //...
# docker build -t angular:latest .
# docker build . --build-arg target=angular:latest

View File

@ -0,0 +1,21 @@
# Use the a previous image as source, or bootstrap to the default image.
ARG target=filipesilva/node-bazel-windows:0.0.2
FROM $target
WORKDIR /src
# Copy package.json and yarn.lock before the other files.
# This allows docker to cache these steps even if source files change.
COPY ./package.json /src/package.json
COPY ./yarn.lock /src/yarn.lock
RUN yarn install --frozen-lockfile --non-interactive --network-timeout 100000
# Copy files.
COPY ./ /src
# Setup.
COPY .circleci/bazel.rc /etc/bazel.bazelrc
RUN del packages\upgrade\static\src
RUN mklink /d packages\upgrade\static\src ..\src
# Run tests.
RUN yarn bazel test //tools/ts-api-guardian:all --noshow_progress

View File

@ -1,10 +1,36 @@
steps: steps:
- label: windows-test - label: windows-test
commands:
- "yarn install --frozen-lockfile --non-interactive --network-timeout 100000"
- "yarn bazel test //tools/ts-api-guardian:all --noshow_progress"
plugins: plugins:
- docker#v2.1.0: - docker-compose#v2.6.0:
image: "filipesilva/node-bazel-windows:0.0.2" build: windows-test
config: .buildkite/dockerfiles/docker-compose.yml
args:
- target=gcr.io/internal-200822/angular-windows:master
agents: agents:
windows: true windows: true
- label: linux-test
plugins:
- docker-compose#v2.6.0:
build: linux-test
config: .buildkite/dockerfiles/docker-compose.yml
# args:
# - target=gcr.io/internal-200822/angular-linux:master
agents:
linux: true
- wait
# - label: windows-update-image
# branches: master
# plugins:
# - docker-compose#v2.6.0:
# push: windows-test:gcr.io/internal-200822/angular-windows:master
# config: .buildkite/dockerfiles/docker-compose.yml
# agents:
# windows: true
# - label: linux-update-image
# # branches: master
# plugins:
# - docker-compose#v2.6.0:
# push: linux-test:gcr.io/internal-200822/angular-linux:master
# config: .buildkite/dockerfiles/docker-compose.yml
# agents:
# linux: true

View File

@ -0,0 +1,26 @@
Follow https://buildkite.com/docs/agent/v3/gcloud#running-the-agent-on-google-container-engine
but :
on the cluster creation chose n1-highcpu-16 instances, and give "read write" permissions to Storage
on the "Create a deployment to start an agent:" step use change
```
env:
- name: BUILDKITE_AGENT_TOKEN
valueFrom: {secretKeyRef: {name: buildkite-agent, key: token}}
```
to
```
env:
- name: BUILDKITE_AGENT_TOKEN
valueFrom: {secretKeyRef: {name: buildkite-agent, key: token}}
- name: BUILDKITE_AGENT_TAGS
value: "linux=true"
- name: BUILDKITE_TIMESTAMP_LINES
value: "true"
```
config kubernetes to access container registry
maybe just give write access to storage https://medium.com/google-cloud/updating-google-container-engine-vm-scopes-with-zero-downtime-50bff87e5f80
https://cloud.google.com/container-registry/docs/using-with-google-cloud-platform
https://container-solutions.com/using-google-container-registry-with-kubernetes/

View File

@ -8,6 +8,8 @@
# We recommend machine type n1-highcpu-16 (16 vCPUs, 14.4 GB memory). # We recommend machine type n1-highcpu-16 (16 vCPUs, 14.4 GB memory).
# Use a windows boot disk with container support such as # Use a windows boot disk with container support such as
# "Windows Server version 1803 Datacenter Core for Containers". # "Windows Server version 1803 Datacenter Core for Containers".
# Give it push access to the Container Registry by clicking "Set access for each API" and
# setting "Storage" to "Read Write".
# Give it a name, then click "Create". # Give it a name, then click "Create".
# VM setup: # VM setup:
@ -56,6 +58,15 @@ Invoke-WebRequest -Uri https://github.com/git-for-windows/git/releases/download/
Add-Path "C:\git\bin" Add-Path "C:\git\bin"
Remove-Item git.exe Remove-Item git.exe
# Install Docker Compose 1.23.2
# https://docs.docker.com/compose/install/#install-compose
Invoke-WebRequest "https://github.com/docker/compose/releases/download/1.23.2/docker-compose-Windows-x86_64.exe" -UseBasicParsing -OutFile $Env:ProgramFiles\docker\docker-compose.exe
# Add the gcloud Docker credential helper to the local system account (used by NSSM)
# https://cloud.google.com/container-registry/docs/advanced-authentication
gcloud auth configure-docker --quiet
Copy-Item C:\Users\angular\.docker -Destination C:\Windows\System32\config\systemprofile\ -Recurse
# Download NSSM (https://nssm.cc/) to run the BuildKite agent as a service. # Download NSSM (https://nssm.cc/) to run the BuildKite agent as a service.
Write-Host "Downloading NSSM." Write-Host "Downloading NSSM."
Invoke-WebRequest -Uri https://nssm.cc/ci/nssm-2.24-101-g897c7ad.zip -OutFile nssm.zip Invoke-WebRequest -Uri https://nssm.cc/ci/nssm-2.24-101-g897c7ad.zip -OutFile nssm.zip

View File

@ -74,7 +74,10 @@ Command syntax is shown as follows:
* Option names are prefixed with a double dash (--). * Option names are prefixed with a double dash (--).
Option aliases are prefixed with a single dash (-). Option aliases are prefixed with a single dash (-).
Arguments are not prefixed. Arguments are not prefixed.
For example: `ng build my-app -c production` For example:
<code-example format="." language="bash">
ng build my-app -c production
</code-example>
* Typically, the name of a generated artifact can be given as an argument to the command or specified with the --name option. * Typically, the name of a generated artifact can be given as an argument to the command or specified with the --name option.

View File

@ -81,7 +81,7 @@ Other JavaScript modules use *import statements* to access public objects from o
<img src="generated/images/guide/architecture/library-module.png" alt="Component" class="left"> <img src="generated/images/guide/architecture/library-module.png" alt="Component" class="left">
Angular loads as a collection of JavaScript modules. You can think of them as library modules. Each Angular library name begins with the `@angular` prefix. Install them with the `npm` package manager and import parts of them with JavaScript `import` statements. Angular loads as a collection of JavaScript modules. You can think of them as library modules. Each Angular library name begins with the `@angular` prefix. Install them with the node package manager `npm` and import parts of them with JavaScript `import` statements.
<br class="clear"> <br class="clear">

View File

@ -54,7 +54,7 @@ The following sets content sets default values for the production build target:
``` ```
export const environment = { export const environment = {
production: true production: true,
apiUrl: 'http://my-prod-url' apiUrl: 'http://my-prod-url'
}; };
``` ```
@ -235,31 +235,31 @@ Each budget entry is a JSON object with the following properties:
</tr> </tr>
<tr> <tr>
<td>baseline</td> <td>baseline</td>
<td>An absolute baseline size for percentage values. </td> <td>The baseline size for comparison.</td>
</tr> </tr>
<tr> <tr>
<td>maximumWarning</td> <td>maximumWarning</td>
<td>Warns when a size exceeds this threshold percentage of the baseline.</td> <td>The maximum threshold for warning relative to the baseline.</td>
</tr> </tr>
<tr> <tr>
<td>maximumError</td> <td>maximumError</td>
<td>Reports an error when the size exceeds this threshold percentage of the baseline.</td> <td>The maximum threshold for error relative to the baseline.</td>
</tr> </tr>
<tr> <tr>
<td>minimumWarning</td> <td>minimumWarning</td>
<td>Warns when the size reaches this threshold percentage of the baseline.</td> <td>The minimum threshold for warning relative to the baseline.</td>
</tr> </tr>
<tr> <tr>
<td>minimumError</td> <td>minimumError</td>
<td>Reports an error when the size reaches this threshold percentage of the baseline.</td> <td>The minimum threshold for error relative to the baseline.</td>
</tr> </tr>
<tr> <tr>
<td>warning</td> <td>warning</td>
<td>Warns when the size ??reaches or exceeds?? this threshold percentage of the baseline.</td> <td>The threshold for warning relative to the baseline (min & max).</td>
</tr> </tr>
<tr> <tr>
<td>error</td> <td>error</td>
<td>Reports an error when the size ??reaches or exceeds?? this threshold percentage of the baseline.</td> <td>The threshold for error relative to the baseline (min & max).</td>
</tr> </tr>
</table> </table>

View File

@ -2,7 +2,7 @@
_Angular elements_ are Angular components packaged as _custom elements_, a web standard for defining new HTML elements in a framework-agnostic way. _Angular elements_ are Angular components packaged as _custom elements_, a web standard for defining new HTML elements in a framework-agnostic way.
[Custom elements](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements) are a Web Platform feature currently supported by Chrome, Opera, and Safari, and available in other browsers through polyfills (see [Browser Support](#browser-support)). [Custom elements](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements) are a Web Platform feature currently supported by Chrome, Firefox, Opera, and Safari, and available in other browsers through polyfills (see [Browser Support](#browser-support)).
A custom element extends HTML by allowing you to define a tag whose content is created and controlled by JavaScript code. A custom element extends HTML by allowing you to define a tag whose content is created and controlled by JavaScript code.
The browser maintains a `CustomElementRegistry` of defined custom elements (also called Web Components), which maps an instantiable JavaScript class to an HTML tag. The browser maintains a `CustomElementRegistry` of defined custom elements (also called Web Components), which maps an instantiable JavaScript class to an HTML tag.

View File

@ -90,6 +90,8 @@ The `installMode` determines how these resources are initially cached. The `inst
* `lazy` does not cache any of the resources up front. Instead, the Angular service worker only caches resources for which it receives requests. This is an on-demand caching mode. Resources that are never requested will not be cached. This is useful for things like images at different resolutions, so the service worker only caches the correct assets for the particular screen and orientation. * `lazy` does not cache any of the resources up front. Instead, the Angular service worker only caches resources for which it receives requests. This is an on-demand caching mode. Resources that are never requested will not be cached. This is useful for things like images at different resolutions, so the service worker only caches the correct assets for the particular screen and orientation.
Defaults to `prefetch`.
### `updateMode` ### `updateMode`
For resources already in the cache, the `updateMode` determines the caching behavior when a new version of the app is discovered. Any resources in the group that have changed since the previous version are updated in accordance with `updateMode`. For resources already in the cache, the `updateMode` determines the caching behavior when a new version of the app is discovered. Any resources in the group that have changed since the previous version are updated in accordance with `updateMode`.
@ -98,6 +100,8 @@ For resources already in the cache, the `updateMode` determines the caching beha
* `lazy` tells the service worker to not cache those resources. Instead, it treats them as unrequested and waits until they're requested again before updating them. An `updateMode` of `lazy` is only valid if the `installMode` is also `lazy`. * `lazy` tells the service worker to not cache those resources. Instead, it treats them as unrequested and waits until they're requested again before updating them. An `updateMode` of `lazy` is only valid if the `installMode` is also `lazy`.
Defaults to the value `installMode` is set to.
### `resources` ### `resources`
This section describes the resources to cache, broken up into three groups. This section describes the resources to cache, broken up into three groups.
@ -141,7 +145,7 @@ Occasionally APIs change formats in a way that is not backward-compatible. A new
`version` provides a mechanism to indicate that the resources being cached have been updated in a backwards-incompatible way, and that the old cache entries&mdash;those from previous versions&mdash;should be discarded. `version` provides a mechanism to indicate that the resources being cached have been updated in a backwards-incompatible way, and that the old cache entries&mdash;those from previous versions&mdash;should be discarded.
`version` is an integer field and defaults to `0`. `version` is an integer field and defaults to `1`.
### `cacheConfig` ### `cacheConfig`
This section defines the policy by which matching requests will be cached. This section defines the policy by which matching requests will be cached.

View File

@ -94,6 +94,16 @@ Notice that all of the files the browser needs to render this application are ca
* `favicon.ico`. * `favicon.ico`.
* Build artifacts (JS and CSS bundles). * Build artifacts (JS and CSS bundles).
* Anything under `assets`. * Anything under `assets`.
* Images and fonts directly under the configured `outputPath` (by default `./dist/<project-name>/`) or `resourcesOutputPath`. See [`ng build`](cli/build) for more information about these options.
<div class="alert is-helpful">
Pay attention to two key points:
1. The generated `ngsw-config.json` includes a limited list of cachable fonts and images extentions. In some cases, you might want to modify the glob pattern to suit your needs.
1. If `resourcesOutputPath` or `assets` paths are modified after the generation of configuration file, you need to change the paths manually in `ngsw-config.json`.
</div>
### Making changes to your application ### Making changes to your application

View File

@ -347,7 +347,7 @@ Add the following `addHero()` method to the `HeroService` class.
`HeroService.addHero()` differs from `updateHero` in two ways. `HeroService.addHero()` differs from `updateHero` in two ways.
* it calls `HttpClient.post()` instead of `put()`. * it calls `HttpClient.post()` instead of `put()`.
* it expects the server to generates an id for the new hero, * it expects the server to generate an id for the new hero,
which it returns in the `Observable<Hero>` to the caller. which it returns in the `Observable<Hero>` to the caller.
Refresh the browser and add some heroes. Refresh the browser and add some heroes.

View File

@ -112,7 +112,7 @@
"cross-spawn": "^5.1.0", "cross-spawn": "^5.1.0",
"css-selector-parser": "^1.3.0", "css-selector-parser": "^1.3.0",
"dgeni": "^0.4.11", "dgeni": "^0.4.11",
"dgeni-packages": "^0.27.0", "dgeni-packages": "^0.27.1",
"entities": "^1.1.1", "entities": "^1.1.1",
"eslint": "^3.19.0", "eslint": "^3.19.0",
"eslint-plugin-jasmine": "^2.2.0", "eslint-plugin-jasmine": "^2.2.0",

View File

@ -27,14 +27,16 @@ describe(browser.baseUrl, () => {
describe('(with legacy URLs)', () => { describe('(with legacy URLs)', () => {
page.legacyUrls.forEach(([fromUrl, toUrl], i) => { page.legacyUrls.forEach(([fromUrl, toUrl], i) => {
const isExternalUrl = /^https?:/.test(toUrl);
it(`should redirect '${fromUrl}' to '${toUrl}' (${i + 1}/${page.legacyUrls.length})`, async () => { it(`should redirect '${fromUrl}' to '${toUrl}' (${i + 1}/${page.legacyUrls.length})`, async () => {
await page.goTo(fromUrl); await page.goTo(fromUrl);
const expectedUrl = stripTrailingSlash(/^http/.test(toUrl) ? toUrl : page.baseUrl + toUrl); const expectedUrl = stripTrailingSlash(isExternalUrl ? toUrl : page.baseUrl + toUrl);
const actualUrl = await getCurrentUrl(); const actualUrl = await getCurrentUrl();
expect(actualUrl).toBe(expectedUrl); expect(actualUrl).toBe(expectedUrl);
}); }, isExternalUrl ? 60000 : undefined);
}); });
}); });

View File

@ -2722,10 +2722,10 @@ devtools-timeline-model@1.1.6:
chrome-devtools-frontend "1.0.401423" chrome-devtools-frontend "1.0.401423"
resolve "1.1.7" resolve "1.1.7"
dgeni-packages@^0.27.0: dgeni-packages@^0.27.1:
version "0.27.0" version "0.27.1"
resolved "https://registry.yarnpkg.com/dgeni-packages/-/dgeni-packages-0.27.0.tgz#99ddf4c97f75bb1f8deb5658ed7d60f6894e9b9d" resolved "https://registry.yarnpkg.com/dgeni-packages/-/dgeni-packages-0.27.1.tgz#f23d78fd3e222910106e45186e1c2e64649464fc"
integrity sha512-BFWJGZTpLb1xAc/iHq7SOcbkyEoxD57NqVG84azfNu63wAVLxoez/9n8VISWNJkrOIT1ITQS7nacgcGxfl0MIw== integrity sha512-zM2HgMni9FvfBFHv2uhWrWRUV0CpaWl4ggoajbGLMT+TEqxkSPKRkCkCQMHek7ZYSXbPdpVb8DuoEKEem74X4g==
dependencies: dependencies:
canonical-path "^1.0.0" canonical-path "^1.0.0"
catharsis "^0.8.1" catharsis "^0.8.1"

View File

@ -8,6 +8,15 @@
} }
} }
}, },
"cli-hello-world-ivy": {
"master": {
"uncompressed": {
"runtime": 1440,
"main": 507677,
"polyfills": 38390
}
}
},
"hello_world__closure": { "hello_world__closure": {
"master": { "master": {
"uncompressed": { "uncompressed": {

View File

@ -0,0 +1,135 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"demo": {
"root": "",
"sourceRoot": "src",
"projectType": "application",
"prefix": "app",
"schematics": {},
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/demo",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.app.json",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.css"
],
"scripts": []
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
}
]
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "demo:build"
},
"configurations": {
"production": {
"browserTarget": "demo:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "demo:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.spec.json",
"karmaConfig": "src/karma.conf.js",
"styles": [
"src/styles.css"
],
"scripts": [],
"assets": [
"src/favicon.ico",
"src/assets"
]
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"src/tsconfig.app.json",
"src/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
},
"demo-e2e": {
"root": "e2e/",
"projectType": "application",
"prefix": "",
"architect": {
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "demo:serve"
},
"configurations": {
"production": {
"devServerTarget": "demo:serve:production"
}
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": "e2e/tsconfig.e2e.json",
"exclude": [
"**/node_modules/**"
]
}
}
}
}
},
"defaultProject": "demo"
}

View File

@ -0,0 +1,20 @@
import { AppPage } from './app.po';
import { browser } from 'protractor';
describe('workspace-project App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
it('should display welcome message', () => {
page.navigateTo();
expect(page.getTitleText()).toEqual('Welcome to demo!');
});
afterEach(async () => {
const logs = await browser.manage().logs().get('browser');
expect(logs).toEqual([]);
});
});

View File

@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Demo</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>

View File

@ -2,7 +2,7 @@
set -eux -o pipefail set -eux -o pipefail
function test() { function testBazel() {
# Set up # Set up
bazel version bazel version
rm -rf demo rm -rf demo
@ -18,4 +18,20 @@ function test() {
ng e2e ng e2e
} }
test function testNonBazel() {
# Replace angular.json that uses Bazel builder with the default generated by CLI
cp ../angular.json.original ./angular.json
# TODO(kyliau) Remove this once the additional assertion is added to CLI
cp ../app.e2e-spec.ts ./e2e/src/
# TODO(kyliau) Remove this once web_package rule is in use
cp ../index.html ./src/
rm -rf dist src/main.dev.ts src/main.prod.ts
# Just make a symlink instead of full yarn install to expose node_modules
ln -s $(bazel info output_base)/external/npm/node_modules node_modules
ng build
ng test --watch=false
ng e2e
}
testBazel
testNonBazel

View File

@ -1,4 +1,4 @@
# Editor configuration, see http://editorconfig.org # Editor configuration, see https://editorconfig.org
root = true root = true
[*] [*]

View File

@ -1,6 +1,6 @@
# CliHelloWorld # CliHelloWorldIvy
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.6.6. This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 7.2.0-rc.0.
## Development server ## Development server
@ -12,7 +12,7 @@ Run `ng generate component component-name` to generate a new component. You can
## Build ## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build. Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
## Running unit tests ## Running unit tests

View File

@ -1,14 +1,17 @@
{ {
"$schema": "./node_modules/@angular-devkit/core/src/workspace/workspace-schema.json", "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1, "version": 1,
"cli": { "cli": {
"packageManager": "yarn" "packageManager": "yarn"
}, },
"newProjectRoot": "projects", "newProjectRoot": "projects",
"projects": { "projects": {
"cli-hello-world": { "cli-hello-world-ivy": {
"root": "", "root": "",
"sourceRoot": "src",
"projectType": "application", "projectType": "application",
"prefix": "app",
"schematics": {},
"architect": { "architect": {
"build": { "build": {
"builder": "@angular-devkit/build-angular:browser", "builder": "@angular-devkit/build-angular:browser",
@ -19,47 +22,36 @@
"polyfills": "src/polyfills.ts", "polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.app.json", "tsConfig": "src/tsconfig.app.json",
"assets": [ "assets": [
{ "src/favicon.ico",
"glob": "assets", "src/assets"
"input": "/src",
"output": "/"
},
{
"glob": "favicon.ico",
"input": "/src",
"output": "/"
}
], ],
"styles": [ "styles": [
{ "src/styles.css"
"input": "src/styles.css"
}
], ],
"scripts": [] "scripts": []
}, },
"configurations": { "configurations": {
"dev": { "production": {
"fileReplacements": [ "fileReplacements": [
{ {
"from": "src/environments/environment.ts", "replace": "src/environments/environment.ts",
"to": "dist/environments/environment.ts" "with": "src/environments/environment.prod.ts"
} }
] ],
},
"production": {
"optimization": true, "optimization": true,
"outputHashing": "all", "outputHashing": "all",
"sourceMap": true, "sourceMap": false,
"extractCss": true, "extractCss": true,
"namedChunks": false, "namedChunks": false,
"aot": true, "aot": true,
"extractLicenses": true, "extractLicenses": true,
"vendorChunk": false, "vendorChunk": false,
"buildOptimizer": true, "buildOptimizer": true,
"fileReplacements": [ "budgets": [
{ {
"src": "src/environments/environment.ts", "type": "initial",
"replaceWith": "src/environments/environment.prod.ts" "maximumWarning": "2mb",
"maximumError": "5mb"
} }
] ]
} }
@ -68,20 +60,17 @@
"serve": { "serve": {
"builder": "@angular-devkit/build-angular:dev-server", "builder": "@angular-devkit/build-angular:dev-server",
"options": { "options": {
"browserTarget": "cli-hello-world:build" "browserTarget": "cli-hello-world-ivy:build"
}, },
"configurations": { "configurations": {
"dev": {
"browserTarget": "cli-hello-world:build:dev"
},
"production": { "production": {
"browserTarget": "cli-hello-world:build:production" "browserTarget": "cli-hello-world-ivy:build:production"
}, },
"ci": { "ci": {
"progress": false "progress": false
}, },
"ci-production": { "ci-production": {
"browserTarget": "cli-hello-world:build:production", "browserTarget": "cli-hello-world-ivy:build:production",
"progress": false "progress": false
} }
} }
@ -89,7 +78,7 @@
"extract-i18n": { "extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n", "builder": "@angular-devkit/build-angular:extract-i18n",
"options": { "options": {
"browserTarget": "cli-hello-world:build" "browserTarget": "cli-hello-world-ivy:build"
} }
}, },
"test": { "test": {
@ -97,25 +86,15 @@
"options": { "options": {
"main": "src/test.ts", "main": "src/test.ts",
"polyfills": "src/polyfills.ts", "polyfills": "src/polyfills.ts",
"karmaConfig": "./karma.conf.js",
"tsConfig": "src/tsconfig.spec.json", "tsConfig": "src/tsconfig.spec.json",
"scripts": [], "karmaConfig": "src/karma.conf.js",
"styles": [ "styles": [
{ "src/styles.css"
"input": "src/styles.css"
}
], ],
"scripts": [],
"assets": [ "assets": [
{ "src/favicon.ico",
"glob": "assets", "src/assets"
"input": "/src",
"output": "/"
},
{
"glob": "favicon.ico",
"input": "/src",
"output": "/"
}
] ]
} }
}, },
@ -133,36 +112,33 @@
} }
} }
}, },
"cli-hello-world-e2e": { "cli-hello-world-ivy-e2e": {
"root": "", "root": "e2e/",
"projectType": "application", "projectType": "application",
"cli": {}, "prefix": "",
"schematics": {},
"architect": { "architect": {
"e2e": { "e2e": {
"builder": "@angular-devkit/build-angular:protractor", "builder": "@angular-devkit/build-angular:protractor",
"options": { "options": {
"protractorConfig": "./protractor.conf.js", "protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "cli-hello-world:serve" "devServerTarget": "cli-hello-world-ivy:serve"
}, },
"configurations": { "configurations": {
"production": { "production": {
"devServerTarget": "cli-hello-world:serve:production" "devServerTarget": "cli-hello-world-ivy:serve:production"
}, },
"ci": { "ci": {
"devServerTarget": "cli-hello-world:serve:ci" "devServerTarget": "cli-hello-world-ivy:serve:ci"
}, },
"ci-production": { "ci-production": {
"devServerTarget": "cli-hello-world:serve:ci-production" "devServerTarget": "cli-hello-world-ivy:serve:ci-production"
} }
} }
}, },
"lint": { "lint": {
"builder": "@angular-devkit/build-angular:tslint", "builder": "@angular-devkit/build-angular:tslint",
"options": { "options": {
"tsConfig": [ "tsConfig": "e2e/tsconfig.e2e.json",
"e2e/tsconfig.e2e.json"
],
"exclude": [ "exclude": [
"**/node_modules/**" "**/node_modules/**"
] ]
@ -171,13 +147,5 @@
} }
} }
}, },
"schematics": { "defaultProject": "cli-hello-world-ivy"
"@schematics/angular:component": {
"prefix": "app",
"styleext": "css"
},
"@schematics/angular:directive": {
"prefix": "app"
}
}
} }

View File

@ -6,7 +6,7 @@ const { SpecReporter } = require('jasmine-spec-reporter');
exports.config = { exports.config = {
allScriptsTimeout: 11000, allScriptsTimeout: 11000,
specs: [ specs: [
'./e2e/**/*.e2e-spec.ts' './src/**/*.e2e-spec.ts'
], ],
capabilities: { capabilities: {
browserName: 'chrome', browserName: 'chrome',
@ -25,7 +25,7 @@ exports.config = {
}, },
onPrepare() { onPrepare() {
require('ts-node').register({ require('ts-node').register({
project: 'e2e/tsconfig.e2e.json' project: require('path').join(__dirname, './tsconfig.e2e.json')
}); });
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
} }

View File

@ -9,7 +9,7 @@ describe('cli-hello-world App', () => {
it('should display welcome message', () => { it('should display welcome message', () => {
page.navigateTo(); page.navigateTo();
expect(page.getParagraphText()).toEqual('Welcome to app!'); expect(page.getParagraphText()).toEqual('Welcome to cli-hello-world-ivy!');
}); });
it('the percent pipe should work', () => { it('the percent pipe should work', () => {

View File

@ -1,8 +1,7 @@
{ {
"extends": "../tsconfig.json", "extends": "../tsconfig.json",
"compilerOptions": { "compilerOptions": {
"outDir": "../out-tsc/e2e", "outDir": "../out-tsc/app",
"baseUrl": "./",
"module": "commonjs", "module": "commonjs",
"target": "es5", "target": "es5",
"types": [ "types": [
@ -11,4 +10,4 @@
"node" "node"
] ]
} }
} }

View File

@ -1,5 +1,5 @@
{ {
"name": "cli-hello-world", "name": "cli-hello-world-ivy",
"version": "0.0.0", "version": "0.0.0",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
@ -29,24 +29,24 @@
"zone.js": "file:../../node_modules/zone.js" "zone.js": "file:../../node_modules/zone.js"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "~0.10.3", "@angular-devkit/build-angular": "~0.12.0-rc.0",
"@angular/cli": "7.0.3", "@angular/cli": "~7.2.0-rc.0",
"@angular/compiler-cli": "file:../../dist/packages-dist/compiler-cli", "@angular/compiler-cli": "file:../../dist/packages-dist/compiler-cli",
"@angular/language-service": "file:../../dist/packages-dist/language-service", "@angular/language-service": "file:../../dist/packages-dist/language-service",
"@types/jasmine": "~2.8.3", "@types/node": "~8.9.4",
"@types/jasminewd2": "~2.0.4", "@types/jasmine": "~2.8.8",
"@types/node": "~6.0.60", "@types/jasminewd2": "~2.0.3",
"codelyzer": "^4.3.0", "codelyzer": "~4.5.0",
"jasmine-core": "~2.8.0", "jasmine-core": "~2.99.1",
"jasmine-spec-reporter": "~4.2.1", "jasmine-spec-reporter": "~4.2.1",
"karma": "~2.0.0", "karma": "~3.1.1",
"karma-chrome-launcher": "~2.2.0", "karma-chrome-launcher": "~2.2.0",
"karma-coverage-istanbul-reporter": "^1.2.1", "karma-coverage-istanbul-reporter": "~2.0.1",
"karma-jasmine": "~1.1.0", "karma-jasmine": "~1.1.2",
"karma-jasmine-html-reporter": "^0.2.2", "karma-jasmine-html-reporter": "^0.2.2",
"protractor": "file:../../node_modules/protractor", "protractor": "file:../../node_modules/protractor",
"ts-node": "~4.1.0", "ts-node": "~7.0.0",
"tslint": "~5.9.1", "tslint": "~5.11.0",
"typescript": "file:../../node_modules/typescript" "typescript": "file:../../node_modules/typescript"
} }
} }

View File

@ -12,7 +12,7 @@
<h2><a target="_blank" rel="noopener" href="https://angular.io/tutorial">Tour of Heroes</a></h2> <h2><a target="_blank" rel="noopener" href="https://angular.io/tutorial">Tour of Heroes</a></h2>
</li> </li>
<li> <li>
<h2><a target="_blank" rel="noopener" href="https://github.com/angular/angular-cli/wiki">CLI Documentation</a></h2> <h2><a target="_blank" rel="noopener" href="https://angular.io/cli">CLI Documentation</a></h2>
</li> </li>
<li> <li>
<h2><a target="_blank" rel="noopener" href="https://blog.angular.io/">Angular blog</a></h2> <h2><a target="_blank" rel="noopener" href="https://blog.angular.io/">Angular blog</a></h2>

View File

@ -1,5 +1,6 @@
import { TestBed, async } from '@angular/core/testing'; import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
describe('AppComponent', () => { describe('AppComponent', () => {
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@ -8,20 +9,23 @@ describe('AppComponent', () => {
], ],
}).compileComponents(); }).compileComponents();
})); }));
it('should create the app', async(() => {
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent); const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance; const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy(); expect(app).toBeTruthy();
})); });
it(`should have as title 'app'`, async(() => {
it(`should have as title 'cli-hello-world-ivy'`, () => {
const fixture = TestBed.createComponent(AppComponent); const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance; const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('app'); expect(app.title).toEqual('cli-hello-world-ivy');
})); });
it('should render title in a h1 tag', async(() => {
it('should render title in a h1 tag', () => {
const fixture = TestBed.createComponent(AppComponent); const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges(); fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement; const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!'); expect(compiled.querySelector('h1').textContent).toContain('Welcome to cli-hello-world-ivy!');
})); });
}); });

View File

@ -6,5 +6,5 @@ import { Component } from '@angular/core';
styleUrls: ['./app.component.css'] styleUrls: ['./app.component.css']
}) })
export class AppComponent { export class AppComponent {
title = 'app'; title = 'cli-hello-world-ivy';
} }

View File

@ -0,0 +1,11 @@
# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
#
# For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed
> 0.5%
last 2 versions
Firefox ESR
not dead
not IE 9-11

View File

@ -1,8 +1,16 @@
// The file contents for the current environment will overwrite these during build. // This file can be replaced during build by using the `fileReplacements` array.
// The build system defaults to the dev environment which uses `environment.ts`, but if you do // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
// `ng build --env=prod` then `environment.prod.ts` will be used instead. // The list of file replacements can be found in `angular.json`.
// The list of which env maps to which file can be found in `.angular-cli.json`.
export const environment = { export const environment = {
production: false production: false
}; };
/*
* For easier debugging in development mode, you can import the following file
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
*
* This import should be commented out in production mode because it will have a negative impact
* on performance if an error is thrown.
*/
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.

View File

@ -2,12 +2,11 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>CliHelloWorld</title> <title>CliHelloWorldIvy</title>
<base href="/"> <base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico"> <link rel="icon" type="image/x-icon" href="favicon.ico">
<script>window['ngDevMode'] = true;</script>
</head> </head>
<body> <body>
<app-root></app-root> <app-root></app-root>

View File

@ -12,16 +12,14 @@ module.exports = function (config) {
require('karma-coverage-istanbul-reporter'), require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma') require('@angular-devkit/build-angular/plugins/karma')
], ],
client:{ client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser clearContext: false // leave Jasmine Spec Runner output visible in browser
}, },
coverageIstanbulReporter: { coverageIstanbulReporter: {
dir: require('path').join(__dirname, 'coverage'), reports: [ 'html', 'lcovonly' ], dir: require('path').join(__dirname, 'coverage'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true fixWebpackSourcePaths: true
}, },
angularCli: {
environment: 'dev'
},
reporters: ['progress', 'kjhtml'], reporters: ['progress', 'kjhtml'],
port: 9876, port: 9876,
colors: true, colors: true,

View File

@ -9,4 +9,4 @@ if (environment.production) {
} }
platformBrowserDynamic().bootstrapModule(AppModule) platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.log(err)); .catch(err => console.error(err));

View File

@ -11,14 +11,17 @@
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
* *
* Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html * Learn more in https://angular.io/guide/browser-support
*/ */
/*************************************************************************************************** /***************************************************************************************************
* BROWSER POLYFILLS * BROWSER POLYFILLS
*/ */
/** IE9, IE10 and IE11 requires all of the following polyfills. **/ /** IE9, IE10, IE11, and Chrome <55 requires all of the following polyfills.
* This also includes Android Emulators with older versions of Chrome and Google Search/Googlebot
*/
// import 'core-js/es6/symbol'; // import 'core-js/es6/symbol';
// import 'core-js/es6/object'; // import 'core-js/es6/object';
// import 'core-js/es6/function'; // import 'core-js/es6/function';
@ -40,19 +43,36 @@
/** IE10 and IE11 requires the following for the Reflect API. */ /** IE10 and IE11 requires the following for the Reflect API. */
// import 'core-js/es6/reflect'; // import 'core-js/es6/reflect';
/** Evergreen browsers require these. **/
// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
import 'core-js/es7/reflect';
/** /**
* Required to support Web Animations `@angular/platform-browser/animations`. * Web Animations `@angular/platform-browser/animations`
* Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
**/ * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
*/
// import 'web-animations-js'; // Run `npm install --save web-animations-js`. // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/**
* By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags
* because those flags need to be set before `zone.js` being loaded, and webpack
* will put import in the top of bundle, so user need to create a separate file
* in this directory (for example: zone-flags.ts), and put the following flags
* into that file, and then add the following code before importing zone.js.
* import './zone-flags.ts';
*
* The flags allowed in zone-flags.ts are listed here.
*
* The following flags will work for all browsers.
*
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
* (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
*
* (window as any).__Zone_enable_cross_context_check = true;
*
*/
/*************************************************************************************************** /***************************************************************************************************
* Zone JS is required by default for Angular itself. * Zone JS is required by default for Angular itself.
@ -60,7 +80,6 @@ import 'core-js/es7/reflect';
import 'zone.js/dist/zone'; // Included with Angular CLI. import 'zone.js/dist/zone'; // Included with Angular CLI.
/*************************************************************************************************** /***************************************************************************************************
* APPLICATION IMPORTS * APPLICATION IMPORTS
*/ */

View File

@ -2,12 +2,13 @@
"extends": "../tsconfig.json", "extends": "../tsconfig.json",
"compilerOptions": { "compilerOptions": {
"outDir": "../out-tsc/app", "outDir": "../out-tsc/app",
"baseUrl": "./",
"module": "es2015",
"types": [] "types": []
}, },
"exclude": [ "exclude": [
"test.ts", "test.ts",
"**/*.spec.ts" "**/*.spec.ts"
] ],
"angularCompilerOptions": {
"enableIvy": "ngtsc"
}
} }

View File

@ -2,8 +2,6 @@
"extends": "../tsconfig.json", "extends": "../tsconfig.json",
"compilerOptions": { "compilerOptions": {
"outDir": "../out-tsc/spec", "outDir": "../out-tsc/spec",
"baseUrl": "./",
"module": "commonjs",
"types": [ "types": [
"jasmine", "jasmine",
"node" "node"
@ -17,4 +15,4 @@
"**/*.spec.ts", "**/*.spec.ts",
"**/*.d.ts" "**/*.d.ts"
] ]
} }

View File

@ -0,0 +1,17 @@
{
"extends": "../tslint.json",
"rules": {
"directive-selector": [
true,
"attribute",
"app",
"camelCase"
],
"component-selector": [
true,
"element",
"app",
"kebab-case"
]
}
}

View File

@ -1,21 +1,21 @@
{ {
"angularCompilerOptions": {
"enableIvy": "ngtsc",
},
"compileOnSave": false, "compileOnSave": false,
"compilerOptions": { "compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc", "outDir": "./dist/out-tsc",
"sourceMap": true, "sourceMap": true,
"declaration": false, "declaration": false,
"module": "es2015",
"moduleResolution": "node", "moduleResolution": "node",
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"importHelpers": true,
"target": "es5", "target": "es5",
"typeRoots": [ "typeRoots": [
"node_modules/@types" "node_modules/@types"
], ],
"lib": [ "lib": [
"es2017", "es2018",
"dom" "dom"
] ]
} }

View File

@ -1,6 +1,6 @@
{ {
"rulesDirectory": [ "rulesDirectory": [
"node_modules/codelyzer" "codelyzer"
], ],
"rules": { "rules": {
"arrow-return-shorthand": true, "arrow-return-shorthand": true,
@ -18,7 +18,6 @@
"forin": true, "forin": true,
"import-blacklist": [ "import-blacklist": [
true, true,
"rxjs",
"rxjs/Rx" "rxjs/Rx"
], ],
"import-spacing": true, "import-spacing": true,
@ -66,6 +65,7 @@
], ],
"no-misused-new": true, "no-misused-new": true,
"no-non-null-assertion": true, "no-non-null-assertion": true,
"no-redundant-jsdoc": true,
"no-shadowed-variable": true, "no-shadowed-variable": true,
"no-string-literal": false, "no-string-literal": false,
"no-string-throw": true, "no-string-throw": true,
@ -117,18 +117,6 @@
"check-separator", "check-separator",
"check-type" "check-type"
], ],
"directive-selector": [
true,
"attribute",
"app",
"camelCase"
],
"component-selector": [
true,
"element",
"app",
"kebab-case"
],
"no-output-on-prefix": true, "no-output-on-prefix": true,
"use-input-property-decorator": true, "use-input-property-decorator": true,
"use-output-property-decorator": true, "use-output-property-decorator": true,

File diff suppressed because it is too large Load Diff

View File

@ -48,9 +48,9 @@ for testDir in $(ls | grep -v node_modules) ; do
yarn install --cache-folder ../$cache yarn install --cache-folder ../$cache
yarn test || exit 1 yarn test || exit 1
# Track payload size for cli-hello-world and hello_world__closure and the render3 tests # Track payload size for cli-hello-world, cli-hello-world-ivy and hello_world__closure
if $CI && ([[ $testDir == cli-hello-world ]] || [[ $testDir == hello_world__closure ]]); then if $CI && ([[ $testDir == cli-hello-world ]] || [[ $testDir == cli-hello-world-ivy ]] || [[ $testDir == hello_world__closure ]]); then
if [[ $testDir == cli-hello-world ]]; then if ([[ $testDir == cli-hello-world ]] || [[ $testDir == cli-hello-world-ivy ]]); then
yarn build yarn build
fi fi

View File

@ -65,7 +65,7 @@
"fs-extra": "4.0.2", "fs-extra": "4.0.2",
"jasmine": "^3.1.0", "jasmine": "^3.1.0",
"jasmine-core": "^3.1.0", "jasmine-core": "^3.1.0",
"karma": "^2.0.4", "karma": "^3.1.4",
"magic-string": "^0.25.0", "magic-string": "^0.25.0",
"minimist": "1.2.0", "minimist": "1.2.0",
"mock-fs": "^4.5.0", "mock-fs": "^4.5.0",
@ -110,7 +110,7 @@
"firefox-profile": "1.0.3", "firefox-profile": "1.0.3",
"glob": "7.1.2", "glob": "7.1.2",
"gulp": "3.9.1", "gulp": "3.9.1",
"gulp-clang-format": "1.0.27", "gulp-clang-format": "1.0.23",
"gulp-connect": "5.0.0", "gulp-connect": "5.0.0",
"gulp-conventional-changelog": "^2.0.3", "gulp-conventional-changelog": "^2.0.3",
"gulp-filter": "^5.1.0", "gulp-filter": "^5.1.0",

View File

@ -19,12 +19,12 @@ def rules_angular_dependencies():
# #
# Download Bazel toolchain dependencies as needed by build actions # Download Bazel toolchain dependencies as needed by build actions
# Use a SHA to get fix for needing symlink_prefix during npm publishing # Use a SHA to get fix for needing symlink_prefix during npm publishing
# TODO(alexeagle): updated to next tagged rules_typescript release # TODO(alexeagle): update to release later than 0.16.4
_maybe( _maybe(
http_archive, http_archive,
name = "build_bazel_rules_nodejs", name = "build_bazel_rules_nodejs",
url = "https://github.com/bazelbuild/rules_nodejs/archive/ee218e2a98b9f09ba07cecac8496a5918c47bc5d.zip", url = "https://github.com/bazelbuild/rules_nodejs/archive/bbf31af8aafad8dd5193356081c6b233ba143aa3.zip",
strip_prefix = "rules_nodejs-ee218e2a98b9f09ba07cecac8496a5918c47bc5d", strip_prefix = "rules_nodejs-bbf31af8aafad8dd5193356081c6b233ba143aa3",
) )
_maybe( _maybe(

View File

@ -95,6 +95,8 @@ def _plain_rollup_bundle(ctx):
run_uglify(ctx, ctx.outputs.build_es5, ctx.outputs.build_es5_min_debug, debug = True) run_uglify(ctx, ctx.outputs.build_es5, ctx.outputs.build_es5_min_debug, debug = True)
umd_rollup_config = write_rollup_config(ctx, filename = "_%s_umd.rollup.conf.js", output_format = "umd") umd_rollup_config = write_rollup_config(ctx, filename = "_%s_umd.rollup.conf.js", output_format = "umd")
run_rollup(ctx, collect_es2015_sources(ctx), umd_rollup_config, ctx.outputs.build_umd) run_rollup(ctx, collect_es2015_sources(ctx), umd_rollup_config, ctx.outputs.build_umd)
cjs_rollup_config = write_rollup_config(ctx, filename = "_%s_cjs.rollup.conf.js", output_format = "cjs")
run_rollup(ctx, collect_es2015_sources(ctx), cjs_rollup_config, ctx.outputs.build_cjs)
run_sourcemapexplorer(ctx, ctx.outputs.build_es5_min, source_map, ctx.outputs.explore_html) run_sourcemapexplorer(ctx, ctx.outputs.build_es5_min, source_map, ctx.outputs.explore_html)
run_brotli(ctx, ctx.outputs.build_es5_min, ctx.outputs.build_es5_min_compressed) run_brotli(ctx, ctx.outputs.build_es5_min, ctx.outputs.build_es5_min_compressed)
@ -134,6 +136,8 @@ def _ng_rollup_bundle(ctx):
umd_rollup_config = write_rollup_config(ctx, filename = "_%s_umd.rollup.conf.js", output_format = "umd") umd_rollup_config = write_rollup_config(ctx, filename = "_%s_umd.rollup.conf.js", output_format = "umd")
run_rollup(ctx, collect_es2015_sources(ctx), umd_rollup_config, ctx.outputs.build_umd) run_rollup(ctx, collect_es2015_sources(ctx), umd_rollup_config, ctx.outputs.build_umd)
cjs_rollup_config = write_rollup_config(ctx, filename = "_%s_cjs.rollup.conf.js", output_format = "cjs")
run_rollup(ctx, collect_es2015_sources(ctx), cjs_rollup_config, ctx.outputs.build_cjs)
run_brotli(ctx, ctx.outputs.build_es5_min, ctx.outputs.build_es5_min_compressed) run_brotli(ctx, ctx.outputs.build_es5_min, ctx.outputs.build_es5_min_compressed)

View File

@ -77,7 +77,10 @@ def _protractor_web_test_impl(ctx):
output = ctx.outputs.executable, output = ctx.outputs.executable,
is_executable = True, is_executable = True,
content = """#!/usr/bin/env bash content = """#!/usr/bin/env bash
if [ -e "$RUNFILE_MANIFEST_FILE" ]; then # Immediately exit if any command fails.
set -e
if [ -e "$RUNFILES_MANIFEST_FILE" ]; then
while read line; do while read line; do
declare -a PARTS=($line) declare -a PARTS=($line)
if [ "${{PARTS[0]}}" == "{TMPL_protractor}" ]; then if [ "${{PARTS[0]}}" == "{TMPL_protractor}" ]; then
@ -85,7 +88,7 @@ if [ -e "$RUNFILE_MANIFEST_FILE" ]; then
elif [ "${{PARTS[0]}}" == "{TMPL_conf}" ]; then elif [ "${{PARTS[0]}}" == "{TMPL_conf}" ]; then
readonly CONF=${{PARTS[1]}} readonly CONF=${{PARTS[1]}}
fi fi
done < $RUNFILE_MANIFEST_FILE done < $RUNFILES_MANIFEST_FILE
else else
readonly PROTRACTOR=../{TMPL_protractor} readonly PROTRACTOR=../{TMPL_protractor}
readonly CONF=../{TMPL_conf} readonly CONF=../{TMPL_conf}

View File

@ -11,7 +11,7 @@ import {defineInjectable, inject} from '@angular/core';
import {DOCUMENT} from './dom_tokens'; import {DOCUMENT} from './dom_tokens';
/** /**
* Manages the scroll position. * Defines a scroll position manager. Implemented by `BrowserViewportScroller`.
* *
* @publicApi * @publicApi
*/ */
@ -24,40 +24,40 @@ export abstract class ViewportScroller {
/** /**
* Configures the top offset used when scrolling to an anchor. * Configures the top offset used when scrolling to an anchor.
* @param offset A position in screen coordinates (a tuple with x and y values)
* or a function that returns the top offset position.
* *
* When given a tuple with two number, the service will always use the numbers.
* When given a function, the service will invoke the function every time it restores scroll
* position.
*/ */
abstract setOffset(offset: [number, number]|(() => [number, number])): void; abstract setOffset(offset: [number, number]|(() => [number, number])): void;
/** /**
* Returns the current scroll position. * Retrieves the current scroll position.
* @returns A position in screen coordinates (a tuple with x and y values).
*/ */
abstract getScrollPosition(): [number, number]; abstract getScrollPosition(): [number, number];
/** /**
* Sets the scroll position. * Scrolls to a specified position.
* @param position A position in screen coordinates (a tuple with x and y values).
*/ */
abstract scrollToPosition(position: [number, number]): void; abstract scrollToPosition(position: [number, number]): void;
/** /**
* Scrolls to the provided anchor. * Scrolls to an anchor element.
* @param anchor The ID of the anchor element.
*/ */
abstract scrollToAnchor(anchor: string): void; abstract scrollToAnchor(anchor: string): void;
/** /**
*
* Disables automatic scroll restoration provided by the browser. * Disables automatic scroll restoration provided by the browser.
*
* See also [window.history.scrollRestoration * See also [window.history.scrollRestoration
* info](https://developers.google.com/web/updates/2015/09/history-api-scroll-restoration) * info](https://developers.google.com/web/updates/2015/09/history-api-scroll-restoration).
*/ */
abstract setHistoryScrollRestoration(scrollRestoration: 'auto'|'manual'): void; abstract setHistoryScrollRestoration(scrollRestoration: 'auto'|'manual'): void;
} }
/** /**
* Manages the scroll position. * Manages the scroll position for a browser window.
*/ */
export class BrowserViewportScroller implements ViewportScroller { export class BrowserViewportScroller implements ViewportScroller {
private offset: () => [number, number] = () => [0, 0]; private offset: () => [number, number] = () => [0, 0];
@ -66,10 +66,9 @@ export class BrowserViewportScroller implements ViewportScroller {
/** /**
* Configures the top offset used when scrolling to an anchor. * Configures the top offset used when scrolling to an anchor.
* @param offset A position in screen coordinates (a tuple with x and y values)
* or a function that returns the top offset position.
* *
* * When given a number, the service will always use the number.
* * When given a function, the service will invoke the function every time it restores scroll
* position.
*/ */
setOffset(offset: [number, number]|(() => [number, number])): void { setOffset(offset: [number, number]|(() => [number, number])): void {
if (Array.isArray(offset)) { if (Array.isArray(offset)) {
@ -80,7 +79,8 @@ export class BrowserViewportScroller implements ViewportScroller {
} }
/** /**
* Returns the current scroll position. * Retrieves the current scroll position.
* @returns The position in screen coordinates.
*/ */
getScrollPosition(): [number, number] { getScrollPosition(): [number, number] {
if (this.supportScrollRestoration()) { if (this.supportScrollRestoration()) {
@ -92,6 +92,7 @@ export class BrowserViewportScroller implements ViewportScroller {
/** /**
* Sets the scroll position. * Sets the scroll position.
* @param position The new position in screen coordinates.
*/ */
scrollToPosition(position: [number, number]): void { scrollToPosition(position: [number, number]): void {
if (this.supportScrollRestoration()) { if (this.supportScrollRestoration()) {
@ -100,7 +101,8 @@ export class BrowserViewportScroller implements ViewportScroller {
} }
/** /**
* Scrolls to the provided anchor. * Scrolls to an anchor element.
* @param anchor The ID of the anchor element.
*/ */
scrollToAnchor(anchor: string): void { scrollToAnchor(anchor: string): void {
if (this.supportScrollRestoration()) { if (this.supportScrollRestoration()) {

View File

@ -15,6 +15,7 @@ ts_library(
"//packages/platform-browser", "//packages/platform-browser",
"//packages/platform-browser-dynamic", "//packages/platform-browser-dynamic",
"//packages/platform-browser/testing", "//packages/platform-browser/testing",
"//packages/private/testing",
], ],
) )

View File

@ -11,6 +11,7 @@ import {NgComponentOutlet} from '@angular/common/src/directives/ng_component_out
import {Compiler, Component, ComponentRef, Inject, InjectionToken, Injector, NO_ERRORS_SCHEMA, NgModule, NgModuleFactory, Optional, QueryList, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef} from '@angular/core'; import {Compiler, Component, ComponentRef, Inject, InjectionToken, Injector, NO_ERRORS_SCHEMA, NgModule, NgModuleFactory, Optional, QueryList, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef} from '@angular/core';
import {TestBed, async} from '@angular/core/testing'; import {TestBed, async} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/src/matchers'; import {expect} from '@angular/platform-browser/testing/src/matchers';
import {modifiedInIvy} from '@angular/private/testing';
describe('insert/remove', () => { describe('insert/remove', () => {
@ -106,17 +107,20 @@ describe('insert/remove', () => {
})); }));
it('should resolve a with injector', async(() => {
let fixture = TestBed.createComponent(TestComponent);
fixture.componentInstance.cmpRef = null; modifiedInIvy('Static ViewChild and ContentChild queries are resolved in update mode')
fixture.componentInstance.currentComponent = InjectedComponent; .it('should resolve with an injector', async(() => {
fixture.detectChanges(); let fixture = TestBed.createComponent(TestComponent);
let cmpRef: ComponentRef<InjectedComponent> = fixture.componentInstance.cmpRef !;
expect(cmpRef).toBeAnInstanceOf(ComponentRef); // We are accessing a ViewChild (ngComponentOutlet) before change detection has run
expect(cmpRef.instance).toBeAnInstanceOf(InjectedComponent); fixture.componentInstance.cmpRef = null;
expect(cmpRef.instance.testToken).toBeNull(); fixture.componentInstance.currentComponent = InjectedComponent;
})); fixture.detectChanges();
let cmpRef: ComponentRef<InjectedComponent> = fixture.componentInstance.cmpRef !;
expect(cmpRef).toBeAnInstanceOf(ComponentRef);
expect(cmpRef.instance).toBeAnInstanceOf(InjectedComponent);
expect(cmpRef.instance.testToken).toBeNull();
}));
it('should render projectable nodes, if supplied', async(() => { it('should render projectable nodes, if supplied', async(() => {
const template = `<ng-template>projected foo</ng-template>${TEST_CMP_TEMPLATE}`; const template = `<ng-template>projected foo</ng-template>${TEST_CMP_TEMPLATE}`;

View File

@ -0,0 +1,118 @@
/**
* @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 * as ts from 'typescript';
import {ReferencesRegistry} from '../../../ngtsc/annotations';
import {Declaration} from '../../../ngtsc/host';
import {ResolvedReference} from '../../../ngtsc/metadata';
import {NgccReflectionHost} from '../host/ngcc_host';
import {isDefined} from '../utils';
export interface ModuleWithProvidersInfo {
/**
* The declaration (in the .d.ts file) of the function that returns
* a `ModuleWithProviders object, but has a signature that needs
* a type parameter adding.
*/
declaration: ts.MethodDeclaration|ts.FunctionDeclaration;
/**
* The NgModule class declaration (in the .d.ts file) to add as a type parameter.
*/
ngModule: Declaration;
}
export type ModuleWithProvidersAnalyses = Map<ts.SourceFile, ModuleWithProvidersInfo[]>;
export const ModuleWithProvidersAnalyses = Map;
export class ModuleWithProvidersAnalyzer {
constructor(private host: NgccReflectionHost, private referencesRegistry: ReferencesRegistry) {}
analyzeProgram(program: ts.Program): ModuleWithProvidersAnalyses {
const analyses = new ModuleWithProvidersAnalyses();
const rootFiles = this.getRootFiles(program);
rootFiles.forEach(f => {
const fns = this.host.getModuleWithProvidersFunctions(f);
fns && fns.forEach(fn => {
const dtsFn = this.getDtsDeclaration(fn.declaration);
const typeParam = dtsFn.type && ts.isTypeReferenceNode(dtsFn.type) &&
dtsFn.type.typeArguments && dtsFn.type.typeArguments[0] ||
null;
if (!typeParam || isAnyKeyword(typeParam)) {
// Either we do not have a parameterized type or the type is `any`.
let ngModule = this.host.getDeclarationOfIdentifier(fn.ngModule);
if (!ngModule) {
throw new Error(
`Cannot find a declaration for NgModule ${fn.ngModule.text} referenced in ${fn.declaration.getText()}`);
}
// For internal (non-library) module references, redirect the module's value declaration
// to its type declaration.
if (ngModule.viaModule === null) {
const dtsNgModule = this.host.getDtsDeclaration(ngModule.node);
if (!dtsNgModule) {
throw new Error(
`No typings declaration can be found for the referenced NgModule class in ${fn.declaration.getText()}.`);
}
if (!ts.isClassDeclaration(dtsNgModule)) {
throw new Error(
`The referenced NgModule in ${fn.declaration.getText()} is not a class declaration in the typings program; instead we get ${dtsNgModule.getText()}`);
}
// Record the usage of the internal module as it needs to become an exported symbol
this.referencesRegistry.add(new ResolvedReference(ngModule.node, fn.ngModule));
ngModule = {node: dtsNgModule, viaModule: null};
}
const dtsFile = dtsFn.getSourceFile();
const analysis = analyses.get(dtsFile) || [];
analysis.push({declaration: dtsFn, ngModule});
analyses.set(dtsFile, analysis);
}
});
});
return analyses;
}
private getRootFiles(program: ts.Program): ts.SourceFile[] {
return program.getRootFileNames().map(f => program.getSourceFile(f)).filter(isDefined);
}
private getDtsDeclaration(fn: ts.SignatureDeclaration) {
let dtsFn: ts.Declaration|null = null;
const containerClass = this.host.getClassSymbol(fn.parent);
const fnName = fn.name && ts.isIdentifier(fn.name) && fn.name.text;
if (containerClass && fnName) {
const dtsClass = this.host.getDtsDeclaration(containerClass.valueDeclaration);
// Get the declaration of the matching static method
dtsFn = dtsClass && ts.isClassDeclaration(dtsClass) ?
dtsClass.members
.find(
member => ts.isMethodDeclaration(member) && ts.isIdentifier(member.name) &&
member.name.text === fnName) as ts.Declaration :
null;
} else {
dtsFn = this.host.getDtsDeclaration(fn);
}
if (!dtsFn) {
throw new Error(`Matching type declaration for ${fn.getText()} is missing`);
}
if (!isFunctionOrMethod(dtsFn)) {
throw new Error(
`Matching type declaration for ${fn.getText()} is not a function: ${dtsFn.getText()}`);
}
return dtsFn;
}
}
function isFunctionOrMethod(declaration: ts.Declaration): declaration is ts.FunctionDeclaration|
ts.MethodDeclaration {
return ts.isFunctionDeclaration(declaration) || ts.isMethodDeclaration(declaration);
}
function isAnyKeyword(typeParam: ts.TypeNode): typeParam is ts.KeywordTypeNode {
return typeParam.kind === ts.SyntaxKind.AnyKeyword;
}

View File

@ -15,7 +15,7 @@ import {hasNameIdentifier, isDefined} from '../utils';
export interface ExportInfo { export interface ExportInfo {
identifier: string; identifier: string;
from: string; from: string;
dtsFrom: string|null; dtsFrom?: string|null;
} }
export type PrivateDeclarationsAnalyses = ExportInfo[]; export type PrivateDeclarationsAnalyses = ExportInfo[];
@ -52,7 +52,7 @@ export class PrivateDeclarationsAnalyzer {
return Array.from(privateDeclarations.keys()).map(id => { return Array.from(privateDeclarations.keys()).map(id => {
const from = id.getSourceFile().fileName; const from = id.getSourceFile().fileName;
const declaration = privateDeclarations.get(id) !; const declaration = privateDeclarations.get(id) !;
const dtsDeclaration = this.host.getDtsDeclarationOfClass(declaration.node); const dtsDeclaration = this.host.getDtsDeclaration(declaration.node);
const dtsFrom = dtsDeclaration && dtsDeclaration.getSourceFile().fileName; const dtsFrom = dtsDeclaration && dtsDeclaration.getSourceFile().fileName;
return {identifier: id.text, from, dtsFrom}; return {identifier: id.text, from, dtsFrom};
}); });

View File

@ -14,7 +14,7 @@ import {BundleProgram} from '../packages/bundle_program';
import {findAll, getNameText, isDefined} from '../utils'; import {findAll, getNameText, isDefined} from '../utils';
import {DecoratedClass} from './decorated_class'; import {DecoratedClass} from './decorated_class';
import {NgccReflectionHost, PRE_R3_MARKER, SwitchableVariableDeclaration, isSwitchableVariableDeclaration} from './ngcc_host'; import {ModuleWithProvidersFunction, NgccReflectionHost, PRE_R3_MARKER, SwitchableVariableDeclaration, isSwitchableVariableDeclaration} from './ngcc_host';
export const DECORATORS = 'decorators' as ts.__String; export const DECORATORS = 'decorators' as ts.__String;
export const PROP_DECORATORS = 'propDecorators' as ts.__String; export const PROP_DECORATORS = 'propDecorators' as ts.__String;
@ -49,10 +49,10 @@ export const CONSTRUCTOR_PARAMS = 'ctorParameters' as ts.__String;
* a static method called `ctorParameters`. * a static method called `ctorParameters`.
*/ */
export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements NgccReflectionHost { export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements NgccReflectionHost {
protected dtsClassMap: Map<string, ts.ClassDeclaration>|null; protected dtsDeclarationMap: Map<string, ts.Declaration>|null;
constructor(protected isCore: boolean, checker: ts.TypeChecker, dts?: BundleProgram|null) { constructor(protected isCore: boolean, checker: ts.TypeChecker, dts?: BundleProgram|null) {
super(checker); super(checker);
this.dtsClassMap = dts && this.computeDtsClassMap(dts.path, dts.program) || null; this.dtsDeclarationMap = dts && this.computeDtsDeclarationMap(dts.path, dts.program) || null;
} }
/** /**
@ -327,15 +327,15 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
* is not a class or has an unknown number of type parameters. * is not a class or has an unknown number of type parameters.
*/ */
getGenericArityOfClass(clazz: ts.Declaration): number|null { getGenericArityOfClass(clazz: ts.Declaration): number|null {
const dtsClass = this.getDtsDeclarationOfClass(clazz); const dtsDeclaration = this.getDtsDeclaration(clazz);
if (dtsClass) { if (dtsDeclaration && ts.isClassDeclaration(dtsDeclaration)) {
return dtsClass.typeParameters ? dtsClass.typeParameters.length : 0; return dtsDeclaration.typeParameters ? dtsDeclaration.typeParameters.length : 0;
} }
return null; return null;
} }
/** /**
* Take an exported declaration of a class (maybe downleveled to a variable) and look up the * Take an exported declaration of a class (maybe down-leveled to a variable) and look up the
* declaration of its type in a separate .d.ts tree. * declaration of its type in a separate .d.ts tree.
* *
* This function is allowed to return `null` if the current compilation unit does not have a * This function is allowed to return `null` if the current compilation unit does not have a
@ -346,19 +346,47 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
* Note that the `ts.ClassDeclaration` returned from this function may not be from the same * Note that the `ts.ClassDeclaration` returned from this function may not be from the same
* `ts.Program` as the input declaration. * `ts.Program` as the input declaration.
*/ */
getDtsDeclarationOfClass(declaration: ts.Declaration): ts.ClassDeclaration|null { getDtsDeclaration(declaration: ts.Declaration): ts.Declaration|null {
if (this.dtsClassMap) { if (!this.dtsDeclarationMap) {
if (ts.isClassDeclaration(declaration)) { return null;
if (!declaration.name || !ts.isIdentifier(declaration.name)) {
throw new Error(
`Cannot get the dts file for a class declaration that has no indetifier: ${declaration.getText()} in ${declaration.getSourceFile().fileName}`);
}
return this.dtsClassMap.get(declaration.name.text) || null;
}
} }
return null; if (!isNamedDeclaration(declaration)) {
throw new Error(
`Cannot get the dts file for a declaration that has no name: ${declaration.getText()} in ${declaration.getSourceFile().fileName}`);
}
return this.dtsDeclarationMap.get(declaration.name.text) || null;
} }
/**
* Search the given source file for exported functions and static class methods that return
* ModuleWithProviders objects.
* @param f The source file to search for these functions
* @returns An array of function declarations that look like they return ModuleWithProviders
* objects.
*/
getModuleWithProvidersFunctions(f: ts.SourceFile): ModuleWithProvidersFunction[] {
const exports = this.getExportsOfModule(f);
if (!exports) return [];
const infos: ModuleWithProvidersFunction[] = [];
exports.forEach((declaration, name) => {
if (this.isClass(declaration.node)) {
this.getMembersOfClass(declaration.node).forEach(member => {
if (member.isStatic) {
const info = this.parseForModuleWithProviders(member.node);
if (info) {
infos.push(info);
}
}
});
} else {
const info = this.parseForModuleWithProviders(declaration.node);
if (info) {
infos.push(info);
}
}
});
return infos;
}
///////////// Protected Helpers ///////////// ///////////// Protected Helpers /////////////
@ -738,7 +766,7 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
} }
if (!name) { if (!name) {
if (isNamedDeclaration(node) && node.name && ts.isIdentifier(node.name)) { if (isNamedDeclaration(node)) {
name = node.name.text; name = node.name.text;
nameNode = node.name; nameNode = node.name;
} else { } else {
@ -846,8 +874,8 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
} }
/** /**
* Get the parameter type and decorators for a class where the information is stored on * Get the parameter type and decorators for a class where the information is stored via
* in calls to `__decorate` helpers. * calls to `__decorate` helpers.
* *
* Reflect over the helpers to find the decorators and types about each of * Reflect over the helpers to find the decorators and types about each of
* the class's constructor parameters. * the class's constructor parameters.
@ -1002,9 +1030,9 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
* @param dtsProgram The program containing all the typings files. * @param dtsProgram The program containing all the typings files.
* @returns a map of class names to class declarations. * @returns a map of class names to class declarations.
*/ */
protected computeDtsClassMap(dtsRootFileName: string, dtsProgram: ts.Program): protected computeDtsDeclarationMap(dtsRootFileName: string, dtsProgram: ts.Program):
Map<string, ts.ClassDeclaration> { Map<string, ts.Declaration> {
const dtsClassMap = new Map<string, ts.ClassDeclaration>(); const dtsDeclarationMap = new Map<string, ts.Declaration>();
const checker = dtsProgram.getTypeChecker(); const checker = dtsProgram.getTypeChecker();
// First add all the classes that are publicly exported from the entry-point // First add all the classes that are publicly exported from the entry-point
@ -1012,13 +1040,38 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
if (!rootFile) { if (!rootFile) {
throw new Error(`The given file ${dtsRootFileName} is not part of the typings program.`); throw new Error(`The given file ${dtsRootFileName} is not part of the typings program.`);
} }
collectExportedClasses(checker, dtsClassMap, rootFile); collectExportedDeclarations(checker, dtsDeclarationMap, rootFile);
// Now add any additional classes that are exported from individual dts files, // Now add any additional classes that are exported from individual dts files,
// but are not publicly exported from the entry-point. // but are not publicly exported from the entry-point.
dtsProgram.getSourceFiles().forEach( dtsProgram.getSourceFiles().forEach(
sourceFile => { collectExportedClasses(checker, dtsClassMap, sourceFile); }); sourceFile => { collectExportedDeclarations(checker, dtsDeclarationMap, sourceFile); });
return dtsClassMap; return dtsDeclarationMap;
}
/**
* Parse the given node, to see if it is a function that returns a `ModuleWithProviders` object.
* @param node a node to check to see if it is a function that returns a `ModuleWithProviders`
* object.
* @returns info about the function if it does return a `ModuleWithProviders` object; `null`
* otherwise.
*/
protected parseForModuleWithProviders(node: ts.Node|null): ModuleWithProvidersFunction|null {
const declaration =
node && (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) ? node : null;
const body = declaration ? this.getDefinitionOfFunction(declaration).body : null;
const lastStatement = body && body[body.length - 1];
const returnExpression =
lastStatement && ts.isReturnStatement(lastStatement) && lastStatement.expression || null;
const ngModuleProperty = returnExpression && ts.isObjectLiteralExpression(returnExpression) &&
returnExpression.properties.find(
prop =>
!!prop.name && ts.isIdentifier(prop.name) && prop.name.text === 'ngModule') ||
null;
const ngModule = ngModuleProperty && ts.isPropertyAssignment(ngModuleProperty) &&
ts.isIdentifier(ngModuleProperty.initializer) && ngModuleProperty.initializer ||
null;
return ngModule && declaration && {ngModule, declaration};
} }
} }
@ -1129,8 +1182,10 @@ function isThisAssignment(node: ts.Declaration): node is ts.BinaryExpression&
node.left.expression.kind === ts.SyntaxKind.ThisKeyword; node.left.expression.kind === ts.SyntaxKind.ThisKeyword;
} }
function isNamedDeclaration(node: ts.Declaration): node is ts.NamedDeclaration { function isNamedDeclaration(node: ts.Declaration): node is ts.NamedDeclaration&
return !!(node as any).name; {name: ts.Identifier} {
const anyNode: any = node;
return !!anyNode.name && ts.isIdentifier(anyNode.name);
} }
@ -1153,13 +1208,11 @@ function getFarLeftIdentifier(propertyAccess: ts.PropertyAccessExpression): ts.I
} }
/** /**
* Search a source file for exported classes, storing them in the provided `dtsClassMap`. * Collect mappings between exported declarations in a source file and its associated
* @param checker The typechecker for the source program. * declaration in the typings program.
* @param dtsClassMap The map in which to store the collected exported classes.
* @param srcFile The source file to search for exported classes.
*/ */
function collectExportedClasses( function collectExportedDeclarations(
checker: ts.TypeChecker, dtsClassMap: Map<string, ts.ClassDeclaration>, checker: ts.TypeChecker, dtsDeclarationMap: Map<string, ts.Declaration>,
srcFile: ts.SourceFile): void { srcFile: ts.SourceFile): void {
const srcModule = srcFile && checker.getSymbolAtLocation(srcFile); const srcModule = srcFile && checker.getSymbolAtLocation(srcFile);
const moduleExports = srcModule && checker.getExportsOfModule(srcModule); const moduleExports = srcModule && checker.getExportsOfModule(srcModule);
@ -1170,8 +1223,8 @@ function collectExportedClasses(
} }
const declaration = exportedSymbol.valueDeclaration; const declaration = exportedSymbol.valueDeclaration;
const name = exportedSymbol.name; const name = exportedSymbol.name;
if (declaration && ts.isClassDeclaration(declaration) && !dtsClassMap.has(name)) { if (declaration && !dtsDeclarationMap.has(name)) {
dtsClassMap.set(name, declaration); dtsDeclarationMap.set(name, declaration);
} }
}); });
} }

View File

@ -19,6 +19,21 @@ export function isSwitchableVariableDeclaration(node: ts.Node):
ts.isIdentifier(node.initializer) && node.initializer.text.endsWith(PRE_R3_MARKER); ts.isIdentifier(node.initializer) && node.initializer.text.endsWith(PRE_R3_MARKER);
} }
/**
* A structure returned from `getModuleWithProviderInfo` that describes functions
* that return ModuleWithProviders objects.
*/
export interface ModuleWithProvidersFunction {
/**
* The declaration of the function that returns the `ModuleWithProviders` object.
*/
declaration: ts.SignatureDeclaration;
/**
* The identifier of the `ngModule` property on the `ModuleWithProviders` object.
*/
ngModule: ts.Identifier;
}
/** /**
* A reflection host that has extra methods for looking at non-Typescript package formats * A reflection host that has extra methods for looking at non-Typescript package formats
*/ */
@ -45,4 +60,13 @@ export interface NgccReflectionHost extends ReflectionHost {
* @returns An array of decorated classes. * @returns An array of decorated classes.
*/ */
findDecoratedClasses(sourceFile: ts.SourceFile): DecoratedClass[]; findDecoratedClasses(sourceFile: ts.SourceFile): DecoratedClass[];
/**
* Search the given source file for exported functions and static class methods that return
* ModuleWithProviders objects.
* @param f The source file to search for these functions
* @returns An array of info items about each of the functions that return ModuleWithProviders
* objects.
*/
getModuleWithProvidersFunctions(f: ts.SourceFile): ModuleWithProvidersFunction[];
} }

View File

@ -11,6 +11,7 @@ import {mkdir, mv} from 'shelljs';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {CompiledFile, DecorationAnalyzer} from '../analysis/decoration_analyzer'; import {CompiledFile, DecorationAnalyzer} from '../analysis/decoration_analyzer';
import {ModuleWithProvidersAnalyses, ModuleWithProvidersAnalyzer} from '../analysis/module_with_providers_analyzer';
import {NgccReferencesRegistry} from '../analysis/ngcc_references_registry'; import {NgccReferencesRegistry} from '../analysis/ngcc_references_registry';
import {ExportInfo, PrivateDeclarationsAnalyzer} from '../analysis/private_declarations_analyzer'; import {ExportInfo, PrivateDeclarationsAnalyzer} from '../analysis/private_declarations_analyzer';
import {SwitchMarkerAnalyses, SwitchMarkerAnalyzer} from '../analysis/switch_marker_analyzer'; import {SwitchMarkerAnalyses, SwitchMarkerAnalyzer} from '../analysis/switch_marker_analyzer';
@ -25,6 +26,7 @@ import {EntryPoint} from './entry_point';
import {EntryPointBundle} from './entry_point_bundle'; import {EntryPointBundle} from './entry_point_bundle';
/** /**
* A Package is stored in a directory on disk and that directory can contain one or more package * A Package is stored in a directory on disk and that directory can contain one or more package
* formats - e.g. fesm2015, UMD, etc. Additionally, each package provides typings (`.d.ts` files). * formats - e.g. fesm2015, UMD, etc. Additionally, each package provides typings (`.d.ts` files).
@ -59,13 +61,14 @@ export class Transformer {
const reflectionHost = this.getHost(isCore, bundle); const reflectionHost = this.getHost(isCore, bundle);
// Parse and analyze the files. // Parse and analyze the files.
const {decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses} = const {decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
this.analyzeProgram(reflectionHost, isCore, bundle); moduleWithProvidersAnalyses} = this.analyzeProgram(reflectionHost, isCore, bundle);
// Transform the source files and source maps. // Transform the source files and source maps.
const renderer = this.getRenderer(reflectionHost, isCore, bundle); const renderer = this.getRenderer(reflectionHost, isCore, bundle);
const renderedFiles = renderer.renderProgram( const renderedFiles = renderer.renderProgram(
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses); decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
moduleWithProvidersAnalyses);
// Write out all the transformed files. // Write out all the transformed files.
renderedFiles.forEach(file => this.writeFile(file)); renderedFiles.forEach(file => this.writeFile(file));
@ -102,16 +105,26 @@ export class Transformer {
ProgramAnalyses { ProgramAnalyses {
const typeChecker = bundle.src.program.getTypeChecker(); const typeChecker = bundle.src.program.getTypeChecker();
const referencesRegistry = new NgccReferencesRegistry(reflectionHost); const referencesRegistry = new NgccReferencesRegistry(reflectionHost);
const switchMarkerAnalyzer = new SwitchMarkerAnalyzer(reflectionHost);
const switchMarkerAnalyses = switchMarkerAnalyzer.analyzeProgram(bundle.src.program);
const decorationAnalyzer = new DecorationAnalyzer( const decorationAnalyzer = new DecorationAnalyzer(
typeChecker, reflectionHost, referencesRegistry, bundle.rootDirs, isCore); typeChecker, reflectionHost, referencesRegistry, bundle.rootDirs, isCore);
const switchMarkerAnalyzer = new SwitchMarkerAnalyzer(reflectionHost); const decorationAnalyses = decorationAnalyzer.analyzeProgram(bundle.src.program);
const moduleWithProvidersAnalyzer =
bundle.dts && new ModuleWithProvidersAnalyzer(reflectionHost, referencesRegistry);
const moduleWithProvidersAnalyses = moduleWithProvidersAnalyzer &&
moduleWithProvidersAnalyzer.analyzeProgram(bundle.src.program);
const privateDeclarationsAnalyzer = const privateDeclarationsAnalyzer =
new PrivateDeclarationsAnalyzer(reflectionHost, referencesRegistry); new PrivateDeclarationsAnalyzer(reflectionHost, referencesRegistry);
const decorationAnalyses = decorationAnalyzer.analyzeProgram(bundle.src.program);
const switchMarkerAnalyses = switchMarkerAnalyzer.analyzeProgram(bundle.src.program);
const privateDeclarationsAnalyses = const privateDeclarationsAnalyses =
privateDeclarationsAnalyzer.analyzeProgram(bundle.src.program); privateDeclarationsAnalyzer.analyzeProgram(bundle.src.program);
return {decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses};
return {decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
moduleWithProvidersAnalyses};
} }
writeFile(file: FileInfo): void { writeFile(file: FileInfo): void {
@ -129,4 +142,5 @@ interface ProgramAnalyses {
decorationAnalyses: Map<ts.SourceFile, CompiledFile>; decorationAnalyses: Map<ts.SourceFile, CompiledFile>;
switchMarkerAnalyses: SwitchMarkerAnalyses; switchMarkerAnalyses: SwitchMarkerAnalyses;
privateDeclarationsAnalyses: ExportInfo[]; privateDeclarationsAnalyses: ExportInfo[];
moduleWithProvidersAnalyses: ModuleWithProvidersAnalyses|null;
} }

View File

@ -15,9 +15,10 @@ import * as ts from 'typescript';
import {Decorator} from '../../../ngtsc/host'; import {Decorator} from '../../../ngtsc/host';
import {CompileResult} from '@angular/compiler-cli/src/ngtsc/transform'; import {CompileResult} from '@angular/compiler-cli/src/ngtsc/transform';
import {translateStatement, translateType} from '../../../ngtsc/translator'; import {translateStatement, translateType, ImportManager} from '../../../ngtsc/translator';
import {NgccImportManager} from './ngcc_import_manager'; import {NgccImportManager} from './ngcc_import_manager';
import {CompiledClass, CompiledFile, DecorationAnalyses} from '../analysis/decoration_analyzer'; import {CompiledClass, CompiledFile, DecorationAnalyses} from '../analysis/decoration_analyzer';
import {ModuleWithProvidersInfo, ModuleWithProvidersAnalyses} from '../analysis/module_with_providers_analyzer';
import {PrivateDeclarationsAnalyses, ExportInfo} from '../analysis/private_declarations_analyzer'; import {PrivateDeclarationsAnalyses, ExportInfo} from '../analysis/private_declarations_analyzer';
import {SwitchMarkerAnalyses, SwitchMarkerAnalysis} from '../analysis/switch_marker_analyzer'; import {SwitchMarkerAnalyses, SwitchMarkerAnalysis} from '../analysis/switch_marker_analyzer';
import {IMPORT_PREFIX} from '../constants'; import {IMPORT_PREFIX} from '../constants';
@ -49,6 +50,20 @@ interface DtsClassInfo {
compilation: CompileResult[]; compilation: CompileResult[];
} }
/**
* A structure that captures information about what needs to be rendered
* in a typings file.
*
* It is created as a result of processing the analysis passed to the renderer.
*
* The `renderDtsFile()` method consumes it when rendering a typings file.
*/
class DtsRenderInfo {
classInfo: DtsClassInfo[] = [];
moduleWithProviders: ModuleWithProvidersInfo[] = [];
privateExports: ExportInfo[] = [];
}
/** /**
* The collected decorators that have become redundant after the compilation * The collected decorators that have become redundant after the compilation
* of Ivy static fields. The map is keyed by the container node, such that we * of Ivy static fields. The map is keyed by the container node, such that we
@ -71,7 +86,8 @@ export abstract class Renderer {
renderProgram( renderProgram(
decorationAnalyses: DecorationAnalyses, switchMarkerAnalyses: SwitchMarkerAnalyses, decorationAnalyses: DecorationAnalyses, switchMarkerAnalyses: SwitchMarkerAnalyses,
privateDeclarationsAnalyses: PrivateDeclarationsAnalyses): FileInfo[] { privateDeclarationsAnalyses: PrivateDeclarationsAnalyses,
moduleWithProvidersAnalyses: ModuleWithProvidersAnalyses|null): FileInfo[] {
const renderedFiles: FileInfo[] = []; const renderedFiles: FileInfo[] = [];
// Transform the source files. // Transform the source files.
@ -87,16 +103,16 @@ export abstract class Renderer {
// Transform the .d.ts files // Transform the .d.ts files
if (this.bundle.dts) { if (this.bundle.dts) {
const dtsFiles = this.getTypingsFilesToRender(decorationAnalyses); const dtsFiles = this.getTypingsFilesToRender(
decorationAnalyses, privateDeclarationsAnalyses, moduleWithProvidersAnalyses);
// If the dts entry-point is not already there (it did not have compiled classes) // If the dts entry-point is not already there (it did not have compiled classes)
// then add it now, to ensure it gets its extra exports rendered. // then add it now, to ensure it gets its extra exports rendered.
if (!dtsFiles.has(this.bundle.dts.file)) { if (!dtsFiles.has(this.bundle.dts.file)) {
dtsFiles.set(this.bundle.dts.file, []); dtsFiles.set(this.bundle.dts.file, new DtsRenderInfo());
} }
dtsFiles.forEach( dtsFiles.forEach(
(classes, file) => renderedFiles.push( (renderInfo, file) => renderedFiles.push(...this.renderDtsFile(file, renderInfo)));
...this.renderDtsFile(file, classes, privateDeclarationsAnalyses)));
} }
return renderedFiles; return renderedFiles;
@ -151,14 +167,12 @@ export abstract class Renderer {
return this.renderSourceAndMap(sourceFile, input, outputText); return this.renderSourceAndMap(sourceFile, input, outputText);
} }
renderDtsFile( renderDtsFile(dtsFile: ts.SourceFile, renderInfo: DtsRenderInfo): FileInfo[] {
dtsFile: ts.SourceFile, dtsClasses: DtsClassInfo[],
privateDeclarationsAnalyses: PrivateDeclarationsAnalyses): FileInfo[] {
const input = this.extractSourceMap(dtsFile); const input = this.extractSourceMap(dtsFile);
const outputText = new MagicString(input.source); const outputText = new MagicString(input.source);
const importManager = new NgccImportManager(false, this.isCore, IMPORT_PREFIX); const importManager = new NgccImportManager(false, this.isCore, IMPORT_PREFIX);
dtsClasses.forEach(dtsClass => { renderInfo.classInfo.forEach(dtsClass => {
const endOfClass = dtsClass.dtsDeclaration.getEnd(); const endOfClass = dtsClass.dtsDeclaration.getEnd();
dtsClass.compilation.forEach(declaration => { dtsClass.compilation.forEach(declaration => {
const type = translateType(declaration.type, importManager); const type = translateType(declaration.type, importManager);
@ -167,26 +181,67 @@ export abstract class Renderer {
}); });
}); });
this.addModuleWithProvidersParams(outputText, renderInfo.moduleWithProviders, importManager);
this.addImports( this.addImports(
outputText, importManager.getAllImports(dtsFile.fileName, this.bundle.dts !.r3SymbolsFile)); outputText, importManager.getAllImports(dtsFile.fileName, this.bundle.dts !.r3SymbolsFile));
if (dtsFile === this.bundle.dts !.file) { this.addExports(outputText, dtsFile.fileName, renderInfo.privateExports);
const dtsExports = privateDeclarationsAnalyses.map(e => {
if (!e.dtsFrom) {
throw new Error(
`There is no typings path for ${e.identifier} in ${e.from}.\n` +
`We need to add an export for this class to a .d.ts typings file because ` +
`Angular compiler needs to be able to reference this class in compiled code, such as templates.\n` +
`The simplest fix for this is to ensure that this class is exported from the package's entry-point.`);
}
return {identifier: e.identifier, from: e.dtsFrom};
});
this.addExports(outputText, dtsFile.fileName, dtsExports);
}
return this.renderSourceAndMap(dtsFile, input, outputText); return this.renderSourceAndMap(dtsFile, input, outputText);
} }
/**
* Add the type parameters to the appropriate functions that return `ModuleWithProviders`
* structures.
*
* This function only gets called on typings files, so it doesn't need different implementations
* for each bundle format.
*/
protected addModuleWithProvidersParams(
outputText: MagicString, moduleWithProviders: ModuleWithProvidersInfo[],
importManager: NgccImportManager): void {
moduleWithProviders.forEach(info => {
const ngModuleName = (info.ngModule.node as ts.ClassDeclaration).name !.text;
const declarationFile = info.declaration.getSourceFile().fileName;
const ngModuleFile = info.ngModule.node.getSourceFile().fileName;
const importPath = info.ngModule.viaModule ||
(declarationFile !== ngModuleFile ?
stripExtension(`./${relative(dirname(declarationFile), ngModuleFile)}`) :
null);
const ngModule = getImportString(importManager, importPath, ngModuleName);
if (info.declaration.type) {
const typeName = info.declaration.type && ts.isTypeReferenceNode(info.declaration.type) ?
info.declaration.type.typeName :
null;
if (this.isCoreModuleWithProvidersType(typeName)) {
// The declaration already returns `ModuleWithProvider` but it needs the `NgModule` type
// parameter adding.
outputText.overwrite(
info.declaration.type.getStart(), info.declaration.type.getEnd(),
`ModuleWithProviders<${ngModule}>`);
} else {
// The declaration returns an unknown type so we need to convert it to a union that
// includes the ngModule property.
const originalTypeString = info.declaration.type.getText();
outputText.overwrite(
info.declaration.type.getStart(), info.declaration.type.getEnd(),
`(${originalTypeString})&{ngModule:${ngModule}}`);
}
} else {
// The declaration has no return type so provide one.
const lastToken = info.declaration.getLastToken();
const insertPoint = lastToken && lastToken.kind === ts.SyntaxKind.SemicolonToken ?
lastToken.getStart() :
info.declaration.getEnd();
outputText.appendLeft(
insertPoint,
`: ${getImportString(importManager, '@angular/core', 'ModuleWithProviders')}<${ngModule}>`);
}
});
}
protected abstract addConstants(output: MagicString, constants: string, file: ts.SourceFile): protected abstract addConstants(output: MagicString, constants: string, file: ts.SourceFile):
void; void;
protected abstract addImports(output: MagicString, imports: {name: string, as: string}[]): void; protected abstract addImports(output: MagicString, imports: {name: string, as: string}[]): void;
@ -302,22 +357,67 @@ export abstract class Renderer {
return result; return result;
} }
protected getTypingsFilesToRender(analyses: DecorationAnalyses): protected getTypingsFilesToRender(
Map<ts.SourceFile, DtsClassInfo[]> { decorationAnalyses: DecorationAnalyses,
const dtsMap = new Map<ts.SourceFile, DtsClassInfo[]>(); privateDeclarationsAnalyses: PrivateDeclarationsAnalyses,
analyses.forEach(compiledFile => { moduleWithProvidersAnalyses: ModuleWithProvidersAnalyses|
null): Map<ts.SourceFile, DtsRenderInfo> {
const dtsMap = new Map<ts.SourceFile, DtsRenderInfo>();
// Capture the rendering info from the decoration analyses
decorationAnalyses.forEach(compiledFile => {
compiledFile.compiledClasses.forEach(compiledClass => { compiledFile.compiledClasses.forEach(compiledClass => {
const dtsDeclaration = this.host.getDtsDeclarationOfClass(compiledClass.declaration); const dtsDeclaration = this.host.getDtsDeclaration(compiledClass.declaration);
if (dtsDeclaration) { if (dtsDeclaration) {
const dtsFile = dtsDeclaration.getSourceFile(); const dtsFile = dtsDeclaration.getSourceFile();
const classes = dtsMap.get(dtsFile) || []; const renderInfo = dtsMap.get(dtsFile) || new DtsRenderInfo();
classes.push({dtsDeclaration, compilation: compiledClass.compilation}); renderInfo.classInfo.push({dtsDeclaration, compilation: compiledClass.compilation});
dtsMap.set(dtsFile, classes); dtsMap.set(dtsFile, renderInfo);
} }
}); });
}); });
// Capture the ModuleWithProviders functions/methods that need updating
if (moduleWithProvidersAnalyses !== null) {
moduleWithProvidersAnalyses.forEach((moduleWithProvidersToFix, dtsFile) => {
const renderInfo = dtsMap.get(dtsFile) || new DtsRenderInfo();
renderInfo.moduleWithProviders = moduleWithProvidersToFix;
dtsMap.set(dtsFile, renderInfo);
});
}
// Capture the private declarations that need to be re-exported
if (privateDeclarationsAnalyses.length) {
const dtsExports = privateDeclarationsAnalyses.map(e => {
if (!e.dtsFrom) {
throw new Error(
`There is no typings path for ${e.identifier} in ${e.from}.\n` +
`We need to add an export for this class to a .d.ts typings file because ` +
`Angular compiler needs to be able to reference this class in compiled code, such as templates.\n` +
`The simplest fix for this is to ensure that this class is exported from the package's entry-point.`);
}
return {identifier: e.identifier, from: e.dtsFrom};
});
const dtsEntryPoint = this.bundle.dts !.file;
const renderInfo = dtsMap.get(dtsEntryPoint) || new DtsRenderInfo();
renderInfo.privateExports = dtsExports;
dtsMap.set(dtsEntryPoint, renderInfo);
}
return dtsMap; return dtsMap;
} }
/**
* Check whether the given type is the core Angular `ModuleWithProviders` interface.
* @param typeName The type to check.
* @returns true if the type is the core Angular `ModuleWithProviders` interface.
*/
private isCoreModuleWithProvidersType(typeName: ts.EntityName|null) {
const id =
typeName && ts.isIdentifier(typeName) ? this.host.getImportOfIdentifier(typeName) : null;
return (
id && id.name === 'ModuleWithProviders' && (this.isCore || id.from === '@angular/core'));
}
} }
/** /**
@ -386,7 +486,7 @@ export function renderDefinitions(
} }
export function stripExtension(filePath: string): string { export function stripExtension(filePath: string): string {
return filePath.replace(/\.(js|d\.ts$)/, ''); return filePath.replace(/\.(js|d\.ts)$/, '');
} }
/** /**
@ -399,3 +499,9 @@ function createAssignmentStatement(
const receiver = new WrappedNodeExpr(receiverName); const receiver = new WrappedNodeExpr(receiverName);
return new WritePropExpr(receiver, propName, initializer).toStmt(); return new WritePropExpr(receiver, propName, initializer).toStmt();
} }
function getImportString(
importManager: ImportManager, importPath: string | null, importName: string) {
const importAs = importPath ? importManager.generateNamedImport(importPath, importName) : null;
return importAs ? `${importAs.moduleImport}.${importAs.symbol}` : `${importName}`;
}

View File

@ -0,0 +1,401 @@
/**
* @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 * as ts from 'typescript';
import {ModuleWithProvidersAnalyses, ModuleWithProvidersAnalyzer} from '../../src/analysis/module_with_providers_analyzer';
import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry';
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
import {BundleProgram} from '../../src/packages/bundle_program';
import {getDeclaration, makeTestBundleProgram, makeTestProgram} from '../helpers/utils';
const TEST_PROGRAM = [
{
name: '/src/entry-point.js',
contents: `
export * from './explicit';
export * from './any';
export * from './implicit';
export * from './no-providers';
export * from './module';
`
},
{
name: '/src/explicit.js',
contents: `
import {ExternalModule} from './module';
import {LibraryModule} from 'some-library';
export class ExplicitInternalModule {}
export function explicitInternalFunction() {
return {
ngModule: ExplicitInternalModule,
providers: []
};
}
export function explicitExternalFunction() {
return {
ngModule: ExternalModule,
providers: []
};
}
export function explicitLibraryFunction() {
return {
ngModule: LibraryModule,
providers: []
};
}
export class ExplicitClass {
static explicitInternalMethod() {
return {
ngModule: ExplicitInternalModule,
providers: []
};
}
static explicitExternalMethod() {
return {
ngModule: ExternalModule,
providers: []
};
}
static explicitLibraryMethod() {
return {
ngModule: LibraryModule,
providers: []
};
}
}
`
},
{
name: '/src/any.js',
contents: `
import {ExternalModule} from './module';
import {LibraryModule} from 'some-library';
export class AnyInternalModule {}
export function anyInternalFunction() {
return {
ngModule: AnyInternalModule,
providers: []
};
}
export function anyExternalFunction() {
return {
ngModule: ExternalModule,
providers: []
};
}
export function anyLibraryFunction() {
return {
ngModule: LibraryModule,
providers: []
};
}
export class AnyClass {
static anyInternalMethod() {
return {
ngModule: AnyInternalModule,
providers: []
};
}
static anyExternalMethod() {
return {
ngModule: ExternalModule,
providers: []
};
}
static anyLibraryMethod() {
return {
ngModule: LibraryModule,
providers: []
};
}
}
`
},
{
name: '/src/implicit.js',
contents: `
import {ExternalModule} from './module';
import {LibraryModule} from 'some-library';
export class ImplicitInternalModule {}
export function implicitInternalFunction() {
return {
ngModule: ImplicitInternalModule,
providers: [],
};
}
export function implicitExternalFunction() {
return {
ngModule: ExternalModule,
providers: [],
};
}
export function implicitLibraryFunction() {
return {
ngModule: LibraryModule,
providers: [],
};
}
export class ImplicitClass {
static implicitInternalMethod() {
return {
ngModule: ImplicitInternalModule,
providers: [],
};
}
static implicitExternalMethod() {
return {
ngModule: ExternalModule,
providers: [],
};
}
static implicitLibraryMethod() {
return {
ngModule: LibraryModule,
providers: [],
};
}
}
`
},
{
name: '/src/no-providers.js',
contents: `
import {ExternalModule} from './module';
import {LibraryModule} from 'some-library';
export class NoProvidersInternalModule {}
export function noProvExplicitInternalFunction() {
return {ngModule: NoProvidersInternalModule};
}
export function noProvExplicitExternalFunction() {
return {ngModule: ExternalModule};
}
export function noProvExplicitLibraryFunction() {
return {ngModule: LibraryModule};
}
export function noProvAnyInternalFunction() {
return {ngModule: NoProvidersInternalModule};
}
export function noProvAnyExternalFunction() {
return {ngModule: ExternalModule};
}
export function noProvAnyLibraryFunction() {
return {ngModule: LibraryModule};
}
export function noProvImplicitInternalFunction() {
return {ngModule: NoProvidersInternalModule};
}
export function noProvImplicitExternalFunction() {
return {ngModule: ExternalModule};
}
export function noProvImplicitLibraryFunction() {
return {ngModule: LibraryModule};
}
`
},
{
name: '/src/module.js',
contents: `
export class ExternalModule {}
`
},
{
name: '/node_modules/some-library/index.d.ts',
contents: 'export declare class LibraryModule {}'
},
];
const TEST_DTS_PROGRAM = [
{
name: '/typings/entry-point.d.ts',
contents: `
export * from './explicit';
export * from './any';
export * from './implicit';
export * from './no-providers';
export * from './module';
`
},
{
name: '/typings/explicit.d.ts',
contents: `
import {ModuleWithProviders} from './core';
import {ExternalModule} from './module';
import {LibraryModule} from 'some-library';
export declare class ExplicitInternalModule {}
export declare function explicitInternalFunction(): ModuleWithProviders<ExplicitInternalModule>;
export declare function explicitExternalFunction(): ModuleWithProviders<ExternalModule>;
export declare function explicitLibraryFunction(): ModuleWithProviders<LibraryModule>;
export declare class ExplicitClass {
static explicitInternalMethod(): ModuleWithProviders<ExplicitInternalModule>;
static explicitExternalMethod(): ModuleWithProviders<ExternalModule>;
static explicitLibraryMethod(): ModuleWithProviders<LibraryModule>;
}
`
},
{
name: '/typings/any.d.ts',
contents: `
import {ModuleWithProviders} from './core';
export declare class AnyInternalModule {}
export declare function anyInternalFunction(): ModuleWithProviders<any>;
export declare function anyExternalFunction(): ModuleWithProviders<any>;
export declare function anyLibraryFunction(): ModuleWithProviders<any>;
export declare class AnyClass {
static anyInternalMethod(): ModuleWithProviders<any>;
static anyExternalMethod(): ModuleWithProviders<any>;
static anyLibraryMethod(): ModuleWithProviders<any>;
}
`
},
{
name: '/typings/implicit.d.ts',
contents: `
import {ExternalModule} from './module';
import {LibraryModule} from 'some-library';
export declare class ImplicitInternalModule {}
export declare function implicitInternalFunction(): { ngModule: typeof ImplicitInternalModule; providers: never[]; };
export declare function implicitExternalFunction(): { ngModule: typeof ExternalModule; providers: never[]; };
export declare function implicitLibraryFunction(): { ngModule: typeof LibraryModule; providers: never[]; };
export declare class ImplicitClass {
static implicitInternalMethod(): { ngModule: typeof ImplicitInternalModule; providers: never[]; };
static implicitExternalMethod(): { ngModule: typeof ExternalModule; providers: never[]; };
static implicitLibraryMethod(): { ngModule: typeof LibraryModule; providers: never[]; };
}
`
},
{
name: '/typings/no-providers.d.ts',
contents: `
import {ModuleWithProviders} from './core';
import {ExternalModule} from './module';
import {LibraryModule} from 'some-library';
export declare class NoProvidersInternalModule {}
export declare function noProvExplicitInternalFunction(): ModuleWithProviders<NoProvidersInternalModule>;
export declare function noProvExplicitExternalFunction(): ModuleWithProviders<ExternalModule>;
export declare function noProvExplicitLibraryFunction(): ModuleWithProviders<LibraryModule>;
export declare function noProvAnyInternalFunction(): ModuleWithProviders<any>;
export declare function noProvAnyExternalFunction(): ModuleWithProviders<any>;
export declare function noProvAnyLibraryFunction(): ModuleWithProviders<any>;
export declare function noProvImplicitInternalFunction(): { ngModule: typeof NoProvidersInternalModule; };
export declare function noProvImplicitExternalFunction(): { ngModule: typeof ExternalModule; };
export declare function noProvImplicitLibraryFunction(): { ngModule: typeof LibraryModule; };
`
},
{
name: '/typings/module.d.ts',
contents: `
export declare class ExternalModule {}
`
},
{
name: '/typings/core.d.ts',
contents: `
export declare interface Type<T> {
new (...args: any[]): T
}
export declare type Provider = any;
export declare interface ModuleWithProviders<T> {
ngModule: Type<T>
providers?: Provider[]
}
`
},
{
name: '/node_modules/some-library/index.d.ts',
contents: 'export declare class LibraryModule {}'
},
];
describe('ModuleWithProvidersAnalyzer', () => {
describe('analyzeProgram()', () => {
let analyses: ModuleWithProvidersAnalyses;
let program: ts.Program;
let dtsProgram: BundleProgram;
let referencesRegistry: NgccReferencesRegistry;
beforeAll(() => {
program = makeTestProgram(...TEST_PROGRAM);
dtsProgram = makeTestBundleProgram(TEST_DTS_PROGRAM);
const host = new Esm2015ReflectionHost(false, program.getTypeChecker(), dtsProgram);
referencesRegistry = new NgccReferencesRegistry(host);
const analyzer = new ModuleWithProvidersAnalyzer(host, referencesRegistry);
analyses = analyzer.analyzeProgram(program);
});
it('should ignore declarations that already have explicit NgModule type params',
() => { expect(getAnalysisDescription(analyses, '/typings/explicit.d.ts')).toEqual([]); });
it('should find declarations that use `any` for the NgModule type param', () => {
const anyAnalysis = getAnalysisDescription(analyses, '/typings/any.d.ts');
expect(anyAnalysis).toContain(['anyInternalFunction', 'AnyInternalModule', null]);
expect(anyAnalysis).toContain(['anyExternalFunction', 'ExternalModule', null]);
expect(anyAnalysis).toContain(['anyLibraryFunction', 'LibraryModule', 'some-library']);
expect(anyAnalysis).toContain(['anyInternalMethod', 'AnyInternalModule', null]);
expect(anyAnalysis).toContain(['anyExternalMethod', 'ExternalModule', null]);
expect(anyAnalysis).toContain(['anyLibraryMethod', 'LibraryModule', 'some-library']);
});
it('should track internal module references in the references registry', () => {
const declarations = referencesRegistry.getDeclarationMap();
const externalModuleDeclaration =
getDeclaration(program, '/src/module.js', 'ExternalModule', ts.isClassDeclaration);
const libraryModuleDeclaration = getDeclaration(
program, '/node_modules/some-library/index.d.ts', 'LibraryModule', ts.isClassDeclaration);
expect(declarations.has(externalModuleDeclaration.name !)).toBe(true);
expect(declarations.has(libraryModuleDeclaration.name !)).toBe(false);
});
it('should find declarations that have implicit return types', () => {
const anyAnalysis = getAnalysisDescription(analyses, '/typings/implicit.d.ts');
expect(anyAnalysis).toContain(['implicitInternalFunction', 'ImplicitInternalModule', null]);
expect(anyAnalysis).toContain(['implicitExternalFunction', 'ExternalModule', null]);
expect(anyAnalysis).toContain(['implicitLibraryFunction', 'LibraryModule', 'some-library']);
expect(anyAnalysis).toContain(['implicitInternalMethod', 'ImplicitInternalModule', null]);
expect(anyAnalysis).toContain(['implicitExternalMethod', 'ExternalModule', null]);
expect(anyAnalysis).toContain(['implicitLibraryMethod', 'LibraryModule', 'some-library']);
});
it('should find declarations that do not specify a `providers` property in the return type',
() => {
const anyAnalysis = getAnalysisDescription(analyses, '/typings/no-providers.d.ts');
expect(anyAnalysis).not.toContain([
'noProvExplicitInternalFunction', 'NoProvidersInternalModule'
]);
expect(anyAnalysis).not.toContain([
'noProvExplicitExternalFunction', 'ExternalModule', null
]);
expect(anyAnalysis).toContain([
'noProvAnyInternalFunction', 'NoProvidersInternalModule', null
]);
expect(anyAnalysis).toContain(['noProvAnyExternalFunction', 'ExternalModule', null]);
expect(anyAnalysis).toContain([
'noProvAnyLibraryFunction', 'LibraryModule', 'some-library'
]);
expect(anyAnalysis).toContain([
'noProvImplicitInternalFunction', 'NoProvidersInternalModule', null
]);
expect(anyAnalysis).toContain(['noProvImplicitExternalFunction', 'ExternalModule', null]);
expect(anyAnalysis).toContain([
'noProvImplicitLibraryFunction', 'LibraryModule', 'some-library'
]);
});
function getAnalysisDescription(analyses: ModuleWithProvidersAnalyses, fileName: string) {
const file = dtsProgram.program.getSourceFile(fileName) !;
const analysis = analyses.get(file);
return analysis ?
analysis.map(
info =>
[info.declaration.name !.getText(),
(info.ngModule.node as ts.ClassDeclaration).name !.getText(),
info.ngModule.viaModule]) :
[];
}
});
});

View File

@ -84,6 +84,8 @@ export function getFakeCore() {
export class InjectionToken { export class InjectionToken {
constructor(name: string) {} constructor(name: string) {}
} }
export interface ModuleWithProviders<T = any> {}
` `
}; };
} }

View File

@ -453,6 +453,7 @@ const TYPINGS_SRC_FILES = [
}, },
{name: '/src/class1.js', contents: 'export class Class1 {}\nexport class MissingClass1 {}'}, {name: '/src/class1.js', contents: 'export class Class1 {}\nexport class MissingClass1 {}'},
{name: '/src/class2.js', contents: 'export class Class2 {}'}, {name: '/src/class2.js', contents: 'export class Class2 {}'},
{name: '/src/func1.js', contents: 'export function mooFn() {}'},
{name: '/src/internal.js', contents: 'export class InternalClass {}\nexport class Class2 {}'}, {name: '/src/internal.js', contents: 'export class InternalClass {}\nexport class Class2 {}'},
{name: '/src/missing-class.js', contents: 'export class MissingClass2 {}'}, { {name: '/src/missing-class.js', contents: 'export class MissingClass2 {}'}, {
name: '/src/flat-file.js', name: '/src/flat-file.js',
@ -476,6 +477,7 @@ const TYPINGS_DTS_FILES = [
contents: contents:
`export declare class Class2 {}\nexport declare interface SomeInterface {}\nexport {Class3 as xClass3} from './class3';` `export declare class Class2 {}\nexport declare interface SomeInterface {}\nexport {Class3 as xClass3} from './class3';`
}, },
{name: '/typings/func1.d.ts', contents: 'export declare function mooFn(): void;'},
{ {
name: '/typings/internal.d.ts', name: '/typings/internal.d.ts',
contents: `export declare class InternalClass {}\nexport declare class Class2 {}` contents: `export declare class InternalClass {}\nexport declare class Class2 {}`
@ -483,6 +485,54 @@ const TYPINGS_DTS_FILES = [
{name: '/typings/class3.d.ts', contents: `export declare class Class3 {}`}, {name: '/typings/class3.d.ts', contents: `export declare class Class3 {}`},
]; ];
const MODULE_WITH_PROVIDERS_PROGRAM = [
{
name: '/src/functions.js',
contents: `
import {ExternalModule} from './module';
export class SomeService {}
export class InternalModule {}
export function aNumber() { return 42; }
export function aString() { return 'foo'; }
export function emptyObject() { return {}; }
export function ngModuleIdentifier() { return { ngModule: InternalModule }; }
export function ngModuleWithEmptyProviders() { return { ngModule: InternalModule, providers: [] }; }
export function ngModuleWithProviders() { return { ngModule: InternalModule, providers: [SomeService] }; }
export function onlyProviders() { return { providers: [SomeService] }; }
export function ngModuleNumber() { return { ngModule: 42 }; }
export function ngModuleString() { return { ngModule: 'foo' }; }
export function ngModuleObject() { return { ngModule: { foo: 42 } }; }
export function externalNgModule() { return { ngModule: ExternalModule }; }
`
},
{
name: '/src/methods.js',
contents: `
import {ExternalModule} from './module';
export class SomeService {}
export class InternalModule {
static aNumber() { return 42; }
static aString() { return 'foo'; }
static emptyObject() { return {}; }
static ngModuleIdentifier() { return { ngModule: InternalModule }; }
static ngModuleWithEmptyProviders() { return { ngModule: InternalModule, providers: [] }; }
static ngModuleWithProviders() { return { ngModule: InternalModule, providers: [SomeService] }; }
static onlyProviders() { return { providers: [SomeService] }; }
static ngModuleNumber() { return { ngModule: 42 }; }
static ngModuleString() { return { ngModule: 'foo' }; }
static ngModuleObject() { return { ngModule: { foo: 42 } }; }
static externalNgModule() { return { ngModule: ExternalModule }; }
instanceNgModuleIdentifier() { return { ngModule: InternalModule }; }
instanceNgModuleWithEmptyProviders() { return { ngModule: InternalModule, providers: [] }; }
instanceNgModuleWithProviders() { return { ngModule: InternalModule, providers: [SomeService] }; }
instanceExternalNgModule() { return { ngModule: ExternalModule }; }
}
`
},
{name: '/src/module', contents: 'export class ExternalModule {}'},
];
describe('Fesm2015ReflectionHost', () => { describe('Fesm2015ReflectionHost', () => {
describe('getDecoratorsOfDeclaration()', () => { describe('getDecoratorsOfDeclaration()', () => {
@ -1286,10 +1336,20 @@ describe('Fesm2015ReflectionHost', () => {
const class1 = getDeclaration(srcProgram, '/src/class1.js', 'Class1', ts.isClassDeclaration); const class1 = getDeclaration(srcProgram, '/src/class1.js', 'Class1', ts.isClassDeclaration);
const host = new Esm2015ReflectionHost(false, srcProgram.getTypeChecker(), dts); const host = new Esm2015ReflectionHost(false, srcProgram.getTypeChecker(), dts);
const dtsDeclaration = host.getDtsDeclarationOfClass(class1); const dtsDeclaration = host.getDtsDeclaration(class1);
expect(dtsDeclaration !.getSourceFile().fileName).toEqual('/typings/class1.d.ts'); expect(dtsDeclaration !.getSourceFile().fileName).toEqual('/typings/class1.d.ts');
}); });
it('should find the dts declaration for exported functions', () => {
const srcProgram = makeTestProgram(...TYPINGS_SRC_FILES);
const dtsProgram = makeTestBundleProgram(TYPINGS_DTS_FILES);
const mooFn = getDeclaration(srcProgram, '/src/func1.js', 'mooFn', ts.isFunctionDeclaration);
const host = new Esm2015ReflectionHost(false, srcProgram.getTypeChecker(), dtsProgram);
const dtsDeclaration = host.getDtsDeclaration(mooFn);
expect(dtsDeclaration !.getSourceFile().fileName).toEqual('/typings/func1.d.ts');
});
it('should return null if there is no matching class in the matching dts file', () => { it('should return null if there is no matching class in the matching dts file', () => {
const srcProgram = makeTestProgram(...TYPINGS_SRC_FILES); const srcProgram = makeTestProgram(...TYPINGS_SRC_FILES);
const dts = makeTestBundleProgram(TYPINGS_DTS_FILES); const dts = makeTestBundleProgram(TYPINGS_DTS_FILES);
@ -1297,7 +1357,7 @@ describe('Fesm2015ReflectionHost', () => {
getDeclaration(srcProgram, '/src/class1.js', 'MissingClass1', ts.isClassDeclaration); getDeclaration(srcProgram, '/src/class1.js', 'MissingClass1', ts.isClassDeclaration);
const host = new Esm2015ReflectionHost(false, srcProgram.getTypeChecker(), dts); const host = new Esm2015ReflectionHost(false, srcProgram.getTypeChecker(), dts);
expect(host.getDtsDeclarationOfClass(missingClass)).toBe(null); expect(host.getDtsDeclaration(missingClass)).toBe(null);
}); });
it('should return null if there is no matching dts file', () => { it('should return null if there is no matching dts file', () => {
@ -1307,7 +1367,7 @@ describe('Fesm2015ReflectionHost', () => {
srcProgram, '/src/missing-class.js', 'MissingClass2', ts.isClassDeclaration); srcProgram, '/src/missing-class.js', 'MissingClass2', ts.isClassDeclaration);
const host = new Esm2015ReflectionHost(false, srcProgram.getTypeChecker(), dts); const host = new Esm2015ReflectionHost(false, srcProgram.getTypeChecker(), dts);
expect(host.getDtsDeclarationOfClass(missingClass)).toBe(null); expect(host.getDtsDeclaration(missingClass)).toBe(null);
}); });
it('should find the dts file that contains a matching class declaration, even if the source files do not match', it('should find the dts file that contains a matching class declaration, even if the source files do not match',
@ -1318,7 +1378,7 @@ describe('Fesm2015ReflectionHost', () => {
getDeclaration(srcProgram, '/src/flat-file.js', 'Class1', ts.isClassDeclaration); getDeclaration(srcProgram, '/src/flat-file.js', 'Class1', ts.isClassDeclaration);
const host = new Esm2015ReflectionHost(false, srcProgram.getTypeChecker(), dts); const host = new Esm2015ReflectionHost(false, srcProgram.getTypeChecker(), dts);
const dtsDeclaration = host.getDtsDeclarationOfClass(class1); const dtsDeclaration = host.getDtsDeclaration(class1);
expect(dtsDeclaration !.getSourceFile().fileName).toEqual('/typings/class1.d.ts'); expect(dtsDeclaration !.getSourceFile().fileName).toEqual('/typings/class1.d.ts');
}); });
@ -1329,7 +1389,7 @@ describe('Fesm2015ReflectionHost', () => {
getDeclaration(srcProgram, '/src/flat-file.js', 'Class3', ts.isClassDeclaration); getDeclaration(srcProgram, '/src/flat-file.js', 'Class3', ts.isClassDeclaration);
const host = new Esm2015ReflectionHost(false, srcProgram.getTypeChecker(), dts); const host = new Esm2015ReflectionHost(false, srcProgram.getTypeChecker(), dts);
const dtsDeclaration = host.getDtsDeclarationOfClass(class3); const dtsDeclaration = host.getDtsDeclaration(class3);
expect(dtsDeclaration !.getSourceFile().fileName).toEqual('/typings/class3.d.ts'); expect(dtsDeclaration !.getSourceFile().fileName).toEqual('/typings/class3.d.ts');
}); });
@ -1341,7 +1401,7 @@ describe('Fesm2015ReflectionHost', () => {
getDeclaration(srcProgram, '/src/internal.js', 'InternalClass', ts.isClassDeclaration); getDeclaration(srcProgram, '/src/internal.js', 'InternalClass', ts.isClassDeclaration);
const host = new Esm2015ReflectionHost(false, srcProgram.getTypeChecker(), dts); const host = new Esm2015ReflectionHost(false, srcProgram.getTypeChecker(), dts);
const dtsDeclaration = host.getDtsDeclarationOfClass(internalClass); const dtsDeclaration = host.getDtsDeclaration(internalClass);
expect(dtsDeclaration !.getSourceFile().fileName).toEqual('/typings/internal.d.ts'); expect(dtsDeclaration !.getSourceFile().fileName).toEqual('/typings/internal.d.ts');
}); });
@ -1355,12 +1415,42 @@ describe('Fesm2015ReflectionHost', () => {
getDeclaration(srcProgram, '/src/internal.js', 'Class2', ts.isClassDeclaration); getDeclaration(srcProgram, '/src/internal.js', 'Class2', ts.isClassDeclaration);
const host = new Esm2015ReflectionHost(false, srcProgram.getTypeChecker(), dts); const host = new Esm2015ReflectionHost(false, srcProgram.getTypeChecker(), dts);
const class2DtsDeclaration = host.getDtsDeclarationOfClass(class2); const class2DtsDeclaration = host.getDtsDeclaration(class2);
expect(class2DtsDeclaration !.getSourceFile().fileName).toEqual('/typings/class2.d.ts'); expect(class2DtsDeclaration !.getSourceFile().fileName).toEqual('/typings/class2.d.ts');
const internalClass2DtsDeclaration = host.getDtsDeclarationOfClass(internalClass2); const internalClass2DtsDeclaration = host.getDtsDeclaration(internalClass2);
expect(internalClass2DtsDeclaration !.getSourceFile().fileName) expect(internalClass2DtsDeclaration !.getSourceFile().fileName)
.toEqual('/typings/class2.d.ts'); .toEqual('/typings/class2.d.ts');
}); });
}); });
describe('getModuleWithProvidersFunctions', () => {
it('should find every exported function that returns an object that looks like a ModuleWithProviders object',
() => {
const srcProgram = makeTestProgram(...MODULE_WITH_PROVIDERS_PROGRAM);
const host = new Esm2015ReflectionHost(false, srcProgram.getTypeChecker());
const file = srcProgram.getSourceFile('/src/functions.js') !;
const fns = host.getModuleWithProvidersFunctions(file);
expect(fns.map(info => [info.declaration.name !.getText(), info.ngModule.text])).toEqual([
['ngModuleIdentifier', 'InternalModule'],
['ngModuleWithEmptyProviders', 'InternalModule'],
['ngModuleWithProviders', 'InternalModule'],
['externalNgModule', 'ExternalModule'],
]);
});
it('should find every static method on exported classes that return an object that looks like a ModuleWithProviders object',
() => {
const srcProgram = makeTestProgram(...MODULE_WITH_PROVIDERS_PROGRAM);
const host = new Esm2015ReflectionHost(false, srcProgram.getTypeChecker());
const file = srcProgram.getSourceFile('/src/methods.js') !;
const fn = host.getModuleWithProvidersFunctions(file);
expect(fn.map(fn => [fn.declaration.name !.getText(), fn.ngModule.text])).toEqual([
['ngModuleIdentifier', 'InternalModule'],
['ngModuleWithEmptyProviders', 'InternalModule'],
['ngModuleWithProviders', 'InternalModule'],
['externalNgModule', 'ExternalModule'],
]);
});
});
}); });

View File

@ -11,6 +11,7 @@ import * as ts from 'typescript';
import {fromObject, generateMapFileComment} from 'convert-source-map'; import {fromObject, generateMapFileComment} from 'convert-source-map';
import {CompiledClass, DecorationAnalyzer} from '../../src/analysis/decoration_analyzer'; import {CompiledClass, DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry'; import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry';
import {ModuleWithProvidersAnalyzer} from '../../src/analysis/module_with_providers_analyzer';
import {PrivateDeclarationsAnalyzer} from '../../src/analysis/private_declarations_analyzer'; import {PrivateDeclarationsAnalyzer} from '../../src/analysis/private_declarations_analyzer';
import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer'; import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer';
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host'; import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
@ -47,9 +48,9 @@ class TestRenderer extends Renderer {
function createTestRenderer( function createTestRenderer(
packageName: string, files: {name: string, contents: string}[], packageName: string, files: {name: string, contents: string}[],
dtsFile?: {name: string, contents: string}) { dtsFiles?: {name: string, contents: string}[]) {
const isCore = packageName === '@angular/core'; const isCore = packageName === '@angular/core';
const bundle = makeTestEntryPointBundle('esm2015', files, dtsFile && [dtsFile]); const bundle = makeTestEntryPointBundle('esm2015', files, dtsFiles);
const typeChecker = bundle.src.program.getTypeChecker(); const typeChecker = bundle.src.program.getTypeChecker();
const host = new Esm2015ReflectionHost(isCore, typeChecker, bundle.dts); const host = new Esm2015ReflectionHost(isCore, typeChecker, bundle.dts);
const referencesRegistry = new NgccReferencesRegistry(host); const referencesRegistry = new NgccReferencesRegistry(host);
@ -57,6 +58,8 @@ function createTestRenderer(
new DecorationAnalyzer(typeChecker, host, referencesRegistry, bundle.rootDirs, isCore) new DecorationAnalyzer(typeChecker, host, referencesRegistry, bundle.rootDirs, isCore)
.analyzeProgram(bundle.src.program); .analyzeProgram(bundle.src.program);
const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(bundle.src.program); const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(bundle.src.program);
const moduleWithProvidersAnalyses =
new ModuleWithProvidersAnalyzer(host, referencesRegistry).analyzeProgram(bundle.src.program);
const privateDeclarationsAnalyses = const privateDeclarationsAnalyses =
new PrivateDeclarationsAnalyzer(host, referencesRegistry).analyzeProgram(bundle.src.program); new PrivateDeclarationsAnalyzer(host, referencesRegistry).analyzeProgram(bundle.src.program);
const renderer = new TestRenderer(host, isCore, bundle); const renderer = new TestRenderer(host, isCore, bundle);
@ -64,7 +67,8 @@ function createTestRenderer(
spyOn(renderer, 'addDefinitions').and.callThrough(); spyOn(renderer, 'addDefinitions').and.callThrough();
spyOn(renderer, 'removeDecorators').and.callThrough(); spyOn(renderer, 'removeDecorators').and.callThrough();
return {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses}; return {renderer, decorationAnalyses, switchMarkerAnalyses, moduleWithProvidersAnalyses,
privateDeclarationsAnalyses};
} }
@ -121,10 +125,11 @@ describe('Renderer', () => {
describe('renderProgram()', () => { describe('renderProgram()', () => {
it('should render the modified contents; and a new map file, if the original provided no map file.', it('should render the modified contents; and a new map file, if the original provided no map file.',
() => { () => {
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses} = const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
createTestRenderer('test-package', [INPUT_PROGRAM]); moduleWithProvidersAnalyses} = createTestRenderer('test-package', [INPUT_PROGRAM]);
const result = renderer.renderProgram( const result = renderer.renderProgram(
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses); decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
moduleWithProvidersAnalyses);
expect(result[0].path).toEqual('/dist/file.js'); expect(result[0].path).toEqual('/dist/file.js');
expect(result[0].contents) expect(result[0].contents)
.toEqual(RENDERED_CONTENTS + '\n' + generateMapFileComment('/dist/file.js.map')); .toEqual(RENDERED_CONTENTS + '\n' + generateMapFileComment('/dist/file.js.map'));
@ -134,9 +139,11 @@ describe('Renderer', () => {
it('should render as JavaScript', () => { it('should render as JavaScript', () => {
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses} = const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
createTestRenderer('test-package', [COMPONENT_PROGRAM]); moduleWithProvidersAnalyses} = createTestRenderer('test-package', [COMPONENT_PROGRAM]);
renderer.renderProgram(decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses); renderer.renderProgram(
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
moduleWithProvidersAnalyses);
const addDefinitionsSpy = renderer.addDefinitions as jasmine.Spy; const addDefinitionsSpy = renderer.addDefinitions as jasmine.Spy;
expect(addDefinitionsSpy.calls.first().args[2]) expect(addDefinitionsSpy.calls.first().args[2])
.toEqual(`/*@__PURE__*/ ɵngcc0.ɵsetClassMetadata(A, [{ .toEqual(`/*@__PURE__*/ ɵngcc0.ɵsetClassMetadata(A, [{
@ -154,10 +161,12 @@ A.ngComponentDef = ɵngcc0.ɵdefineComponent({ type: A, selectors: [["a"]], fact
describe('calling abstract methods', () => { describe('calling abstract methods', () => {
it('should call addImports with the source code and info about the core Angular library.', it('should call addImports with the source code and info about the core Angular library.',
() => { () => {
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses} = const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
moduleWithProvidersAnalyses} =
createTestRenderer('test-package', [INPUT_PROGRAM]); createTestRenderer('test-package', [INPUT_PROGRAM]);
const result = renderer.renderProgram( const result = renderer.renderProgram(
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses); decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
moduleWithProvidersAnalyses);
const addImportsSpy = renderer.addImports as jasmine.Spy; const addImportsSpy = renderer.addImports as jasmine.Spy;
expect(addImportsSpy.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS); expect(addImportsSpy.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS);
expect(addImportsSpy.calls.first().args[1]).toEqual([ expect(addImportsSpy.calls.first().args[1]).toEqual([
@ -167,10 +176,12 @@ A.ngComponentDef = ɵngcc0.ɵdefineComponent({ type: A, selectors: [["a"]], fact
it('should call addDefinitions with the source code, the analyzed class and the rendered definitions.', it('should call addDefinitions with the source code, the analyzed class and the rendered definitions.',
() => { () => {
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses} = const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
moduleWithProvidersAnalyses} =
createTestRenderer('test-package', [INPUT_PROGRAM]); createTestRenderer('test-package', [INPUT_PROGRAM]);
const result = renderer.renderProgram( const result = renderer.renderProgram(
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses); decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
moduleWithProvidersAnalyses);
const addDefinitionsSpy = renderer.addDefinitions as jasmine.Spy; const addDefinitionsSpy = renderer.addDefinitions as jasmine.Spy;
expect(addDefinitionsSpy.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS); expect(addDefinitionsSpy.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS);
expect(addDefinitionsSpy.calls.first().args[1]).toEqual(jasmine.objectContaining({ expect(addDefinitionsSpy.calls.first().args[1]).toEqual(jasmine.objectContaining({
@ -187,10 +198,12 @@ A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""
it('should call removeDecorators with the source code, a map of class decorators that have been analyzed', it('should call removeDecorators with the source code, a map of class decorators that have been analyzed',
() => { () => {
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses} = const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
moduleWithProvidersAnalyses} =
createTestRenderer('test-package', [INPUT_PROGRAM]); createTestRenderer('test-package', [INPUT_PROGRAM]);
const result = renderer.renderProgram( const result = renderer.renderProgram(
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses); decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
moduleWithProvidersAnalyses);
const removeDecoratorsSpy = renderer.removeDecorators as jasmine.Spy; const removeDecoratorsSpy = renderer.removeDecorators as jasmine.Spy;
expect(removeDecoratorsSpy.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS); expect(removeDecoratorsSpy.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS);
@ -212,14 +225,16 @@ A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""
describe('source map merging', () => { describe('source map merging', () => {
it('should merge any inline source map from the original file and write the output as an inline source map', it('should merge any inline source map from the original file and write the output as an inline source map',
() => { () => {
const {decorationAnalyses, renderer, switchMarkerAnalyses, privateDeclarationsAnalyses} = const {decorationAnalyses, renderer, switchMarkerAnalyses, privateDeclarationsAnalyses,
moduleWithProvidersAnalyses} =
createTestRenderer( createTestRenderer(
'test-package', [{ 'test-package', [{
...INPUT_PROGRAM, ...INPUT_PROGRAM,
contents: INPUT_PROGRAM.contents + '\n' + INPUT_PROGRAM_MAP.toComment() contents: INPUT_PROGRAM.contents + '\n' + INPUT_PROGRAM_MAP.toComment()
}]); }]);
const result = renderer.renderProgram( const result = renderer.renderProgram(
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses); decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
moduleWithProvidersAnalyses);
expect(result[0].path).toEqual('/dist/file.js'); expect(result[0].path).toEqual('/dist/file.js');
expect(result[0].contents) expect(result[0].contents)
.toEqual(RENDERED_CONTENTS + '\n' + MERGED_OUTPUT_PROGRAM_MAP.toComment()); .toEqual(RENDERED_CONTENTS + '\n' + MERGED_OUTPUT_PROGRAM_MAP.toComment());
@ -230,14 +245,16 @@ A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""
() => { () => {
// Mock out reading the map file from disk // Mock out reading the map file from disk
spyOn(fs, 'readFileSync').and.returnValue(INPUT_PROGRAM_MAP.toJSON()); spyOn(fs, 'readFileSync').and.returnValue(INPUT_PROGRAM_MAP.toJSON());
const {decorationAnalyses, renderer, switchMarkerAnalyses, privateDeclarationsAnalyses} = const {decorationAnalyses, renderer, switchMarkerAnalyses, privateDeclarationsAnalyses,
moduleWithProvidersAnalyses} =
createTestRenderer( createTestRenderer(
'test-package', [{ 'test-package', [{
...INPUT_PROGRAM, ...INPUT_PROGRAM,
contents: INPUT_PROGRAM.contents + '\n//# sourceMappingURL=file.js.map' contents: INPUT_PROGRAM.contents + '\n//# sourceMappingURL=file.js.map'
}]); }]);
const result = renderer.renderProgram( const result = renderer.renderProgram(
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses); decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
moduleWithProvidersAnalyses);
expect(result[0].path).toEqual('/dist/file.js'); expect(result[0].path).toEqual('/dist/file.js');
expect(result[0].contents) expect(result[0].contents)
.toEqual(RENDERED_CONTENTS + '\n' + generateMapFileComment('/dist/file.js.map')); .toEqual(RENDERED_CONTENTS + '\n' + generateMapFileComment('/dist/file.js.map'));
@ -259,10 +276,12 @@ A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""
contents: `export const NgModule = () => null;` contents: `export const NgModule = () => null;`
}; };
// The package name of `@angular/core` indicates that we are compiling the core library. // The package name of `@angular/core` indicates that we are compiling the core library.
const {decorationAnalyses, renderer, switchMarkerAnalyses, privateDeclarationsAnalyses} = const {decorationAnalyses, renderer, switchMarkerAnalyses, privateDeclarationsAnalyses,
moduleWithProvidersAnalyses} =
createTestRenderer('@angular/core', [CORE_FILE, R3_SYMBOLS_FILE]); createTestRenderer('@angular/core', [CORE_FILE, R3_SYMBOLS_FILE]);
renderer.renderProgram( renderer.renderProgram(
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses); decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
moduleWithProvidersAnalyses);
const addDefinitionsSpy = renderer.addDefinitions as jasmine.Spy; const addDefinitionsSpy = renderer.addDefinitions as jasmine.Spy;
expect(addDefinitionsSpy.calls.first().args[2]) expect(addDefinitionsSpy.calls.first().args[2])
.toContain(`/*@__PURE__*/ ɵngcc0.setClassMetadata(`); .toContain(`/*@__PURE__*/ ɵngcc0.setClassMetadata(`);
@ -277,10 +296,11 @@ A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""
export class MyModule {}\nMyModule.decorators = [\n { type: NgModule, args: [] }\n];\n` export class MyModule {}\nMyModule.decorators = [\n { type: NgModule, args: [] }\n];\n`
}; };
const {decorationAnalyses, renderer, switchMarkerAnalyses, privateDeclarationsAnalyses} = const {decorationAnalyses, renderer, switchMarkerAnalyses, privateDeclarationsAnalyses,
createTestRenderer('@angular/core', [CORE_FILE]); moduleWithProvidersAnalyses} = createTestRenderer('@angular/core', [CORE_FILE]);
renderer.renderProgram( renderer.renderProgram(
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses); decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
moduleWithProvidersAnalyses);
const addDefinitionsSpy = renderer.addDefinitions as jasmine.Spy; const addDefinitionsSpy = renderer.addDefinitions as jasmine.Spy;
expect(addDefinitionsSpy.calls.first().args[2]) expect(addDefinitionsSpy.calls.first().args[2])
.toContain(`/*@__PURE__*/ setClassMetadata(`); .toContain(`/*@__PURE__*/ setClassMetadata(`);
@ -291,10 +311,12 @@ A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""
describe('rendering typings', () => { describe('rendering typings', () => {
it('should render extract types into typings files', () => { it('should render extract types into typings files', () => {
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses} = const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
createTestRenderer('test-package', [INPUT_PROGRAM], INPUT_DTS_PROGRAM); moduleWithProvidersAnalyses} =
createTestRenderer('test-package', [INPUT_PROGRAM], [INPUT_DTS_PROGRAM]);
const result = renderer.renderProgram( const result = renderer.renderProgram(
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses); decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
moduleWithProvidersAnalyses);
const typingsFile = result.find(f => f.path === '/typings/file.d.ts') !; const typingsFile = result.find(f => f.path === '/typings/file.d.ts') !;
expect(typingsFile.contents) expect(typingsFile.contents)
@ -303,30 +325,195 @@ A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""
}); });
it('should render imports into typings files', () => { it('should render imports into typings files', () => {
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses} = const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
createTestRenderer('test-package', [INPUT_PROGRAM], INPUT_DTS_PROGRAM); moduleWithProvidersAnalyses} =
createTestRenderer('test-package', [INPUT_PROGRAM], [INPUT_DTS_PROGRAM]);
const result = renderer.renderProgram( const result = renderer.renderProgram(
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses); decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
moduleWithProvidersAnalyses);
const typingsFile = result.find(f => f.path === '/typings/file.d.ts') !; const typingsFile = result.find(f => f.path === '/typings/file.d.ts') !;
expect(typingsFile.contents).toContain(`// ADD IMPORTS\nexport declare class A`); expect(typingsFile.contents).toContain(`// ADD IMPORTS\nexport declare class A`);
}); });
it('should render exports into typings files', () => { it('should render exports into typings files', () => {
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses} = const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
createTestRenderer('test-package', [INPUT_PROGRAM], INPUT_DTS_PROGRAM); moduleWithProvidersAnalyses} =
createTestRenderer('test-package', [INPUT_PROGRAM], [INPUT_DTS_PROGRAM]);
// Add a mock export to trigger export rendering // Add a mock export to trigger export rendering
privateDeclarationsAnalyses.push( privateDeclarationsAnalyses.push(
{identifier: 'ComponentB', from: '/src/file.js', dtsFrom: '/typings/b.d.ts'}); {identifier: 'ComponentB', from: '/src/file.js', dtsFrom: '/typings/b.d.ts'});
const result = renderer.renderProgram( const result = renderer.renderProgram(
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses); decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
moduleWithProvidersAnalyses);
const typingsFile = result.find(f => f.path === '/typings/file.d.ts') !; const typingsFile = result.find(f => f.path === '/typings/file.d.ts') !;
expect(typingsFile.contents) expect(typingsFile.contents)
.toContain(`// ADD EXPORTS\n\n// ADD IMPORTS\nexport declare class A`); .toContain(`// ADD EXPORTS\n\n// ADD IMPORTS\nexport declare class A`);
}); });
it('should fixup functions/methods that return ModuleWithProviders structures', () => {
const MODULE_WITH_PROVIDERS_PROGRAM = [
{
name: '/src/index.js',
contents: `
import {ExternalModule} from './module';
import {LibraryModule} from 'some-library';
export class SomeClass {}
export class SomeModule {
static withProviders1() {
return {ngModule: SomeModule};
}
static withProviders2() {
return {ngModule: SomeModule};
}
static withProviders3() {
return {ngModule: SomeClass};
}
static withProviders4() {
return {ngModule: ExternalModule};
}
static withProviders5() {
return {ngModule: ExternalModule};
}
static withProviders6() {
return {ngModule: LibraryModule};
}
static withProviders7() {
return {ngModule: SomeModule, providers: []};
};
static withProviders8() {
return {ngModule: SomeModule};
}
}
export function withProviders1() {
return {ngModule: SomeModule};
}
export function withProviders2() {
return {ngModule: SomeModule};
}
export function withProviders3() {
return {ngModule: SomeClass};
}
export function withProviders4() {
return {ngModule: ExternalModule};
}
export function withProviders5() {
return {ngModule: ExternalModule};
}
export function withProviders6() {
return {ngModule: LibraryModule};
}
export function withProviders7() {
return {ngModule: SomeModule, providers: []};
};
export function withProviders8() {
return {ngModule: SomeModule};
}`,
},
{
name: '/src/module.js',
contents: `
export class ExternalModule {
static withProviders1() {
return {ngModule: ExternalModule};
}
static withProviders2() {
return {ngModule: ExternalModule};
}
}`
},
{
name: '/node_modules/some-library/index.d.ts',
contents: 'export declare class LibraryModule {}'
},
];
const MODULE_WITH_PROVIDERS_DTS_PROGRAM = [
{
name: '/typings/index.d.ts',
contents: `
import {ModuleWithProviders} from '@angular/core';
export declare class SomeClass {}
export interface MyModuleWithProviders extends ModuleWithProviders {}
export declare class SomeModule {
static withProviders1(): ModuleWithProviders;
static withProviders2(): ModuleWithProviders<any>;
static withProviders3(): ModuleWithProviders<SomeClass>;
static withProviders4(): ModuleWithProviders;
static withProviders5();
static withProviders6(): ModuleWithProviders;
static withProviders7(): {ngModule: SomeModule, providers: any[]};
static withProviders8(): MyModuleWithProviders;
}
export declare function withProviders1(): ModuleWithProviders;
export declare function withProviders2(): ModuleWithProviders<any>;
export declare function withProviders3(): ModuleWithProviders<SomeClass>;
export declare function withProviders4(): ModuleWithProviders;
export declare function withProviders5();
export declare function withProviders6(): ModuleWithProviders;
export declare function withProviders7(): {ngModule: SomeModule, providers: any[]};
export declare function withProviders8(): MyModuleWithProviders;`
},
{
name: '/typings/module.d.ts',
contents: `
export interface ModuleWithProviders {}
export declare class ExternalModule {
static withProviders1(): ModuleWithProviders;
static withProviders2(): ModuleWithProviders;
}`
},
{
name: '/node_modules/some-library/index.d.ts',
contents: 'export declare class LibraryModule {}'
},
];
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
moduleWithProvidersAnalyses} =
createTestRenderer(
'test-package', MODULE_WITH_PROVIDERS_PROGRAM, MODULE_WITH_PROVIDERS_DTS_PROGRAM);
const result = renderer.renderProgram(
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
moduleWithProvidersAnalyses);
const typingsFile = result.find(f => f.path === '/typings/index.d.ts') !;
expect(typingsFile.contents).toContain(`
static withProviders1(): ModuleWithProviders<SomeModule>;
static withProviders2(): ModuleWithProviders<SomeModule>;
static withProviders3(): ModuleWithProviders<SomeClass>;
static withProviders4(): ModuleWithProviders<ɵngcc0.ExternalModule>;
static withProviders5(): ɵngcc1.ModuleWithProviders<ɵngcc0.ExternalModule>;
static withProviders6(): ModuleWithProviders<ɵngcc2.LibraryModule>;
static withProviders7(): ({ngModule: SomeModule, providers: any[]})&{ngModule:SomeModule};
static withProviders8(): (MyModuleWithProviders)&{ngModule:SomeModule};`);
expect(typingsFile.contents).toContain(`
export declare function withProviders1(): ModuleWithProviders<SomeModule>;
export declare function withProviders2(): ModuleWithProviders<SomeModule>;
export declare function withProviders3(): ModuleWithProviders<SomeClass>;
export declare function withProviders4(): ModuleWithProviders<ɵngcc0.ExternalModule>;
export declare function withProviders5(): ɵngcc1.ModuleWithProviders<ɵngcc0.ExternalModule>;
export declare function withProviders6(): ModuleWithProviders<ɵngcc2.LibraryModule>;
export declare function withProviders7(): ({ngModule: SomeModule, providers: any[]})&{ngModule:SomeModule};
export declare function withProviders8(): (MyModuleWithProviders)&{ngModule:SomeModule};`);
expect(renderer.addImports).toHaveBeenCalledWith(jasmine.any(MagicString), [
{name: './module', as: 'ɵngcc0'},
{name: '@angular/core', as: 'ɵngcc1'},
{name: 'some-library', as: 'ɵngcc2'},
]);
// The following expectation checks that we do not mistake `ModuleWithProviders` types
// that are not imported from `@angular/core`.
const typingsFile2 = result.find(f => f.path === '/typings/module.d.ts') !;
expect(typingsFile2.contents).toContain(`
static withProviders1(): (ModuleWithProviders)&{ngModule:ExternalModule};
static withProviders2(): (ModuleWithProviders)&{ngModule:ExternalModule};`);
});
}); });
}); });
}); });

View File

@ -109,7 +109,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
const valueContext = node.getSourceFile(); const valueContext = node.getSourceFile();
let typeContext = valueContext; let typeContext = valueContext;
const typeNode = this.reflector.getDtsDeclarationOfClass(node); const typeNode = this.reflector.getDtsDeclaration(node);
if (typeNode !== null) { if (typeNode !== null) {
typeContext = typeNode.getSourceFile(); typeContext = typeNode.getSourceFile();
} }
@ -183,8 +183,8 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
return toR3Reference(valueRef, valueRef, valueContext, valueContext); return toR3Reference(valueRef, valueRef, valueContext, valueContext);
} else { } else {
let typeRef = valueRef; let typeRef = valueRef;
let typeNode = this.reflector.getDtsDeclarationOfClass(typeRef.node); let typeNode = this.reflector.getDtsDeclaration(typeRef.node);
if (typeNode !== null) { if (typeNode !== null && ts.isClassDeclaration(typeNode)) {
typeRef = new ResolvedReference(typeNode, typeNode.name !); typeRef = new ResolvedReference(typeNode, typeNode.name !);
} }
return toR3Reference(valueRef, typeRef, valueContext, typeContext); return toR3Reference(valueRef, typeRef, valueContext, typeContext);
@ -197,9 +197,20 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
*/ */
private _extractModuleFromModuleWithProvidersFn(node: ts.FunctionDeclaration| private _extractModuleFromModuleWithProvidersFn(node: ts.FunctionDeclaration|
ts.MethodDeclaration): ts.Expression|null { ts.MethodDeclaration): ts.Expression|null {
const type = node.type; const type = node.type || null;
return type &&
(this._reflectModuleFromTypeParam(type) || this._reflectModuleFromLiteralType(type));
}
/**
* Retrieve an `NgModule` identifier (T) from the specified `type`, if it is of the form:
* `ModuleWithProviders<T>`
* @param type The type to reflect on.
* @returns the identifier of the NgModule type if found, or null otherwise.
*/
private _reflectModuleFromTypeParam(type: ts.TypeNode): ts.Expression|null {
// Examine the type of the function to see if it's a ModuleWithProviders reference. // Examine the type of the function to see if it's a ModuleWithProviders reference.
if (type === undefined || !ts.isTypeReferenceNode(type) || !ts.isIdentifier(type.typeName)) { if (!ts.isTypeReferenceNode(type) || !ts.isIdentifier(type.typeName)) {
return null; return null;
} }
@ -226,6 +237,32 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
return typeNodeToValueExpr(arg); return typeNodeToValueExpr(arg);
} }
/**
* Retrieve an `NgModule` identifier (T) from the specified `type`, if it is of the form:
* `A|B|{ngModule: T}|C`.
* @param type The type to reflect on.
* @returns the identifier of the NgModule type if found, or null otherwise.
*/
private _reflectModuleFromLiteralType(type: ts.TypeNode): ts.Expression|null {
if (!ts.isIntersectionTypeNode(type)) {
return null;
}
for (const t of type.types) {
if (ts.isTypeLiteralNode(t)) {
for (const m of t.members) {
const ngModuleType = ts.isPropertySignature(m) && ts.isIdentifier(m.name) &&
m.name.text === 'ngModule' && m.type ||
null;
const ngModuleExpression = ngModuleType && typeNodeToValueExpr(ngModuleType);
if (ngModuleExpression) {
return ngModuleExpression;
}
}
}
}
return null;
}
/** /**
* Compute a list of `Reference`s from a resolved metadata value. * Compute a list of `Reference`s from a resolved metadata value.
*/ */

View File

@ -448,7 +448,7 @@ export interface ReflectionHost {
getVariableValue(declaration: ts.VariableDeclaration): ts.Expression|null; getVariableValue(declaration: ts.VariableDeclaration): ts.Expression|null;
/** /**
* Take an exported declaration of a class (maybe downleveled to a variable) and look up the * Take an exported declaration (maybe a class down-leveled to a variable) and look up the
* declaration of its type in a separate .d.ts tree. * declaration of its type in a separate .d.ts tree.
* *
* This function is allowed to return `null` if the current compilation unit does not have a * This function is allowed to return `null` if the current compilation unit does not have a
@ -456,8 +456,8 @@ export interface ReflectionHost {
* are produced only during the emit of such a compilation. When compiling .js code, however, * are produced only during the emit of such a compilation. When compiling .js code, however,
* there is frequently a parallel .d.ts tree which this method exposes. * there is frequently a parallel .d.ts tree which this method exposes.
* *
* Note that the `ts.ClassDeclaration` returned from this function may not be from the same * Note that the `ts.Declaration` returned from this function may not be from the same
* `ts.Program` as the input declaration. * `ts.Program` as the input declaration.
*/ */
getDtsDeclarationOfClass(declaration: ts.Declaration): ts.ClassDeclaration|null; getDtsDeclaration(declaration: ts.Declaration): ts.Declaration|null;
} }

View File

@ -188,7 +188,7 @@ export class TypeScriptReflectionHost implements ReflectionHost {
return declaration.initializer || null; return declaration.initializer || null;
} }
getDtsDeclarationOfClass(_: ts.Declaration): ts.ClassDeclaration|null { return null; } getDtsDeclaration(_: ts.Declaration): ts.Declaration|null { return null; }
/** /**
* Resolve a `ts.Symbol` to its declaration, keeping track of the `viaModule` along the way. * Resolve a `ts.Symbol` to its declaration, keeping track of the `viaModule` along the way.

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AttributeMarker, InitialStylingFlags} from '@angular/compiler/src/core'; import {AttributeMarker} from '@angular/compiler/src/core';
import {setup} from '@angular/compiler/test/aot/test_util'; import {setup} from '@angular/compiler/test/aot/test_util';
import {compile, expectEmit} from './mock_compile'; import {compile, expectEmit} from './mock_compile';
@ -48,17 +48,15 @@ describe('compiler compliance', () => {
// The template should look like this (where IDENT is a wild card for an identifier): // The template should look like this (where IDENT is a wild card for an identifier):
const template = ` const template = `
const $c1$ = ["title", "Hello"]; const $c1$ = ["title", "Hello", ${AttributeMarker.Classes}, "my-app"];
const $c2$ = ["my-app", ${InitialStylingFlags.VALUES_MODE}, "my-app", true]; const $c2$ = ["cx", "20", "cy", "30", "r", "50"];
const $c3$ = ["cx", "20", "cy", "30", "r", "50"];
template: function MyComponent_Template(rf, ctx) { template: function MyComponent_Template(rf, ctx) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵelementStart(0, "div", $c1$); $r3$.ɵelementStart(0, "div", $c1$);
$r3$.ɵelementStyling($c2$);
$r3$.ɵnamespaceSVG(); $r3$.ɵnamespaceSVG();
$r3$.ɵelementStart(1, "svg"); $r3$.ɵelementStart(1, "svg");
$r3$.ɵelement(2, "circle", $c3$); $r3$.ɵelement(2, "circle", $c2$);
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
$r3$.ɵnamespaceHTML(); $r3$.ɵnamespaceHTML();
$r3$.ɵelementStart(3, "p"); $r3$.ɵelementStart(3, "p");
@ -100,13 +98,11 @@ describe('compiler compliance', () => {
// The template should look like this (where IDENT is a wild card for an identifier): // The template should look like this (where IDENT is a wild card for an identifier):
const template = ` const template = `
const $c1$ = ["title", "Hello"]; const $c1$ = ["title", "Hello", ${AttributeMarker.Classes}, "my-app"];
const $c2$ = ["my-app", ${InitialStylingFlags.VALUES_MODE}, "my-app", true];
template: function MyComponent_Template(rf, ctx) { template: function MyComponent_Template(rf, ctx) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵelementStart(0, "div", $c1$); $r3$.ɵelementStart(0, "div", $c1$);
$r3$.ɵelementStyling($c2$);
$r3$.ɵnamespaceMathML(); $r3$.ɵnamespaceMathML();
$r3$.ɵelementStart(1, "math"); $r3$.ɵelementStart(1, "math");
$r3$.ɵelement(2, "infinity"); $r3$.ɵelement(2, "infinity");
@ -150,13 +146,11 @@ describe('compiler compliance', () => {
// The template should look like this (where IDENT is a wild card for an identifier): // The template should look like this (where IDENT is a wild card for an identifier):
const template = ` const template = `
const $c1$ = ["title", "Hello"]; const $c1$ = ["title", "Hello", ${AttributeMarker.Classes}, "my-app"];
const $c2$ = ["my-app", ${InitialStylingFlags.VALUES_MODE}, "my-app", true];
template: function MyComponent_Template(rf, ctx) { template: function MyComponent_Template(rf, ctx) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵelementStart(0, "div", $c1$); $r3$.ɵelementStart(0, "div", $c1$);
$r3$.ɵelementStyling($c2$);
$r3$.ɵtext(1, "Hello "); $r3$.ɵtext(1, "Hello ");
$r3$.ɵelementStart(2, "b"); $r3$.ɵelementStart(2, "b");
$r3$.ɵtext(3, "World"); $r3$.ɵtext(3, "World");
@ -486,8 +480,8 @@ describe('compiler compliance', () => {
const factory = const factory =
'factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }'; 'factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }';
const template = ` const template = `
const _c0 = ["error"]; const $e0_classBindings$ = ["error"];
const _c1 = ["background-color"]; const $e0_styleBindings$ = ["background-color"];
MyComponent.ngComponentDef = i0.ɵdefineComponent({type:MyComponent,selectors:[["my-component"]], MyComponent.ngComponentDef = i0.ɵdefineComponent({type:MyComponent,selectors:[["my-component"]],
factory: function MyComponent_Factory(t){ factory: function MyComponent_Factory(t){
@ -498,7 +492,7 @@ describe('compiler compliance', () => {
template: function MyComponent_Template(rf,ctx){ template: function MyComponent_Template(rf,ctx){
if (rf & 1) { if (rf & 1) {
$r3$.ɵelementStart(0, "div"); $r3$.ɵelementStart(0, "div");
$r3$.ɵelementStyling(_c0, _c1); $r3$.ɵelementStyling($e0_classBindings$, $e0_styleBindings$);
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
@ -1092,156 +1086,224 @@ describe('compiler compliance', () => {
}); });
}); });
it('should support content projection in root template', () => { describe('content projection', () => {
const files = {
app: {
'spec.ts': `
import {Component, Directive, NgModule, TemplateRef} from '@angular/core';
@Component({selector: 'simple', template: '<div><ng-content></ng-content></div>'}) it('should support content projection in root template', () => {
export class SimpleComponent {} const files = {
app: {
@Component({ 'spec.ts': `
selector: 'complex', import {Component, Directive, NgModule, TemplateRef} from '@angular/core';
template: \`
<div id="first"><ng-content select="span[title=toFirst]"></ng-content></div> @Component({selector: 'simple', template: '<div><ng-content></ng-content></div>'})
<div id="second"><ng-content SELECT="span[title=toSecond]"></ng-content></div>\` export class SimpleComponent {}
@Component({
selector: 'complex',
template: \`
<div id="first"><ng-content select="span[title=toFirst]"></ng-content></div>
<div id="second"><ng-content SELECT="span[title=toSecond]"></ng-content></div>\`
})
export class ComplexComponent { }
@NgModule({declarations: [SimpleComponent, ComplexComponent]})
export class MyModule {}
@Component({
selector: 'my-app',
template: '<simple>content</simple> <complex></complex>'
}) })
export class ComplexComponent { } export class MyApp {}
`
}
};
@NgModule({declarations: [SimpleComponent, ComplexComponent]}) const SimpleComponentDefinition = `
export class MyModule {} SimpleComponent.ngComponentDef = $r3$.ɵdefineComponent({
type: SimpleComponent,
selectors: [["simple"]],
factory: function SimpleComponent_Factory(t) { return new (t || SimpleComponent)(); },
consts: 2,
vars: 0,
template: function SimpleComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵprojectionDef();
$r3$.ɵelementStart(0, "div");
$r3$.ɵprojection(1);
$r3$.ɵelementEnd();
}
},
encapsulation: 2
});`;
@Component({ const ComplexComponentDefinition = `
selector: 'my-app', const $c3$ = ["id","first"];
template: '<simple>content</simple> <complex></complex>' const $c4$ = ["id","second"];
}) const $c1$ = [[["span", "title", "tofirst"]], [["span", "title", "tosecond"]]];
export class MyApp {} const $c2$ = ["span[title=toFirst]", "span[title=toSecond]"];
`
} ComplexComponent.ngComponentDef = $r3$.ɵdefineComponent({
}; type: ComplexComponent,
selectors: [["complex"]],
factory: function ComplexComponent_Factory(t) { return new (t || ComplexComponent)(); },
consts: 4,
vars: 0,
template: function ComplexComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵprojectionDef($c1$, $c2$);
$r3$.ɵelementStart(0, "div", $c3$);
$r3$.ɵprojection(1, 1);
$r3$.ɵelementEnd();
$r3$.ɵelementStart(2, "div", $c4$);
$r3$.ɵprojection(3, 2);
$r3$.ɵelementEnd();
}
},
encapsulation: 2
});
`;
const SimpleComponentDefinition = ` const result = compile(files, angularFiles);
SimpleComponent.ngComponentDef = $r3$.ɵdefineComponent({ const source = result.source;
type: SimpleComponent,
selectors: [["simple"]],
factory: function SimpleComponent_Factory(t) { return new (t || SimpleComponent)(); },
consts: 2,
vars: 0,
template: function SimpleComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵprojectionDef();
$r3$.ɵelementStart(0, "div");
$r3$.ɵprojection(1);
$r3$.ɵelementEnd();
}
},
encapsulation: 2
});`;
const ComplexComponentDefinition = ` expectEmit(
const $c3$ = ["id","first"]; result.source, SimpleComponentDefinition, 'Incorrect SimpleComponent definition');
const $c4$ = ["id","second"]; expectEmit(
const $c1$ = [[["span", "title", "tofirst"]], [["span", "title", "tosecond"]]]; result.source, ComplexComponentDefinition, 'Incorrect ComplexComponent definition');
const $c2$ = ["span[title=toFirst]", "span[title=toSecond]"]; });
ComplexComponent.ngComponentDef = $r3$.ɵdefineComponent({ it('should support content projection in nested templates', () => {
type: ComplexComponent, const files = {
selectors: [["complex"]], app: {
factory: function ComplexComponent_Factory(t) { return new (t || ComplexComponent)(); }, 'spec.ts': `
consts: 4, import {Component, NgModule} from '@angular/core';
vars: 0,
template: function ComplexComponent_Template(rf, ctx) { @Component({
if (rf & 1) { template: \`
$r3$.ɵprojectionDef($c1$, $c2$); <div id="second" *ngIf="visible">
$r3$.ɵelementStart(0, "div", $c3$); <ng-content SELECT="span[title=toFirst]"></ng-content>
</div>
<div id="third" *ngIf="visible">
No ng-content, no instructions generated.
</div>
<ng-template>
'*' selector: <ng-content></ng-content>
</ng-template>
\`,
})
class Cmp {}
@NgModule({ declarations: [Cmp] })
class Module {}
`
}
};
const output = `
const $_c0$ = [${AttributeMarker.SelectOnly}, "ngIf"];
const $_c1$ = ["id", "second"];
function Cmp_div_Template_0(rf, ctx) { if (rf & 1) {
$r3$.ɵelementStart(0, "div", $_c1$);
$r3$.ɵprojection(1, 1); $r3$.ɵprojection(1, 1);
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
$r3$.ɵelementStart(2, "div", $c4$); } }
$r3$.ɵprojection(3, 2); const $_c4$ = ["id", "third"];
function Cmp_div_Template_1(rf, ctx) {
if (rf & 1) {
$r3$.ɵelementStart(0, "div", $_c4$);
$r3$.ɵtext(1, " No ng-content, no instructions generated. ");
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
} }
},
encapsulation: 2
});
`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(result.source, SimpleComponentDefinition, 'Incorrect SimpleComponent definition');
expectEmit(
result.source, ComplexComponentDefinition, 'Incorrect ComplexComponent definition');
});
it('should support content projection in nested templates', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
template: \`
<div id="second" *ngIf="visible">
<ng-content SELECT="span[title=toFirst]"></ng-content>
</div>
<div id="third" *ngIf="visible">
No ng-content, no instructions generated.
</div>
<ng-template>
'*' selector: <ng-content></ng-content>
</ng-template>
\`,
})
class Cmp {}
@NgModule({ declarations: [Cmp] })
class Module {}
`
}
};
const output = `
const $_c0$ = [1, "ngIf"];
const $_c1$ = ["id", "second"];
const $_c2$ = [[["span", "title", "tofirst"]]];
const $_c3$ = ["span[title=toFirst]"];
function Cmp_div_Template_0(rf, ctx) { if (rf & 1) {
$r3$.ɵprojectionDef($_c2$, $_c3$);
$r3$.ɵelementStart(0, "div", $_c1$);
$r3$.ɵprojection(1, 1);
$r3$.ɵelementEnd();
} }
const $_c4$ = ["id", "third"];
function Cmp_div_Template_1(rf, ctx) {
if (rf & 1) {
$r3$.ɵelementStart(0, "div", $_c4$);
$r3$.ɵtext(1, " No ng-content, no instructions generated. ");
$r3$.ɵelementEnd();
} }
} function Cmp_ng_template_Template_2(rf, ctx) {
function Cmp_ng_template_Template_2(rf, ctx) { if (rf & 1) {
if (rf & 1) { $r3$.ɵtext(0, " '*' selector: ");
$r3$.ɵprojectionDef(); $r3$.ɵprojection(1);
$r3$.ɵtext(0, " '*' selector: "); }
$r3$.ɵprojection(1);
} }
} const $_c2$ = [[["span", "title", "tofirst"]]];
const $_c3$ = ["span[title=toFirst]"];
template: function Cmp_Template(rf, ctx) {
if (rf & 1) { template: function Cmp_Template(rf, ctx) {
$r3$.ɵtemplate(0, Cmp_div_Template_0, 2, 0, "div", $_c0$); if (rf & 1) {
$r3$template(1, Cmp_div_Template_1, 2, 0, "div", $_c0$); $r3$projectionDef($_c2$, $_c3$);
$r3$.ɵtemplate(2, Cmp_ng_template_Template_2, 2, 0, "ng-template"); $r3$.ɵtemplate(0, Cmp_div_Template_0, 2, 0, "div", $_c0$);
$r3$.ɵtemplate(1, Cmp_div_Template_1, 2, 0, "div", $_c0$);
$r3$.ɵtemplate(2, Cmp_ng_template_Template_2, 2, 0, "ng-template");
}
if (rf & 2) {
$r3$.ɵelementProperty(0, "ngIf", $r3$.ɵbind(ctx.visible));
$r3$.ɵelementProperty(1, "ngIf", $r3$.ɵbind(ctx.visible));
}
} }
if (rf & 2) { `;
$r3$.ɵelementProperty(0, "ngIf", $r3$.ɵbind(ctx.visible));
$r3$.ɵelementProperty(1, "ngIf", $r3$.ɵbind(ctx.visible));
}
}
`;
const {source} = compile(files, angularFiles); const {source} = compile(files, angularFiles);
expectEmit(source, output, 'Invalid content projection instructions generated'); expectEmit(source, output, 'Invalid content projection instructions generated');
});
it('should support content projection in both the root and nested templates', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
template: \`
<ng-content select="[id=toMainBefore]"></ng-content>
<ng-template>
<ng-content select="[id=toTemplate]"></ng-content>
<ng-template>
<ng-content select="[id=toNestedTemplate]"></ng-content>
</ng-template>
</ng-template>
<ng-template>
'*' selector in a template: <ng-content></ng-content>
</ng-template>
<ng-content select="[id=toMainAfter]"></ng-content>
\`,
})
class Cmp {}
@NgModule({ declarations: [Cmp] })
class Module {}
`
}
};
const output = `
function Cmp_ng_template_ng_template_Template_1(rf, ctx) {
if (rf & 1) {
$r3$.ɵprojection(0, 4);
}
}
function Cmp_ng_template_Template_1(rf, ctx) {
if (rf & 1) {
$r3$.ɵprojection(0, 3);
$r3$.ɵtemplate(1, Cmp_ng_template_ng_template_Template_1, 1, 0, "ng-template");
}
}
function Cmp_ng_template_Template_2(rf, ctx) {
if (rf & 1) {
$r3$.ɵtext(0, " '*' selector in a template: ");
$r3$.ɵprojection(1);
}
}
const $_c0$ = [[["", "id", "tomainbefore"]], [["", "id", "tomainafter"]], [["", "id", "totemplate"]], [["", "id", "tonestedtemplate"]]];
const $_c1$ = ["[id=toMainBefore]", "[id=toMainAfter]", "[id=toTemplate]", "[id=toNestedTemplate]"];
template: function Cmp_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵprojectionDef($_c2$, $_c3$);
$r3$.ɵprojection(0, 1);
$r3$.ɵtemplate(1, Cmp_ng_template_Template_1, 2, 0, "ng-template");
$r3$.ɵtemplate(2, Cmp_ng_template_Template_2, 2, 0, "ng-template");
$r3$.ɵprojection(3, 2);
}
}
`;
const {source} = compile(files, angularFiles);
expectEmit(source, output, 'Invalid content projection instructions generated');
});
}); });
describe('queries', () => { describe('queries', () => {

View File

@ -236,7 +236,7 @@ describe('compiler compliance: directives', () => {
const MyComponentDefinition = ` const MyComponentDefinition = `
const $_c0$ = [1, "ngIf"]; const $_c0$ = [${AttributeMarker.SelectOnly}, "ngIf"];
const $_c1$ = ["directiveA", ""]; const $_c1$ = ["directiveA", ""];
function MyComponent_ng_container_Template_0(rf, ctx) { function MyComponent_ng_container_Template_0(rf, ctx) {
if (rf & 1) { if (rf & 1) {

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AttributeMarker} from '@angular/compiler/src/core';
import {setup} from '@angular/compiler/test/aot/test_util'; import {setup} from '@angular/compiler/test/aot/test_util';
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../../../compiler/src/compiler'; import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../../../compiler/src/compiler';
@ -393,7 +394,7 @@ describe('i18n support in the view compiler', () => {
`; `;
const output = String.raw ` const output = String.raw `
const $_c0$ = ["ngFor", "", 1, "ngForOf"]; const $_c0$ = ["ngFor", "", ${AttributeMarker.SelectOnly}, "ngForOf"];
/** /**
* @desc d * @desc d
* @meaning m * @meaning m
@ -522,7 +523,7 @@ describe('i18n support in the view compiler', () => {
`; `;
const output = String.raw ` const output = String.raw `
const $_c0$ = ["ngFor", "", 1, "ngForOf"]; const $_c0$ = ["ngFor", "", ${AttributeMarker.SelectOnly}, "ngForOf"];
/** /**
* @desc d * @desc d
* @meaning m * @meaning m
@ -922,7 +923,7 @@ describe('i18n support in the view compiler', () => {
`; `;
const output = String.raw ` const output = String.raw `
const $_c0$ = [1, "ngIf"]; const $_c0$ = [${AttributeMarker.SelectOnly}, "ngIf"];
const $MSG_EXTERNAL_7679414751795588050$$APP_SPEC_TS__1$ = goog.getMsg(" Some other content {$interpolation} {$startTagDiv} More nested levels with bindings {$interpolation_1} {$closeTagDiv}", { const $MSG_EXTERNAL_7679414751795588050$$APP_SPEC_TS__1$ = goog.getMsg(" Some other content {$interpolation} {$startTagDiv} More nested levels with bindings {$interpolation_1} {$closeTagDiv}", {
"interpolation": "\uFFFD0\uFFFD", "interpolation": "\uFFFD0\uFFFD",
"startTagDiv": "\uFFFD#3\uFFFD", "startTagDiv": "\uFFFD#3\uFFFD",
@ -976,7 +977,7 @@ describe('i18n support in the view compiler', () => {
const output = String.raw ` const output = String.raw `
const $_c0$ = ["src", "logo.png"]; const $_c0$ = ["src", "logo.png"];
const $_c1$ = [1, "ngIf"]; const $_c1$ = [${AttributeMarker.SelectOnly}, "ngIf"];
function MyComponent_img_Template_1(rf, ctx) { function MyComponent_img_Template_1(rf, ctx) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵelement(0, "img", $_c0$); $r3$.ɵelement(0, "img", $_c0$);
@ -1043,7 +1044,7 @@ describe('i18n support in the view compiler', () => {
`; `;
const output = String.raw ` const output = String.raw `
const $_c0$ = [1, "ngIf"]; const $_c0$ = [${AttributeMarker.SelectOnly}, "ngIf"];
function MyComponent_div_div_Template_4(rf, ctx) { function MyComponent_div_div_Template_4(rf, ctx) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵi18nStart(0, $I18N_EXTERNAL_1221890473527419724$$APP_SPEC_TS_0$, 2); $r3$.ɵi18nStart(0, $I18N_EXTERNAL_1221890473527419724$$APP_SPEC_TS_0$, 2);
@ -1136,7 +1137,7 @@ describe('i18n support in the view compiler', () => {
`; `;
const output = String.raw ` const output = String.raw `
const $_c0$ = [1, "ngIf"]; const $_c0$ = [${AttributeMarker.SelectOnly}, "ngIf"];
const $MSG_EXTERNAL_119975189388320493$$APP_SPEC_TS__1$ = goog.getMsg("Some other content {$startTagSpan}{$interpolation}{$closeTagSpan}", { const $MSG_EXTERNAL_119975189388320493$$APP_SPEC_TS__1$ = goog.getMsg("Some other content {$startTagSpan}{$interpolation}{$closeTagSpan}", {
"startTagSpan": "\uFFFD#2\uFFFD", "startTagSpan": "\uFFFD#2\uFFFD",
"interpolation": "\uFFFD0\uFFFD", "interpolation": "\uFFFD0\uFFFD",
@ -1259,23 +1260,21 @@ describe('i18n support in the view compiler', () => {
`; `;
const output = String.raw ` const output = String.raw `
const $_c0$ = ["myClass", 1, "myClass", true]; const $_c0$ = [${AttributeMarker.Classes}, "myClass"];
const $MSG_EXTERNAL_5295701706185791735$$APP_SPEC_TS_0$ = goog.getMsg("Text #1"); const $MSG_EXTERNAL_5295701706185791735$$APP_SPEC_TS_0$ = goog.getMsg("Text #1");
const $_c1$ = ["padding", 1, "padding", "10px"]; const $_c1$ = [${AttributeMarker.Styles}, "padding", "10px"];
const $MSG_EXTERNAL_4722270221386399294$$APP_SPEC_TS_2$ = goog.getMsg("Text #2"); const $MSG_EXTERNAL_4722270221386399294$$APP_SPEC_TS_2$ = goog.getMsg("Text #2");
consts: 4, consts: 4,
vars: 0, vars: 0,
template: function MyComponent_Template(rf, ctx) { template: function MyComponent_Template(rf, ctx) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵelementStart(0, "span"); $r3$.ɵelementStart(0, "span", $_c0$);
$r3$.ɵi18nStart(1, $MSG_EXTERNAL_5295701706185791735$$APP_SPEC_TS_0$); $r3$.ɵi18nStart(1, $MSG_EXTERNAL_5295701706185791735$$APP_SPEC_TS_0$);
$r3$.ɵelementStyling($_c0$);
$r3$.ɵi18nEnd(); $r3$.ɵi18nEnd();
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
$r3$.ɵelementStart(2, "span"); $r3$.ɵelementStart(2, "span", $_c1$);
$r3$.ɵi18nStart(3, $MSG_EXTERNAL_4722270221386399294$$APP_SPEC_TS_2$); $r3$.ɵi18nStart(3, $MSG_EXTERNAL_4722270221386399294$$APP_SPEC_TS_2$);
$r3$.ɵelementStyling(null, $_c1$);
$r3$.ɵi18nEnd(); $r3$.ɵi18nEnd();
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
} }
@ -1701,7 +1700,7 @@ describe('i18n support in the view compiler', () => {
const $I18N_EXTERNAL_7842238767399919809$$APP_SPEC_TS_0$ = $r3$.ɵi18nPostprocess($MSG_EXTERNAL_7842238767399919809$$APP_SPEC_TS_0$, { const $I18N_EXTERNAL_7842238767399919809$$APP_SPEC_TS_0$ = $r3$.ɵi18nPostprocess($MSG_EXTERNAL_7842238767399919809$$APP_SPEC_TS_0$, {
"VAR_SELECT": "\uFFFD0\uFFFD" "VAR_SELECT": "\uFFFD0\uFFFD"
}); });
const $_c0$ = [1, "ngIf"]; const $_c0$ = [${AttributeMarker.SelectOnly}, "ngIf"];
const $_c1$ = ["title", "icu only"]; const $_c1$ = ["title", "icu only"];
const $MSG_EXTERNAL_8806993169187953163$$APP_SPEC_TS__3$ = goog.getMsg("{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}"); const $MSG_EXTERNAL_8806993169187953163$$APP_SPEC_TS__3$ = goog.getMsg("{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}");
const $I18N_EXTERNAL_8806993169187953163$$APP_SPEC_TS__3$ = $r3$.ɵi18nPostprocess($MSG_EXTERNAL_8806993169187953163$$APP_SPEC_TS__3$, { const $I18N_EXTERNAL_8806993169187953163$$APP_SPEC_TS__3$ = $r3$.ɵi18nPostprocess($MSG_EXTERNAL_8806993169187953163$$APP_SPEC_TS__3$, {
@ -1942,7 +1941,7 @@ describe('i18n support in the view compiler', () => {
const $I18N_APP_SPEC_TS_2$ = $r3$.ɵi18nPostprocess($MSG_APP_SPEC_TS_2$, { const $I18N_APP_SPEC_TS_2$ = $r3$.ɵi18nPostprocess($MSG_APP_SPEC_TS_2$, {
"VAR_SELECT": "\uFFFD1\uFFFD" "VAR_SELECT": "\uFFFD1\uFFFD"
}); });
const $_c3$ = [1, "ngIf"]; const $_c3$ = [${AttributeMarker.SelectOnly}, "ngIf"];
const $MSG_APP_SPEC_TS__4$ = goog.getMsg("{VAR_SELECT, select, male {male} female {female} other {other}}"); const $MSG_APP_SPEC_TS__4$ = goog.getMsg("{VAR_SELECT, select, male {male} female {female} other {other}}");
const $I18N_APP_SPEC_TS__4$ = $r3$.ɵi18nPostprocess($MSG_APP_SPEC_TS__4$, { const $I18N_APP_SPEC_TS__4$ = $r3$.ɵi18nPostprocess($MSG_APP_SPEC_TS__4$, {
"VAR_SELECT": "\uFFFD0:1\uFFFD" "VAR_SELECT": "\uFFFD0:1\uFFFD"
@ -2050,7 +2049,7 @@ describe('i18n support in the view compiler', () => {
const $I18N_EXTERNAL_7842238767399919809$$APP_SPEC_TS_1$ = $r3$.ɵi18nPostprocess($MSG_EXTERNAL_7842238767399919809$$APP_SPEC_TS_1$, { const $I18N_EXTERNAL_7842238767399919809$$APP_SPEC_TS_1$ = $r3$.ɵi18nPostprocess($MSG_EXTERNAL_7842238767399919809$$APP_SPEC_TS_1$, {
"VAR_SELECT": "\uFFFD0\uFFFD" "VAR_SELECT": "\uFFFD0\uFFFD"
}); });
const $_c0$ = [1, "ngIf"]; const $_c0$ = [${AttributeMarker.SelectOnly}, "ngIf"];
const $MSG_EXTERNAL_7068143081688428291$$APP_SPEC_TS__3$ = goog.getMsg("{VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other}}"); const $MSG_EXTERNAL_7068143081688428291$$APP_SPEC_TS__3$ = goog.getMsg("{VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other}}");
const $I18N_EXTERNAL_7068143081688428291$$APP_SPEC_TS__3$ = $r3$.ɵi18nPostprocess($MSG_EXTERNAL_7068143081688428291$$APP_SPEC_TS__3$, { const $I18N_EXTERNAL_7068143081688428291$$APP_SPEC_TS__3$ = $r3$.ɵi18nPostprocess($MSG_EXTERNAL_7068143081688428291$$APP_SPEC_TS__3$, {
"VAR_SELECT": "\uFFFD0:1\uFFFD" "VAR_SELECT": "\uFFFD0:1\uFFFD"
@ -2113,7 +2112,7 @@ describe('i18n support in the view compiler', () => {
const $I18N_EXTERNAL_7825031864601787094$$APP_SPEC_TS_1$ = $r3$.ɵi18nPostprocess($MSG_EXTERNAL_7825031864601787094$$APP_SPEC_TS_1$, { const $I18N_EXTERNAL_7825031864601787094$$APP_SPEC_TS_1$ = $r3$.ɵi18nPostprocess($MSG_EXTERNAL_7825031864601787094$$APP_SPEC_TS_1$, {
"VAR_SELECT": "\uFFFD0\uFFFD" "VAR_SELECT": "\uFFFD0\uFFFD"
}); });
const $_c0$ = [1, "ngIf"]; const $_c0$ = [${AttributeMarker.SelectOnly}, "ngIf"];
const $MSG_EXTERNAL_2310343208266678305$$APP_SPEC_TS__3$ = goog.getMsg("{VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other: {$interpolation}}}", { const $MSG_EXTERNAL_2310343208266678305$$APP_SPEC_TS__3$ = goog.getMsg("{VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other: {$interpolation}}}", {
"interpolation": "\uFFFD1:1\uFFFD" "interpolation": "\uFFFD1:1\uFFFD"
}); });

View File

@ -120,4 +120,70 @@ describe('r3_view_compiler', () => {
expectEmit(result.source, bV_call, 'Incorrect bV call'); expectEmit(result.source, bV_call, 'Incorrect bV call');
}); });
}); });
describe('animations', () => {
it('should keep @attr but suppress [@attr]', () => {
const files: MockDirectory = {
app: {
'example.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-app',
template: '<div @attrOnly [@myAnimation]="exp"></div>'
})
export class MyApp {
}
@NgModule({declarations: [MyApp]})
export class MyModule {}`
}
};
const template = `
const _c0 = ["@attrOnly", ""];
// ...
template: function MyApp_Template(rf, ctx) {
if (rf & 1) {
$i0$.ɵelement(0, "div", _c0);
// ...
}
// ...
}`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect initialization attributes');
});
it('should dedup multiple [@event] listeners', () => {
const files: MockDirectory = {
app: {
'example.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-app',
template: '<div (@mySelector.start)="false" (@mySelector.done)="false" [@mySelector]="0"></div>'
})
export class MyApp {
}
@NgModule({declarations: [MyApp]})
export class MyModule {}`
}
};
const template = `
const _c0 = [3, "mySelector"];
// ...
template: function MyApp_Template(rf, ctx) {
if (rf & 1) {
$i0$.ɵelementStart(0, "div", _c0);
// ...
}
// ...
}`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect initialization attributes');
});
});
}); });

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AttributeMarker, InitialStylingFlags, ViewEncapsulation} from '@angular/compiler/src/core'; import {AttributeMarker, ViewEncapsulation} from '@angular/compiler/src/core';
import {setup} from '@angular/compiler/test/aot/test_util'; import {setup} from '@angular/compiler/test/aot/test_util';
import {compile, expectEmit} from './mock_compile'; import {compile, expectEmit} from './mock_compile';
@ -214,7 +214,6 @@ describe('compiler compliance: styling', () => {
}; };
const template = ` const template = `
const $e0_attrs$ = ["@foo", ""];
const $e1_attrs$ = ["@bar", ""]; const $e1_attrs$ = ["@bar", ""];
const $e2_attrs$ = ["@baz", ""]; const $e2_attrs$ = ["@baz", ""];
@ -224,7 +223,7 @@ describe('compiler compliance: styling', () => {
vars: 1, vars: 1,
template: function MyComponent_Template(rf, $ctx$) { template: function MyComponent_Template(rf, $ctx$) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵelement(0, "div", $e0_attrs$); $r3$.ɵelement(0, "div");
$r3$.ɵelement(1, "div", $e1_attrs$); $r3$.ɵelement(1, "div", $e1_attrs$);
$r3$.ɵelement(2, "div", $e2_attrs$); $r3$.ɵelement(2, "div", $e2_attrs$);
} }
@ -366,8 +365,8 @@ describe('compiler compliance: styling', () => {
}; };
const template = ` const template = `
const $e0_attrs$ = [${AttributeMarker.SelectOnly}, "style"]; const $_c0$ = [${AttributeMarker.Styles}, "opacity", "1", ${AttributeMarker.SelectOnly}, "style"];
const $e0_styling$ = ["opacity","width","height",${InitialStylingFlags.VALUES_MODE},"opacity","1"]; const $_c1$ = ["width", "height"];
MyComponent.ngComponentDef = $r3$.ɵdefineComponent({ MyComponent.ngComponentDef = $r3$.ɵdefineComponent({
type: MyComponent, type: MyComponent,
@ -379,14 +378,14 @@ describe('compiler compliance: styling', () => {
vars: 1, vars: 1,
template: function MyComponent_Template(rf, $ctx$) { template: function MyComponent_Template(rf, $ctx$) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵelementStart(0, "div", $e0_attrs$); $r3$.ɵelementStart(0, "div", $_c0$);
$r3$.ɵelementStyling(null, $e0_styling$, $r3$.ɵdefaultStyleSanitizer); $r3$.ɵelementStyling(null, $_c1$, $r3$.ɵdefaultStyleSanitizer);
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵelementStylingMap(0, null, $ctx$.myStyleExp); $r3$.ɵelementStylingMap(0, null, $ctx$.myStyleExp);
$r3$.ɵelementStyleProp(0, 1, $ctx$.myWidth); $r3$.ɵelementStyleProp(0, 0, $ctx$.myWidth);
$r3$.ɵelementStyleProp(0, 2, $ctx$.myHeight); $r3$.ɵelementStyleProp(0, 1, $ctx$.myHeight);
$r3$.ɵelementStylingApply(0); $r3$.ɵelementStylingApply(0);
$r3$.ɵelementAttribute(0, "style", $r3$.ɵbind("border-width: 10px"), $r3$.ɵsanitizeStyle); $r3$.ɵelementAttribute(0, "style", $r3$.ɵbind("border-width: 10px"), $r3$.ɵsanitizeStyle);
} }
@ -421,7 +420,7 @@ describe('compiler compliance: styling', () => {
}; };
const template = ` const template = `
const _c0 = ["background-image"]; const $_c0$ = ["background-image"];
export class MyComponent { export class MyComponent {
constructor() { constructor() {
this.myImage = 'url(foo.jpg)'; this.myImage = 'url(foo.jpg)';
@ -456,7 +455,6 @@ describe('compiler compliance: styling', () => {
}); });
it('should support [style.foo.suffix] style bindings with a suffix', () => { it('should support [style.foo.suffix] style bindings with a suffix', () => {
const files = { const files = {
app: { app: {
'spec.ts': ` 'spec.ts': `
@ -476,7 +474,7 @@ describe('compiler compliance: styling', () => {
}; };
const template = ` const template = `
const $e0_styles$= ["font-size"]; const $e0_styles$ = ["font-size"];
template: function MyComponent_Template(rf, ctx) { template: function MyComponent_Template(rf, ctx) {
if (rf & 1) { if (rf & 1) {
@ -564,8 +562,8 @@ describe('compiler compliance: styling', () => {
}; };
const template = ` const template = `
const $e0_attrs$ = [${AttributeMarker.SelectOnly}, "class"]; const $e0_attrs$ = [${AttributeMarker.Classes}, "grape", ${AttributeMarker.SelectOnly}, "class"];
const $e0_cd$ = ["grape","apple","orange",${InitialStylingFlags.VALUES_MODE},"grape",true]; const $e0_bindings$ = ["apple", "orange"];
MyComponent.ngComponentDef = $r3$.ɵdefineComponent({ MyComponent.ngComponentDef = $r3$.ɵdefineComponent({
type: MyComponent, type: MyComponent,
@ -578,13 +576,13 @@ describe('compiler compliance: styling', () => {
template: function MyComponent_Template(rf, $ctx$) { template: function MyComponent_Template(rf, $ctx$) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵelementStart(0, "div", $e0_attrs$); $r3$.ɵelementStart(0, "div", $e0_attrs$);
$r3$.ɵelementStyling($e0_cd$); $r3$.ɵelementStyling($e0_bindings$);
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵelementStylingMap(0, $ctx$.myClassExp); $r3$.ɵelementStylingMap(0, $ctx$.myClassExp);
$r3$.ɵelementClassProp(0, 1, $ctx$.yesToApple); $r3$.ɵelementClassProp(0, 0, $ctx$.yesToApple);
$r3$.ɵelementClassProp(0, 2, $ctx$.yesToOrange); $r3$.ɵelementClassProp(0, 1, $ctx$.yesToOrange);
$r3$.ɵelementStylingApply(0); $r3$.ɵelementStylingApply(0);
$r3$.ɵelementAttribute(0, "class", $r3$.ɵbind("banana")); $r3$.ɵelementAttribute(0, "class", $r3$.ɵbind("banana"));
} }
@ -606,7 +604,7 @@ describe('compiler compliance: styling', () => {
@Component({ @Component({
selector: 'my-component', selector: 'my-component',
template: \`<div class="foo" template: \`<div class=" foo "
style="width:100px" style="width:100px"
[attr.class]="'round'" [attr.class]="'round'"
[attr.style]="'height:100px'"></div>\` [attr.style]="'height:100px'"></div>\`
@ -620,9 +618,7 @@ describe('compiler compliance: styling', () => {
}; };
const template = ` const template = `
const $e0_attrs$ = [${AttributeMarker.SelectOnly}, "class", "style"]; const $e0_attrs$ = [${AttributeMarker.Classes}, "foo", ${AttributeMarker.Styles}, "width", "100px", ${AttributeMarker.SelectOnly}, "class", "style"];
const $e0_cd$ = ["foo",${InitialStylingFlags.VALUES_MODE},"foo",true];
const $e0_sd$ = ["width",${InitialStylingFlags.VALUES_MODE},"width","100px"];
MyComponent.ngComponentDef = $r3$.ɵdefineComponent({ MyComponent.ngComponentDef = $r3$.ɵdefineComponent({
type: MyComponent, type: MyComponent,
@ -635,7 +631,6 @@ describe('compiler compliance: styling', () => {
template: function MyComponent_Template(rf, $ctx$) { template: function MyComponent_Template(rf, $ctx$) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵelementStart(0, "div", $e0_attrs$); $r3$.ɵelementStart(0, "div", $e0_attrs$);
$r3$.ɵelementStyling($e0_cd$, $e0_sd$);
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
@ -765,10 +760,13 @@ describe('compiler compliance: styling', () => {
}; };
const template = ` const template = `
const $e0_classBindings$ = ["foo"];
const $e0_styleBindings$ = ["bar", "baz"];
template: function MyComponent_Template(rf, $ctx$) { template: function MyComponent_Template(rf, $ctx$) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵelementStart(0, "div"); $r3$.ɵelementStart(0, "div");
$r3$.ɵelementStyling($e0_styling$, $e1_styling$, $r3$.ɵdefaultStyleSanitizer); $r3$.ɵelementStyling($e0_classBindings$, $e0_styleBindings$, $r3$.ɵdefaultStyleSanitizer);
$r3$.ɵpipe(1, "pipe"); $r3$.ɵpipe(1, "pipe");
$r3$.ɵpipe(2, "pipe"); $r3$.ɵpipe(2, "pipe");
$r3$.ɵpipe(3, "pipe"); $r3$.ɵpipe(3, "pipe");
@ -828,16 +826,18 @@ describe('compiler compliance: styling', () => {
}; };
const template = ` const template = `
const _c0 = ["foo", "baz", ${InitialStylingFlags.VALUES_MODE}, "foo", true, "baz", true]; const $e0_attrs$ = [${AttributeMarker.Classes}, "foo", "baz", ${AttributeMarker.Styles}, "width", "200px", "height", "500px"];
const _c1 = ["width", "height", "color", ${InitialStylingFlags.VALUES_MODE}, "width", "200px", "height", "500px"]; const $e0_classBindings$ = ["foo"];
const $e0_styleBindings$ = ["color"];
hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) { hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵelementStyling(_c0, _c1, $r3$.ɵdefaultStyleSanitizer, ctx); $r3$.ɵelementHostAttrs(ctx, $e0_attrs$);
$r3$.ɵelementStyling($e0_classBindings$, $e0_styleBindings$, $r3$.ɵdefaultStyleSanitizer, ctx);
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵelementStylingMap(elIndex, ctx.myClass, ctx.myStyle, ctx); $r3$.ɵelementStylingMap(elIndex, ctx.myClass, ctx.myStyle, ctx);
$r3$.ɵelementStyleProp(elIndex, 2, ctx.myColorProp, null, ctx); $r3$.ɵelementStyleProp(elIndex, 0, ctx.myColorProp, null, ctx);
$r3$.ɵelementClassProp(elIndex, 0, ctx.myFooClass, ctx); $r3$.ɵelementClassProp(elIndex, 0, ctx.myFooClass, ctx);
$r3$.ɵelementStylingApply(elIndex, ctx); $r3$.ɵelementStylingApply(elIndex, ctx);
} }
@ -959,10 +959,10 @@ describe('compiler compliance: styling', () => {
}; };
const template = ` const template = `
const _c0 = ["foo"]; const $widthDir_classes$ = ["foo"];
const _c1 = ["width"]; const $widthDir_styles$ = ["width"];
const _c2 = ["bar"]; const $heightDir_classes$ = ["bar"];
const _c3 = ["height"]; const $heightDir_styles$ = ["height"];
function ClassDirective_HostBindings(rf, ctx, elIndex) { function ClassDirective_HostBindings(rf, ctx, elIndex) {
if (rf & 1) { if (rf & 1) {
@ -976,7 +976,7 @@ describe('compiler compliance: styling', () => {
function WidthDirective_HostBindings(rf, ctx, elIndex) { function WidthDirective_HostBindings(rf, ctx, elIndex) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵelementStyling(_c0, _c1, null, ctx); $r3$.ɵelementStyling($widthDir_classes$, $widthDir_styles$, null, ctx);
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵelementStyleProp(elIndex, 0, ctx.myWidth, null, ctx); $r3$.ɵelementStyleProp(elIndex, 0, ctx.myWidth, null, ctx);
@ -987,7 +987,7 @@ describe('compiler compliance: styling', () => {
function HeightDirective_HostBindings(rf, ctx, elIndex) { function HeightDirective_HostBindings(rf, ctx, elIndex) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵelementStyling(_c2, _c3, null, ctx); $r3$.ɵelementStyling($heightDir_classes$, $heightDir_styles$, null, ctx);
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵelementStyleProp(elIndex, 0, ctx.myHeight, null, ctx); $r3$.ɵelementStyleProp(elIndex, 0, ctx.myHeight, null, ctx);
@ -1014,7 +1014,8 @@ describe('compiler compliance: styling', () => {
template: '', template: '',
host: { host: {
'style': 'width:200px; height:500px', 'style': 'width:200px; height:500px',
'class': 'foo baz' 'class': 'foo baz',
'title': 'foo title'
} }
}) })
export class MyComponent { export class MyComponent {
@ -1029,6 +1030,9 @@ describe('compiler compliance: styling', () => {
@HostBinding('title') @HostBinding('title')
title = 'some title'; title = 'some title';
@Input('name')
name = '';
} }
@NgModule({declarations: [MyComponent]}) @NgModule({declarations: [MyComponent]})
@ -1038,13 +1042,13 @@ describe('compiler compliance: styling', () => {
}; };
const template = ` const template = `
const $_c0$ = ["foo", "baz", ${InitialStylingFlags.VALUES_MODE}, "foo", true, "baz", true]; const $_c0$ = [${AttributeMarker.Classes}, "foo", "baz", ${AttributeMarker.Styles}, "width", "200px", "height", "500px"];
const $_c1$ = ["width", "height", ${InitialStylingFlags.VALUES_MODE}, "width", "200px", "height", "500px"];
hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) { hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵallocHostVars(2); $r3$.ɵallocHostVars(2);
$r3$.ɵelementStyling($_c0$, $_c1$, $r3$.ɵdefaultStyleSanitizer, ctx); $r3$.ɵelementHostAttrs(ctx, $_c0$);
$r3$.ɵelementStyling(null, null, $r3$.ɵdefaultStyleSanitizer, ctx);
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵelementProperty(elIndex, "id", $r3$.ɵbind(ctx.id), null, true); $r3$.ɵelementProperty(elIndex, "id", $r3$.ɵbind(ctx.id), null, true);

View File

@ -441,6 +441,39 @@ describe('ngtsc behavioral tests', () => {
}); });
}); });
it('should unwrap a ModuleWithProviders-like function if a matching literal type is provided for it',
() => {
env.tsconfig();
env.write(`test.ts`, `
import {NgModule} from '@angular/core';
import {RouterModule} from 'router';
@NgModule({imports: [RouterModule.forRoot()]})
export class TestModule {}
`);
env.write('node_modules/router/index.d.ts', `
import {ModuleWithProviders} from '@angular/core';
export interface MyType extends ModuleWithProviders {}
declare class RouterModule {
static forRoot(): (MyType)&{ngModule:RouterModule};
}
`);
env.driveMain();
const jsContents = env.getContents('test.js');
expect(jsContents).toContain('imports: [[RouterModule.forRoot()]]');
const dtsContents = env.getContents('test.d.ts');
expect(dtsContents).toContain(`import * as i1 from 'router';`);
expect(dtsContents)
.toContain(
'i0.ɵNgModuleDefWithMeta<TestModule, never, [typeof i1.RouterModule], never>');
});
it('should inject special types according to the metadata', () => { it('should inject special types according to the metadata', () => {
env.tsconfig(); env.tsconfig();
env.write(`test.ts`, ` env.write(`test.ts`, `

View File

@ -134,10 +134,13 @@ export interface R3ComponentMetadataFacade extends R3DirectiveMetadataFacade {
encapsulation: ViewEncapsulation; encapsulation: ViewEncapsulation;
viewProviders: Provider[]|null; viewProviders: Provider[]|null;
interpolation?: [string, string]; interpolation?: [string, string];
changeDetection?: ChangeDetectionStrategy;
} }
export type ViewEncapsulation = number; export type ViewEncapsulation = number;
export type ChangeDetectionStrategy = number;
export interface R3QueryMetadataFacade { export interface R3QueryMetadataFacade {
propertyName: string; propertyName: string;
first: boolean; first: boolean;

View File

@ -379,13 +379,9 @@ export const enum RenderFlags {
Update = 0b10 Update = 0b10
} }
export const enum InitialStylingFlags {
VALUES_MODE = 0b1,
}
// Pasted from render3/interfaces/node.ts // Pasted from render3/interfaces/node.ts
/** /**
* A set of marker values to be used in the attributes arrays. Those markers indicate that some * A set of marker values to be used in the attributes arrays. These markers indicate that some
* items are not regular attributes and the processing should be adapted accordingly. * items are not regular attributes and the processing should be adapted accordingly.
*/ */
export const enum AttributeMarker { export const enum AttributeMarker {
@ -396,11 +392,48 @@ export const enum AttributeMarker {
*/ */
NamespaceURI = 0, NamespaceURI = 0,
/**
* Signals class declaration.
*
* Each value following `Classes` designates a class name to include on the element.
* ## Example:
*
* Given:
* ```
* <div class="foo bar baz">...<d/vi>
* ```
*
* the generated code is:
* ```
* var _c1 = [AttributeMarker.Classes, 'foo', 'bar', 'baz'];
* ```
*/
Classes = 1,
/**
* Signals style declaration.
*
* Each pair of values following `Styles` designates a style name and value to include on the
* element.
* ## Example:
*
* Given:
* ```
* <div style="width:100px; height:200px; color:red">...</div>
* ```
*
* the generated code is:
* ```
* var _c1 = [AttributeMarker.Styles, 'width', '100px', 'height'. '200px', 'color', 'red'];
* ```
*/
Styles = 2,
/** /**
* This marker indicates that the following attribute names were extracted from bindings (ex.: * This marker indicates that the following attribute names were extracted from bindings (ex.:
* [foo]="exp") and / or event handlers (ex. (bar)="doSth()"). * [foo]="exp") and / or event handlers (ex. (bar)="doSth()").
* Taking the above bindings and outputs as an example an attributes array could look as follows: * Taking the above bindings and outputs as an example an attributes array could look as follows:
* ['class', 'fade in', AttributeMarker.SelectOnly, 'foo', 'bar'] * ['class', 'fade in', AttributeMarker.SelectOnly, 'foo', 'bar']
*/ */
SelectOnly = 1 SelectOnly = 3,
} }

View File

@ -130,6 +130,7 @@ export class CompilerFacadeImpl implements CompilerFacade {
styles: facade.styles || [], styles: facade.styles || [],
encapsulation: facade.encapsulation as any, encapsulation: facade.encapsulation as any,
interpolation: interpolationConfig, interpolation: interpolationConfig,
changeDetection: facade.changeDetection,
animations: facade.animations != null ? new WrappedNodeExpr(facade.animations) : null, animations: facade.animations != null ? new WrappedNodeExpr(facade.animations) : null,
viewProviders: facade.viewProviders != null ? new WrappedNodeExpr(facade.viewProviders) : viewProviders: facade.viewProviders != null ? new WrappedNodeExpr(facade.viewProviders) :
null, null,

View File

@ -43,6 +43,8 @@ export class Identifiers {
static elementStyling: o.ExternalReference = {name: 'ɵelementStyling', moduleName: CORE}; static elementStyling: o.ExternalReference = {name: 'ɵelementStyling', moduleName: CORE};
static elementHostAttrs: o.ExternalReference = {name: 'ɵelementHostAttrs', moduleName: CORE};
static elementStylingMap: o.ExternalReference = {name: 'ɵelementStylingMap', moduleName: CORE}; static elementStylingMap: o.ExternalReference = {name: 'ɵelementStylingMap', moduleName: CORE};
static elementStyleProp: o.ExternalReference = {name: 'ɵelementStyleProp', moduleName: CORE}; static elementStyleProp: o.ExternalReference = {name: 'ɵelementStyleProp', moduleName: CORE};

View File

@ -6,13 +6,14 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ViewEncapsulation} from '../../core'; import {ChangeDetectionStrategy, ViewEncapsulation} from '../../core';
import {InterpolationConfig} from '../../ml_parser/interpolation_config'; import {InterpolationConfig} from '../../ml_parser/interpolation_config';
import * as o from '../../output/output_ast'; import * as o from '../../output/output_ast';
import {ParseSourceSpan} from '../../parse_util'; import {ParseSourceSpan} from '../../parse_util';
import * as t from '../r3_ast'; import * as t from '../r3_ast';
import {R3DependencyMetadata} from '../r3_factory'; import {R3DependencyMetadata} from '../r3_factory';
/** /**
* Information needed to compile a directive for the render3 runtime. * Information needed to compile a directive for the render3 runtime.
*/ */
@ -184,14 +185,19 @@ export interface R3ComponentMetadata extends R3DirectiveMetadata {
/** /**
* Whether translation variable name should contain external message id * Whether translation variable name should contain external message id
* (used by Closure Compiler's output of `goog.getMsg` for transition period) * (used by Closure Compiler's output of `goog.getMsg` for transition period).
*/ */
i18nUseExternalIds: boolean; i18nUseExternalIds: boolean;
/** /**
* Overrides the default interpolation start and end delimiters ({{ and }}) * Overrides the default interpolation start and end delimiters ({{ and }}).
*/ */
interpolation: InterpolationConfig; interpolation: InterpolationConfig;
/**
* Strategy used for detecting changes in the component.
*/
changeDetection?: ChangeDetectionStrategy;
} }
/** /**

View File

@ -28,7 +28,7 @@ import {Render3ParseResult} from '../r3_template_transform';
import {typeWithParameters} from '../util'; import {typeWithParameters} from '../util';
import {R3ComponentDef, R3ComponentMetadata, R3DirectiveDef, R3DirectiveMetadata, R3QueryMetadata} from './api'; import {R3ComponentDef, R3ComponentMetadata, R3DirectiveDef, R3DirectiveMetadata, R3QueryMetadata} from './api';
import {StylingBuilder, StylingInstruction} from './styling'; import {StylingBuilder, StylingInstruction} from './styling_builder';
import {BindingScope, TemplateDefinitionBuilder, ValueConverter, renderFlagCheckIfStmt} from './template'; import {BindingScope, TemplateDefinitionBuilder, ValueConverter, renderFlagCheckIfStmt} from './template';
import {CONTEXT_NAME, DefinitionMap, RENDER_FLAGS, TEMPORARY_NAME, asLiteral, conditionallyCreateMapObjectLiteral, getQueryPredicate, temporaryAllocator} from './util'; import {CONTEXT_NAME, DefinitionMap, RENDER_FLAGS, TEMPORARY_NAME, asLiteral, conditionallyCreateMapObjectLiteral, getQueryPredicate, temporaryAllocator} from './util';
@ -251,6 +251,7 @@ export function compileComponentFromMetadata(
const directivesUsed = new Set<o.Expression>(); const directivesUsed = new Set<o.Expression>();
const pipesUsed = new Set<o.Expression>(); const pipesUsed = new Set<o.Expression>();
const changeDetection = meta.changeDetection;
const template = meta.template; const template = meta.template;
const templateBuilder = new TemplateDefinitionBuilder( const templateBuilder = new TemplateDefinitionBuilder(
@ -313,6 +314,11 @@ export function compileComponentFromMetadata(
'data', o.literalMap([{key: 'animation', value: meta.animations, quoted: false}])); 'data', o.literalMap([{key: 'animation', value: meta.animations, quoted: false}]));
} }
// Only set the change detection flag if it's defined and it's not the default.
if (changeDetection != null && changeDetection !== core.ChangeDetectionStrategy.Default) {
definitionMap.set('changeDetection', o.literal(changeDetection));
}
// On the type side, remove newlines from the selector as it will need to fit into a TypeScript // On the type side, remove newlines from the selector as it will need to fit into a TypeScript
// string literal, which must be on one line. // string literal, which must be on one line.
const selectorForType = (meta.selector || '').replace(/\n/g, ''); const selectorForType = (meta.selector || '').replace(/\n/g, '');
@ -703,16 +709,35 @@ function createHostBindingsFunction(
} }
} }
if (styleBuilder.hasBindingsOrInitialValues) { if (styleBuilder.hasBindingsOrInitialValues()) {
const createInstruction = styleBuilder.buildCreateLevelInstruction(null, constantPool); // since we're dealing with directives here and directives have a hostBinding
if (createInstruction) { // function, we need to generate special instructions that deal with styling
const createStmt = createStylingStmt(createInstruction, bindingContext, bindingFn); // (both bindings and initial values). The instruction below will instruct
createStatements.push(createStmt); // all initial styling (styling that is inside of a host binding within a
// directive) to be attached to the host element of the directive.
const hostAttrsInstruction =
styleBuilder.buildDirectiveHostAttrsInstruction(null, constantPool);
if (hostAttrsInstruction) {
createStatements.push(createStylingStmt(hostAttrsInstruction, bindingContext, bindingFn));
} }
// singular style/class bindings (things like `[style.prop]` and `[class.name]`)
// MUST be registered on a given element within the component/directive
// templateFn/hostBindingsFn functions. The instruction below will figure out
// what all the bindings are and then generate the statements required to register
// those bindings to the element via `elementStyling`.
const elementStylingInstruction =
styleBuilder.buildElementStylingInstruction(null, constantPool);
if (elementStylingInstruction) {
createStatements.push(
createStylingStmt(elementStylingInstruction, bindingContext, bindingFn));
}
// finally each binding that was registered in the statement above will need to be added to
// the update block of a component/directive templateFn/hostBindingsFn so that the bindings
// are evaluated and updated for the element.
styleBuilder.buildUpdateLevelInstructions(valueConverter).forEach(instruction => { styleBuilder.buildUpdateLevelInstructions(valueConverter).forEach(instruction => {
const updateStmt = createStylingStmt(instruction, bindingContext, bindingFn); updateStatements.push(createStylingStmt(instruction, bindingContext, bindingFn));
updateStatements.push(updateStmt);
}); });
} }
} }

View File

@ -23,10 +23,15 @@ const enum Char {
* *
* @param value string representation of style as used in the `style` attribute in HTML. * @param value string representation of style as used in the `style` attribute in HTML.
* Example: `color: red; height: auto`. * Example: `color: red; height: auto`.
* @returns an object literal. `{ color: 'red', height: 'auto'}`. * @returns An array of style property name and value pairs, e.g. `['color', 'red', 'height',
* 'auto']`
*/ */
export function parse(value: string): {[key: string]: any} { export function parse(value: string): string[] {
const styles: {[key: string]: any} = {}; // we use a string array here instead of a string map
// because a string-map is not guaranteed to retain the
// order of the entries whereas a string array can be
// construted in a [key, value, key, value] format.
const styles: string[] = [];
let i = 0; let i = 0;
let parenDepth = 0; let parenDepth = 0;
@ -72,7 +77,7 @@ export function parse(value: string): {[key: string]: any} {
case Char.Semicolon: case Char.Semicolon:
if (currentProp && valueStart > 0 && parenDepth === 0 && quote === Char.QuoteNone) { if (currentProp && valueStart > 0 && parenDepth === 0 && quote === Char.QuoteNone) {
const styleVal = value.substring(valueStart, i - 1).trim(); const styleVal = value.substring(valueStart, i - 1).trim();
styles[currentProp] = valueHasQuotes ? stripUnnecessaryQuotes(styleVal) : styleVal; styles.push(currentProp, valueHasQuotes ? stripUnnecessaryQuotes(styleVal) : styleVal);
propStart = i; propStart = i;
valueStart = 0; valueStart = 0;
currentProp = null; currentProp = null;
@ -84,7 +89,7 @@ export function parse(value: string): {[key: string]: any} {
if (currentProp && valueStart) { if (currentProp && valueStart) {
const styleVal = value.substr(valueStart).trim(); const styleVal = value.substr(valueStart).trim();
styles[currentProp] = valueHasQuotes ? stripUnnecessaryQuotes(styleVal) : styleVal; styles.push(currentProp, valueHasQuotes ? stripUnnecessaryQuotes(styleVal) : styleVal);
} }
return styles; return styles;

View File

@ -6,8 +6,8 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ConstantPool} from '../../constant_pool'; import {ConstantPool} from '../../constant_pool';
import {InitialStylingFlags} from '../../core'; import {AttributeMarker} from '../../core';
import {AST, BindingType, ParseSpan} from '../../expression_parser/ast'; import {AST, BindingType} from '../../expression_parser/ast';
import * as o from '../../output/output_ast'; import * as o from '../../output/output_ast';
import {ParseSourceSpan} from '../../parse_util'; import {ParseSourceSpan} from '../../parse_util';
import * as t from '../r3_ast'; import * as t from '../r3_ast';
@ -40,6 +40,10 @@ interface BoundStylingEntry {
/** /**
* Produces creation/update instructions for all styling bindings (class and style) * Produces creation/update instructions for all styling bindings (class and style)
* *
* It also produces the creation instruction to register all initial styling values
* (which are all the static class="..." and style="..." attribute values that exist
* on an element within a template).
*
* The builder class below handles producing instructions for the following cases: * The builder class below handles producing instructions for the following cases:
* *
* - Static style/class attributes (style="..." and class="...") * - Static style/class attributes (style="..." and class="...")
@ -63,25 +67,57 @@ interface BoundStylingEntry {
* The creation/update methods within the builder class produce these instructions. * The creation/update methods within the builder class produce these instructions.
*/ */
export class StylingBuilder { export class StylingBuilder {
public readonly hasBindingsOrInitialValues = false; /** Whether or not there are any static styling values present */
private _hasInitialValues = false;
/**
* Whether or not there are any styling bindings present
* (i.e. `[style]`, `[class]`, `[style.prop]` or `[class.name]`)
*/
private _hasBindings = false;
/** the input for [class] (if it exists) */
private _classMapInput: BoundStylingEntry|null = null; private _classMapInput: BoundStylingEntry|null = null;
/** the input for [style] (if it exists) */
private _styleMapInput: BoundStylingEntry|null = null; private _styleMapInput: BoundStylingEntry|null = null;
/** an array of each [style.prop] input */
private _singleStyleInputs: BoundStylingEntry[]|null = null; private _singleStyleInputs: BoundStylingEntry[]|null = null;
/** an array of each [class.name] input */
private _singleClassInputs: BoundStylingEntry[]|null = null; private _singleClassInputs: BoundStylingEntry[]|null = null;
private _lastStylingInput: BoundStylingEntry|null = null; private _lastStylingInput: BoundStylingEntry|null = null;
// maps are used instead of hash maps because a Map will // maps are used instead of hash maps because a Map will
// retain the ordering of the keys // retain the ordering of the keys
/**
* Represents the location of each style binding in the template
* (e.g. `<div [style.width]="w" [style.height]="h">` implies
* that `width=0` and `height=1`)
*/
private _stylesIndex = new Map<string, number>(); private _stylesIndex = new Map<string, number>();
/**
* Represents the location of each class binding in the template
* (e.g. `<div [class.big]="b" [class.hidden]="h">` implies
* that `big=0` and `hidden=1`)
*/
private _classesIndex = new Map<string, number>(); private _classesIndex = new Map<string, number>();
private _initialStyleValues: {[propName: string]: string} = {}; private _initialStyleValues: string[] = [];
private _initialClassValues: {[className: string]: boolean} = {}; private _initialClassValues: string[] = [];
// certain style properties ALWAYS need sanitization
// this is checked each time new styles are encountered
private _useDefaultSanitizer = false; private _useDefaultSanitizer = false;
private _applyFnRequired = false;
constructor(private _elementIndexExpr: o.Expression, private _directiveExpr: o.Expression|null) {} constructor(private _elementIndexExpr: o.Expression, private _directiveExpr: o.Expression|null) {}
hasBindingsOrInitialValues() { return this._hasBindings || this._hasInitialValues; }
/**
* Registers a given input to the styling builder to be later used when producing AOT code.
*
* The code below will only accept the input if it is somehow tied to styling (whether it be
* style/class bindings or static style/class attributes).
*/
registerBoundInput(input: t.BoundAttribute): boolean { registerBoundInput(input: t.BoundAttribute): boolean {
// [attr.style] or [attr.class] are skipped in the code below, // [attr.style] or [attr.class] are skipped in the code below,
// they should not be treated as styling-based bindings since // they should not be treated as styling-based bindings since
@ -117,14 +153,12 @@ export class StylingBuilder {
(this._singleStyleInputs = this._singleStyleInputs || []).push(entry); (this._singleStyleInputs = this._singleStyleInputs || []).push(entry);
this._useDefaultSanitizer = this._useDefaultSanitizer || isStyleSanitizable(propertyName); this._useDefaultSanitizer = this._useDefaultSanitizer || isStyleSanitizable(propertyName);
registerIntoMap(this._stylesIndex, propertyName); registerIntoMap(this._stylesIndex, propertyName);
(this as any).hasBindingsOrInitialValues = true;
} else { } else {
this._useDefaultSanitizer = true; this._useDefaultSanitizer = true;
this._styleMapInput = entry; this._styleMapInput = entry;
} }
this._lastStylingInput = entry; this._lastStylingInput = entry;
(this as any).hasBindingsOrInitialValues = true; this._hasBindings = true;
this._applyFnRequired = true;
return entry; return entry;
} }
@ -133,107 +167,152 @@ export class StylingBuilder {
const entry = { name: className, value, sourceSpan } as BoundStylingEntry; const entry = { name: className, value, sourceSpan } as BoundStylingEntry;
if (className) { if (className) {
(this._singleClassInputs = this._singleClassInputs || []).push(entry); (this._singleClassInputs = this._singleClassInputs || []).push(entry);
(this as any).hasBindingsOrInitialValues = true;
registerIntoMap(this._classesIndex, className); registerIntoMap(this._classesIndex, className);
} else { } else {
this._classMapInput = entry; this._classMapInput = entry;
} }
this._lastStylingInput = entry; this._lastStylingInput = entry;
(this as any).hasBindingsOrInitialValues = true; this._hasBindings = true;
this._applyFnRequired = true;
return entry; return entry;
} }
/**
* Registers the element's static style string value to the builder.
*
* @param value the style string (e.g. `width:100px; height:200px;`)
*/
registerStyleAttr(value: string) { registerStyleAttr(value: string) {
this._initialStyleValues = parseStyle(value); this._initialStyleValues = parseStyle(value);
Object.keys(this._initialStyleValues).forEach(prop => { this._hasInitialValues = true;
registerIntoMap(this._stylesIndex, prop);
(this as any).hasBindingsOrInitialValues = true;
});
} }
/**
* Registers the element's static class string value to the builder.
*
* @param value the className string (e.g. `disabled gold zoom`)
*/
registerClassAttr(value: string) { registerClassAttr(value: string) {
this._initialClassValues = {}; this._initialClassValues = value.trim().split(/\s+/g);
value.split(/\s+/g).forEach(className => { this._hasInitialValues = true;
this._initialClassValues[className] = true;
registerIntoMap(this._classesIndex, className);
(this as any).hasBindingsOrInitialValues = true;
});
} }
private _buildInitExpr(registry: Map<string, number>, initialValues: {[key: string]: any}): /**
o.Expression|null { * Appends all styling-related expressions to the provided attrs array.
const exprs: o.Expression[] = []; *
const nameAndValueExprs: o.Expression[] = []; * @param attrs an existing array where each of the styling expressions
* will be inserted into.
// _c0 = [prop, prop2, prop3, ...] */
registry.forEach((value, key) => { populateInitialStylingAttrs(attrs: o.Expression[]): void {
const keyLiteral = o.literal(key); // [CLASS_MARKER, 'foo', 'bar', 'baz' ...]
exprs.push(keyLiteral); if (this._initialClassValues.length) {
const initialValue = initialValues[key]; attrs.push(o.literal(AttributeMarker.Classes));
if (initialValue) { for (let i = 0; i < this._initialClassValues.length; i++) {
nameAndValueExprs.push(keyLiteral, o.literal(initialValue)); attrs.push(o.literal(this._initialClassValues[i]));
} }
});
if (nameAndValueExprs.length) {
// _c0 = [... MARKER ...]
exprs.push(o.literal(InitialStylingFlags.VALUES_MODE));
// _c0 = [prop, VALUE, prop2, VALUE2, ...]
exprs.push(...nameAndValueExprs);
} }
return exprs.length ? o.literalArr(exprs) : null; // [STYLE_MARKER, 'width', '200px', 'height', '100px', ...]
if (this._initialStyleValues.length) {
attrs.push(o.literal(AttributeMarker.Styles));
for (let i = 0; i < this._initialStyleValues.length; i += 2) {
attrs.push(
o.literal(this._initialStyleValues[i]), o.literal(this._initialStyleValues[i + 1]));
}
}
} }
buildCreateLevelInstruction(sourceSpan: ParseSourceSpan|null, constantPool: ConstantPool): /**
* Builds an instruction with all the expressions and parameters for `elementHostAttrs`.
*
* The instruction generation code below is used for producing the AOT statement code which is
* responsible for registering initial styles (within a directive hostBindings' creation block)
* to the directive host element.
*/
buildDirectiveHostAttrsInstruction(sourceSpan: ParseSourceSpan|null, constantPool: ConstantPool):
StylingInstruction|null { StylingInstruction|null {
if (this.hasBindingsOrInitialValues) { if (this._hasInitialValues && this._directiveExpr) {
const initialClasses = this._buildInitExpr(this._classesIndex, this._initialClassValues); return {
const initialStyles = this._buildInitExpr(this._stylesIndex, this._initialStyleValues); sourceSpan,
reference: R3.elementHostAttrs,
// in the event that a [style] binding is used then sanitization will buildParams: () => {
// always be imported because it is not possible to know ahead of time const attrs: o.Expression[] = [];
// whether style bindings will use or not use any sanitizable properties this.populateInitialStylingAttrs(attrs);
// that isStyleSanitizable() will detect return [this._directiveExpr !, getConstantLiteralFromArray(constantPool, attrs)];
const useSanitizer = this._useDefaultSanitizer;
const params: (o.Expression)[] = [];
if (initialClasses) {
// the template compiler handles initial class styling (e.g. class="foo") values
// in a special command called `elementClass` so that the initial class
// can be processed during runtime. These initial class values are bound to
// a constant because the inital class values do not change (since they're static).
params.push(constantPool.getConstLiteral(initialClasses, true));
} else if (initialStyles || useSanitizer || this._directiveExpr) {
// no point in having an extra `null` value unless there are follow-up params
params.push(o.NULL_EXPR);
}
if (initialStyles) {
// the template compiler handles initial style (e.g. style="foo") values
// in a special command called `elementStyle` so that the initial styles
// can be processed during runtime. These initial styles values are bound to
// a constant because the inital style values do not change (since they're static).
params.push(constantPool.getConstLiteral(initialStyles, true));
} else if (useSanitizer || this._directiveExpr) {
// no point in having an extra `null` value unless there are follow-up params
params.push(o.NULL_EXPR);
}
if (useSanitizer || this._directiveExpr) {
params.push(useSanitizer ? o.importExpr(R3.defaultStyleSanitizer) : o.NULL_EXPR);
if (this._directiveExpr) {
params.push(this._directiveExpr);
} }
} };
return {sourceSpan, reference: R3.elementStyling, buildParams: () => params};
} }
return null; return null;
} }
private _buildStylingMap(valueConverter: ValueConverter): StylingInstruction|null { /**
* Builds an instruction with all the expressions and parameters for `elementStyling`.
*
* The instruction generation code below is used for producing the AOT statement code which is
* responsible for registering style/class bindings to an element.
*/
buildElementStylingInstruction(sourceSpan: ParseSourceSpan|null, constantPool: ConstantPool):
StylingInstruction|null {
if (this._hasBindings) {
return {
sourceSpan,
reference: R3.elementStyling,
buildParams: () => {
// a string array of every style-based binding
const styleBindingProps =
this._singleStyleInputs ? this._singleStyleInputs.map(i => o.literal(i.name)) : [];
// a string array of every class-based binding
const classBindingNames =
this._singleClassInputs ? this._singleClassInputs.map(i => o.literal(i.name)) : [];
// to salvage space in the AOT generated code, there is no point in passing
// in `null` into a param if any follow-up params are not used. Therefore,
// only when a trailing param is used then it will be filled with nulls in between
// (otherwise a shorter amount of params will be filled). The code below helps
// determine how many params are required in the expression code.
//
// min params => elementStyling()
// max params => elementStyling(classBindings, styleBindings, sanitizer, directive)
let expectedNumberOfArgs = 0;
if (this._directiveExpr) {
expectedNumberOfArgs = 4;
} else if (this._useDefaultSanitizer) {
expectedNumberOfArgs = 3;
} else if (styleBindingProps.length) {
expectedNumberOfArgs = 2;
} else if (classBindingNames.length) {
expectedNumberOfArgs = 1;
}
const params: o.Expression[] = [];
addParam(
params, classBindingNames.length > 0,
getConstantLiteralFromArray(constantPool, classBindingNames), 1,
expectedNumberOfArgs);
addParam(
params, styleBindingProps.length > 0,
getConstantLiteralFromArray(constantPool, styleBindingProps), 2,
expectedNumberOfArgs);
addParam(
params, this._useDefaultSanitizer, o.importExpr(R3.defaultStyleSanitizer), 3,
expectedNumberOfArgs);
if (this._directiveExpr) {
params.push(this._directiveExpr);
}
return params;
}
};
}
return null;
}
/**
* Builds an instruction with all the expressions and parameters for `elementStylingMap`.
*
* The instruction data will contain all expressions for `elementStylingMap` to function
* which include the `[style]` and `[class]` expression params (if they exist) as well as
* the sanitizer and directive reference expression.
*/
buildElementStylingMapInstruction(valueConverter: ValueConverter): StylingInstruction|null {
if (this._classMapInput || this._styleMapInput) { if (this._classMapInput || this._styleMapInput) {
const stylingInput = this._classMapInput ! || this._styleMapInput !; const stylingInput = this._classMapInput ! || this._styleMapInput !;
@ -332,18 +411,20 @@ export class StylingBuilder {
}; };
} }
/**
* Constructs all instructions which contain the expressions that will be placed
* into the update block of a template function or a directive hostBindings function.
*/
buildUpdateLevelInstructions(valueConverter: ValueConverter) { buildUpdateLevelInstructions(valueConverter: ValueConverter) {
const instructions: StylingInstruction[] = []; const instructions: StylingInstruction[] = [];
if (this.hasBindingsOrInitialValues) { if (this._hasBindings) {
const mapInstruction = this._buildStylingMap(valueConverter); const mapInstruction = this.buildElementStylingMapInstruction(valueConverter);
if (mapInstruction) { if (mapInstruction) {
instructions.push(mapInstruction); instructions.push(mapInstruction);
} }
instructions.push(...this._buildStyleInputs(valueConverter)); instructions.push(...this._buildStyleInputs(valueConverter));
instructions.push(...this._buildClassInputs(valueConverter)); instructions.push(...this._buildClassInputs(valueConverter));
if (this._applyFnRequired) { instructions.push(this._buildApplyFn());
instructions.push(this._buildApplyFn());
}
} }
return instructions; return instructions;
} }
@ -363,3 +444,26 @@ function isStyleSanitizable(prop: string): boolean {
return prop === 'background-image' || prop === 'background' || prop === 'border-image' || return prop === 'background-image' || prop === 'background' || prop === 'border-image' ||
prop === 'filter' || prop === 'list-style' || prop === 'list-style-image'; prop === 'filter' || prop === 'list-style' || prop === 'list-style-image';
} }
/**
* Simple helper function to either provide the constant literal that will house the value
* here or a null value if the provided values are empty.
*/
function getConstantLiteralFromArray(
constantPool: ConstantPool, values: o.Expression[]): o.Expression {
return values.length ? constantPool.getConstLiteral(o.literalArr(values), true) : o.NULL_EXPR;
}
/**
* Simple helper function that adds a parameter or does nothing at all depending on the provided
* predicate and totalExpectedArgs values
*/
function addParam(
params: o.Expression[], predicate: boolean, value: o.Expression, argNumber: number,
totalExpectedArgs: number) {
if (predicate) {
params.push(value);
} else if (argNumber < totalExpectedArgs) {
params.push(o.NULL_EXPR);
}
}

View File

@ -10,7 +10,7 @@ import {flatten, sanitizeIdentifier} from '../../compile_metadata';
import {BindingForm, BuiltinFunctionCall, LocalResolver, convertActionBinding, convertPropertyBinding} from '../../compiler_util/expression_converter'; import {BindingForm, BuiltinFunctionCall, LocalResolver, convertActionBinding, convertPropertyBinding} from '../../compiler_util/expression_converter';
import {ConstantPool} from '../../constant_pool'; import {ConstantPool} from '../../constant_pool';
import * as core from '../../core'; import * as core from '../../core';
import {AST, AstMemoryEfficientTransformer, BindingPipe, BindingType, FunctionCall, ImplicitReceiver, Interpolation, LiteralArray, LiteralMap, LiteralPrimitive, ParsedEventType, PropertyRead} from '../../expression_parser/ast'; import {AST, ASTWithSource, AstMemoryEfficientTransformer, BindingPipe, BindingType, FunctionCall, ImplicitReceiver, Interpolation, LiteralArray, LiteralMap, LiteralPrimitive, ParsedEventType, PropertyRead} from '../../expression_parser/ast';
import {Lexer} from '../../expression_parser/lexer'; import {Lexer} from '../../expression_parser/lexer';
import {Parser} from '../../expression_parser/parser'; import {Parser} from '../../expression_parser/parser';
import * as i18n from '../../i18n/i18n_ast'; import * as i18n from '../../i18n/i18n_ast';
@ -35,7 +35,7 @@ import {I18nContext} from './i18n/context';
import {I18nMetaVisitor} from './i18n/meta'; import {I18nMetaVisitor} from './i18n/meta';
import {getSerializedI18nContent} from './i18n/serializer'; import {getSerializedI18nContent} from './i18n/serializer';
import {I18N_ICU_MAPPING_PREFIX, assembleBoundTextPlaceholders, assembleI18nBoundString, formatI18nPlaceholderName, getTranslationConstPrefix, getTranslationDeclStmts, icuFromI18nMessage, isI18nRootNode, isSingleI18nIcu, metaFromI18nMessage, placeholdersToParams, wrapI18nPlaceholder} from './i18n/util'; import {I18N_ICU_MAPPING_PREFIX, assembleBoundTextPlaceholders, assembleI18nBoundString, formatI18nPlaceholderName, getTranslationConstPrefix, getTranslationDeclStmts, icuFromI18nMessage, isI18nRootNode, isSingleI18nIcu, metaFromI18nMessage, placeholdersToParams, wrapI18nPlaceholder} from './i18n/util';
import {StylingBuilder, StylingInstruction} from './styling'; import {StylingBuilder, StylingInstruction} from './styling_builder';
import {CONTEXT_NAME, IMPLICIT_REFERENCE, NON_BINDABLE_ATTR, REFERENCE_PREFIX, RENDER_FLAGS, asLiteral, getAttrsForDirectiveMatching, invalid, trimTrailingNulls, unsupported} from './util'; import {CONTEXT_NAME, IMPLICIT_REFERENCE, NON_BINDABLE_ATTR, REFERENCE_PREFIX, RENDER_FLAGS, asLiteral, getAttrsForDirectiveMatching, invalid, trimTrailingNulls, unsupported} from './util';
function mapBindingToInstruction(type: BindingType): o.ExternalReference|undefined { function mapBindingToInstruction(type: BindingType): o.ExternalReference|undefined {
@ -114,6 +114,10 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
// Selectors found in the <ng-content> tags in the template. // Selectors found in the <ng-content> tags in the template.
private _ngContentSelectors: string[] = []; private _ngContentSelectors: string[] = [];
// Number of non-default selectors found in all parent templates of this template. We need to
// track it to properly adjust projection bucket index in the `projection` instruction.
private _ngContentSelectorsOffset = 0;
constructor( constructor(
private constantPool: ConstantPool, parentBindingScope: BindingScope, private level = 0, private constantPool: ConstantPool, parentBindingScope: BindingScope, private level = 0,
private contextName: string|null, private i18nContext: I18nContext|null, private contextName: string|null, private i18nContext: I18nContext|null,
@ -166,7 +170,11 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
}); });
} }
buildTemplateFunction(nodes: t.Node[], variables: t.Variable[], i18n?: i18n.AST): o.FunctionExpr { buildTemplateFunction(
nodes: t.Node[], variables: t.Variable[], ngContentSelectorsOffset: number = 0,
i18n?: i18n.AST): o.FunctionExpr {
this._ngContentSelectorsOffset = ngContentSelectorsOffset;
if (this._namespace !== R3.namespaceHTML) { if (this._namespace !== R3.namespaceHTML) {
this.creationInstruction(null, this._namespace); this.creationInstruction(null, this._namespace);
} }
@ -192,8 +200,23 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
// resolving bindings. We also count bindings in this pass as we walk bound expressions. // resolving bindings. We also count bindings in this pass as we walk bound expressions.
t.visitAll(this, nodes); t.visitAll(this, nodes);
// Output a `ProjectionDef` instruction when some `<ng-content>` are present // Add total binding count to pure function count so pure function instructions are
if (this._hasNgContent) { // generated with the correct slot offset when update instructions are processed.
this._pureFunctionSlots += this._bindingSlots;
// Pipes are walked in the first pass (to enqueue `pipe()` creation instructions and
// `pipeBind` update instructions), so we have to update the slot offsets manually
// to account for bindings.
this._valueConverter.updatePipeSlotOffsets(this._bindingSlots);
// Nested templates must be processed before creation instructions so template()
// instructions can be generated with the correct internal const count.
this._nestedTemplateFns.forEach(buildTemplateFn => buildTemplateFn());
// Output the `projectionDef` instruction when some `<ng-content>` are present.
// The `projectionDef` instruction only emitted for the component template and it is skipped for
// nested templates (<ng-template> tags).
if (this.level === 0 && this._hasNgContent) {
const parameters: o.Expression[] = []; const parameters: o.Expression[] = [];
// Only selectors with a non-default value are generated // Only selectors with a non-default value are generated
@ -212,19 +235,6 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
this.creationInstruction(null, R3.projectionDef, parameters, /* prepend */ true); this.creationInstruction(null, R3.projectionDef, parameters, /* prepend */ true);
} }
// Add total binding count to pure function count so pure function instructions are
// generated with the correct slot offset when update instructions are processed.
this._pureFunctionSlots += this._bindingSlots;
// Pipes are walked in the first pass (to enqueue `pipe()` creation instructions and
// `pipeBind` update instructions), so we have to update the slot offsets manually
// to account for bindings.
this._valueConverter.updatePipeSlotOffsets(this._bindingSlots);
// Nested templates must be processed before creation instructions so template()
// instructions can be generated with the correct internal const count.
this._nestedTemplateFns.forEach(buildTemplateFn => buildTemplateFn());
if (initI18nContext) { if (initI18nContext) {
this.i18nEnd(null, selfClosingI18nInstruction); this.i18nEnd(null, selfClosingI18nInstruction);
} }
@ -419,7 +429,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
const slot = this.allocateDataSlot(); const slot = this.allocateDataSlot();
let selectorIndex = ngContent.selector === DEFAULT_NG_CONTENT_SELECTOR ? let selectorIndex = ngContent.selector === DEFAULT_NG_CONTENT_SELECTOR ?
0 : 0 :
this._ngContentSelectors.push(ngContent.selector); this._ngContentSelectors.push(ngContent.selector) + this._ngContentSelectorsOffset;
const parameters: o.Expression[] = [o.literal(slot)]; const parameters: o.Expression[] = [o.literal(slot)];
const attributeAsList: string[] = []; const attributeAsList: string[] = [];
@ -522,7 +532,8 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
// this will build the instructions so that they fall into the following syntax // this will build the instructions so that they fall into the following syntax
// add attributes for directive matching purposes // add attributes for directive matching purposes
attributes.push(...this.prepareSyntheticAndSelectOnlyAttrs(allOtherInputs, element.outputs)); attributes.push(...this.prepareSyntheticAndSelectOnlyAttrs(
allOtherInputs, element.outputs, stylingBuilder));
parameters.push(this.toAttrsParam(attributes)); parameters.push(this.toAttrsParam(attributes));
// local refs (ex.: <div #foo #bar="baz">) // local refs (ex.: <div #foo #bar="baz">)
@ -552,11 +563,11 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
return element.children.length > 0; return element.children.length > 0;
}; };
const createSelfClosingInstruction = !stylingBuilder.hasBindingsOrInitialValues && const createSelfClosingInstruction = !stylingBuilder.hasBindingsOrInitialValues() &&
!isNgContainer && element.outputs.length === 0 && i18nAttrs.length === 0 && !hasChildren(); !isNgContainer && element.outputs.length === 0 && i18nAttrs.length === 0 && !hasChildren();
const createSelfClosingI18nInstruction = !createSelfClosingInstruction && const createSelfClosingI18nInstruction = !createSelfClosingInstruction &&
!stylingBuilder.hasBindingsOrInitialValues && hasTextChildrenOnly(element.children); !stylingBuilder.hasBindingsOrInitialValues() && hasTextChildrenOnly(element.children);
if (createSelfClosingInstruction) { if (createSelfClosingInstruction) {
this.creationInstruction(element.sourceSpan, R3.element, trimTrailingNulls(parameters)); this.creationInstruction(element.sourceSpan, R3.element, trimTrailingNulls(parameters));
@ -606,10 +617,16 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
} }
} }
// initial styling for static style="..." and class="..." attributes // The style bindings code is placed into two distinct blocks within the template function AOT
// code: creation and update. The creation code contains the `elementStyling` instructions
// which will apply the collected binding values to the element. `elementStyling` is
// designed to run inside of `elementStart` and `elementEnd`. The update instructions
// (things like `elementStyleProp`, `elementClassProp`, etc..) are applied later on in this
// file
this.processStylingInstruction( this.processStylingInstruction(
implicit, implicit,
stylingBuilder.buildCreateLevelInstruction(element.sourceSpan, this.constantPool), true); stylingBuilder.buildElementStylingInstruction(element.sourceSpan, this.constantPool),
true);
// Generate Listeners (outputs) // Generate Listeners (outputs)
element.outputs.forEach((outputAst: t.BoundEvent) => { element.outputs.forEach((outputAst: t.BoundEvent) => {
@ -619,6 +636,10 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
}); });
} }
// the code here will collect all update-level styling instructions and add them to the
// update block of the template function AOT code. Instructions like `elementStyleProp`,
// `elementStylingMap`, `elementClassProp` and `elementStylingApply` are all generated
// and assign in the code below.
stylingBuilder.buildUpdateLevelInstructions(this._valueConverter).forEach(instruction => { stylingBuilder.buildUpdateLevelInstructions(this._valueConverter).forEach(instruction => {
this.processStylingInstruction(implicit, instruction, false); this.processStylingInstruction(implicit, instruction, false);
}); });
@ -738,8 +759,13 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
// template definition. e.g. <div *ngIf="showing"> {{ foo }} </div> <div #foo></div> // template definition. e.g. <div *ngIf="showing"> {{ foo }} </div> <div #foo></div>
this._nestedTemplateFns.push(() => { this._nestedTemplateFns.push(() => {
const templateFunctionExpr = templateVisitor.buildTemplateFunction( const templateFunctionExpr = templateVisitor.buildTemplateFunction(
template.children, template.variables, template.i18n); template.children, template.variables,
this._ngContentSelectors.length + this._ngContentSelectorsOffset, template.i18n);
this.constantPool.statements.push(templateFunctionExpr.toDeclStmt(templateName, null)); this.constantPool.statements.push(templateFunctionExpr.toDeclStmt(templateName, null));
if (templateVisitor._hasNgContent) {
this._hasNgContent = true;
this._ngContentSelectors.push(...templateVisitor._ngContentSelectors);
}
}); });
// e.g. template(1, MyComp_Template_1) // e.g. template(1, MyComp_Template_1)
@ -919,10 +945,51 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
} }
} }
private prepareSyntheticAndSelectOnlyAttrs(inputs: t.BoundAttribute[], outputs: t.BoundEvent[]): /**
o.Expression[] { * Prepares all attribute expression values for the `TAttributes` array.
*
* The purpose of this function is to properly construct an attributes array that
* is passed into the `elementStart` (or just `element`) functions. Because there
* are many different types of attributes, the array needs to be constructed in a
* special way so that `elementStart` can properly evaluate them.
*
* The format looks like this:
*
* ```
* attrs = [prop, value, prop2, value2,
* CLASSES, class1, class2,
* STYLES, style1, value1, style2, value2,
* SELECT_ONLY, name1, name2, name2, ...]
* ```
*/
private prepareSyntheticAndSelectOnlyAttrs(
inputs: t.BoundAttribute[], outputs: t.BoundEvent[],
styles?: StylingBuilder): o.Expression[] {
const attrExprs: o.Expression[] = []; const attrExprs: o.Expression[] = [];
const nonSyntheticInputs: t.BoundAttribute[] = []; const nonSyntheticInputs: t.BoundAttribute[] = [];
const alreadySeen = new Set<string>();
function isASTWithSource(ast: AST): ast is ASTWithSource {
return ast instanceof ASTWithSource;
}
function isLiteralPrimitive(ast: AST): ast is LiteralPrimitive {
return ast instanceof LiteralPrimitive;
}
function addAttrExpr(key: string | number, value?: o.Expression): void {
if (typeof key === 'string') {
if (!alreadySeen.has(key)) {
attrExprs.push(o.literal(key));
if (value !== undefined) {
attrExprs.push(value);
}
alreadySeen.add(key);
}
} else {
attrExprs.push(o.literal(key));
}
}
if (inputs.length) { if (inputs.length) {
const EMPTY_STRING_EXPR = asLiteral(''); const EMPTY_STRING_EXPR = asLiteral('');
@ -932,17 +999,30 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
// may be supported differently in future versions of angular. However, // may be supported differently in future versions of angular. However,
// @triggers should always just be treated as regular attributes (it's up // @triggers should always just be treated as regular attributes (it's up
// to the renderer to detect and use them in a special way). // to the renderer to detect and use them in a special way).
attrExprs.push(asLiteral(prepareSyntheticAttributeName(input.name)), EMPTY_STRING_EXPR); const valueExp = input.value;
if (isASTWithSource(valueExp)) {
const literal = valueExp.ast;
if (isLiteralPrimitive(literal) && literal.value === undefined) {
addAttrExpr(prepareSyntheticAttributeName(input.name), EMPTY_STRING_EXPR);
}
}
} else { } else {
nonSyntheticInputs.push(input); nonSyntheticInputs.push(input);
} }
}); });
} }
// it's important that this occurs before SelectOnly because once `elementStart`
// comes across the SelectOnly marker then it will continue reading each value as
// as single property value cell by cell.
if (styles) {
styles.populateInitialStylingAttrs(attrExprs);
}
if (nonSyntheticInputs.length || outputs.length) { if (nonSyntheticInputs.length || outputs.length) {
attrExprs.push(o.literal(core.AttributeMarker.SelectOnly)); addAttrExpr(core.AttributeMarker.SelectOnly);
nonSyntheticInputs.forEach((i: t.BoundAttribute) => attrExprs.push(asLiteral(i.name))); nonSyntheticInputs.forEach((i: t.BoundAttribute) => addAttrExpr(i.name));
outputs.forEach((o: t.BoundEvent) => attrExprs.push(asLiteral(o.name))); outputs.forEach((o: t.BoundEvent) => addAttrExpr(o.name));
} }
return attrExprs; return attrExprs;

View File

@ -53,7 +53,7 @@ describe('compiler (unbundled Angular)', () => {
function compileApp(): GeneratedFile { function compileApp(): GeneratedFile {
const {genFiles} = compile([rootDir, angularFiles]); const {genFiles} = compile([rootDir, angularFiles]);
return genFiles.find( return genFiles.find(
genFile => genFile.srcFileUrl === componentPath && genFile.genFileUrl.endsWith('.ts')); genFile => genFile.srcFileUrl === componentPath && genFile.genFileUrl.endsWith('.ts')) !;
} }
function findLineAndColumn( function findLineAndColumn(
@ -321,7 +321,7 @@ describe('compiler (unbundled Angular)', () => {
const genFilePreamble = '/* Hello world! */'; const genFilePreamble = '/* Hello world! */';
const {genFiles} = compile([FILES, angularFiles]); const {genFiles} = compile([FILES, angularFiles]);
const genFile = const genFile =
genFiles.find(gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts')); genFiles.find(gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts')) !;
const genSource = toTypeScript(genFile, genFilePreamble); const genSource = toTypeScript(genFile, genFilePreamble);
expect(genSource.startsWith(genFilePreamble)).toBe(true); expect(genSource.startsWith(genFilePreamble)).toBe(true);
}); });
@ -440,7 +440,7 @@ describe('compiler (unbundled Angular)', () => {
} }
}; };
const {genFiles} = compile([FILES, angularFiles]); const {genFiles} = compile([FILES, angularFiles]);
const genFile = genFiles.find(genFile => genFile.srcFileUrl === '/app/app.ts'); const genFile = genFiles.find(genFile => genFile.srcFileUrl === '/app/app.ts') !;
const genSource = toTypeScript(genFile); const genSource = toTypeScript(genFile);
const createComponentFactoryCall = /ɵccf\([^)]*\)/m.exec(genSource) ![0].replace(/\s*/g, ''); const createComponentFactoryCall = /ɵccf\([^)]*\)/m.exec(genSource) ![0].replace(/\s*/g, '');
// selector // selector
@ -471,7 +471,7 @@ describe('compiler (unbundled Angular)', () => {
}; };
const {genFiles} = compile([FILES, angularFiles]); const {genFiles} = compile([FILES, angularFiles]);
const genFile = const genFile =
genFiles.find(gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts')); genFiles.find(gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts')) !;
const genSource = toTypeScript(genFile); const genSource = toTypeScript(genFile);
expect(genSource).not.toContain('check('); expect(genSource).not.toContain('check(');
@ -510,7 +510,7 @@ describe('compiler (unbundled Angular)', () => {
const {outDir: libOutDir} = compile([libInput, angularSummaryFiles], {useSummaries: true}); const {outDir: libOutDir} = compile([libInput, angularSummaryFiles], {useSummaries: true});
const {genFiles: appGenFiles} = const {genFiles: appGenFiles} =
compile([appInput, libOutDir, angularSummaryFiles], {useSummaries: true}); compile([appInput, libOutDir, angularSummaryFiles], {useSummaries: true});
const appNgFactory = appGenFiles.find((f) => f.genFileUrl === '/app/main.ngfactory.ts'); const appNgFactory = appGenFiles.find((f) => f.genFileUrl === '/app/main.ngfactory.ts') !;
const appNgFactoryTs = toTypeScript(appNgFactory); const appNgFactoryTs = toTypeScript(appNgFactory);
expect(appNgFactoryTs).not.toContain('AType'); expect(appNgFactoryTs).not.toContain('AType');
expect(appNgFactoryTs).toContain('AValue'); expect(appNgFactoryTs).toContain('AValue');
@ -557,7 +557,7 @@ describe('compiler (unbundled Angular)', () => {
const {outDir: libOutDir} = compile([libInput, angularSummaryFiles], {useSummaries: true}); const {outDir: libOutDir} = compile([libInput, angularSummaryFiles], {useSummaries: true});
const {genFiles: appGenFiles} = const {genFiles: appGenFiles} =
compile([appInput, libOutDir, angularSummaryFiles], {useSummaries: true}); compile([appInput, libOutDir, angularSummaryFiles], {useSummaries: true});
const appNgFactory = appGenFiles.find((f) => f.genFileUrl === '/app/main.ngfactory.ts'); const appNgFactory = appGenFiles.find((f) => f.genFileUrl === '/app/main.ngfactory.ts') !;
const appNgFactoryTs = toTypeScript(appNgFactory); const appNgFactoryTs = toTypeScript(appNgFactory);
// metadata of ctor calls is preserved, so we reexport the argument // metadata of ctor calls is preserved, so we reexport the argument
@ -598,7 +598,7 @@ describe('compiler (unbundled Angular)', () => {
const {outDir: libOutDir} = compile([libInput, angularSummaryFiles], {useSummaries: true}); const {outDir: libOutDir} = compile([libInput, angularSummaryFiles], {useSummaries: true});
const {genFiles: appGenFiles} = const {genFiles: appGenFiles} =
compile([appInput, libOutDir, angularSummaryFiles], {useSummaries: true}); compile([appInput, libOutDir, angularSummaryFiles], {useSummaries: true});
const appNgFactory = appGenFiles.find((f) => f.genFileUrl === '/app/main.ngfactory.ts'); const appNgFactory = appGenFiles.find((f) => f.genFileUrl === '/app/main.ngfactory.ts') !;
const appNgFactoryTs = toTypeScript(appNgFactory); const appNgFactoryTs = toTypeScript(appNgFactory);
// we don't need to reexport exported symbols via the .ngfactory // we don't need to reexport exported symbols via the .ngfactory
@ -690,7 +690,7 @@ describe('compiler (unbundled Angular)', () => {
compile([libInput, getAngularSummaryFiles()], {useSummaries: true}); compile([libInput, getAngularSummaryFiles()], {useSummaries: true});
const {genFiles} = const {genFiles} =
compile([libOutDir, appInput, getAngularSummaryFiles()], {useSummaries: true}); compile([libOutDir, appInput, getAngularSummaryFiles()], {useSummaries: true});
const mainNgFactory = genFiles.find(gf => gf.srcFileUrl === '/app/main.ts'); const mainNgFactory = genFiles.find(gf => gf.srcFileUrl === '/app/main.ts') !;
const flags = NodeFlags.TypeDirective | NodeFlags.Component | NodeFlags.OnDestroy; const flags = NodeFlags.TypeDirective | NodeFlags.Component | NodeFlags.OnDestroy;
expect(toTypeScript(mainNgFactory)) expect(toTypeScript(mainNgFactory))
.toContain(`${flags},(null as any),0,i1.Extends,[i2.AParam]`); .toContain(`${flags},(null as any),0,i1.Extends,[i2.AParam]`);
@ -743,7 +743,7 @@ describe('compiler (unbundled Angular)', () => {
compile([lib1OutDir, lib2Input, getAngularSummaryFiles()], {useSummaries: true}); compile([lib1OutDir, lib2Input, getAngularSummaryFiles()], {useSummaries: true});
const {genFiles} = const {genFiles} =
compile([lib2OutDir, appInput, getAngularSummaryFiles()], {useSummaries: true}); compile([lib2OutDir, appInput, getAngularSummaryFiles()], {useSummaries: true});
const mainNgFactory = genFiles.find(gf => gf.srcFileUrl === '/app/main.ts'); const mainNgFactory = genFiles.find(gf => gf.srcFileUrl === '/app/main.ts') !;
const flags = NodeFlags.TypeDirective | NodeFlags.Component | NodeFlags.OnDestroy; const flags = NodeFlags.TypeDirective | NodeFlags.Component | NodeFlags.OnDestroy;
expect(toTypeScript(mainNgFactory)) expect(toTypeScript(mainNgFactory))
.toContain(`${flags},(null as any),0,i1.Extends,[i2.AParam_2]`); .toContain(`${flags},(null as any),0,i1.Extends,[i2.AParam_2]`);

View File

@ -41,7 +41,7 @@ describe('aot summaries for jit', () => {
const rootDir = {'app': appDir}; const rootDir = {'app': appDir};
const genFile = const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts'); compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts') !;
const genSource = toTypeScript(genFile); const genSource = toTypeScript(genFile);
expect(genSource).toContain(`import * as i0 from '/app/app.module'`); expect(genSource).toContain(`import * as i0 from '/app/app.module'`);
@ -70,7 +70,7 @@ describe('aot summaries for jit', () => {
const rootDir = {'app': appDir}; const rootDir = {'app': appDir};
const genFile = const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts'); compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts') !;
const genSource = toTypeScript(genFile); const genSource = toTypeScript(genFile);
expect(genSource).toContain(`import * as i0 from '/app/app.module'`); expect(genSource).toContain(`import * as i0 from '/app/app.module'`);
@ -99,7 +99,7 @@ describe('aot summaries for jit', () => {
const rootDir = {'app': appDir}; const rootDir = {'app': appDir};
const genFile = const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts'); compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts') !;
const genSource = toTypeScript(genFile); const genSource = toTypeScript(genFile);
expect(genSource).toContain(`import * as i0 from '/app/app.module'`); expect(genSource).toContain(`import * as i0 from '/app/app.module'`);
@ -125,7 +125,7 @@ describe('aot summaries for jit', () => {
const rootDir = {'app': appDir}; const rootDir = {'app': appDir};
const genFile = const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts'); compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts') !;
const genSource = toTypeScript(genFile); const genSource = toTypeScript(genFile);
expect(genSource).toContain(`import * as i0 from '/app/app.module'`); expect(genSource).toContain(`import * as i0 from '/app/app.module'`);
@ -164,7 +164,7 @@ describe('aot summaries for jit', () => {
const rootDir = {'app': appDir}; const rootDir = {'app': appDir};
const genFile = const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts'); compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts') !;
const genSource = toTypeScript(genFile); const genSource = toTypeScript(genFile);
expect(genSource).toMatch(/useClass:\{\s*reference:i1.MyService/); expect(genSource).toMatch(/useClass:\{\s*reference:i1.MyService/);
@ -198,7 +198,7 @@ describe('aot summaries for jit', () => {
const rootDir = {'app': appDir}; const rootDir = {'app': appDir};
const genFile = const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts'); compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts') !;
const genSource = toTypeScript(genFile); const genSource = toTypeScript(genFile);
expect(genSource).toMatch(/useClass:\{\s*reference:i1.MyService/); expect(genSource).toMatch(/useClass:\{\s*reference:i1.MyService/);
@ -225,7 +225,7 @@ describe('aot summaries for jit', () => {
const rootDir = {'app': appDir}; const rootDir = {'app': appDir};
const genFile = const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts'); compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts') !;
const genSource = toTypeScript(genFile); const genSource = toTypeScript(genFile);
expect(genSource).toMatch( expect(genSource).toMatch(
@ -247,7 +247,7 @@ describe('aot summaries for jit', () => {
const rootDir = {'app': appDir}; const rootDir = {'app': appDir};
const genFile = const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts'); compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts') !;
const genSource = toTypeScript(genFile); const genSource = toTypeScript(genFile);
expect(genSource).toMatch( expect(genSource).toMatch(
@ -294,9 +294,10 @@ describe('aot summaries for jit', () => {
}; };
const {outDir: lib3In, genFiles: lib2Gen} = compileApp(lib2In, {useSummaries: true}); const {outDir: lib3In, genFiles: lib2Gen} = compileApp(lib2In, {useSummaries: true});
const lib2ModuleNgSummary = lib2Gen.find(f => f.genFileUrl === '/lib2/module.ngsummary.ts'); const lib2ModuleNgSummary =
lib2Gen.find(f => f.genFileUrl === '/lib2/module.ngsummary.ts') !;
const lib2ReexportNgSummary = const lib2ReexportNgSummary =
lib2Gen.find(f => f.genFileUrl === '/lib2/reexport.ngsummary.ts'); lib2Gen.find(f => f.genFileUrl === '/lib2/reexport.ngsummary.ts') !;
// ngsummaries should add reexports for imported NgModules from a direct dependency // ngsummaries should add reexports for imported NgModules from a direct dependency
expect(toTypeScript(lib2ModuleNgSummary)) expect(toTypeScript(lib2ModuleNgSummary))
@ -325,9 +326,10 @@ describe('aot summaries for jit', () => {
}; };
const lib3Gen = compileApp(lib3In, {useSummaries: true}).genFiles; const lib3Gen = compileApp(lib3In, {useSummaries: true}).genFiles;
const lib3ModuleNgSummary = lib3Gen.find(f => f.genFileUrl === '/lib3/module.ngsummary.ts'); const lib3ModuleNgSummary =
lib3Gen.find(f => f.genFileUrl === '/lib3/module.ngsummary.ts') !;
const lib3ReexportNgSummary = const lib3ReexportNgSummary =
lib3Gen.find(f => f.genFileUrl === '/lib3/reexport.ngsummary.ts'); lib3Gen.find(f => f.genFileUrl === '/lib3/reexport.ngsummary.ts') !;
// ngsummary.ts files should use the reexported values from direct and deep deps // ngsummary.ts files should use the reexported values from direct and deep deps
const lib3ModuleNgSummarySource = toTypeScript(lib3ModuleNgSummary); const lib3ModuleNgSummarySource = toTypeScript(lib3ModuleNgSummary);

View File

@ -422,7 +422,7 @@ export class MockSummaryResolver implements SummaryResolver<StaticSymbol> {
}[] = []) {} }[] = []) {}
addSummary(summary: Summary<StaticSymbol>) { this.summaries.push(summary); } addSummary(summary: Summary<StaticSymbol>) { this.summaries.push(summary); }
resolveSummary(reference: StaticSymbol): Summary<StaticSymbol> { resolveSummary(reference: StaticSymbol): Summary<StaticSymbol> {
return this.summaries.find(summary => summary.symbol === reference); return this.summaries.find(summary => summary.symbol === reference) !;
} }
getSymbolsOf(filePath: string): StaticSymbol[]|null { getSymbolsOf(filePath: string): StaticSymbol[]|null {
const symbols = this.summaries.filter(summary => summary.symbol.filePath === filePath) const symbols = this.summaries.filter(summary => summary.symbol.filePath === filePath)

View File

@ -10,55 +10,53 @@ import {hyphenate, parse as parseStyle, stripUnnecessaryQuotes} from '../../src/
describe('style parsing', () => { describe('style parsing', () => {
it('should parse empty or blank strings', () => { it('should parse empty or blank strings', () => {
const result1 = parseStyle(''); const result1 = parseStyle('');
expect(result1).toEqual({}); expect(result1).toEqual([]);
const result2 = parseStyle(' '); const result2 = parseStyle(' ');
expect(result2).toEqual({}); expect(result2).toEqual([]);
}); });
it('should parse a string into a key/value map', () => { it('should parse a string into a key/value map', () => {
const result = parseStyle('width:100px;height:200px;opacity:0'); const result = parseStyle('width:100px;height:200px;opacity:0');
expect(result).toEqual({width: '100px', height: '200px', opacity: '0'}); expect(result).toEqual(['width', '100px', 'height', '200px', 'opacity', '0']);
}); });
it('should trim values and properties', () => { it('should trim values and properties', () => {
const result = parseStyle('width :333px ; height:666px ; opacity: 0.5;'); const result = parseStyle('width :333px ; height:666px ; opacity: 0.5;');
expect(result).toEqual({width: '333px', height: '666px', opacity: '0.5'}); expect(result).toEqual(['width', '333px', 'height', '666px', 'opacity', '0.5']);
}); });
it('should chomp out start/end quotes', () => { it('should chomp out start/end quotes', () => {
const result = parseStyle( const result = parseStyle(
'content: "foo"; opacity: \'0.5\'; font-family: "Verdana", Helvetica, "sans-serif"'); 'content: "foo"; opacity: \'0.5\'; font-family: "Verdana", Helvetica, "sans-serif"');
expect(result).toEqual( expect(result).toEqual(
{content: 'foo', opacity: '0.5', 'font-family': '"Verdana", Helvetica, "sans-serif"'}); ['content', 'foo', 'opacity', '0.5', 'font-family', '"Verdana", Helvetica, "sans-serif"']);
}); });
it('should not mess up with quoted strings that contain [:;] values', () => { it('should not mess up with quoted strings that contain [:;] values', () => {
const result = parseStyle('content: "foo; man: guy"; width: 100px'); const result = parseStyle('content: "foo; man: guy"; width: 100px');
expect(result).toEqual({content: 'foo; man: guy', width: '100px'}); expect(result).toEqual(['content', 'foo; man: guy', 'width', '100px']);
}); });
it('should not mess up with quoted strings that contain inner quote values', () => { it('should not mess up with quoted strings that contain inner quote values', () => {
const quoteStr = '"one \'two\' three \"four\" five"'; const quoteStr = '"one \'two\' three \"four\" five"';
const result = parseStyle(`content: ${quoteStr}; width: 123px`); const result = parseStyle(`content: ${quoteStr}; width: 123px`);
expect(result).toEqual({content: quoteStr, width: '123px'}); expect(result).toEqual(['content', quoteStr, 'width', '123px']);
}); });
it('should respect parenthesis that are placed within a style', () => { it('should respect parenthesis that are placed within a style', () => {
const result = parseStyle('background-image: url("foo.jpg")'); const result = parseStyle('background-image: url("foo.jpg")');
expect(result).toEqual({'background-image': 'url("foo.jpg")'}); expect(result).toEqual(['background-image', 'url("foo.jpg")']);
}); });
it('should respect multi-level parenthesis that contain special [:;] characters', () => { it('should respect multi-level parenthesis that contain special [:;] characters', () => {
const result = parseStyle('color: rgba(calc(50 * 4), var(--cool), :5;); height: 100px;'); const result = parseStyle('color: rgba(calc(50 * 4), var(--cool), :5;); height: 100px;');
expect(result).toEqual({color: 'rgba(calc(50 * 4), var(--cool), :5;)', height: '100px'}); expect(result).toEqual(['color', 'rgba(calc(50 * 4), var(--cool), :5;)', 'height', '100px']);
}); });
it('should hyphenate style properties from camel case', () => { it('should hyphenate style properties from camel case', () => {
const result = parseStyle('borderWidth: 200px'); const result = parseStyle('borderWidth: 200px');
expect(result).toEqual({ expect(result).toEqual(['border-width', '200px']);
'border-width': '200px',
});
}); });
describe('quote chomping', () => { describe('quote chomping', () => {

View File

@ -17,7 +17,7 @@ export enum ChangeDetectionStrategy {
/** /**
* Use the `CheckOnce` strategy, meaning that automatic change detection is deactivated * Use the `CheckOnce` strategy, meaning that automatic change detection is deactivated
* until reactivated by setting the strategy to `Default` (`CheckAlways`). * until reactivated by setting the strategy to `Default` (`CheckAlways`).
* Change detection can still be explictly invoked. * Change detection can still be explicitly invoked.
*/ */
OnPush = 0, OnPush = 0,
@ -46,7 +46,7 @@ export enum ChangeDetectorStatus {
Checked, Checked,
/** /**
* A state in which change detection continues automatically until explictly * A state in which change detection continues automatically until explicitly
* deactivated. * deactivated.
*/ */
CheckAlways, CheckAlways,

View File

@ -92,6 +92,7 @@ export {
elementContainerStart as ɵelementContainerStart, elementContainerStart as ɵelementContainerStart,
elementContainerEnd as ɵelementContainerEnd, elementContainerEnd as ɵelementContainerEnd,
elementStyling as ɵelementStyling, elementStyling as ɵelementStyling,
elementHostAttrs as ɵelementHostAttrs,
elementStylingMap as ɵelementStylingMap, elementStylingMap as ɵelementStylingMap,
elementStyleProp as ɵelementStyleProp, elementStyleProp as ɵelementStyleProp,
elementStylingApply as ɵelementStylingApply, elementStylingApply as ɵelementStylingApply,

View File

@ -12,7 +12,7 @@ import {getComponent, getContext, getInjectionTokens, getInjector, getListeners,
import {TNode} from '../render3/interfaces/node'; import {TNode} from '../render3/interfaces/node';
import {StylingIndex} from '../render3/interfaces/styling'; import {StylingIndex} from '../render3/interfaces/styling';
import {TVIEW} from '../render3/interfaces/view'; import {TVIEW} from '../render3/interfaces/view';
import {getProp, getValue, isClassBased} from '../render3/styling/class_and_style_bindings'; import {getProp, getValue, isClassBasedValue} from '../render3/styling/class_and_style_bindings';
import {getStylingContext} from '../render3/styling/util'; import {getStylingContext} from '../render3/styling/util';
import {DebugContext} from '../view/index'; import {DebugContext} from '../view/index';
@ -273,7 +273,7 @@ class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugEleme
if (stylingContext) { if (stylingContext) {
for (let i = StylingIndex.SingleStylesStartPosition; i < lNode.length; for (let i = StylingIndex.SingleStylesStartPosition; i < lNode.length;
i += StylingIndex.Size) { i += StylingIndex.Size) {
if (isClassBased(lNode, i)) { if (isClassBasedValue(lNode, i)) {
const className = getProp(lNode, i); const className = getProp(lNode, i);
const value = getValue(lNode, i); const value = getValue(lNode, i);
if (typeof value == 'boolean') { if (typeof value == 'boolean') {
@ -303,7 +303,7 @@ class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugEleme
if (stylingContext) { if (stylingContext) {
for (let i = StylingIndex.SingleStylesStartPosition; i < lNode.length; for (let i = StylingIndex.SingleStylesStartPosition; i < lNode.length;
i += StylingIndex.Size) { i += StylingIndex.Size) {
if (!isClassBased(lNode, i)) { if (!isClassBasedValue(lNode, i)) {
const styleName = getProp(lNode, i); const styleName = getProp(lNode, i);
const value = getValue(lNode, i) as string | null; const value = getValue(lNode, i) as string | null;
if (value !== null) { if (value !== null) {

View File

@ -11,7 +11,7 @@ import {devModeEqual} from '../change_detection/change_detection_util';
import {assertDataInRange, assertLessThan, assertNotEqual} from './assert'; import {assertDataInRange, assertLessThan, assertNotEqual} from './assert';
import {throwErrorIfNoChangesMode} from './errors'; import {throwErrorIfNoChangesMode} from './errors';
import {BINDING_INDEX, LView} from './interfaces/view'; import {BINDING_INDEX, LView} from './interfaces/view';
import {getCheckNoChangesMode, getCreationMode} from './state'; import {getCheckNoChangesMode, isCreationMode} from './state';
import {NO_CHANGE} from './tokens'; import {NO_CHANGE} from './tokens';
import {isDifferent} from './util'; import {isDifferent} from './util';
@ -44,7 +44,7 @@ export function bindingUpdated(lView: LView, bindingIndex: number, value: any):
} else if (isDifferent(lView[bindingIndex], value)) { } else if (isDifferent(lView[bindingIndex], value)) {
if (ngDevMode && getCheckNoChangesMode()) { if (ngDevMode && getCheckNoChangesMode()) {
if (!devModeEqual(lView[bindingIndex], value)) { if (!devModeEqual(lView[bindingIndex], value)) {
throwErrorIfNoChangesMode(getCreationMode(), lView[bindingIndex], value); throwErrorIfNoChangesMode(isCreationMode(lView), lView[bindingIndex], value);
} }
} }
lView[bindingIndex] = value; lView[bindingIndex] = value;

View File

@ -22,7 +22,7 @@ import {ComponentDef, ComponentType, RenderFlags} from './interfaces/definition'
import {TElementNode, TNode, TNodeFlags, TNodeType} from './interfaces/node'; import {TElementNode, TNode, TNodeFlags, TNodeType} from './interfaces/node';
import {PlayerHandler} from './interfaces/player'; import {PlayerHandler} from './interfaces/player';
import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer'; import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
import {CONTEXT, HEADER_OFFSET, HOST, HOST_NODE, LView, LViewFlags, RootContext, RootContextFlags, TVIEW} from './interfaces/view'; import {CONTEXT, FLAGS, HEADER_OFFSET, HOST, HOST_NODE, LView, LViewFlags, RootContext, RootContextFlags, TVIEW} from './interfaces/view';
import {enterView, getPreviousOrParentTNode, leaveView, resetComponentState, setCurrentDirectiveDef} from './state'; import {enterView, getPreviousOrParentTNode, leaveView, resetComponentState, setCurrentDirectiveDef} from './state';
import {defaultScheduler, getRootView, readPatchedLView, stringify} from './util'; import {defaultScheduler, getRootView, readPatchedLView, stringify} from './util';
@ -133,7 +133,9 @@ export function renderComponent<T>(
component = createRootComponent( component = createRootComponent(
componentView, componentDef, rootView, rootContext, opts.hostFeatures || null); componentView, componentDef, rootView, rootContext, opts.hostFeatures || null);
refreshDescendantViews(rootView, null); refreshDescendantViews(rootView); // creation mode pass
rootView[FLAGS] &= ~LViewFlags.CreationMode;
refreshDescendantViews(rootView); // update mode pass
} finally { } finally {
leaveView(oldView); leaveView(oldView);
if (rendererFactory.end) rendererFactory.end(); if (rendererFactory.end) rendererFactory.end();

View File

@ -208,10 +208,9 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
componentView, this.componentDef, rootLView, rootContext, [LifecycleHooksFeature]); componentView, this.componentDef, rootLView, rootContext, [LifecycleHooksFeature]);
addToViewTree(rootLView, HEADER_OFFSET, componentView); addToViewTree(rootLView, HEADER_OFFSET, componentView);
refreshDescendantViews(rootLView);
refreshDescendantViews(rootLView, RenderFlags.Create);
} finally { } finally {
leaveView(oldLView, true); leaveView(oldLView);
if (rendererFactory.end) rendererFactory.end(); if (rendererFactory.end) rendererFactory.end();
} }

View File

@ -7,12 +7,13 @@
*/ */
import './ng_dev_mode'; import './ng_dev_mode';
import {assertDomNode} from './assert'; import {assertDomNode} from './assert';
import {EMPTY_ARRAY} from './definition'; import {EMPTY_ARRAY} from './empty';
import {LContext, MONKEY_PATCH_KEY_NAME} from './interfaces/context'; import {LContext, MONKEY_PATCH_KEY_NAME} from './interfaces/context';
import {TNode, TNodeFlags} from './interfaces/node'; import {TNode, TNodeFlags} from './interfaces/node';
import {RElement} from './interfaces/renderer'; import {RElement} from './interfaces/renderer';
import {CONTEXT, HEADER_OFFSET, HOST, LView, TVIEW} from './interfaces/view'; import {CONTEXT, HEADER_OFFSET, HOST, LView, TVIEW} from './interfaces/view';
import {getComponentViewByIndex, getNativeByTNode, getTNode, readElementValue, readPatchedData} from './util'; import {getComponentViewByIndex, getNativeByTNode, readElementValue, readPatchedData} from './util';
/** Returns the matching `LContext` data for a given DOM node, directive or component instance. /** Returns the matching `LContext` data for a given DOM node, directive or component instance.

View File

@ -9,22 +9,15 @@
import './ng_dev_mode'; import './ng_dev_mode';
import {ChangeDetectionStrategy} from '../change_detection/constants'; import {ChangeDetectionStrategy} from '../change_detection/constants';
import {Provider} from '../di/provider';
import {NgModuleDef} from '../metadata/ng_module'; import {NgModuleDef} from '../metadata/ng_module';
import {ViewEncapsulation} from '../metadata/view'; import {ViewEncapsulation} from '../metadata/view';
import {Mutable, Type} from '../type'; import {Mutable, Type} from '../type';
import {noSideEffects, stringify} from '../util'; import {noSideEffects, stringify} from '../util';
import {EMPTY_ARRAY, EMPTY_OBJ} from './empty';
import {NG_COMPONENT_DEF, NG_DIRECTIVE_DEF, NG_MODULE_DEF, NG_PIPE_DEF} from './fields'; import {NG_COMPONENT_DEF, NG_DIRECTIVE_DEF, NG_MODULE_DEF, NG_PIPE_DEF} from './fields';
import {BaseDef, ComponentDef, ComponentDefFeature, ComponentQuery, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFeature, DirectiveType, DirectiveTypesOrFactory, HostBindingsFunction, PipeDef, PipeType, PipeTypesOrFactory} from './interfaces/definition'; import {BaseDef, ComponentDef, ComponentDefFeature, ComponentQuery, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFeature, DirectiveType, DirectiveTypesOrFactory, HostBindingsFunction, PipeDef, PipeType, PipeTypesOrFactory} from './interfaces/definition';
import {CssSelectorList, SelectorFlags} from './interfaces/projection'; import {CssSelectorList} from './interfaces/projection';
export const EMPTY: {} = {};
export const EMPTY_ARRAY: any[] = [];
if (typeof ngDevMode !== 'undefined' && ngDevMode) {
Object.freeze(EMPTY);
Object.freeze(EMPTY_ARRAY);
}
let _renderCompCount = 0; let _renderCompCount = 0;
/** /**
@ -389,7 +382,7 @@ export function defineNgModule<T>(def: {type: T} & Partial<NgModuleDef<T>>): nev
*/ */
function invertObject(obj: any, secondary?: any): any { function invertObject(obj: any, secondary?: any): any {
if (obj == null) return EMPTY; if (obj == null) return EMPTY_OBJ;
const newLookup: any = {}; const newLookup: any = {};
for (const minifiedKey in obj) { for (const minifiedKey in obj) {
if (obj.hasOwnProperty(minifiedKey)) { if (obj.hasOwnProperty(minifiedKey)) {

View File

@ -21,7 +21,8 @@ import {AttributeMarker, TContainerNode, TElementContainerNode, TElementNode, TN
import {DECLARATION_VIEW, HOST_NODE, INJECTOR, LView, TData, TVIEW, TView} from './interfaces/view'; import {DECLARATION_VIEW, HOST_NODE, INJECTOR, LView, TData, TVIEW, TView} from './interfaces/view';
import {assertNodeOfPossibleTypes} from './node_assert'; import {assertNodeOfPossibleTypes} from './node_assert';
import {getLView, getPreviousOrParentTNode, setTNodeAndViewData} from './state'; import {getLView, getPreviousOrParentTNode, setTNodeAndViewData} from './state';
import {getParentInjectorIndex, getParentInjectorView, hasParentInjector, isComponent, stringify} from './util'; import {getHostTElementNode, getParentInjectorIndex, getParentInjectorView, hasParentInjector, isComponent, isComponentDef, stringify} from './util';
/** /**
* Defines if the call to `inject` should include `viewProviders` in its resolution. * Defines if the call to `inject` should include `viewProviders` in its resolution.
@ -197,7 +198,7 @@ export function getInjectorIndex(tNode: TNode, hostView: LView): number {
*/ */
export function getParentInjectorLocation(tNode: TNode, view: LView): RelativeInjectorLocation { export function getParentInjectorLocation(tNode: TNode, view: LView): RelativeInjectorLocation {
if (tNode.parent && tNode.parent.injectorIndex !== -1) { if (tNode.parent && tNode.parent.injectorIndex !== -1) {
return tNode.parent.injectorIndex as any; // ViewOffset is 0, AcrossHostBoundary is 0 return tNode.parent.injectorIndex as any; // ViewOffset is 0
} }
// For most cases, the parent injector index can be found on the host node (e.g. for component // For most cases, the parent injector index can be found on the host node (e.g. for component
@ -210,13 +211,9 @@ export function getParentInjectorLocation(tNode: TNode, view: LView): RelativeIn
hostTNode = view[HOST_NODE] !; hostTNode = view[HOST_NODE] !;
viewOffset++; viewOffset++;
} }
const acrossHostBoundary = hostTNode && hostTNode.type === TNodeType.Element ?
RelativeInjectorLocationFlags.AcrossHostBoundary :
0;
return hostTNode ? return hostTNode ?
hostTNode.injectorIndex | (viewOffset << RelativeInjectorLocationFlags.ViewOffsetShift) | hostTNode.injectorIndex | (viewOffset << RelativeInjectorLocationFlags.ViewOffsetShift) :
acrossHostBoundary :
-1 as any; -1 as any;
} }
@ -323,6 +320,7 @@ export function getOrCreateInjectable<T>(
let previousTView: TView|null = null; let previousTView: TView|null = null;
let injectorIndex = getInjectorIndex(tNode, lView); let injectorIndex = getInjectorIndex(tNode, lView);
let parentLocation: RelativeInjectorLocation = NO_PARENT_INJECTOR; let parentLocation: RelativeInjectorLocation = NO_PARENT_INJECTOR;
let hostTElementNode: TNode|null = flags & InjectFlags.Host ? getHostTElementNode(lView) : null;
// If we should skip this injector, or if there is no injector on this node, start by searching // If we should skip this injector, or if there is no injector on this node, start by searching
// the parent injector. // the parent injector.
@ -330,7 +328,7 @@ export function getOrCreateInjectable<T>(
parentLocation = injectorIndex === -1 ? getParentInjectorLocation(tNode, lView) : parentLocation = injectorIndex === -1 ? getParentInjectorLocation(tNode, lView) :
lView[injectorIndex + PARENT_INJECTOR]; lView[injectorIndex + PARENT_INJECTOR];
if (!shouldSearchParent(flags, parentLocation)) { if (!shouldSearchParent(flags, false)) {
injectorIndex = -1; injectorIndex = -1;
} else { } else {
previousTView = lView[TVIEW]; previousTView = lView[TVIEW];
@ -350,13 +348,14 @@ export function getOrCreateInjectable<T>(
// At this point, we have an injector which *may* contain the token, so we step through // At this point, we have an injector which *may* contain the token, so we step through
// the providers and directives associated with the injector's corresponding node to get // the providers and directives associated with the injector's corresponding node to get
// the instance. // the instance.
const instance: T|null = const instance: T|null = searchTokensOnInjector<T>(
searchTokensOnInjector<T>(injectorIndex, lView, token, previousTView); injectorIndex, lView, token, previousTView, flags, hostTElementNode);
if (instance !== NOT_FOUND) { if (instance !== NOT_FOUND) {
return instance; return instance;
} }
} }
if (shouldSearchParent(flags, parentLocation) && if (shouldSearchParent(
flags, lView[TVIEW].data[injectorIndex + TNODE] === hostTElementNode) &&
bloomHasToken(bloomHash, injectorIndex, lView)) { bloomHasToken(bloomHash, injectorIndex, lView)) {
// The def wasn't found anywhere on this node, so it was a false positive. // The def wasn't found anywhere on this node, so it was a false positive.
// Traverse up the tree and continue searching. // Traverse up the tree and continue searching.
@ -396,7 +395,7 @@ const NOT_FOUND = {};
function searchTokensOnInjector<T>( function searchTokensOnInjector<T>(
injectorIndex: number, lView: LView, token: Type<T>| InjectionToken<T>, injectorIndex: number, lView: LView, token: Type<T>| InjectionToken<T>,
previousTView: TView | null) { previousTView: TView | null, flags: InjectFlags, hostTElementNode: TNode | null) {
const currentTView = lView[TVIEW]; const currentTView = lView[TVIEW];
const tNode = currentTView.data[injectorIndex + TNODE] as TNode; const tNode = currentTView.data[injectorIndex + TNODE] as TNode;
// First, we need to determine if view providers can be accessed by the starting element. // First, we need to determine if view providers can be accessed by the starting element.
@ -418,7 +417,12 @@ function searchTokensOnInjector<T>(
// into the ViewProviders. // into the ViewProviders.
(previousTView != currentTView && (tNode.type === TNodeType.Element)); (previousTView != currentTView && (tNode.type === TNodeType.Element));
const injectableIdx = locateDirectiveOrProvider(tNode, lView, token, canAccessViewProviders); // This special case happens when there is a @host on the inject and when we are searching
// on the host element node.
const isHostSpecialCase = (flags & InjectFlags.Host) && hostTElementNode === tNode;
const injectableIdx =
locateDirectiveOrProvider(tNode, lView, token, canAccessViewProviders, isHostSpecialCase);
if (injectableIdx !== null) { if (injectableIdx !== null) {
return getNodeInjectable(currentTView.data, lView, injectableIdx, tNode as TElementNode); return getNodeInjectable(currentTView.data, lView, injectableIdx, tNode as TElementNode);
} else { } else {
@ -433,13 +437,13 @@ function searchTokensOnInjector<T>(
* @param lView The view we are currently processing * @param lView The view we are currently processing
* @param token Provider token or type of a directive to look for. * @param token Provider token or type of a directive to look for.
* @param canAccessViewProviders Whether view providers should be considered. * @param canAccessViewProviders Whether view providers should be considered.
* @param isHostSpecialCase Whether the host special case applies.
* @returns Index of a found directive or provider, or null when none found. * @returns Index of a found directive or provider, or null when none found.
*/ */
export function locateDirectiveOrProvider<T>( export function locateDirectiveOrProvider<T>(
tNode: TNode, lView: LView, token: Type<T>| InjectionToken<T>, tNode: TNode, lView: LView, token: Type<T>| InjectionToken<T>, canAccessViewProviders: boolean,
canAccessViewProviders: boolean): number|null { isHostSpecialCase: boolean | number): number|null {
const tView = lView[TVIEW]; const tView = lView[TVIEW];
const nodeFlags = tNode.flags;
const nodeProviderIndexes = tNode.providerIndexes; const nodeProviderIndexes = tNode.providerIndexes;
const tInjectables = tView.data; const tInjectables = tView.data;
@ -450,13 +454,21 @@ export function locateDirectiveOrProvider<T>(
nodeProviderIndexes >> TNodeProviderIndexes.CptViewProvidersCountShift; nodeProviderIndexes >> TNodeProviderIndexes.CptViewProvidersCountShift;
const startingIndex = const startingIndex =
canAccessViewProviders ? injectablesStart : injectablesStart + cptViewProvidersCount; canAccessViewProviders ? injectablesStart : injectablesStart + cptViewProvidersCount;
for (let i = startingIndex; i < directiveEnd; i++) { // When the host special case applies, only the viewProviders and the component are visible
const endIndex = isHostSpecialCase ? injectablesStart + cptViewProvidersCount : directiveEnd;
for (let i = startingIndex; i < endIndex; i++) {
const providerTokenOrDef = tInjectables[i] as InjectionToken<any>| Type<any>| DirectiveDef<any>; const providerTokenOrDef = tInjectables[i] as InjectionToken<any>| Type<any>| DirectiveDef<any>;
if (i < directivesStart && token === providerTokenOrDef || if (i < directivesStart && token === providerTokenOrDef ||
i >= directivesStart && (providerTokenOrDef as DirectiveDef<any>).type === token) { i >= directivesStart && (providerTokenOrDef as DirectiveDef<any>).type === token) {
return i; return i;
} }
} }
if (isHostSpecialCase) {
const dirDef = tInjectables[directivesStart] as DirectiveDef<any>;
if (dirDef && isComponentDef(dirDef) && dirDef.type === token) {
return directivesStart;
}
}
return null; return null;
} }
@ -546,12 +558,8 @@ export function bloomHasToken(
} }
/** Returns true if flags prevent parent injector from being searched for tokens */ /** Returns true if flags prevent parent injector from being searched for tokens */
function shouldSearchParent(flags: InjectFlags, parentLocation: RelativeInjectorLocation): boolean| function shouldSearchParent(flags: InjectFlags, isFirstHostTNode: boolean): boolean|number {
number { return !(flags & InjectFlags.Self) && !(flags & InjectFlags.Host && isFirstHostTNode);
return !(
flags & InjectFlags.Self ||
(flags & InjectFlags.Host &&
((parentLocation as any as number) & RelativeInjectorLocationFlags.AcrossHostBoundary)));
} }
export function injectInjector() { export function injectInjector() {

View File

@ -7,6 +7,7 @@
*/ */
import {Injector} from '../di/injector'; import {Injector} from '../di/injector';
import {assertDefined} from './assert'; import {assertDefined} from './assert';
import {discoverLocalRefs, getComponentAtNodeIndex, getDirectivesAtNodeIndex, getLContext} from './context_discovery'; import {discoverLocalRefs, getComponentAtNodeIndex, getDirectivesAtNodeIndex, getLContext} from './context_discovery';
import {NodeInjector} from './di'; import {NodeInjector} from './di';
@ -14,7 +15,8 @@ import {LContext} from './interfaces/context';
import {DirectiveDef} from './interfaces/definition'; import {DirectiveDef} from './interfaces/definition';
import {TElementNode, TNode, TNodeProviderIndexes} from './interfaces/node'; import {TElementNode, TNode, TNodeProviderIndexes} from './interfaces/node';
import {CLEANUP, CONTEXT, FLAGS, HOST, LView, LViewFlags, PARENT, RootContext, TVIEW} from './interfaces/view'; import {CLEANUP, CONTEXT, FLAGS, HOST, LView, LViewFlags, PARENT, RootContext, TVIEW} from './interfaces/view';
import {readPatchedLView, stringify} from './util'; import {readElementValue, readPatchedLView, stringify} from './util';
/** /**
@ -327,7 +329,7 @@ export function getListeners(element: Element): Listener[] {
const secondParam = tCleanup[i++]; const secondParam = tCleanup[i++];
if (typeof firstParam === 'string') { if (typeof firstParam === 'string') {
const name: string = firstParam; const name: string = firstParam;
const listenerElement: Element = lView[secondParam]; const listenerElement = readElementValue(lView[secondParam]) as any as Element;
const callback: (value: any) => any = lCleanup[tCleanup[i++]]; const callback: (value: any) => any = lCleanup[tCleanup[i++]];
const useCaptureOrIndx = tCleanup[i++]; const useCaptureOrIndx = tCleanup[i++];
// if useCaptureOrIndx is boolean then report it as is. // if useCaptureOrIndx is boolean then report it as is.

View File

@ -0,0 +1,24 @@
/**
* @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 './ng_dev_mode';
/**
* This file contains reuseable "empty" symbols that can be used as default return values
* in different parts of the rendering code. Because the same symbols are returned, this
* allows for identity checks against these values to be consistently used by the framework
* code.
*/
export const EMPTY_OBJ: {} = {};
export const EMPTY_ARRAY: any[] = [];
// freezing the values prevents any code from accidentally inserting new values in
if (typeof ngDevMode !== 'undefined' && ngDevMode) {
Object.freeze(EMPTY_OBJ);
Object.freeze(EMPTY_ARRAY);
}

View File

@ -8,8 +8,8 @@
import {Type} from '../../type'; import {Type} from '../../type';
import {fillProperties} from '../../util/property'; import {fillProperties} from '../../util/property';
import {EMPTY, EMPTY_ARRAY} from '../definition'; import {EMPTY_ARRAY, EMPTY_OBJ} from '../empty';
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefFeature, RenderFlags} from '../interfaces/definition'; import {ComponentDef, DirectiveDef, DirectiveDefFeature, RenderFlags} from '../interfaces/definition';
@ -178,7 +178,7 @@ export function InheritDefinitionFeature(definition: DirectiveDef<any>| Componen
function maybeUnwrapEmpty<T>(value: T[]): T[]; function maybeUnwrapEmpty<T>(value: T[]): T[];
function maybeUnwrapEmpty<T>(value: T): T; function maybeUnwrapEmpty<T>(value: T): T;
function maybeUnwrapEmpty(value: any): any { function maybeUnwrapEmpty(value: any): any {
if (value === EMPTY) { if (value === EMPTY_OBJ) {
return {}; return {};
} else if (value === EMPTY_ARRAY) { } else if (value === EMPTY_ARRAY) {
return []; return [];

View File

@ -93,9 +93,10 @@ function queueDestroyHooks(def: DirectiveDef<any>, tView: TView, i: number): voi
* *
* @param currentView The current view * @param currentView The current view
*/ */
export function executeInitHooks(currentView: LView, tView: TView, creationMode: boolean): void { export function executeInitHooks(
if (currentView[FLAGS] & LViewFlags.RunInit) { currentView: LView, tView: TView, checkNoChangesMode: boolean): void {
executeHooks(currentView, tView.initHooks, tView.checkHooks, creationMode); if (!checkNoChangesMode && currentView[FLAGS] & LViewFlags.RunInit) {
executeHooks(currentView, tView.initHooks, tView.checkHooks, checkNoChangesMode);
currentView[FLAGS] &= ~LViewFlags.RunInit; currentView[FLAGS] &= ~LViewFlags.RunInit;
} }
} }
@ -106,17 +107,19 @@ export function executeInitHooks(currentView: LView, tView: TView, creationMode:
* @param currentView The current view * @param currentView The current view
*/ */
export function executeHooks( export function executeHooks(
data: LView, allHooks: HookData | null, checkHooks: HookData | null, currentView: LView, allHooks: HookData | null, checkHooks: HookData | null,
creationMode: boolean): void { checkNoChangesMode: boolean): void {
const hooksToCall = creationMode ? allHooks : checkHooks; if (checkNoChangesMode) return;
const hooksToCall = currentView[FLAGS] & LViewFlags.FirstLViewPass ? allHooks : checkHooks;
if (hooksToCall) { if (hooksToCall) {
callHooks(data, hooksToCall); callHooks(currentView, hooksToCall);
} }
} }
/** /**
* Calls lifecycle hooks with their contexts, skipping init hooks if it's not * Calls lifecycle hooks with their contexts, skipping init hooks if it's not
* creation mode. * the first LView pass.
* *
* @param currentView The current view * @param currentView The current view
* @param arr The array in which the hooks are found * @param arr The array in which the hooks are found

Some files were not shown because too many files have changed in this diff Show More