Compare commits
76 Commits
6.0.0-beta
...
5.2x
Author | SHA1 | Date | |
---|---|---|---|
5c89d6bffa | |||
3e6a86fb0a | |||
a7ebf5aadd | |||
b42921bbd2 | |||
722dec11b0 | |||
9e6268ba59 | |||
435f6eecd2 | |||
1c1cbba04b | |||
3b692a55a7 | |||
69a0578e00 | |||
b5ca275590 | |||
519f022b02 | |||
236a9320df | |||
28ac24444f | |||
99909bbf2c | |||
ee60bb5b36 | |||
f6120c09e7 | |||
e2bdef4cf6 | |||
8115edc82f | |||
a8b5465e24 | |||
9ce495b3d8 | |||
d40263447d | |||
31c5c1060a | |||
c9ebd60435 | |||
5a14e2238f | |||
3ceee99e22 | |||
28b23f954c | |||
ad17e5e791 | |||
c30d329faa | |||
991300b86c | |||
7c45db3a19 | |||
67cf11d071 | |||
49082d7ab2 | |||
6b627f67db | |||
5c320b4c2a | |||
ac2b04a5ab | |||
a63b764b54 | |||
2654357c72 | |||
4ec40c6ab2 | |||
80d424798e | |||
7fa2d4b503 | |||
f4845fae12 | |||
f693be3996 | |||
a73d5308e0 | |||
e1bf067090 | |||
884de18cba | |||
dfa2fb95d5 | |||
2639b4bffb | |||
978f97cc59 | |||
f1a063298e | |||
d241532488 | |||
f755db78dc | |||
5dd2b5135d | |||
7ac34e42a0 | |||
029dbf0e18 | |||
bba65e0f41 | |||
a069e08354 | |||
03d93c96a3 | |||
020338230f | |||
a1d86daa71 | |||
7078fbffb4 | |||
0aa9b46b79 | |||
831592c381 | |||
f628797d91 | |||
47f51c2ead | |||
ba9cd5bbc4 | |||
b54ad053f9 | |||
5b8eb9c5c7 | |||
0b683123d2 | |||
363498b6b4 | |||
a1bb56f739 | |||
5bb9fcad3e | |||
f4697f351e | |||
1d571b299d | |||
3a0b5a928c | |||
265ac8a106 |
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
@ -25,7 +25,7 @@ ISSUES MISSING IMPORTANT INFORMATION MAY BE CLOSED WITHOUT INVESTIGATION.
|
||||
## Minimal reproduction of the problem with instructions
|
||||
<!--
|
||||
For bug reports please provide the *STEPS TO REPRODUCE* and if possible a *MINIMAL DEMO* of the problem via
|
||||
https://plnkr.co or similar (you can use this template as a starting point: http://plnkr.co/edit/tpl:AvJOMERrnz94ekVua0u5).
|
||||
https://stackblitz.com or similar (you can use this template as a starting point: https://stackblitz.com/fork/angular-gitter).
|
||||
-->
|
||||
|
||||
## What is the motivation / use case for changing the behavior?
|
||||
|
@ -7,12 +7,11 @@
|
||||
* **bazel:** allow TS to read ambient typings ([#21876](https://github.com/angular/angular/issues/21876)) ([b081dfe](https://github.com/angular/angular/commit/b081dfe)), closes [#21872](https://github.com/angular/angular/issues/21872)
|
||||
* **bazel:** improve error message for missing assets ([#22096](https://github.com/angular/angular/issues/22096)) ([dcf64a0](https://github.com/angular/angular/commit/dcf64a0)), closes [#22095](https://github.com/angular/angular/issues/22095)
|
||||
* **common:** add locale currency values ([#21783](https://github.com/angular/angular/issues/21783)) ([420cc7a](https://github.com/angular/angular/commit/420cc7a)), closes [#20385](https://github.com/angular/angular/issues/20385)
|
||||
* **common:** regenerate i18n locale data files ([#21783](https://github.com/angular/angular/issues/21783)) ([0b2f7d1](https://github.com/angular/angular/commit/0b2f7d1))
|
||||
* **common:** round currencies based on decimal digits in `CurrencyPipe` ([#21783](https://github.com/angular/angular/issues/21783)) ([44154e7](https://github.com/angular/angular/commit/44154e7)), closes [#10189](https://github.com/angular/angular/issues/10189)
|
||||
* **common:** weaken AsyncPipe transform signature ([#22169](https://github.com/angular/angular/issues/22169)) ([be59c3a](https://github.com/angular/angular/commit/be59c3a))
|
||||
* **compiler:** make unary plus operator consistent to JavaScript ([#22154](https://github.com/angular/angular/issues/22154)) ([72f8abd](https://github.com/angular/angular/commit/72f8abd)), closes [#22089](https://github.com/angular/angular/issues/22089)
|
||||
* **core:** add stacktrace in log when error during cleanup component in TestBed ([#22162](https://github.com/angular/angular/issues/22162)) ([16d1700](https://github.com/angular/angular/commit/16d1700))
|
||||
* **core:** ensure initial value of QueryList length ([#21980](https://github.com/angular/angular/issues/21980)) ([#21982](https://github.com/angular/angular/issues/21982)) ([e56de10](https://github.com/angular/angular/commit/e56de10)), closes [/github.com/angular/angular/commit/e54474215629aa6a0e0497fe61bfc896cea532c9#diff-a85dbe0991a7577ea24b49374e9ae90](https://github.com//github.com/angular/angular/commit/e54474215629aa6a0e0497fe61bfc896cea532c9/issues/diff-a85dbe0991a7577ea24b49374e9ae90)
|
||||
* **core:** ensure initial value of QueryList length ([#21980](https://github.com/angular/angular/issues/21980)) ([#21982](https://github.com/angular/angular/issues/21982)) ([e56de10](https://github.com/angular/angular/commit/e56de10)), closes [#21980](https://github.com/angular/angular/issues/21980)
|
||||
* **core:** use appropriate inert document strategy for Firefox & Safari ([#17019](https://github.com/angular/angular/issues/17019)) ([a751649](https://github.com/angular/angular/commit/a751649))
|
||||
* **forms:** make Validators.email support optional controls ([#20869](https://github.com/angular/angular/issues/20869)) ([140e7c0](https://github.com/angular/angular/commit/140e7c0))
|
||||
* **forms:** prevent event emission on enable/disable when emitEvent is false ([#12366](https://github.com/angular/angular/issues/12366)) ([#21018](https://github.com/angular/angular/issues/21018)) ([0bcfae7](https://github.com/angular/angular/commit/0bcfae7))
|
||||
@ -37,13 +36,13 @@
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
(https://github.com/angular/angular/commit/15ff7ba)), closes [#21377](https://github.com/angular/angular/issues/21377)
|
||||
* **aio:** update Firebase redirects and SW routes ([#21763](https://github.com/angular/angular/pull/21763)) ([#22104](https://github.com/angular/angular/pull/22104)) ([15ff7ba](https://github.com/angular/angular/commit/15ff7ba)), closes [#21377](https://github.com/angular/angular/issues/21377)
|
||||
* **bazel:** allow TS to read ambient typings ([#21876](https://github.com/angular/angular/issues/21876)) ([d57fd0b](https://github.com/angular/angular/commit/d57fd0b)), closes [#21872](https://github.com/angular/angular/issues/21872)
|
||||
* **bazel:** improve error message for missing assets ([#22096](https://github.com/angular/angular/issues/22096)) ([c5ec8d9](https://github.com/angular/angular/commit/c5ec8d9)), closes [#22095](https://github.com/angular/angular/issues/22095)
|
||||
* **common:** weaken AsyncPipe transform signature ([#22169](https://github.com/angular/angular/issues/22169)) ([c6bdc83](https://github.com/angular/angular/commit/c6bdc83))
|
||||
* **compiler:** make unary plus operator consistent to JavaScript ([#22154](https://github.com/angular/angular/issues/22154)) ([1b8ea10](https://github.com/angular/angular/commit/1b8ea10)), closes [#22089](https://github.com/angular/angular/issues/22089)
|
||||
* **core:** add stacktrace in log when error during cleanup component in TestBed ([#22162](https://github.com/angular/angular/issues/22162)) ([c4f841f](https://github.com/angular/angular/commit/c4f841f))
|
||||
* **core:** ensure initial value of QueryList length ([#21980](https://github.com/angular/angular/issues/21980)) ([#21982](https://github.com/angular/angular/issues/21982)) ([47b73fd](https://github.com/angular/angular/commit/47b73fd)), closes [/github.com/angular/angular/commit/e54474215629aa6a0e0497fe61bfc896cea532c9#diff-a85dbe0991a7577ea24b49374e9ae90](https://github.com//github.com/angular/angular/commit/e54474215629aa6a0e0497fe61bfc896cea532c9/issues/diff-a85dbe0991a7577ea24b49374e9ae90)
|
||||
* **core:** ensure initial value of QueryList length ([#21980](https://github.com/angular/angular/issues/21980)) ([#21982](https://github.com/angular/angular/issues/21982)) ([47b73fd](https://github.com/angular/angular/commit/47b73fd)), closes [#21980](https://github.com/angular/angular/issues/21980)
|
||||
* **core:** use appropriate inert document strategy for Firefox & Safari ([#17019](https://github.com/angular/angular/issues/17019)) ([47b71d9](https://github.com/angular/angular/commit/47b71d9))
|
||||
* **forms:** prevent event emission on enable/disable when emitEvent is false ([#12366](https://github.com/angular/angular/issues/12366)) ([#21018](https://github.com/angular/angular/issues/21018)) ([56b9591](https://github.com/angular/angular/commit/56b9591))
|
||||
* **language-service:** correct instructions to install the language service ([#22000](https://github.com/angular/angular/issues/22000)) ([0b23573](https://github.com/angular/angular/commit/0b23573))
|
||||
|
@ -51,7 +51,7 @@ and help you to craft the change so that it is successfully accepted into the pr
|
||||
|
||||
Before you submit an issue, please search the issue tracker, maybe an issue for your problem already exists and the discussion might inform you of workarounds readily available.
|
||||
|
||||
We want to fix all the issues as soon as possible, but before fixing a bug we need to reproduce and confirm it. In order to reproduce bugs we will systematically ask you to provide a minimal reproduction scenario using http://plnkr.co. Having a live, reproducible scenario gives us wealth of important information without going back & forth to you with additional questions like:
|
||||
We want to fix all the issues as soon as possible, but before fixing a bug we need to reproduce and confirm it. In order to reproduce bugs, we will systematically ask you to provide a minimal reproduction scenario using http://plnkr.co. Having a live, reproducible scenario gives us a wealth of important information without going back & forth to you with additional questions like:
|
||||
|
||||
- version of Angular used
|
||||
- 3rd-party libraries and their versions
|
||||
@ -61,7 +61,7 @@ A minimal reproduce scenario using http://plnkr.co/ allows us to quickly confirm
|
||||
|
||||
We will be insisting on a minimal reproduce scenario in order to save maintainers time and ultimately be able to fix more bugs. Interestingly, from our experience users often find coding problems themselves while preparing a minimal plunk. We understand that sometimes it might be hard to extract essentials bits of code from a larger code-base but we really need to isolate the problem before we can fix it.
|
||||
|
||||
Unfortunately we are not able to investigate / fix bugs without a minimal reproduction, so if we don't hear back from you we are going to close an issue that don't have enough info to be reproduced.
|
||||
Unfortunately, we are not able to investigate / fix bugs without a minimal reproduction, so if we don't hear back from you we are going to close an issue that doesn't have enough info to be reproduced.
|
||||
|
||||
You can file new issues by filling out our [new issue form](https://github.com/angular/angular/issues/new).
|
||||
|
||||
@ -173,12 +173,12 @@ The **header** is mandatory and the **scope** of the header is optional.
|
||||
Any line of the commit message cannot be longer 100 characters! This allows the message to be easier
|
||||
to read on GitHub as well as in various git tools.
|
||||
|
||||
Footer should contain a [closing reference to an issue](https://help.github.com/articles/closing-issues-via-commit-messages/) if any.
|
||||
The footer should contain a [closing reference to an issue](https://help.github.com/articles/closing-issues-via-commit-messages/) if any.
|
||||
|
||||
Samples: (even more [samples](https://github.com/angular/angular/commits/master))
|
||||
|
||||
```
|
||||
docs(changelog): update change log to beta.5
|
||||
docs(changelog): update changelog to beta.5
|
||||
```
|
||||
```
|
||||
fix(release): need to depend on latest rxjs and zone.js
|
||||
@ -203,7 +203,7 @@ Must be one of the following:
|
||||
* **test**: Adding missing tests or correcting existing tests
|
||||
|
||||
### Scope
|
||||
The scope should be the name of the npm package affected (as perceived by person reading changelog generated from commit messages.
|
||||
The scope should be the name of the npm package affected (as perceived by the person reading the changelog generated from commit messages.
|
||||
|
||||
The following is the list of supported scopes:
|
||||
|
||||
@ -232,10 +232,10 @@ There are currently a few exceptions to the "use package name" rule:
|
||||
* none/empty string: useful for `style`, `test` and `refactor` changes that are done across all packages (e.g. `style: add missing semicolons`)
|
||||
|
||||
### Subject
|
||||
The subject contains succinct description of the change:
|
||||
The subject contains a succinct description of the change:
|
||||
|
||||
* use the imperative, present tense: "change" not "changed" nor "changes"
|
||||
* don't capitalize first letter
|
||||
* don't capitalize the first letter
|
||||
* no dot (.) at the end
|
||||
|
||||
### Body
|
||||
@ -266,7 +266,7 @@ changes to be accepted, the CLA must be signed. It's a quick process, we promise
|
||||
* https://help.github.com/articles/setting-your-commit-email-address-in-git/
|
||||
* https://stackoverflow.com/questions/37245303/what-does-usera-committed-with-userb-13-days-ago-on-github-mean
|
||||
* https://help.github.com/articles/about-commit-email-addresses/
|
||||
* https://help.github.com/articles/blocking-command-line-pushes-that-expose-your-personal-email-address/
|
||||
* https://help.github.com/articles/blocking-command-line-pushes-that-expose-your-personal-email-address/
|
||||
|
||||
Note that if you have more than one Git identity, it is important to verify that you are logged in with the same ID with which you signed the CLA, before you commit changes. If not, your PR will fail the CLA check.
|
||||
|
||||
|
16
WORKSPACE
16
WORKSPACE
@ -1,10 +1,14 @@
|
||||
workspace(name = "angular")
|
||||
|
||||
# Using a pre-release snapshot to pick up a commit that makes all nodejs_binary
|
||||
# programs produce source-mapped stack traces.
|
||||
RULES_NODEJS_VERSION = "926349cea4cd360afcd5647ccdd09d2d2fb471aa"
|
||||
|
||||
http_archive(
|
||||
name = "build_bazel_rules_nodejs",
|
||||
url = "https://github.com/bazelbuild/rules_nodejs/archive/0.4.1.zip",
|
||||
strip_prefix = "rules_nodejs-0.4.1",
|
||||
sha256 = "e9bc013417272b17f302dc169ad597f05561bb277451f010043f4da493417607",
|
||||
url = "https://github.com/bazelbuild/rules_nodejs/archive/%s.zip" % RULES_NODEJS_VERSION,
|
||||
strip_prefix = "rules_nodejs-%s" % RULES_NODEJS_VERSION,
|
||||
sha256 = "5ba3c8c209078c2e3f0c6aa4abd01a1a561f92a5bfda04e25604af5f4734d69d",
|
||||
)
|
||||
|
||||
load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories")
|
||||
@ -12,10 +16,12 @@ load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_reposi
|
||||
check_bazel_version("0.9.0")
|
||||
node_repositories(package_json = ["//:package.json"])
|
||||
|
||||
RULES_TYPESCRIPT_VERSION = "0.10.1"
|
||||
|
||||
http_archive(
|
||||
name = "build_bazel_rules_typescript",
|
||||
url = "https://github.com/bazelbuild/rules_typescript/archive/0.10.1.zip",
|
||||
strip_prefix = "rules_typescript-0.10.1",
|
||||
url = "https://github.com/bazelbuild/rules_typescript/archive/%s.zip" % RULES_TYPESCRIPT_VERSION,
|
||||
strip_prefix = "rules_typescript-%s" % RULES_TYPESCRIPT_VERSION,
|
||||
sha256 = "a2c81776a4a492ff9f878f9705639f5647bef345f7f3e1da09c9eeb8dec80485",
|
||||
)
|
||||
|
||||
|
@ -3,6 +3,6 @@
|
||||
<h2>Messages</h2>
|
||||
<button class="clear"
|
||||
(click)="messageService.clear()">clear</button>
|
||||
<div *ngFor='let message of messageService.messages'> {{message}} </div>
|
||||
<div *ngFor="let message of messageService.messages"> {{message}} </div>
|
||||
|
||||
</div>
|
||||
|
@ -190,6 +190,29 @@ describe('Tutorial part 6', () => {
|
||||
const maxId = heroesBefore[heroesBefore.length - 1].id;
|
||||
expect(heroesAfter[numHeroes]).toEqual({id: maxId + 1, name: newHeroName});
|
||||
});
|
||||
|
||||
it('displays correctly styled buttons', async () => {
|
||||
element.all(by.buttonText('x')).then(buttons => {
|
||||
for (const button of buttons) {
|
||||
// Inherited styles from styles.css
|
||||
expect(button.getCssValue('font-family')).toBe('Arial');
|
||||
expect(button.getCssValue('border')).toContain('none');
|
||||
expect(button.getCssValue('padding')).toBe('5px 10px');
|
||||
expect(button.getCssValue('border-radius')).toBe('4px');
|
||||
// Styles defined in heroes.component.css
|
||||
expect(button.getCssValue('left')).toBe('194px');
|
||||
expect(button.getCssValue('top')).toBe('-32px');
|
||||
}
|
||||
});
|
||||
|
||||
const addButton = element(by.buttonText('add'));
|
||||
// Inherited styles from styles.css
|
||||
expect(addButton.getCssValue('font-family')).toBe('Arial');
|
||||
expect(addButton.getCssValue('border')).toContain('none');
|
||||
expect(addButton.getCssValue('padding')).toBe('5px 10px');
|
||||
expect(addButton.getCssValue('border-radius')).toBe('4px');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Progressive hero search', () => {
|
||||
|
@ -51,7 +51,7 @@
|
||||
}
|
||||
|
||||
/* #docregion additions */
|
||||
.button {
|
||||
button {
|
||||
background-color: #eee;
|
||||
border: none;
|
||||
padding: 5px 10px;
|
||||
|
@ -92,7 +92,7 @@ You can control your app compilation by providing template compiler options in t
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"fullTemplateTypeCheck": true,
|
||||
"preserveWhitespaces": false,
|
||||
"preserveWhitespaces": true,
|
||||
...
|
||||
}
|
||||
}
|
||||
@ -234,9 +234,7 @@ done manually.
|
||||
### *preserveWhitespaces*
|
||||
|
||||
This option tells the compiler whether to remove blank text nodes from compiled templates.
|
||||
This option is `true` by default.
|
||||
|
||||
*Note*: It is recommended to set this explicitly to `false` as it emits smaller template factory modules and might be set to `false` by default in the future.
|
||||
As of v6, this option is `false` by default, which results in smaller emitted template factory modules.
|
||||
|
||||
### *allowEmptyCodegenFiles*
|
||||
|
||||
|
@ -140,6 +140,11 @@ is available to <code>declarations</code> of this module.</p>
|
||||
<td><p>Binds the presence of CSS classes on the element to the truthiness of the associated map values. The right-hand expression should return {class-name: true/false} map.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><div <b>[ngStyle]</b>="{'property': 'value'}"></code><br><code><div <b>[ngStyle]</b>="dynamicStyles()"></code></td>
|
||||
<td><p>Allows you to assign styles to an HTML element using CSS. You can use CSS directly, as in the first example, or you can call a method from the component.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
|
||||
<table class="is-full-width is-fixed-layout">
|
||||
|
@ -512,10 +512,6 @@ You rarely access Angular feature modules directly. You usually import them from
|
||||
|
||||
## NgModule
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
Helps you organize an application into cohesive blocks of functionality.
|
||||
An NgModule identifies the components, directives, and pipes that the application uses along with the list of external NgModules that the application needs, such as `FormsModule`.
|
||||
|
||||
@ -526,7 +522,7 @@ For details and examples, see [NgModules](guide/ngmodules) and the
|
||||
related files in that section.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
{@a O}
|
||||
|
||||
@ -631,13 +627,10 @@ For more information, see the [Routing & Navigation](guide/router) page.
|
||||
|
||||
## Router module
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
A separate [NgModule](guide/glossary#ngmodule) that provides the necessary service providers and directives for navigating through application views.
|
||||
|
||||
For more information, see the [Routing & Navigation](guide/router) page.
|
||||
|
||||
</div>
|
||||
|
||||
## Routing component
|
||||
|
||||
|
@ -8,7 +8,7 @@ checks it when its data-bound properties change, and destroys it before removing
|
||||
Angular offers **lifecycle hooks**
|
||||
that provide visibility into these key life moments and the ability to act when they occur.
|
||||
|
||||
A directive has the same set of lifecycle hooks, minus the hooks that are specific to component content and views.
|
||||
A directive has the same set of lifecycle hooks.
|
||||
|
||||
{@a hooks-overview}
|
||||
|
||||
@ -25,7 +25,7 @@ that Angular calls shortly after creating the component:
|
||||
|
||||
<code-example path="lifecycle-hooks/src/app/peek-a-boo.component.ts" region="ngOnInit" title="peek-a-boo.component.ts (excerpt)" linenums="false"></code-example>
|
||||
|
||||
No directive or component will implement all of the lifecycle hooks and some of the hooks only make sense for components.
|
||||
No directive or component will implement all of the lifecycle hooks.
|
||||
Angular only calls a directive/component hook method *if it is defined*.
|
||||
|
||||
{@a hooks-purpose-timing}
|
||||
@ -86,12 +86,10 @@ calls the lifecycle hook methods in the following sequence at specific moments:
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Respond after Angular projects external content into the component's view.
|
||||
Respond after Angular projects external content into the component's view / the view that a directive is in.
|
||||
|
||||
Called _once_ after the first `ngDoCheck()`.
|
||||
|
||||
_A component-only hook_.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr style='vertical-align:top'>
|
||||
@ -100,12 +98,10 @@ calls the lifecycle hook methods in the following sequence at specific moments:
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Respond after Angular checks the content projected into the component.
|
||||
Respond after Angular checks the content projected into the directive/component.
|
||||
|
||||
Called after the `ngAfterContentInit()` and every subsequent `ngDoCheck()`.
|
||||
|
||||
_A component-only hook_.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr style='vertical-align:top'>
|
||||
@ -114,12 +110,10 @@ calls the lifecycle hook methods in the following sequence at specific moments:
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Respond after Angular initializes the component's views and child views.
|
||||
Respond after Angular initializes the component's views and child views / the view that a directive is in.
|
||||
|
||||
Called _once_ after the first `ngAfterContentChecked()`.
|
||||
|
||||
_A component-only hook_.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr style='vertical-align:top'>
|
||||
@ -128,12 +122,10 @@ calls the lifecycle hook methods in the following sequence at specific moments:
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Respond after Angular checks the component's views and child views.
|
||||
Respond after Angular checks the component's views and child views / the view that a directive is in.
|
||||
|
||||
Called after the `ngAfterViewInit` and every subsequent `ngAfterContentChecked()`.
|
||||
|
||||
_A component-only hook_.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr style='vertical-align:top'>
|
||||
|
@ -418,7 +418,7 @@ To see the form model, add the following line after the
|
||||
closing `form` tag in the `hero-detail.component.html`:
|
||||
|
||||
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-3.component.html" region="form-value-json" title="src/app/hero-detail/hero-detail.component.html" linenums="false">
|
||||
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-2.component.html" region="form-value-json" title="src/app/hero-detail/hero-detail.component.html" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
BIN
aio/content/images/marketing/home/ng-conf.png
Normal file
BIN
aio/content/images/marketing/home/ng-conf.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.5 KiB |
@ -1,9 +1,9 @@
|
||||
[
|
||||
{
|
||||
"startDate": "2018-01-01",
|
||||
"endDate": "2018-02-02",
|
||||
"message": "Join us in Atlanta for ngATL<br/>Jan 30 - Feb 2, 2018",
|
||||
"imageUrl": "generated/images/marketing/home/ng-atl.png",
|
||||
"linkUrl": "http://ng-atl.org/"
|
||||
"startDate": "2018-02-14",
|
||||
"endDate": "2018-04-22",
|
||||
"message": "Join us for ng-conf<br/>Apr 18th-20th, 2018",
|
||||
"imageUrl": "generated/images/marketing/home/ng-conf.png",
|
||||
"linkUrl": "http://ng-conf.org/"
|
||||
}
|
||||
]
|
||||
]
|
||||
|
@ -20,7 +20,7 @@
|
||||
|
||||
<div class="feature">
|
||||
<div class="feature-title">Native</div>
|
||||
<p class="text-body">Build native mobile apps with strategies from Ionic Framework, NativeScript, and React Native.</p>
|
||||
<p class="text-body">Build native mobile apps with strategies from Cordova, Ionic, or NativeScript.</p>
|
||||
</div>
|
||||
|
||||
<div class="feature">
|
||||
|
@ -247,6 +247,13 @@
|
||||
"rev": true,
|
||||
"title": "Angular Playground",
|
||||
"url": "http://www.angularplayground.it/"
|
||||
},
|
||||
"nx": {
|
||||
"desc": "Nx (Nrwl Extensions for Angular) is an open source toolkit built on top of Angular CLI to help enterprise teams develop Angular at scale.",
|
||||
"rev": true,
|
||||
"title": "Nx",
|
||||
"logo": "https://nrwl.io/assets/nx-logo.png",
|
||||
"url": "https://nrwl.io/nx"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -444,6 +451,13 @@
|
||||
"rev": true,
|
||||
"title": "Essential Angular",
|
||||
"url": "https://gumroad.com/l/essential_angular"
|
||||
},
|
||||
"angular-buch": {
|
||||
"desc": "The first German book about Angular. It gives you a detailed practical overview of the key concepts of the platform. In each chapter a sample application is built upon with a new Angular topic. All sources are available on GitHub.",
|
||||
"logo": "https://angular-buch.com/assets/img/brand.svg",
|
||||
"rev": true,
|
||||
"title": "Angular-Buch (German)",
|
||||
"url": "https://angular-buch.com/"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -488,6 +502,13 @@
|
||||
"title": "CodeSchool: Accelerating Through Angular",
|
||||
"url": "https://www.codeschool.com/courses/accelerating-through-angular-2"
|
||||
},
|
||||
"angular-playbook": {
|
||||
"desc": "Learn advanced Angular best practices for enterprise teams, created by Nrwl.io.",
|
||||
"logo": "https://nrwl.io/assets/logo_footer_2x.png",
|
||||
"rev": true,
|
||||
"title": "Angular Enterprise Playbook",
|
||||
"url": "https://angularplaybook.com"
|
||||
},
|
||||
"a2b": {
|
||||
"desc": "Hundreds of Angular courses for all skill levels",
|
||||
"logo": "",
|
||||
|
@ -29,7 +29,7 @@ and annotate the component class with `@Component`.
|
||||
|
||||
The CLI generated three metadata properties:
|
||||
|
||||
1. `selector`— the components CSS element selector
|
||||
1. `selector`— the component's CSS element selector
|
||||
1. `templateUrl`— the location of the component's template file.
|
||||
1. `styleUrls`— the location of the component's private CSS styles.
|
||||
|
||||
|
@ -96,7 +96,7 @@ Bind the `HeroesComponent.selectedHero` to the element's `hero` property like th
|
||||
`[hero]="selectedHero"` is an Angular [property binding](guide/template-syntax#property-binding).
|
||||
|
||||
It's a _one way_ data binding from
|
||||
the `selectedHero` property of the `HeroComponent` to the `hero` property of the target element, which maps to the `hero` property of the `HeroDetailComponent`.
|
||||
the `selectedHero` property of the `HeroesComponent` to the `hero` property of the target element, which maps to the `hero` property of the `HeroDetailComponent`.
|
||||
|
||||
Now when the user clicks a hero in the list, the `selectedHero` changes.
|
||||
When the `selectedHero` changes, the _property binding_ updates `hero`
|
||||
|
@ -4,7 +4,7 @@
|
||||
<mat-progress-bar mode="indeterminate" color="warn"></mat-progress-bar>
|
||||
</div>
|
||||
|
||||
<mat-toolbar color="primary" class="app-toolbar" [class.transitioning]="isTransitioning">
|
||||
<mat-toolbar color="primary" class="app-toolbar no-print" [class.transitioning]="isTransitioning">
|
||||
<mat-toolbar-row class="notification-container">
|
||||
<aio-notification
|
||||
icon="insert_comment"
|
||||
@ -29,6 +29,7 @@
|
||||
<aio-search-box class="search-container" #searchBox (onSearch)="doSearch($event)" (onFocus)="doSearch($event)"></aio-search-box>
|
||||
</mat-toolbar-row>
|
||||
</mat-toolbar>
|
||||
|
||||
<aio-search-results #searchResultsView *ngIf="showSearchResults" [searchResults]="searchResults | async" (resultSelected)="hideSearchResults()"></aio-search-results>
|
||||
|
||||
<mat-sidenav-container class="sidenav-container" [class.starting]="isStarting" [class.has-floating-toc]="hasFloatingToc" role="main">
|
||||
@ -56,11 +57,11 @@
|
||||
|
||||
</mat-sidenav-container>
|
||||
|
||||
<div *ngIf="hasFloatingToc" class="toc-container" [style.max-height.px]="tocMaxHeight" (mousewheel)="restrainScrolling($event)">
|
||||
<div *ngIf="hasFloatingToc" class="toc-container no-print" [style.max-height.px]="tocMaxHeight" (mousewheel)="restrainScrolling($event)">
|
||||
<aio-toc></aio-toc>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<footer class="no-print">
|
||||
<aio-footer [nodes]="footerNodes" [versionInfo]="versionInfo" ></aio-footer>
|
||||
</footer>
|
||||
|
||||
|
@ -29,7 +29,7 @@ const defaultLineNumsCount = 10; // by default, show linenums over this number
|
||||
selector: 'aio-code',
|
||||
template: `
|
||||
<pre class="prettyprint lang-{{language}}">
|
||||
<button *ngIf="!hideCopy" class="material-icons copy-button"
|
||||
<button *ngIf="!hideCopy" class="material-icons copy-button no-print"
|
||||
title="Copy code snippet"
|
||||
[attr.aria-label]="ariaLabel"
|
||||
(click)="doCopy()">
|
||||
|
@ -1,4 +1,4 @@
|
||||
<div *ngIf="type !== 'None'" class="toc-inner" [class.collapsed]="isCollapsed">
|
||||
<div *ngIf="type !== 'None'" class="toc-inner no-print" [class.collapsed]="isCollapsed">
|
||||
|
||||
<div *ngIf="type === 'EmbeddedSimple'" class="toc-heading embedded">
|
||||
Contents
|
||||
|
@ -12,3 +12,4 @@
|
||||
@import 'sidenav';
|
||||
@import 'table-of-contents';
|
||||
@import 'top-menu';
|
||||
@import 'print-layout';
|
@ -125,6 +125,7 @@ section#intro {
|
||||
.announcement-bar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
max-width: 50vw;
|
||||
|
110
aio/src/styles/1-layouts/_print-layout.scss
Normal file
110
aio/src/styles/1-layouts/_print-layout.scss
Normal file
@ -0,0 +1,110 @@
|
||||
@media print {
|
||||
|
||||
// General Adjustments
|
||||
* {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
h1 {
|
||||
height: 40px !important;
|
||||
color: $darkgray !important;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
ul, ol, img, code-example, table, tr, .alert, .l-subsection, .feature {
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
table tbody tr:last-child td {
|
||||
border-bottom: 1px solid $lightgray !important;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
p {
|
||||
widows: 4;
|
||||
}
|
||||
|
||||
p > code, li > code, table code {
|
||||
color: $blue !important;
|
||||
}
|
||||
|
||||
// No Print Class
|
||||
.no-print {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
// Custom No Print for Sidenav Menu
|
||||
mat-sidenav.sidenav.mat-sidenav {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
// Custom No Print Element Adjustments
|
||||
.mat-sidenav-content {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
mat-sidenav-container.sidenav-container {
|
||||
min-width: 100vw;
|
||||
}
|
||||
|
||||
.sidenav-content {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.filetree {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
aio-code code {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
code-example {
|
||||
pre.lang-bash code span {
|
||||
color: $mediumgray !important;
|
||||
}
|
||||
|
||||
pre.lang-sh code span {
|
||||
color: $darkgray !important;
|
||||
}
|
||||
|
||||
header {
|
||||
border: 0.5px solid $lightgray;
|
||||
color: $darkgray;
|
||||
}
|
||||
}
|
||||
|
||||
.content code {
|
||||
border: 0.5px solid $lightgray;
|
||||
}
|
||||
|
||||
.mat-tab-labels {
|
||||
div.mat-tab-label {
|
||||
&:not(.mat-tab-label-active) span {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
&.mat-tab-label-active span {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.api-header label {
|
||||
color: $darkgray !important;
|
||||
font-weight: bold !important;
|
||||
margin: 2px !important;
|
||||
padding: 0 !important;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.feature-section img {
|
||||
max-width: 70px !important;
|
||||
}
|
||||
}
|
@ -85,7 +85,6 @@ aio-code pre {
|
||||
|
||||
|
||||
.copy-button {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
right: -32px;
|
||||
|
@ -5,8 +5,15 @@ const {generateDocs} = require('./index.js');
|
||||
const { DOCS_OUTPUT_PATH } = require('../config');
|
||||
|
||||
describe('authors-package (integration tests)', () => {
|
||||
let originalJasmineTimeout;
|
||||
let files;
|
||||
|
||||
beforeAll(() => {
|
||||
originalJasmineTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
|
||||
});
|
||||
afterAll(() => jasmine.DEFAULT_TIMEOUT_INTERVAL = originalJasmineTimeout);
|
||||
|
||||
beforeEach(() => {
|
||||
files = [];
|
||||
spyOn(fs, 'writeFile').and.callFake((file, content, callback) => {
|
||||
@ -24,7 +31,7 @@ describe('authors-package (integration tests)', () => {
|
||||
expect(files).toContain(resolve(DOCS_OUTPUT_PATH, '../resources.json'));
|
||||
done();
|
||||
});
|
||||
}, 4000);
|
||||
});
|
||||
|
||||
it('should generate tutorial docs if the "fileChanged" is a tutorial doc', (done) => {
|
||||
generateDocs('aio/content/tutorial/toh-pt5.md', { silent: true }).then(() => {
|
||||
@ -32,7 +39,7 @@ describe('authors-package (integration tests)', () => {
|
||||
expect(files).toContain(resolve(DOCS_OUTPUT_PATH, 'tutorial/toh-pt5.json'));
|
||||
done();
|
||||
});
|
||||
}, 4000);
|
||||
});
|
||||
|
||||
it('should generate tutorial docs if the "fileChanged" is the tutorial index', (done) => {
|
||||
generateDocs('aio/content/tutorial/index.md', { silent: true }).then(() => {
|
||||
@ -40,7 +47,7 @@ describe('authors-package (integration tests)', () => {
|
||||
expect(files).toContain(resolve(DOCS_OUTPUT_PATH, 'tutorial.json'));
|
||||
done();
|
||||
});
|
||||
}, 4000);
|
||||
});
|
||||
|
||||
it('should generate tutorial docs if the "fileChanged" is a tutorial example', (done) => {
|
||||
generateDocs('aio/content/examples/toh-pt3/app/app.component.1.html', { silent: true }).then(() => {
|
||||
@ -48,7 +55,7 @@ describe('authors-package (integration tests)', () => {
|
||||
expect(files).toContain(resolve(DOCS_OUTPUT_PATH, 'tutorial/toh-pt3.json'));
|
||||
done();
|
||||
});
|
||||
}, 4000);
|
||||
});
|
||||
|
||||
it('should generate guide doc if the "fileChanged" is a guide doc', (done) => {
|
||||
generateDocs('aio/content/guide/architecture.md', { silent: true }).then(() => {
|
||||
@ -56,7 +63,7 @@ describe('authors-package (integration tests)', () => {
|
||||
expect(files).toContain(resolve(DOCS_OUTPUT_PATH, 'guide/architecture.json'));
|
||||
done();
|
||||
});
|
||||
}, 4000);
|
||||
});
|
||||
|
||||
it('should generate guide doc if the "fileChanged" is a guide example', (done) => {
|
||||
generateDocs('aio/content/examples/architecture/src/app/app.module.ts', { silent: true }).then(() => {
|
||||
@ -64,7 +71,7 @@ describe('authors-package (integration tests)', () => {
|
||||
expect(files).toContain(resolve(DOCS_OUTPUT_PATH, 'guide/architecture.json'));
|
||||
done();
|
||||
});
|
||||
}, 4000);
|
||||
});
|
||||
|
||||
it('should generate API doc if the "fileChanged" is an API doc', (done) => {
|
||||
generateDocs('packages/forms/src/form_builder.ts', { silent: true }).then(() => {
|
||||
@ -81,4 +88,4 @@ describe('authors-package (integration tests)', () => {
|
||||
done();
|
||||
});
|
||||
}, 16000);
|
||||
});
|
||||
});
|
||||
|
@ -17,6 +17,14 @@ you run the first build.
|
||||
|
||||
[install]: https://bazel.build/versions/master/docs/install.html
|
||||
|
||||
### Installation of ibazel
|
||||
|
||||
Install interactive bazel runner / fs watcher via:
|
||||
|
||||
```
|
||||
yarn global add @bazel/ibazel
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
The `WORKSPACE` file indicates that our root directory is a
|
||||
@ -112,6 +120,33 @@ Apple+Shift+D on Mac) and click on the green play icon next to the configuration
|
||||
- Open chrome at: [http://localhost:9876/debug.html](http://localhost:9876/debug.html)
|
||||
- Open chrome inspector
|
||||
|
||||
### Debugging Bazel rules
|
||||
|
||||
Open `external` directory which contains everything that bazel downloaded while executing the workspace file:
|
||||
```sh
|
||||
open $(bazel info output_base)/external
|
||||
```
|
||||
|
||||
See subcommands that bazel executes (helpful for debugging):
|
||||
```sh
|
||||
bazel build //packages/core:package -s
|
||||
```
|
||||
|
||||
To debug nodejs_binary executable paths uncomment `find . -name rollup 1>&2` (~ line 96) in
|
||||
```sh
|
||||
open $(bazel info output_base)/external/build_bazel_rules_nodejs/internal/node_launcher.sh
|
||||
```
|
||||
|
||||
## Stamping
|
||||
|
||||
Bazel supports the ability to include non-hermetic information from the version control system in built artifacts. This is called stamping.
|
||||
You can see an overview at https://www.kchodorow.com/blog/2017/03/27/stamping-your-builds/
|
||||
In our repo, here is how it's configured:
|
||||
|
||||
1) In `tools/bazel_stamp_vars.sh` we run the `git` commands to generate our versioning info.
|
||||
1) In `tools/bazel.rc` we register this script as the value for the `workspace_status_command` flag. Bazel will run the script when it needs to stamp a binary.
|
||||
1) In `tools/BUILD.bazel` we have a target `stamp_data` with the special `stamp=1` attribute, which requests that Bazel run the `workspace_status_command`. The result is written to a text file that can be used as an input to other rules.
|
||||
|
||||
## Remote cache
|
||||
|
||||
Bazel supports fetching action results from a cache, allowing a clean build to pick up artifacts from prior builds.
|
||||
@ -159,10 +194,13 @@ source: https://github.com/bazelbuild/bazel/issues/4603
|
||||
|
||||
If VSCode is not the root cause, you might try:
|
||||
|
||||
- Quit VSCode (make sure no VSCode is running).
|
||||
|
||||
```
|
||||
bazel clean --expunge
|
||||
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
|
||||
sudo xcodebuild -license
|
||||
bazel build //packages/core # Run a build outside VSCode to pre-build the xcode; then safe to run VSCode
|
||||
```
|
||||
|
||||
Source: https://stackoverflow.com/questions/45276830/xcode-version-must-be-specified-to-use-an-apple-crosstool
|
||||
|
@ -5,25 +5,25 @@ The following are canned responses that the Angular team should use to close iss
|
||||
Since GitHub currently doesn't allow us to have a repository-wide or organization-wide list of [saved replies](https://help.github.com/articles/working-with-saved-replies/), these replies need to be maintained by individual team members. Since the responses can be modified in the future, all responses are versioned to simplify the process of keeping the responses up to date.
|
||||
|
||||
|
||||
## Angular: Already Fixed (v1)
|
||||
## Angular: Already Fixed (v2)
|
||||
```
|
||||
Thanks for reporting this issue. Luckily it has already been fixed in one of the recent releases. Please update to the most recent version to resolve the problem.
|
||||
|
||||
If after upgrade the problem still exists in your application please open a new issue and provide a plunker reproducing the problem and describing the difference between the expected and current behavior. You can use this plunker template: http://plnkr.co/edit/tpl:AvJOMERrnz94ekVua0u5?p=catalogue
|
||||
If after upgrade the problem still exists in your application please open a new issue and provide a StackBlitz reproducing the problem and describing the difference between the expected and current behavior. You can use this StackBlitz template: https://stackblitz.com/fork/angular-gitter
|
||||
```
|
||||
|
||||
## Angular: Don't Understand (v1)
|
||||
## Angular: Don't Understand (v2)
|
||||
```
|
||||
I'm sorry but we don't understand the problem you are reporting.
|
||||
|
||||
If the problem still exists please open a new issue and provide a plunker reproducing the problem and describing the difference between the expected and current behavior. You can use this plunker template: http://plnkr.co/edit/tpl:AvJOMERrnz94ekVua0u5?p=catalogue
|
||||
If the problem still exists please open a new issue and provide a StackBlitz reproducing the problem and describing the difference between the expected and current behavior. You can use this StackBlitz template: https://stackblitz.com/fork/angular-gitter
|
||||
```
|
||||
|
||||
## Angular: Plunker Needed (v1)
|
||||
## Angular: StackBlitz Needed (v1)
|
||||
```
|
||||
I'm sorry but reported issues require a plunker reproducing the problem.
|
||||
I'm sorry but reported issues require a StackBlitz reproducing the problem.
|
||||
|
||||
If this issue persists, please create a plunker using this template and describe the difference between the expected and current behavior and create a new issue: http://plnkr.co/edit/tpl:AvJOMERrnz94ekVua0u5?p=catalogue
|
||||
If this issue persists, please create a StackBlitz using this template and describe the difference between the expected and current behavior and create a new issue: https://stackblitz.com/fork/angular-gitter
|
||||
```
|
||||
|
||||
## Angular: Duplicate (v1)
|
||||
|
@ -3,7 +3,7 @@
|
||||
"master": {
|
||||
"uncompressed": {
|
||||
"inline": 1447,
|
||||
"main": 159944,
|
||||
"main": 155112,
|
||||
"polyfills": 59179
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ɵC as C, ɵE as E, ɵT as T, ɵV as V, ɵb as b, ɵb1 as b1, ɵcR as cR, ɵcr as cr, ɵdefineComponent as defineComponent, ɵdetectChanges as _detectChanges, ɵe as e, ɵp as p, ɵr as r, ɵs as s, ɵt as t, ɵv as v} from '@angular/core';
|
||||
import {ɵC as C, ɵE as E, ɵT as T, ɵV as V, ɵb as b, ɵcR as cR, ɵcr as cr, ɵdefineComponent as defineComponent, ɵdetectChanges as _detectChanges, ɵe as e, ɵi1 as i1, ɵp as p, ɵr as r, ɵs as s, ɵt as t, ɵv as v} from '@angular/core';
|
||||
import {ComponentDef} from '@angular/core/src/render3/interfaces/definition';
|
||||
|
||||
import {TreeNode, buildTree, emptyTree} from '../util';
|
||||
@ -47,7 +47,7 @@ export class TreeComponent {
|
||||
C(3);
|
||||
}
|
||||
s(0, 'background-color', b(ctx.data.depth % 2 ? '' : 'grey'));
|
||||
t(1, b1(' ', ctx.data.value, ' '));
|
||||
t(1, i1(' ', ctx.data.value, ' '));
|
||||
cR(2);
|
||||
{
|
||||
if (ctx.data.left != null) {
|
||||
@ -117,7 +117,7 @@ export function TreeTpl(ctx: TreeNode, cm: boolean) {
|
||||
e();
|
||||
}
|
||||
s(1, 'background-color', b(ctx.depth % 2 ? '' : 'grey'));
|
||||
t(2, b1(' ', ctx.value, ' '));
|
||||
t(2, i1(' ', ctx.value, ' '));
|
||||
cR(3);
|
||||
{
|
||||
if (ctx.left != null) {
|
||||
|
@ -11,3 +11,8 @@ ts_library(
|
||||
name = "types",
|
||||
srcs = glob(["*.ts"]),
|
||||
)
|
||||
|
||||
exports_files([
|
||||
"license-banner.txt",
|
||||
"README.md",
|
||||
])
|
||||
|
@ -1,8 +1,8 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load("//tools:defaults.bzl", "ts_library")
|
||||
load("//tools:defaults.bzl", "ng_module")
|
||||
|
||||
ts_library(
|
||||
ng_module(
|
||||
name = "animations",
|
||||
srcs = glob(
|
||||
[
|
||||
|
@ -1,8 +1,8 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load("//tools:defaults.bzl", "ts_library")
|
||||
load("//tools:defaults.bzl", "ng_module")
|
||||
|
||||
ts_library(
|
||||
ng_module(
|
||||
name = "browser",
|
||||
srcs = glob(
|
||||
[
|
||||
|
@ -59,10 +59,13 @@ export class AnimationTransitionFactory {
|
||||
driver, element, this.ast.animation, enterClassName, leaveClassName, currentStateStyles,
|
||||
nextStateStyles, animationOptions, subInstructions, errors);
|
||||
|
||||
let totalTime = 0;
|
||||
timelines.forEach(tl => { totalTime = Math.max(tl.duration + tl.delay, totalTime); });
|
||||
|
||||
if (errors.length) {
|
||||
return createTransitionInstruction(
|
||||
element, this._triggerName, currentState, nextState, isRemoval, currentStateStyles,
|
||||
nextStateStyles, [], [], preStyleMap, postStyleMap, errors);
|
||||
nextStateStyles, [], [], preStyleMap, postStyleMap, totalTime, errors);
|
||||
}
|
||||
|
||||
timelines.forEach(tl => {
|
||||
@ -81,7 +84,7 @@ export class AnimationTransitionFactory {
|
||||
const queriedElementsList = iteratorToArray(queriedElements.values());
|
||||
return createTransitionInstruction(
|
||||
element, this._triggerName, currentState, nextState, isRemoval, currentStateStyles,
|
||||
nextStateStyles, timelines, queriedElementsList, preStyleMap, postStyleMap);
|
||||
nextStateStyles, timelines, queriedElementsList, preStyleMap, postStyleMap, totalTime);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@ export interface AnimationTransitionInstruction extends AnimationEngineInstructi
|
||||
queriedElements: any[];
|
||||
preStyleProps: Map<any, {[prop: string]: boolean}>;
|
||||
postStyleProps: Map<any, {[prop: string]: boolean}>;
|
||||
totalTime: number;
|
||||
errors?: any[];
|
||||
}
|
||||
|
||||
@ -29,7 +30,7 @@ export function createTransitionInstruction(
|
||||
isRemovalTransition: boolean, fromStyles: ɵStyleData, toStyles: ɵStyleData,
|
||||
timelines: AnimationTimelineInstruction[], queriedElements: any[],
|
||||
preStyleProps: Map<any, {[prop: string]: boolean}>,
|
||||
postStyleProps: Map<any, {[prop: string]: boolean}>,
|
||||
postStyleProps: Map<any, {[prop: string]: boolean}>, totalTime: number,
|
||||
errors?: any[]): AnimationTransitionInstruction {
|
||||
return {
|
||||
type: AnimationTransitionInstructionType.TransitionAnimation,
|
||||
@ -44,6 +45,7 @@ export function createTransitionInstruction(
|
||||
queriedElements,
|
||||
preStyleProps,
|
||||
postStyleProps,
|
||||
totalTime,
|
||||
errors
|
||||
};
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ export class NoopAnimationDriver implements AnimationDriver {
|
||||
animate(
|
||||
element: any, keyframes: {[key: string]: string | number}[], duration: number, delay: number,
|
||||
easing: string, previousPlayers: any[] = []): AnimationPlayer {
|
||||
return new NoopAnimationPlayer();
|
||||
return new NoopAnimationPlayer(duration, delay);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,23 +75,24 @@ export function listenOnPlayer(
|
||||
callback: (event: any) => any) {
|
||||
switch (eventName) {
|
||||
case 'start':
|
||||
player.onStart(() => callback(event && copyAnimationEvent(event, 'start', player.totalTime)));
|
||||
player.onStart(() => callback(event && copyAnimationEvent(event, 'start', player)));
|
||||
break;
|
||||
case 'done':
|
||||
player.onDone(() => callback(event && copyAnimationEvent(event, 'done', player.totalTime)));
|
||||
player.onDone(() => callback(event && copyAnimationEvent(event, 'done', player)));
|
||||
break;
|
||||
case 'destroy':
|
||||
player.onDestroy(
|
||||
() => callback(event && copyAnimationEvent(event, 'destroy', player.totalTime)));
|
||||
player.onDestroy(() => callback(event && copyAnimationEvent(event, 'destroy', player)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
export function copyAnimationEvent(
|
||||
e: AnimationEvent, phaseName?: string, totalTime?: number): AnimationEvent {
|
||||
e: AnimationEvent, phaseName: string, player: AnimationPlayer): AnimationEvent {
|
||||
const totalTime = player.totalTime;
|
||||
const disabled = (player as any).disabled ? true : false;
|
||||
const event = makeAnimationEvent(
|
||||
e.element, e.triggerName, e.fromState, e.toState, phaseName || e.phaseName,
|
||||
totalTime == undefined ? e.totalTime : totalTime);
|
||||
totalTime == undefined ? e.totalTime : totalTime, disabled);
|
||||
const data = (e as any)['_data'];
|
||||
if (data != null) {
|
||||
(event as any)['_data'] = data;
|
||||
@ -101,8 +102,8 @@ export function copyAnimationEvent(
|
||||
|
||||
export function makeAnimationEvent(
|
||||
element: any, triggerName: string, fromState: string, toState: string, phaseName: string = '',
|
||||
totalTime: number = 0): AnimationEvent {
|
||||
return {element, triggerName, fromState, toState, phaseName, totalTime};
|
||||
totalTime: number = 0, disabled?: boolean): AnimationEvent {
|
||||
return {element, triggerName, fromState, toState, phaseName, totalTime, disabled: !!disabled};
|
||||
}
|
||||
|
||||
export function getOrSetAsInMap(
|
||||
|
@ -1081,6 +1081,8 @@ export class TransitionAnimationEngine {
|
||||
if (subTimelines.has(element)) {
|
||||
if (disabledElementsSet.has(element)) {
|
||||
player.onDestroy(() => setStyles(element, instruction.toStyles));
|
||||
player.disabled = true;
|
||||
player.overrideTotalTime(instruction.totalTime);
|
||||
skippedPlayers.push(player);
|
||||
return;
|
||||
}
|
||||
@ -1311,7 +1313,8 @@ export class TransitionAnimationEngine {
|
||||
|
||||
// FIXME (matsko): make sure to-be-removed animations are removed properly
|
||||
const details = element[REMOVAL_FLAG];
|
||||
if (details && details.removedBeforeQueried) return new NoopAnimationPlayer();
|
||||
if (details && details.removedBeforeQueried)
|
||||
return new NoopAnimationPlayer(timelineInstruction.duration, timelineInstruction.delay);
|
||||
|
||||
const isQueriedElement = element !== rootElement;
|
||||
const previousPlayers =
|
||||
@ -1379,7 +1382,7 @@ export class TransitionAnimationEngine {
|
||||
|
||||
// special case for when an empty transition|definition is provided
|
||||
// ... there is no point in rendering an empty animation
|
||||
return new NoopAnimationPlayer();
|
||||
return new NoopAnimationPlayer(instruction.duration, instruction.delay);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1392,8 +1395,10 @@ export class TransitionAnimationPlayer implements AnimationPlayer {
|
||||
public parentPlayer: AnimationPlayer;
|
||||
|
||||
public markedForDestroy: boolean = false;
|
||||
public disabled = false;
|
||||
|
||||
readonly queued: boolean = true;
|
||||
public readonly totalTime: number = 0;
|
||||
|
||||
constructor(public namespaceId: string, public triggerName: string, public element: any) {}
|
||||
|
||||
@ -1407,15 +1412,18 @@ export class TransitionAnimationPlayer implements AnimationPlayer {
|
||||
});
|
||||
this._queuedCallbacks = {};
|
||||
this._containsRealPlayer = true;
|
||||
this.overrideTotalTime(player.totalTime);
|
||||
(this as{queued: boolean}).queued = false;
|
||||
}
|
||||
|
||||
getRealPlayer() { return this._player; }
|
||||
|
||||
overrideTotalTime(totalTime: number) { (this as any).totalTime = totalTime; }
|
||||
|
||||
syncPlayerEvents(player: AnimationPlayer) {
|
||||
const p = this._player as any;
|
||||
if (p.triggerCallback) {
|
||||
player.onStart(() => p.triggerCallback('start'));
|
||||
player.onStart(() => p.triggerCallback !('start'));
|
||||
}
|
||||
player.onDone(() => this.finish());
|
||||
player.onDestroy(() => this.destroy());
|
||||
@ -1473,8 +1481,6 @@ export class TransitionAnimationPlayer implements AnimationPlayer {
|
||||
|
||||
getPosition(): number { return this.queued ? 0 : this._player.getPosition(); }
|
||||
|
||||
get totalTime(): number { return this._player.totalTime; }
|
||||
|
||||
/* @internal */
|
||||
triggerCallback(phaseName: string): void {
|
||||
const p = this._player as any;
|
||||
|
@ -299,7 +299,8 @@ const DEFAULT_NAMESPACE_ID = 'id';
|
||||
phaseName: 'start',
|
||||
fromState: '123',
|
||||
toState: '456',
|
||||
totalTime: 1234
|
||||
totalTime: 1234,
|
||||
disabled: false
|
||||
});
|
||||
|
||||
capture = null !;
|
||||
@ -313,7 +314,8 @@ const DEFAULT_NAMESPACE_ID = 'id';
|
||||
phaseName: 'done',
|
||||
fromState: '123',
|
||||
toState: '456',
|
||||
totalTime: 1234
|
||||
totalTime: 1234,
|
||||
disabled: false
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,8 +1,8 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load("//tools:defaults.bzl", "ts_library")
|
||||
load("//tools:defaults.bzl", "ng_module")
|
||||
|
||||
ts_library(
|
||||
ng_module(
|
||||
name = "testing",
|
||||
testonly = 1,
|
||||
srcs = glob(["**/*.ts"]),
|
||||
|
@ -58,7 +58,7 @@ export class MockAnimationPlayer extends NoopAnimationPlayer {
|
||||
public element: any, public keyframes: {[key: string]: string | number}[],
|
||||
public duration: number, public delay: number, public easing: string,
|
||||
public previousPlayers: any[]) {
|
||||
super();
|
||||
super(duration, delay);
|
||||
|
||||
if (allowPreviousPlayerStylesMerge(duration, delay)) {
|
||||
previousPlayers.forEach(player => {
|
||||
@ -68,8 +68,6 @@ export class MockAnimationPlayer extends NoopAnimationPlayer {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.totalTime = delay + duration;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
|
@ -44,4 +44,5 @@ export interface AnimationEvent {
|
||||
phaseName: string;
|
||||
element: any;
|
||||
triggerName: string;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
@ -352,6 +352,14 @@ export interface AnimationStaggerMetadata extends AnimationMetadata {
|
||||
elements located in disabled areas of the template and still animate them as it sees fit. This is
|
||||
also the case for when a sub animation is queried by a parent and then later animated using {@link
|
||||
animateChild animateChild}.
|
||||
|
||||
* ### Detecting when an animation is disabled
|
||||
* If a region of the DOM (or the entire application) has its animations disabled, then animation
|
||||
* trigger callbacks will still fire just as normal (only for zero seconds).
|
||||
*
|
||||
* When a trigger callback fires it will provide an instance of an {@link AnimationEvent}. If
|
||||
animations
|
||||
* are disabled then the `.disabled` flag on the event will be true.
|
||||
*
|
||||
* @experimental Animation support is experimental.
|
||||
*/
|
||||
|
@ -33,6 +33,8 @@ export interface AnimationPlayer {
|
||||
beforeDestroy?: () => any;
|
||||
/* @internal */
|
||||
triggerCallback?: (phaseName: string) => void;
|
||||
/* @internal */
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -46,8 +48,8 @@ export class NoopAnimationPlayer implements AnimationPlayer {
|
||||
private _destroyed = false;
|
||||
private _finished = false;
|
||||
public parentPlayer: AnimationPlayer|null = null;
|
||||
public totalTime = 0;
|
||||
constructor() {}
|
||||
public readonly totalTime: number;
|
||||
constructor(duration: number = 0, delay: number = 0) { this.totalTime = duration + delay; }
|
||||
private _onFinish() {
|
||||
if (!this._finished) {
|
||||
this._finished = true;
|
||||
@ -100,4 +102,4 @@ export class NoopAnimationPlayer implements AnimationPlayer {
|
||||
methods.forEach(fn => fn());
|
||||
methods.length = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,6 @@
|
||||
Users should not load files under "/src"
|
||||
"""
|
||||
|
||||
load("//src:ng_module.bzl", _ng_module = "ng_module")
|
||||
load("//packages/bazel/src:ng_module.bzl", _ng_module = "ng_module")
|
||||
|
||||
ng_module = _ng_module
|
||||
|
@ -1,3 +1,5 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary")
|
||||
|
||||
nodejs_binary(
|
||||
@ -8,12 +10,22 @@ nodejs_binary(
|
||||
# additional npm dependencies when we run rollup or uglify.
|
||||
entry_point = "build_bazel_rules_nodejs_rollup_deps/node_modules/rollup/bin/rollup",
|
||||
node_modules = "@build_bazel_rules_nodejs_rollup_deps//:node_modules",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
nodejs_binary(
|
||||
name = "modify_tsconfig",
|
||||
data = ["modify_tsconfig.js"],
|
||||
entry_point = "angular/packages/bazel/src/modify_tsconfig.js",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
nodejs_binary(
|
||||
name = "index_bundler",
|
||||
data = [
|
||||
# BEGIN-INTERNAL
|
||||
# Only needed when compiling within the Angular repo.
|
||||
# Users will get this dependency from node_modules.
|
||||
"//packages/compiler-cli",
|
||||
# END-INTERNAL
|
||||
],
|
||||
entry_point = "@angular/compiler-cli/src/metadata/bundle_index_main.js",
|
||||
)
|
||||
|
@ -12,6 +12,7 @@ load(":rules_typescript.bzl",
|
||||
"compile_ts",
|
||||
"DEPS_ASPECTS",
|
||||
"ts_providers_dict_to_struct",
|
||||
"json_marshal",
|
||||
)
|
||||
|
||||
def _basename_of(ctx, file):
|
||||
@ -90,8 +91,7 @@ def _ngc_tsconfig(ctx, files, srcs, **kwargs):
|
||||
"enableSummariesForJit": True,
|
||||
"fullTemplateTypeCheck": ctx.attr.type_check,
|
||||
# FIXME: wrong place to de-dupe
|
||||
"expectedOut": depset([o.path for o in expected_outs]).to_list(),
|
||||
"preserveWhitespaces": False,
|
||||
"expectedOut": depset([o.path for o in expected_outs]).to_list()
|
||||
}
|
||||
})
|
||||
|
||||
@ -236,6 +236,43 @@ def _ts_expected_outs(ctx, label):
|
||||
_ignored = [label]
|
||||
return _expected_outs(ctx)
|
||||
|
||||
def _write_bundle_index(ctx):
|
||||
basename = "_%s.bundle_index" % ctx.label.name
|
||||
tsconfig_file = ctx.actions.declare_file("%s.tsconfig.json" % basename)
|
||||
metadata_file = ctx.actions.declare_file("%s.metadata.json" % basename)
|
||||
tstyping_file = ctx.actions.declare_file("%s.d.ts" % basename)
|
||||
|
||||
tsconfig = dict(tsc_wrapped_tsconfig(ctx, ctx.files.srcs, ctx.files.srcs), **{
|
||||
"angularCompilerOptions": {
|
||||
"flatModuleOutFile": basename,
|
||||
},
|
||||
})
|
||||
if ctx.attr.module_name:
|
||||
tsconfig["angularCompilerOptions"]["flatModuleId"] = ctx.attr.module_name
|
||||
|
||||
# createBundleIndexHost in bundle_index_host.ts will throw if the "files" has more than one entry.
|
||||
# We don't want to fail() here, however, because not all ng_module's will have the bundle index written.
|
||||
# So we make the assumption that the index.ts file in the highest parent directory is the entry point.
|
||||
index_file = None
|
||||
for f in tsconfig["files"]:
|
||||
if f.endswith("/index.ts"):
|
||||
if not index_file or len(f) < len(index_file):
|
||||
index_file = f
|
||||
tsconfig["files"] = [index_file]
|
||||
|
||||
ctx.actions.write(tsconfig_file, json_marshal(tsconfig))
|
||||
|
||||
outputs = [metadata_file, tstyping_file]
|
||||
|
||||
ctx.action(
|
||||
progress_message = "Producing metadata for bundle %s" % ctx.label.name,
|
||||
executable = ctx.executable._index_bundler,
|
||||
inputs = ctx.files.srcs + [tsconfig_file],
|
||||
outputs = outputs,
|
||||
arguments = ["-p", tsconfig_file.path],
|
||||
)
|
||||
return outputs
|
||||
|
||||
def ng_module_impl(ctx, ts_compile_actions):
|
||||
"""Implementation function for the ng_module rule.
|
||||
|
||||
@ -263,6 +300,13 @@ def ng_module_impl(ctx, ts_compile_actions):
|
||||
}
|
||||
providers["ngc_messages"] = outs.i18n_messages
|
||||
|
||||
# Only produces the flattened "index bundle" metadata when requested by some other rule
|
||||
# and only under Bazel
|
||||
if hasattr(ctx.executable, "_index_bundler"):
|
||||
bundle_index_metadata = _write_bundle_index(ctx)
|
||||
# note, not recursive
|
||||
providers["angular"]["flat_module_metadata"] = depset(bundle_index_metadata)
|
||||
|
||||
return providers
|
||||
|
||||
def _ng_module_impl(ctx):
|
||||
@ -315,6 +359,10 @@ ng_module = rule(
|
||||
"node_modules": attr.label(
|
||||
default = Label("@//:node_modules")
|
||||
),
|
||||
"_index_bundler": attr.label(
|
||||
executable = True,
|
||||
cfg = "host",
|
||||
default = Label("//packages/bazel/src:index_bundler")),
|
||||
}),
|
||||
outputs = COMMON_OUTPUTS,
|
||||
)
|
||||
|
@ -12,8 +12,8 @@ ts_library(
|
||||
visibility = ["//packages/bazel/test/ngc-wrapped:__subpackages__"],
|
||||
deps = [
|
||||
# BEGIN-INTERNAL
|
||||
# Only needed when compiling Angular from sources.
|
||||
# Users with an npm depnedency will get this dependency from node_modules.
|
||||
# Only needed when compiling within the Angular repo.
|
||||
# Users will get this dependency from node_modules.
|
||||
"//packages/compiler-cli",
|
||||
# END-INTERNAL
|
||||
"@build_bazel_rules_typescript//internal/tsc_wrapped",
|
||||
|
@ -10,12 +10,8 @@ ts_library(
|
||||
],
|
||||
tsconfig = ":tsconfig.json",
|
||||
deps = [
|
||||
# BEGIN-INTERNAL
|
||||
# Only needed when compiling within the Angular repo.
|
||||
# Users will get this dependency from node_modules.
|
||||
"//packages/compiler-cli",
|
||||
# END-INTERNAL
|
||||
"//packages/bazel/src/ngc-wrapped:ngc_lib",
|
||||
"//packages/compiler-cli",
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -14,7 +14,7 @@ const globals = {
|
||||
'rxjs/Observer': 'Rx',
|
||||
'rxjs/Subject': 'Rx',
|
||||
|
||||
'rxjs/observable/of': 'Rx.Observable.prototype',
|
||||
'rxjs/observable/of': 'Rx.Observable',
|
||||
|
||||
'rxjs/operator/concatMap': 'Rx.Observable.prototype',
|
||||
'rxjs/operator/filter': 'Rx.Observable.prototype',
|
||||
|
@ -156,7 +156,7 @@ export class JsonpClientBackend implements HttpBackend {
|
||||
statusText: 'OK', url,
|
||||
}));
|
||||
|
||||
// Complete the stream, the resposne is over.
|
||||
// Complete the stream, the response is over.
|
||||
observer.complete();
|
||||
};
|
||||
|
||||
|
@ -1,10 +1,9 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load("//tools:defaults.bzl", "ts_library")
|
||||
load("//tools:defaults.bzl", "ng_module")
|
||||
|
||||
ts_library(
|
||||
ng_module(
|
||||
name = "testing",
|
||||
testonly = 1,
|
||||
srcs = glob(
|
||||
[
|
||||
"*.ts",
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Directive, EmbeddedViewRef, Input, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||
import {Directive, EmbeddedViewRef, Input, TemplateRef, ViewContainerRef, ɵstringify as stringify} from '@angular/core';
|
||||
|
||||
|
||||
/**
|
||||
@ -117,14 +117,16 @@ export class NgIf {
|
||||
}
|
||||
|
||||
@Input()
|
||||
set ngIfThen(templateRef: TemplateRef<NgIfContext>) {
|
||||
set ngIfThen(templateRef: TemplateRef<NgIfContext>|null) {
|
||||
assertTemplate('ngIfThen', templateRef);
|
||||
this._thenTemplateRef = templateRef;
|
||||
this._thenViewRef = null; // clear previous view if any.
|
||||
this._updateView();
|
||||
}
|
||||
|
||||
@Input()
|
||||
set ngIfElse(templateRef: TemplateRef<NgIfContext>) {
|
||||
set ngIfElse(templateRef: TemplateRef<NgIfContext>|null) {
|
||||
assertTemplate('ngIfElse', templateRef);
|
||||
this._elseTemplateRef = templateRef;
|
||||
this._elseViewRef = null; // clear previous view if any.
|
||||
this._updateView();
|
||||
@ -163,3 +165,10 @@ export class NgIfContext {
|
||||
public $implicit: any = null;
|
||||
public ngIf: any = null;
|
||||
}
|
||||
|
||||
function assertTemplate(property: string, templateRef: TemplateRef<any>| null): void {
|
||||
const isTemplateRefOrNull = !!(!templateRef || templateRef.createEmbeddedView);
|
||||
if (!isTemplateRefOrNull) {
|
||||
throw new Error(`${property} must be a TemplateRef, but received '${stringify(templateRef)}'.`);
|
||||
}
|
||||
}
|
||||
|
@ -138,7 +138,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
expect(fixture.nativeElement).toHaveText('hello');
|
||||
}));
|
||||
|
||||
describe('else', () => {
|
||||
describe('then/else templates', () => {
|
||||
it('should support else', async(() => {
|
||||
const template = '<span *ngIf="booleanCondition; else elseBlock">TRUE</span>' +
|
||||
'<ng-template #elseBlock>FALSE</ng-template>';
|
||||
@ -169,6 +169,37 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
expect(fixture.nativeElement).toHaveText('ELSE');
|
||||
}));
|
||||
|
||||
it('should support removing the then/else templates', () => {
|
||||
const template = `<span *ngIf="booleanCondition;
|
||||
then nestedBooleanCondition ? tplRef : null;
|
||||
else nestedBooleanCondition ? tplRef : null"></span>
|
||||
<ng-template #tplRef>Template</ng-template>`;
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
const comp = fixture.componentInstance;
|
||||
// then template
|
||||
comp.booleanCondition = true;
|
||||
|
||||
comp.nestedBooleanCondition = true;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('Template');
|
||||
|
||||
comp.nestedBooleanCondition = false;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('');
|
||||
|
||||
// else template
|
||||
comp.booleanCondition = true;
|
||||
|
||||
comp.nestedBooleanCondition = true;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('Template');
|
||||
|
||||
comp.nestedBooleanCondition = false;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('');
|
||||
});
|
||||
|
||||
it('should support dynamic else', async(() => {
|
||||
const template =
|
||||
'<span *ngIf="booleanCondition; else nestedBooleanCondition ? b1 : b2">TRUE</span>' +
|
||||
@ -217,6 +248,28 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
expect(fixture.nativeElement).toHaveText('false');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Type guarding', () => {
|
||||
it('should throw when then block is not template', async(() => {
|
||||
const template = '<span *ngIf="booleanCondition; then thenBlock">IGNORE</span>' +
|
||||
'<div #thenBlock>THEN</div>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
expect(() => fixture.detectChanges())
|
||||
.toThrowError(/ngIfThen must be a TemplateRef, but received/);
|
||||
}));
|
||||
|
||||
it('should throw when else block is not template', async(() => {
|
||||
const template = '<span *ngIf="booleanCondition; else elseBlock">IGNORE</span>' +
|
||||
'<div #elseBlock>ELSE</div>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
expect(() => fixture.detectChanges())
|
||||
.toThrowError(/ngIfElse must be a TemplateRef, but received/);
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,9 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load("//tools:defaults.bzl", "ts_library")
|
||||
load("//tools:defaults.bzl", "ng_module")
|
||||
|
||||
ts_library(
|
||||
ng_module(
|
||||
name = "testing",
|
||||
testonly = 1,
|
||||
srcs = glob(["**/*.ts"]),
|
||||
module_name = "@angular/common/testing",
|
||||
deps = [
|
||||
|
@ -17,11 +17,11 @@ ts_library(
|
||||
],
|
||||
exclude = [
|
||||
"src/extract_i18n.ts",
|
||||
"src/main.ts",
|
||||
"src/integrationtest/**/*.ts",
|
||||
],
|
||||
),
|
||||
module_name = "@angular/compiler-cli",
|
||||
node_modules = "@//:node_modules",
|
||||
tsconfig = ":tsconfig",
|
||||
deps = [
|
||||
"//packages/compiler",
|
||||
|
@ -16,6 +16,7 @@ ng_module(
|
||||
"//packages/core",
|
||||
"//packages/platform-browser",
|
||||
"//packages/platform-server",
|
||||
"//packages/router",
|
||||
"@rxjs",
|
||||
],
|
||||
)
|
||||
|
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* @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 {APP_ROOT_SCOPE, Component, Injectable, NgModule, Optional, Self} from '@angular/core';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {ServerModule} from '@angular/platform-server';
|
||||
import {RouterModule} from '@angular/router';
|
||||
|
||||
import {LazyModuleNgFactory} from './root_lazy.ngfactory';
|
||||
|
||||
@Component({
|
||||
selector: 'root-app',
|
||||
template: '<router-outlet></router-outlet>',
|
||||
})
|
||||
export class AppComponent {
|
||||
}
|
||||
|
||||
export function children(): any {
|
||||
console.error('children', LazyModuleNgFactory);
|
||||
return LazyModuleNgFactory;
|
||||
}
|
||||
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule.withServerTransition({appId: 'id-app'}),
|
||||
ServerModule,
|
||||
RouterModule.forRoot(
|
||||
[
|
||||
{path: '', pathMatch: 'prefix', loadChildren: children},
|
||||
],
|
||||
{initialNavigation: 'enabled'}),
|
||||
],
|
||||
declarations: [AppComponent],
|
||||
bootstrap: [AppComponent],
|
||||
})
|
||||
export class RootAppModule {
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* @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 {Component, NgModule, Optional, Self} from '@angular/core';
|
||||
import {RouterModule} from '@angular/router';
|
||||
import {Service} from './root_service';
|
||||
|
||||
@Component({
|
||||
selector: 'lazy-route',
|
||||
template: '{{service}}:{{serviceInLazyInjector}}',
|
||||
})
|
||||
export class RouteComponent {
|
||||
service: boolean;
|
||||
serviceInLazyInjector: boolean;
|
||||
constructor(@Optional() service: Service, @Optional() @Self() lazyService: Service) {
|
||||
this.service = !!service;
|
||||
this.serviceInLazyInjector = !!lazyService;
|
||||
}
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [RouteComponent],
|
||||
imports: [
|
||||
RouterModule.forChild([
|
||||
{path: '', pathMatch: 'prefix', component: RouteComponent},
|
||||
]),
|
||||
],
|
||||
})
|
||||
export class LazyModule {
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* @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 {APP_ROOT_SCOPE, Injectable} from '@angular/core';
|
||||
|
||||
@Injectable({
|
||||
scope: APP_ROOT_SCOPE,
|
||||
})
|
||||
export class Service {
|
||||
}
|
@ -12,6 +12,16 @@ import {ServerModule} from '@angular/platform-server';
|
||||
|
||||
export interface IService { readonly data: string; }
|
||||
|
||||
@NgModule({})
|
||||
export class TokenModule {
|
||||
}
|
||||
|
||||
export const TOKEN = new InjectionToken('test', {
|
||||
scope: TokenModule,
|
||||
factory: () => new Service(),
|
||||
});
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'token-app',
|
||||
template: '{{data}}',
|
||||
@ -25,18 +35,12 @@ export class AppComponent {
|
||||
imports: [
|
||||
BrowserModule.withServerTransition({appId: 'id-app'}),
|
||||
ServerModule,
|
||||
TokenModule,
|
||||
],
|
||||
declarations: [AppComponent],
|
||||
bootstrap: [AppComponent],
|
||||
providers: [{provide: forwardRef(() => TOKEN), useClass: forwardRef(() => Service)}]
|
||||
})
|
||||
export class TokenAppModule {
|
||||
}
|
||||
|
||||
export class Service { readonly data = 'fromToken'; }
|
||||
|
||||
export const TOKEN = new InjectionToken('test', {
|
||||
scope: TokenAppModule,
|
||||
useClass: Service,
|
||||
deps: [],
|
||||
});
|
||||
export class Service { readonly data = 'fromToken'; }
|
@ -10,6 +10,7 @@ import {enableProdMode} from '@angular/core';
|
||||
import {renderModuleFactory} from '@angular/platform-server';
|
||||
import {BasicAppModuleNgFactory} from 'app_built/src/basic.ngfactory';
|
||||
import {HierarchyAppModuleNgFactory} from 'app_built/src/hierarchy.ngfactory';
|
||||
import {RootAppModuleNgFactory} from 'app_built/src/root.ngfactory';
|
||||
import {SelfAppModuleNgFactory} from 'app_built/src/self.ngfactory';
|
||||
import {TokenAppModuleNgFactory} from 'app_built/src/token.ngfactory';
|
||||
|
||||
@ -55,4 +56,14 @@ describe('ngInjectableDef Bazel Integration', () => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('APP_ROOT_SCOPE works', done => {
|
||||
renderModuleFactory(RootAppModuleNgFactory, {
|
||||
document: '<root-app></root-app>',
|
||||
url: '/',
|
||||
}).then(html => {
|
||||
expect(html).toMatch(/>true:false<\//);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -0,0 +1,23 @@
|
||||
load("//packages/bazel:index.bzl", "ng_module")
|
||||
|
||||
ng_module(
|
||||
name = "test_module",
|
||||
srcs = glob(["*.ts"]),
|
||||
module_name = "some_npm_module",
|
||||
deps = ["//packages/core"],
|
||||
)
|
||||
|
||||
load(":extract_flat_module_index.bzl", "extract_flat_module_index")
|
||||
|
||||
extract_flat_module_index(
|
||||
name = "flat_module_index",
|
||||
deps = [":test_module"],
|
||||
)
|
||||
|
||||
load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test")
|
||||
|
||||
jasmine_node_test(
|
||||
name = "test",
|
||||
srcs = ["spec.js"],
|
||||
data = [":flat_module_index"],
|
||||
)
|
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* @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 {NgModule} from '@angular/core';
|
||||
import {Parent} from './parent';
|
||||
|
||||
@NgModule({imports: [Parent]})
|
||||
export class Child {
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
# 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
|
||||
"""Test utility to extract the "flat_module_metadata" from transitive Angular deps.
|
||||
"""
|
||||
|
||||
def _extract_flat_module_index(ctx):
|
||||
return [DefaultInfo(files = depset(transitive = [
|
||||
dep.angular.flat_module_metadata
|
||||
for dep in ctx.attr.deps
|
||||
if hasattr(dep, "angular")
|
||||
]))]
|
||||
|
||||
extract_flat_module_index = rule(
|
||||
implementation = _extract_flat_module_index,
|
||||
attrs = {
|
||||
"deps": attr.label_list(),
|
||||
},
|
||||
)
|
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
export * from './child';
|
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* @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 {NgModule} from '@angular/core';
|
||||
|
||||
@NgModule({})
|
||||
export class Parent {
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const PKG = 'angular/packages/compiler-cli/integrationtest/bazel/ng_module';
|
||||
describe('flat module index', () => {
|
||||
describe('child metadata', () => {
|
||||
it('should have contents', () => {
|
||||
const metadata = fs.readFileSync(
|
||||
require.resolve(`${PKG}/_test_module.bundle_index.metadata.json`), {encoding: 'utf-8'});
|
||||
expect(metadata).toContain('"__symbolic":"module"');
|
||||
expect(metadata).toContain('"__symbolic":"reference","module":"@angular/core"');
|
||||
expect(metadata).toContain('"origins":{"Child":"./child","ɵa":"./parent"}');
|
||||
expect(metadata).toContain('"importAs":"some_npm_module"');
|
||||
});
|
||||
});
|
||||
describe('child typings', () => {
|
||||
it('should have contents', () => {
|
||||
const dts = fs.readFileSync(
|
||||
require.resolve(`${PKG}/_test_module.bundle_index.d.ts`), {encoding: 'utf-8'});
|
||||
|
||||
expect(dts).toContain('export * from \'./index\';');
|
||||
expect(dts).toContain('export { Parent as ɵa } from \'./parent\';');
|
||||
});
|
||||
});
|
||||
});
|
@ -11,7 +11,7 @@
|
||||
"dependencies": {
|
||||
"reflect-metadata": "^0.1.2",
|
||||
"minimist": "^1.2.0",
|
||||
"tsickle": "^0.26.0",
|
||||
"tsickle": "^0.27.2",
|
||||
"chokidar": "^1.4.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
43
packages/compiler-cli/src/metadata/bundle_index_main.ts
Normal file
43
packages/compiler-cli/src/metadata/bundle_index_main.ts
Normal file
@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
// Must be imported first, because Angular decorators throw on load.
|
||||
import 'reflect-metadata';
|
||||
|
||||
import * as ts from 'typescript';
|
||||
import * as path from 'path';
|
||||
import {readCommandLineAndConfiguration} from '../main';
|
||||
import {createBundleIndexHost} from './bundle_index_host';
|
||||
import * as ng from '../transformers/entry_points';
|
||||
|
||||
export function main(args: string[], consoleError: (s: string) => void = console.error): number {
|
||||
const {options, rootNames} = readCommandLineAndConfiguration(args);
|
||||
const host = ng.createCompilerHost({options});
|
||||
const {host: bundleHost, indexName, errors} = createBundleIndexHost(options, rootNames, host);
|
||||
if (!indexName) {
|
||||
console.error('Did not find an index.ts in the top-level of the package.');
|
||||
return 1;
|
||||
}
|
||||
// The index file is synthetic, so we have to add it to the program after parsing the tsconfig
|
||||
rootNames.push(indexName);
|
||||
const program = ts.createProgram(rootNames, options, bundleHost);
|
||||
const indexSourceFile = program.getSourceFile(indexName);
|
||||
if (!indexSourceFile) {
|
||||
console.error(`${indexSourceFile} is not in the program. Please file a bug.`);
|
||||
return 1;
|
||||
}
|
||||
program.emit(indexSourceFile);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// CLI entry point
|
||||
if (require.main === module) {
|
||||
const args = process.argv.slice(2);
|
||||
process.exitCode = main(args);
|
||||
}
|
@ -597,6 +597,7 @@ export class CompilerHostAdapter implements MetadataBundlerHost {
|
||||
constructor(private host: ts.CompilerHost) {}
|
||||
|
||||
getMetadataFor(fileName: string): ModuleMetadata|undefined {
|
||||
if (!this.host.fileExists(fileName + '.ts')) return undefined;
|
||||
const sourceFile = this.host.getSourceFile(fileName + '.ts', ts.ScriptTarget.Latest);
|
||||
return sourceFile && this.collector.getMetadata(sourceFile);
|
||||
}
|
||||
|
@ -381,9 +381,6 @@ export class Evaluator {
|
||||
case ts.SyntaxKind.NewExpression:
|
||||
const newExpression = <ts.NewExpression>node;
|
||||
const newArgs = arrayOrEmpty(newExpression.arguments).map(arg => this.evaluateNode(arg));
|
||||
if (!this.options.verboseInvalidExpression && newArgs.some(isMetadataError)) {
|
||||
return recordEntry(newArgs.find(isMetadataError), node);
|
||||
}
|
||||
const newTarget = this.evaluateNode(newExpression.expression);
|
||||
if (isMetadataError(newTarget)) {
|
||||
return recordEntry(newTarget, node);
|
||||
|
@ -60,6 +60,7 @@ export interface CompilerOptions extends ts.CompilerOptions {
|
||||
i18nInFile?: string;
|
||||
i18nInMissingTranslations?: 'error'|'warning'|'ignore';
|
||||
preserveWhitespaces?: boolean;
|
||||
disableTypeScriptVersionCheck?: boolean;
|
||||
}
|
||||
|
||||
export interface CompilerHost extends ts.CompilerHost {
|
||||
|
@ -132,6 +132,9 @@ export interface CompilerOptions extends ts.CompilerOptions {
|
||||
// position.
|
||||
disableExpressionLowering?: boolean;
|
||||
|
||||
// Disable TypeScript Version Check.
|
||||
disableTypeScriptVersionCheck?: boolean;
|
||||
|
||||
// Locale of the application
|
||||
i18nOutLocale?: string;
|
||||
// Export format (xlf, xlf2 or xmb)
|
||||
@ -148,8 +151,8 @@ export interface CompilerOptions extends ts.CompilerOptions {
|
||||
// How to handle missing messages
|
||||
i18nInMissingTranslations?: 'error'|'warning'|'ignore';
|
||||
|
||||
// Whether to remove blank text nodes from compiled templates. It is `true` by default
|
||||
// in Angular 5 and will be re-visited in Angular 6.
|
||||
// Whether to remove blank text nodes from compiled templates. It is `false` by default starting
|
||||
// from Angular 6.
|
||||
preserveWhitespaces?: boolean;
|
||||
|
||||
/** generate all possible generated files */
|
||||
|
@ -71,10 +71,11 @@ class AngularCompilerProgram implements Program {
|
||||
rootNames: ReadonlyArray<string>, private options: CompilerOptions,
|
||||
private host: CompilerHost, oldProgram?: Program) {
|
||||
this.rootNames = [...rootNames];
|
||||
const [major, minor] = ts.version.split('.');
|
||||
|
||||
Number(major) > 2 || (Number(major) === 2 && Number(minor) >= 4) ||
|
||||
userError('The Angular Compiler requires TypeScript >= 2.4.');
|
||||
if (ts.version < '2.4.2' || (ts.version >= '2.7.0' && !options.disableTypeScriptVersionCheck)) {
|
||||
throw new Error(
|
||||
`The Angular Compiler requires TypeScript >=2.4.2 and <2.7 but ${ts.version} was found instead.`);
|
||||
}
|
||||
|
||||
this.oldTsProgram = oldProgram ? oldProgram.getTsProgram() : undefined;
|
||||
if (oldProgram) {
|
||||
|
@ -2094,5 +2094,24 @@ describe('ngc transformer command-line', () => {
|
||||
`);
|
||||
expect(source).toMatch(/ngInjectableDef.*return ..\(..\.inject\(Existing, undefined, 1\)/);
|
||||
});
|
||||
|
||||
it('compiles a service that depends on a token', () => {
|
||||
const source = compileService(`
|
||||
import {Inject, Injectable, InjectionToken} from '@angular/core';
|
||||
import {Module} from './module';
|
||||
|
||||
export const TOKEN = new InjectionToken('desc', {scope: Module, factory: () => true});
|
||||
|
||||
@Injectable({
|
||||
scope: Module,
|
||||
})
|
||||
export class Service {
|
||||
constructor(@Inject(TOKEN) value: boolean) {}
|
||||
}
|
||||
`);
|
||||
expect(source).toMatch(/ngInjectableDef = .+\.defineInjectable\(/);
|
||||
expect(source).toMatch(/ngInjectableDef.*token: Service/);
|
||||
expect(source).toMatch(/ngInjectableDef.*scope: .+\.Module/);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -12,6 +12,9 @@
|
||||
},
|
||||
"outDir": "../../dist/packages/compiler-cli"
|
||||
},
|
||||
"bazelOptions": {
|
||||
"suppressTsconfigOverrideWarnings": true
|
||||
},
|
||||
|
||||
"exclude": [
|
||||
"integrationtest"
|
||||
@ -21,6 +24,7 @@
|
||||
"index.ts",
|
||||
"ngtools2.ts",
|
||||
"src/main.ts",
|
||||
"src/metadata/bundle_index_main.ts",
|
||||
"src/extract_i18n.ts",
|
||||
"src/language_services.ts",
|
||||
"../../node_modules/@types/node/index.d.ts",
|
||||
|
@ -20,6 +20,7 @@ import {NgModuleCompiler} from '../ng_module_compiler';
|
||||
import {OutputEmitter} from '../output/abstract_emitter';
|
||||
import * as o from '../output/output_ast';
|
||||
import {ParseError} from '../parse_util';
|
||||
import {compilePipe as compileIvyPipe} from '../render3/r3_pipe_compiler';
|
||||
import {compileComponent as compileIvyComponent, compileDirective as compileIvyDirective} from '../render3/r3_view_compiler';
|
||||
import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
|
||||
import {SummaryResolver} from '../summary_resolver';
|
||||
@ -356,14 +357,22 @@ export class AotCompiler {
|
||||
error(
|
||||
`Cannot determine the module for component '${identifierName(directiveMetadata.type)}'`);
|
||||
|
||||
const {template: parsedTemplate} =
|
||||
const {template: parsedTemplate, pipes: parsedPipes} =
|
||||
this._parseTemplate(directiveMetadata, module, module.transitiveModule.directives);
|
||||
compileIvyComponent(context, directiveMetadata, parsedTemplate, this._reflector);
|
||||
compileIvyComponent(
|
||||
context, directiveMetadata, parsedPipes, parsedTemplate, this._reflector);
|
||||
} else {
|
||||
compileIvyDirective(context, directiveMetadata, this._reflector);
|
||||
}
|
||||
});
|
||||
|
||||
pipes.forEach(pipeType => {
|
||||
const pipeMetadata = this._metadataResolver.getPipeMetadata(pipeType);
|
||||
if (pipeMetadata) {
|
||||
compileIvyPipe(context, pipeMetadata, this._reflector);
|
||||
}
|
||||
});
|
||||
|
||||
injectables.forEach(injectable => this._injectableCompiler.compile(injectable, context));
|
||||
|
||||
if (context.statements && context.statements.length > 0) {
|
||||
|
@ -46,6 +46,6 @@ export class CompilerConfig {
|
||||
}
|
||||
|
||||
export function preserveWhitespacesDefault(
|
||||
preserveWhitespacesOption: boolean | null, defaultSetting = true): boolean {
|
||||
preserveWhitespacesOption: boolean | null, defaultSetting = false): boolean {
|
||||
return preserveWhitespacesOption === null ? defaultSetting : preserveWhitespacesOption;
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import {OutputContext, error} from './util';
|
||||
|
||||
const CONSTANT_PREFIX = '_c';
|
||||
|
||||
export const enum DefinitionKind {Injector, Directive, Component}
|
||||
export const enum DefinitionKind {Injector, Directive, Component, Pipe}
|
||||
|
||||
/**
|
||||
* A node that is a place-holder that allows the node to be replaced when the actual
|
||||
@ -51,6 +51,7 @@ export class ConstantPool {
|
||||
private injectorDefinitions = new Map<any, FixupExpression>();
|
||||
private directiveDefinitions = new Map<any, FixupExpression>();
|
||||
private componentDefinitions = new Map<any, FixupExpression>();
|
||||
private pipeDefinitions = new Map<any, FixupExpression>();
|
||||
|
||||
private nextNameIndex = 0;
|
||||
|
||||
@ -75,18 +76,19 @@ export class ConstantPool {
|
||||
return fixup;
|
||||
}
|
||||
|
||||
getDefinition(type: any, kind: DefinitionKind, ctx: OutputContext): o.Expression {
|
||||
const declarations = kind == DefinitionKind.Component ?
|
||||
this.componentDefinitions :
|
||||
kind == DefinitionKind.Directive ? this.directiveDefinitions : this.injectorDefinitions;
|
||||
let fixup = declarations.get(type);
|
||||
getDefinition(type: any, kind: DefinitionKind, ctx: OutputContext, forceShared: boolean = false):
|
||||
o.Expression {
|
||||
const definitions = this.definitionsOf(kind);
|
||||
let fixup = definitions.get(type);
|
||||
let newValue = false;
|
||||
if (!fixup) {
|
||||
const property = kind == DefinitionKind.Component ?
|
||||
'ngComponentDef' :
|
||||
kind == DefinitionKind.Directive ? 'ngDirectiveDef' : 'ngInjectorDef';
|
||||
const property = this.propertyNameOf(kind);
|
||||
fixup = new FixupExpression(ctx.importExpr(type).prop(property));
|
||||
declarations.set(type, fixup);
|
||||
} else if (!fixup.shared) {
|
||||
definitions.set(type, fixup);
|
||||
newValue = true;
|
||||
}
|
||||
|
||||
if ((!newValue && !fixup.shared) || (newValue && forceShared)) {
|
||||
const name = this.freshName();
|
||||
this.statements.push(
|
||||
o.variable(name).set(fixup.resolved).toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]));
|
||||
@ -104,6 +106,36 @@ export class ConstantPool {
|
||||
*/
|
||||
uniqueName(prefix: string): string { return `${prefix}${this.nextNameIndex++}`; }
|
||||
|
||||
private definitionsOf(kind: DefinitionKind): Map<any, FixupExpression> {
|
||||
switch (kind) {
|
||||
case DefinitionKind.Component:
|
||||
return this.componentDefinitions;
|
||||
case DefinitionKind.Directive:
|
||||
return this.directiveDefinitions;
|
||||
case DefinitionKind.Injector:
|
||||
return this.injectorDefinitions;
|
||||
case DefinitionKind.Pipe:
|
||||
return this.pipeDefinitions;
|
||||
}
|
||||
error(`Unknown definition kind ${kind}`);
|
||||
return this.componentDefinitions;
|
||||
}
|
||||
|
||||
public propertyNameOf(kind: DefinitionKind): string {
|
||||
switch (kind) {
|
||||
case DefinitionKind.Component:
|
||||
return 'ngComponentDef';
|
||||
case DefinitionKind.Directive:
|
||||
return 'ngDirectiveDef';
|
||||
case DefinitionKind.Injector:
|
||||
return 'ngInjectorDef';
|
||||
case DefinitionKind.Pipe:
|
||||
return 'ngPipeDef';
|
||||
}
|
||||
error(`Unknown definition kind ${kind}`);
|
||||
return '<unknown>';
|
||||
}
|
||||
|
||||
private freshName(): string { return this.uniqueName(CONSTANT_PREFIX); }
|
||||
|
||||
private keyOf(expression: o.Expression) {
|
||||
|
@ -435,6 +435,173 @@ export class AstTransformer implements AstVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
// A transformer that only creates new nodes if the transformer makes a change or
|
||||
// a change is made a child node.
|
||||
export class AstMemoryEfficientTransformer implements AstVisitor {
|
||||
visitImplicitReceiver(ast: ImplicitReceiver, context: any): AST { return ast; }
|
||||
|
||||
visitInterpolation(ast: Interpolation, context: any): Interpolation {
|
||||
const expressions = this.visitAll(ast.expressions);
|
||||
if (expressions !== ast.expressions)
|
||||
return new Interpolation(ast.span, ast.strings, expressions);
|
||||
return ast;
|
||||
}
|
||||
|
||||
visitLiteralPrimitive(ast: LiteralPrimitive, context: any): AST { return ast; }
|
||||
|
||||
visitPropertyRead(ast: PropertyRead, context: any): AST {
|
||||
const receiver = ast.receiver.visit(this);
|
||||
if (receiver !== ast.receiver) {
|
||||
return new PropertyRead(ast.span, receiver, ast.name);
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
visitPropertyWrite(ast: PropertyWrite, context: any): AST {
|
||||
const receiver = ast.receiver.visit(this);
|
||||
const value = ast.value.visit(this);
|
||||
if (receiver !== ast.receiver || value !== ast.value) {
|
||||
return new PropertyWrite(ast.span, receiver, ast.name, value);
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
visitSafePropertyRead(ast: SafePropertyRead, context: any): AST {
|
||||
const receiver = ast.receiver.visit(this);
|
||||
if (receiver !== ast.receiver) {
|
||||
return new SafePropertyRead(ast.span, receiver, ast.name);
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
visitMethodCall(ast: MethodCall, context: any): AST {
|
||||
const receiver = ast.receiver.visit(this);
|
||||
if (receiver !== ast.receiver) {
|
||||
return new MethodCall(ast.span, receiver, ast.name, this.visitAll(ast.args));
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
visitSafeMethodCall(ast: SafeMethodCall, context: any): AST {
|
||||
const receiver = ast.receiver.visit(this);
|
||||
const args = this.visitAll(ast.args);
|
||||
if (receiver !== ast.receiver || args !== ast.args) {
|
||||
return new SafeMethodCall(ast.span, receiver, ast.name, args);
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
visitFunctionCall(ast: FunctionCall, context: any): AST {
|
||||
const target = ast.target && ast.target.visit(this);
|
||||
const args = this.visitAll(ast.args);
|
||||
if (target !== ast.target || args !== ast.args) {
|
||||
return new FunctionCall(ast.span, target, args);
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
visitLiteralArray(ast: LiteralArray, context: any): AST {
|
||||
const expressions = this.visitAll(ast.expressions);
|
||||
if (expressions !== ast.expressions) {
|
||||
return new LiteralArray(ast.span, expressions);
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
visitLiteralMap(ast: LiteralMap, context: any): AST {
|
||||
const values = this.visitAll(ast.values);
|
||||
if (values !== ast.values) {
|
||||
return new LiteralMap(ast.span, ast.keys, values);
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
visitBinary(ast: Binary, context: any): AST {
|
||||
const left = ast.left.visit(this);
|
||||
const right = ast.right.visit(this);
|
||||
if (left !== ast.left || right !== ast.right) {
|
||||
return new Binary(ast.span, ast.operation, left, right);
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
visitPrefixNot(ast: PrefixNot, context: any): AST {
|
||||
const expression = ast.expression.visit(this);
|
||||
if (expression !== ast.expression) {
|
||||
return new PrefixNot(ast.span, expression);
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
visitNonNullAssert(ast: NonNullAssert, context: any): AST {
|
||||
const expression = ast.expression.visit(this);
|
||||
if (expression !== ast.expression) {
|
||||
return new NonNullAssert(ast.span, expression);
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
visitConditional(ast: Conditional, context: any): AST {
|
||||
const condition = ast.condition.visit(this);
|
||||
const trueExp = ast.trueExp.visit(this);
|
||||
const falseExp = ast.falseExp.visit(this);
|
||||
if (condition !== ast.condition || trueExp !== ast.trueExp || falseExp !== falseExp) {
|
||||
return new Conditional(ast.span, condition, trueExp, falseExp);
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
visitPipe(ast: BindingPipe, context: any): AST {
|
||||
const exp = ast.exp.visit(this);
|
||||
const args = this.visitAll(ast.args);
|
||||
if (exp !== ast.exp || args !== ast.args) {
|
||||
return new BindingPipe(ast.span, exp, ast.name, args);
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
visitKeyedRead(ast: KeyedRead, context: any): AST {
|
||||
const obj = ast.obj.visit(this);
|
||||
const key = ast.key.visit(this);
|
||||
if (obj !== ast.obj || key !== ast.key) {
|
||||
return new KeyedRead(ast.span, obj, key);
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
visitKeyedWrite(ast: KeyedWrite, context: any): AST {
|
||||
const obj = ast.obj.visit(this);
|
||||
const key = ast.key.visit(this);
|
||||
const value = ast.value.visit(this);
|
||||
if (obj !== ast.obj || key !== ast.key || value !== ast.value) {
|
||||
return new KeyedWrite(ast.span, obj, key, value);
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
visitAll(asts: any[]): any[] {
|
||||
const res = new Array(asts.length);
|
||||
let modified = false;
|
||||
for (let i = 0; i < asts.length; ++i) {
|
||||
const original = asts[i];
|
||||
const value = original.visit(this);
|
||||
res[i] = value;
|
||||
modified = modified || value !== original;
|
||||
}
|
||||
return modified ? res : asts;
|
||||
}
|
||||
|
||||
visitChain(ast: Chain, context: any): AST {
|
||||
const expressions = this.visitAll(ast.expressions);
|
||||
if (expressions !== ast.expressions) {
|
||||
return new Chain(ast.span, expressions);
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
visitQuote(ast: Quote, context: any): AST { return ast; }
|
||||
}
|
||||
|
||||
export function visitAstChildren(ast: AST, visitor: AstVisitor, context?: any) {
|
||||
function visit(ast: AST) {
|
||||
visitor.visit && visitor.visit(ast, context) || ast.visit(visitor, context);
|
||||
|
@ -48,12 +48,14 @@ export class InjectableCompiler {
|
||||
} else if (v.ngMetadataName === 'Self') {
|
||||
flags |= InjectFlags.Self;
|
||||
} else if (v.ngMetadataName === 'Inject') {
|
||||
throw new Error('@Inject() is not implemented');
|
||||
token = v.token;
|
||||
} else {
|
||||
token = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (flags !== InjectFlags.Default || defaultValue !== undefined) {
|
||||
args = [ctx.importExpr(token), o.literal(defaultValue), o.literal(flags)];
|
||||
} else {
|
||||
args = [ctx.importExpr(token)];
|
||||
|
@ -21,6 +21,7 @@ export class Identifiers {
|
||||
/* Methods */
|
||||
static NEW_METHOD = 'n';
|
||||
static HOST_BINDING_METHOD = 'h';
|
||||
static TRANSFORM_METHOD = 'transform';
|
||||
|
||||
/* Instructions */
|
||||
static createElement: o.ExternalReference = {name: 'ɵE', moduleName: CORE};
|
||||
@ -53,17 +54,25 @@ export class Identifiers {
|
||||
|
||||
static bind: o.ExternalReference = {name: 'ɵb', moduleName: CORE};
|
||||
|
||||
static bind1: o.ExternalReference = {name: 'ɵb1', moduleName: CORE};
|
||||
static bind2: o.ExternalReference = {name: 'ɵb2', moduleName: CORE};
|
||||
static bind3: o.ExternalReference = {name: 'ɵb3', moduleName: CORE};
|
||||
static bind4: o.ExternalReference = {name: 'ɵb4', moduleName: CORE};
|
||||
static bind5: o.ExternalReference = {name: 'ɵb5', moduleName: CORE};
|
||||
static bind6: o.ExternalReference = {name: 'ɵb6', moduleName: CORE};
|
||||
static bind7: o.ExternalReference = {name: 'ɵb7', moduleName: CORE};
|
||||
static bind8: o.ExternalReference = {name: 'ɵb8', moduleName: CORE};
|
||||
static bindV: o.ExternalReference = {name: 'ɵbV', moduleName: CORE};
|
||||
static interpolation1: o.ExternalReference = {name: 'ɵi1', moduleName: CORE};
|
||||
static interpolation2: o.ExternalReference = {name: 'ɵi2', moduleName: CORE};
|
||||
static interpolation3: o.ExternalReference = {name: 'ɵi3', moduleName: CORE};
|
||||
static interpolation4: o.ExternalReference = {name: 'ɵi4', moduleName: CORE};
|
||||
static interpolation5: o.ExternalReference = {name: 'ɵi5', moduleName: CORE};
|
||||
static interpolation6: o.ExternalReference = {name: 'ɵi6', moduleName: CORE};
|
||||
static interpolation7: o.ExternalReference = {name: 'ɵi7', moduleName: CORE};
|
||||
static interpolation8: o.ExternalReference = {name: 'ɵi8', moduleName: CORE};
|
||||
static interpolationV: o.ExternalReference = {name: 'ɵiV', moduleName: CORE};
|
||||
|
||||
static memory: o.ExternalReference = {name: 'ɵm', moduleName: CORE};
|
||||
static pipeBind1: o.ExternalReference = {name: 'ɵpb1', moduleName: CORE};
|
||||
static pipeBind2: o.ExternalReference = {name: 'ɵpb2', moduleName: CORE};
|
||||
static pipeBind3: o.ExternalReference = {name: 'ɵpb3', moduleName: CORE};
|
||||
static pipeBind4: o.ExternalReference = {name: 'ɵpb4', moduleName: CORE};
|
||||
static pipeBindV: o.ExternalReference = {name: 'ɵpbV', moduleName: CORE};
|
||||
|
||||
static load: o.ExternalReference = {name: 'ɵld', moduleName: CORE};
|
||||
|
||||
static pipe: o.ExternalReference = {name: 'ɵPp', moduleName: CORE};
|
||||
|
||||
static projection: o.ExternalReference = {name: 'ɵP', moduleName: CORE};
|
||||
static projectionDef: o.ExternalReference = {name: 'ɵpD', moduleName: CORE};
|
||||
@ -88,5 +97,7 @@ export class Identifiers {
|
||||
moduleName: CORE,
|
||||
};
|
||||
|
||||
static definePipe: o.ExternalReference = {name: 'ɵdefinePipe', moduleName: CORE};
|
||||
|
||||
static NgOnChangesFeature: o.ExternalReference = {name: 'ɵNgOnChangesFeature', moduleName: CORE};
|
||||
}
|
50
packages/compiler/src/render3/r3_pipe_compiler.ts
Normal file
50
packages/compiler/src/render3/r3_pipe_compiler.ts
Normal file
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* @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 {CompileDirectiveMetadata, CompilePipeMetadata, identifierName} from '../compile_metadata';
|
||||
import {CompileReflector} from '../compile_reflector';
|
||||
import {DefinitionKind} from '../constant_pool';
|
||||
import * as o from '../output/output_ast';
|
||||
import {OutputContext, error} from '../util';
|
||||
|
||||
import {Identifiers as R3} from './r3_identifiers';
|
||||
import {createFactory} from './r3_view_compiler';
|
||||
|
||||
export function compilePipe(
|
||||
outputCtx: OutputContext, pipe: CompilePipeMetadata, reflector: CompileReflector) {
|
||||
const definitionMapValues: {key: string, quoted: boolean, value: o.Expression}[] = [];
|
||||
|
||||
// e.g. 'type: MyPipe`
|
||||
definitionMapValues.push(
|
||||
{key: 'type', value: outputCtx.importExpr(pipe.type.reference), quoted: false});
|
||||
|
||||
// e.g. factory: function MyPipe_Factory() { return new MyPipe(); },
|
||||
const templateFactory = createFactory(pipe.type, outputCtx, reflector);
|
||||
definitionMapValues.push({key: 'factory', value: templateFactory, quoted: false});
|
||||
|
||||
// e.g. pure: true
|
||||
if (pipe.pure) {
|
||||
definitionMapValues.push({key: 'pure', value: o.literal(true), quoted: false});
|
||||
}
|
||||
|
||||
const className = identifierName(pipe.type) !;
|
||||
className || error(`Cannot resolve the name of ${pipe.type}`);
|
||||
|
||||
outputCtx.statements.push(new o.ClassStmt(
|
||||
/* name */ className,
|
||||
/* parent */ null,
|
||||
/* fields */[new o.ClassField(
|
||||
/* name */ outputCtx.constantPool.propertyNameOf(DefinitionKind.Pipe),
|
||||
/* type */ o.INFERRED_TYPE,
|
||||
/* modifiers */[o.StmtModifier.Static],
|
||||
/* initializer */ o.importExpr(R3.definePipe).callFn([o.literalMap(
|
||||
definitionMapValues)]))],
|
||||
/* getters */[],
|
||||
/* constructorMethod */ new o.ClassMethod(null, [], []),
|
||||
/* methods */[]));
|
||||
}
|
@ -8,9 +8,9 @@
|
||||
|
||||
import {CompileDirectiveMetadata, CompilePipeSummary, CompileTokenMetadata, CompileTypeMetadata, flatten, identifierName, rendererTypeName, tokenReference, viewClassName} from '../compile_metadata';
|
||||
import {CompileReflector} from '../compile_reflector';
|
||||
import {BindingForm, BuiltinConverter, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter';
|
||||
import {BindingForm, BuiltinConverter, ConvertPropertyBindingResult, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter';
|
||||
import {ConstantPool, DefinitionKind} from '../constant_pool';
|
||||
import {AST} from '../expression_parser/ast';
|
||||
import {AST, AstMemoryEfficientTransformer, AstTransformer, BindingPipe, FunctionCall, ImplicitReceiver, LiteralPrimitive, MethodCall, ParseSpan, PropertyRead} from '../expression_parser/ast';
|
||||
import {Identifiers} from '../identifiers';
|
||||
import {LifecycleHooks} from '../lifecycle_reflector';
|
||||
import * as o from '../output/output_ast';
|
||||
@ -21,6 +21,7 @@ import {OutputContext, error} from '../util';
|
||||
|
||||
import {Identifiers as R3} from './r3_identifiers';
|
||||
|
||||
|
||||
/** Name of the context parameter passed into a template function */
|
||||
const CONTEXT_NAME = 'ctx';
|
||||
|
||||
@ -62,7 +63,7 @@ export function compileDirective(
|
||||
/* name */ className,
|
||||
/* parent */ null,
|
||||
/* fields */[new o.ClassField(
|
||||
/* name */ 'ngDirectiveDef',
|
||||
/* name */ outputCtx.constantPool.propertyNameOf(DefinitionKind.Directive),
|
||||
/* type */ o.INFERRED_TYPE,
|
||||
/* modifiers */[o.StmtModifier.Static],
|
||||
/* initializer */ o.importExpr(R3.defineDirective).callFn([o.literalMap(
|
||||
@ -73,8 +74,8 @@ export function compileDirective(
|
||||
}
|
||||
|
||||
export function compileComponent(
|
||||
outputCtx: OutputContext, component: CompileDirectiveMetadata, template: TemplateAst[],
|
||||
reflector: CompileReflector) {
|
||||
outputCtx: OutputContext, component: CompileDirectiveMetadata, pipes: CompilePipeSummary[],
|
||||
template: TemplateAst[], reflector: CompileReflector) {
|
||||
const definitionMapValues: {key: string, quoted: boolean, value: o.Expression}[] = [];
|
||||
|
||||
// e.g. `type: MyApp`
|
||||
@ -112,10 +113,11 @@ export function compileComponent(
|
||||
// e.g. `template: function MyComponent_Template(_ctx, _cm) {...}`
|
||||
const templateTypeName = component.type.reference.name;
|
||||
const templateName = templateTypeName ? `${templateTypeName}_Template` : null;
|
||||
const pipeMap = new Map(pipes.map<[string, CompilePipeSummary]>(pipe => [pipe.name, pipe]));
|
||||
const templateFunctionExpression =
|
||||
new TemplateDefinitionBuilder(
|
||||
outputCtx, outputCtx.constantPool, reflector, CONTEXT_NAME, ROOT_SCOPE.nestedScope(), 0,
|
||||
component.template !.ngContentSelectors, templateTypeName, templateName)
|
||||
component.template !.ngContentSelectors, templateTypeName, templateName, pipeMap)
|
||||
.buildTemplateFunction(template, []);
|
||||
definitionMapValues.push({key: 'template', value: templateFunctionExpression, quoted: false});
|
||||
|
||||
@ -143,7 +145,7 @@ export function compileComponent(
|
||||
/* name */ className,
|
||||
/* parent */ null,
|
||||
/* fields */[new o.ClassField(
|
||||
/* name */ 'ngComponentDef',
|
||||
/* name */ outputCtx.constantPool.propertyNameOf(DefinitionKind.Component),
|
||||
/* type */ o.INFERRED_TYPE,
|
||||
/* modifiers */[o.StmtModifier.Static],
|
||||
/* initializer */ o.importExpr(R3.defineComponent).callFn([o.literalMap(
|
||||
@ -178,25 +180,41 @@ function interpolate(args: o.Expression[]): o.Expression {
|
||||
args = args.slice(1); // Ignore the length prefix added for render2
|
||||
switch (args.length) {
|
||||
case 3:
|
||||
return o.importExpr(R3.bind1).callFn(args);
|
||||
return o.importExpr(R3.interpolation1).callFn(args);
|
||||
case 5:
|
||||
return o.importExpr(R3.bind2).callFn(args);
|
||||
return o.importExpr(R3.interpolation2).callFn(args);
|
||||
case 7:
|
||||
return o.importExpr(R3.bind3).callFn(args);
|
||||
return o.importExpr(R3.interpolation3).callFn(args);
|
||||
case 9:
|
||||
return o.importExpr(R3.bind4).callFn(args);
|
||||
return o.importExpr(R3.interpolation4).callFn(args);
|
||||
case 11:
|
||||
return o.importExpr(R3.bind5).callFn(args);
|
||||
return o.importExpr(R3.interpolation5).callFn(args);
|
||||
case 13:
|
||||
return o.importExpr(R3.bind6).callFn(args);
|
||||
return o.importExpr(R3.interpolation6).callFn(args);
|
||||
case 15:
|
||||
return o.importExpr(R3.bind7).callFn(args);
|
||||
return o.importExpr(R3.interpolation7).callFn(args);
|
||||
case 17:
|
||||
return o.importExpr(R3.bind8).callFn(args);
|
||||
return o.importExpr(R3.interpolation8).callFn(args);
|
||||
}
|
||||
(args.length >= 19 && args.length % 2 == 1) ||
|
||||
error(`Invalid interpolation argument length ${args.length}`);
|
||||
return o.importExpr(R3.bindV).callFn([o.literalArr(args)]);
|
||||
return o.importExpr(R3.interpolationV).callFn([o.literalArr(args)]);
|
||||
}
|
||||
|
||||
function pipeBinding(args: o.Expression[]): o.ExternalReference {
|
||||
switch (args.length) {
|
||||
case 0:
|
||||
// The first parameter to pipeBind is always the value to be transformed followed
|
||||
// by arg.length arguments so the total number of arguments to pipeBind are
|
||||
// arg.length + 1.
|
||||
return R3.pipeBind1;
|
||||
case 1:
|
||||
return R3.pipeBind2;
|
||||
case 2:
|
||||
return R3.pipeBind3;
|
||||
default:
|
||||
return R3.pipeBindV;
|
||||
}
|
||||
}
|
||||
|
||||
class BindingScope {
|
||||
@ -219,10 +237,10 @@ class BindingScope {
|
||||
return null;
|
||||
}
|
||||
|
||||
set(name: string, variableName: string): BindingScope {
|
||||
set(name: string, value: o.Expression): BindingScope {
|
||||
!this.map.has(name) ||
|
||||
error(`The name ${name} is already defined in scope to be ${this.map.get(name)}`);
|
||||
this.map.set(name, o.variable(variableName));
|
||||
this.map.set(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -236,7 +254,7 @@ class BindingScope {
|
||||
}
|
||||
}
|
||||
|
||||
const ROOT_SCOPE = new BindingScope(null).set('$event', '$event');
|
||||
const ROOT_SCOPE = new BindingScope(null).set('$event', o.variable('$event'));
|
||||
|
||||
class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||
private _dataIndex = 0;
|
||||
@ -251,6 +269,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||
private _postfix: o.Statement[] = [];
|
||||
private _contentProjections: Map<NgContentAst, NgContentInfo>;
|
||||
private _projectionDefinitionIndex = 0;
|
||||
private _pipeConverter: PipeConverter;
|
||||
private unsupported = unsupported;
|
||||
private invalid = invalid;
|
||||
|
||||
@ -258,7 +277,23 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||
private outputCtx: OutputContext, private constantPool: ConstantPool,
|
||||
private reflector: CompileReflector, private contextParameter: string,
|
||||
private bindingScope: BindingScope, private level = 0, private ngContentSelectors: string[],
|
||||
private contextName: string|null, private templateName: string|null) {}
|
||||
private contextName: string|null, private templateName: string|null,
|
||||
private pipes: Map<string, CompilePipeSummary>) {
|
||||
this._pipeConverter =
|
||||
new PipeConverter(() => this.allocateDataSlot(), (name, localName, slot, value) => {
|
||||
bindingScope.set(localName, value);
|
||||
const pipe = pipes.get(name) !;
|
||||
pipe || error(`Could not find pipe ${name}`);
|
||||
const pipeDefinition = constantPool.getDefinition(
|
||||
pipe.type.reference, DefinitionKind.Pipe, outputCtx, /* forceShared */ true);
|
||||
this._creationMode.push(
|
||||
o.importExpr(R3.pipe)
|
||||
.callFn([
|
||||
o.literal(slot), pipeDefinition, pipeDefinition.callMethod(R3.NEW_METHOD, [])
|
||||
])
|
||||
.toStmt());
|
||||
});
|
||||
}
|
||||
|
||||
buildTemplateFunction(asts: TemplateAst[], variables: VariableAst[]): o.FunctionExpr {
|
||||
// Create variable bindings
|
||||
@ -272,7 +307,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||
]);
|
||||
|
||||
// Add the reference to the local scope.
|
||||
this.bindingScope.set(variableName, scopedName);
|
||||
this.bindingScope.set(variableName, o.variable(scopedName));
|
||||
|
||||
// Declare the local variable in binding mode
|
||||
this._bindingMode.push(declaration);
|
||||
@ -332,8 +367,10 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||
o.INFERRED_TYPE, null, this.templateName);
|
||||
}
|
||||
|
||||
// LocalResolver
|
||||
getLocal(name: string): o.Expression|null { return this.bindingScope.get(name); }
|
||||
|
||||
// TemplateAstVisitor
|
||||
visitNgContent(ast: NgContentAst) {
|
||||
const info = this._contentProjections.get(ast) !;
|
||||
info || error(`Expected ${ast.sourceSpan} to be included in content projection collection`);
|
||||
@ -361,6 +398,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||
};
|
||||
}
|
||||
|
||||
// TemplateAstVisitor
|
||||
visitElement(ast: ElementAst) {
|
||||
let bindingCount = 0;
|
||||
const elementIndex = this.allocateDataSlot();
|
||||
@ -408,9 +446,9 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||
// Generate the update temporary.
|
||||
const variableName = this.bindingScope.freshReferenceName();
|
||||
this._bindingMode.push(o.variable(variableName, o.INFERRED_TYPE)
|
||||
.set(o.importExpr(R3.memory).callFn([o.literal(slot)]))
|
||||
.set(o.importExpr(R3.load).callFn([o.literal(slot)]))
|
||||
.toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]));
|
||||
this.bindingScope.set(reference.name, variableName);
|
||||
this.bindingScope.set(reference.name, o.variable(variableName));
|
||||
return [reference.name, reference.originalValue];
|
||||
})).map(value => o.literal(value));
|
||||
parameters.push(
|
||||
@ -434,18 +472,14 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||
if (input.isAnimation) {
|
||||
this.unsupported('animations');
|
||||
}
|
||||
// TODO(chuckj): Built-in transform?
|
||||
const convertedBinding = convertPropertyBinding(
|
||||
this, implicit, input.value, this.bindingContext(), BindingForm.TrySimple, interpolate);
|
||||
this._bindingMode.push(...convertedBinding.stmts);
|
||||
const parameters =
|
||||
[o.literal(elementIndex), o.literal(input.name), convertedBinding.currValExpr];
|
||||
const convertedBinding = this.convertPropertyBinding(implicit, input.value);
|
||||
const parameters = [o.literal(elementIndex), o.literal(input.name), convertedBinding];
|
||||
const instruction = BINDING_INSTRUCTION_MAP[input.type];
|
||||
if (instruction) {
|
||||
// TODO(chuckj): runtime: security context?
|
||||
this.instruction(
|
||||
this._bindingMode, input.sourceSpan, instruction, o.literal(elementIndex),
|
||||
o.literal(input.name), convertedBinding.currValExpr);
|
||||
o.literal(input.name), convertedBinding);
|
||||
} else {
|
||||
this.unsupported(`binding ${PropertyBindingType[input.type]}`);
|
||||
}
|
||||
@ -479,13 +513,10 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||
|
||||
// Bindings
|
||||
for (const input of directive.inputs) {
|
||||
const convertedBinding = convertPropertyBinding(
|
||||
this, implicit, input.value, this.bindingContext(), BindingForm.TrySimple, interpolate);
|
||||
this._bindingMode.push(...convertedBinding.stmts);
|
||||
const convertedBinding = this.convertPropertyBinding(implicit, input.value);
|
||||
this.instruction(
|
||||
this._bindingMode, directive.sourceSpan, R3.elementProperty, o.literal(nodeIndex),
|
||||
o.literal(input.templateName),
|
||||
o.importExpr(R3.bind).callFn([convertedBinding.currValExpr]));
|
||||
o.literal(input.templateName), o.importExpr(R3.bind).callFn([convertedBinding]));
|
||||
}
|
||||
|
||||
// e.g. MyDirective.ngDirectiveDef.h(0, 0);
|
||||
@ -501,6 +532,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||
}
|
||||
}
|
||||
|
||||
// TemplateAstVisitor
|
||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst) {
|
||||
const templateIndex = this.allocateDataSlot();
|
||||
|
||||
@ -539,7 +571,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||
const templateVisitor = new TemplateDefinitionBuilder(
|
||||
this.outputCtx, this.constantPool, this.reflector, templateContext,
|
||||
this.bindingScope.nestedScope(), this.level + 1, this.ngContentSelectors, contextName,
|
||||
templateName);
|
||||
templateName, this.pipes);
|
||||
const templateFunctionExpr = templateVisitor.buildTemplateFunction(ast.children, ast.variables);
|
||||
this._postfix.push(templateFunctionExpr.toDeclStmt(templateName, null));
|
||||
}
|
||||
@ -551,6 +583,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||
readonly visitElementProperty = invalid;
|
||||
readonly visitAttr = invalid;
|
||||
|
||||
// TemplateAstVisitor
|
||||
visitBoundText(ast: BoundTextAst) {
|
||||
const nodeIndex = this.allocateDataSlot();
|
||||
|
||||
@ -563,6 +596,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||
this.bind(o.variable(CONTEXT_NAME), ast.value, ast.sourceSpan));
|
||||
}
|
||||
|
||||
// TemplateAstVisitor
|
||||
visitText(ast: TextAst) {
|
||||
// Text is defined in creation mode only.
|
||||
this.instruction(
|
||||
@ -600,8 +634,10 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||
}
|
||||
|
||||
private convertPropertyBinding(implicit: o.Expression, value: AST): o.Expression {
|
||||
const pipesConvertedValue = value.visit(this._pipeConverter);
|
||||
const convertedPropertyBinding = convertPropertyBinding(
|
||||
this, implicit, value, this.bindingContext(), BindingForm.TrySimple, interpolate);
|
||||
this, implicit, pipesConvertedValue, this.bindingContext(), BindingForm.TrySimple,
|
||||
interpolate);
|
||||
this._refreshMode.push(...convertedPropertyBinding.stmts);
|
||||
return convertedPropertyBinding.currValExpr;
|
||||
}
|
||||
@ -611,7 +647,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||
}
|
||||
}
|
||||
|
||||
function createFactory(
|
||||
export function createFactory(
|
||||
type: CompileTypeMetadata, outputCtx: OutputContext,
|
||||
reflector: CompileReflector): o.FunctionExpr {
|
||||
let args: o.Expression[] = [];
|
||||
@ -652,6 +688,35 @@ function createFactory(
|
||||
o.INFERRED_TYPE, null, type.reference.name ? `${type.reference.name}_Factory` : null);
|
||||
}
|
||||
|
||||
class PipeConverter extends AstMemoryEfficientTransformer {
|
||||
private pipeSlots = new Map<string, number>();
|
||||
constructor(
|
||||
private allocateSlot: () => number,
|
||||
private definePipe:
|
||||
(name: string, localName: string, slot: number, value: o.Expression) => void) {
|
||||
super();
|
||||
}
|
||||
|
||||
// AstMemoryEfficientTransformer
|
||||
visitPipe(ast: BindingPipe, context: any): AST {
|
||||
// Allocate a slot to create the pipe
|
||||
let slot = this.pipeSlots.get(ast.name);
|
||||
if (slot == null) {
|
||||
slot = this.allocateSlot();
|
||||
this.pipeSlots.set(ast.name, slot);
|
||||
}
|
||||
const slotPseudoLocal = `PIPE:${slot}`;
|
||||
const target = new PropertyRead(ast.span, new ImplicitReceiver(ast.span), slotPseudoLocal);
|
||||
const bindingId = pipeBinding(ast.args);
|
||||
this.definePipe(ast.name, slotPseudoLocal, slot, o.importExpr(bindingId));
|
||||
const value = ast.exp.visit(this);
|
||||
const args = this.visitAll(ast.args);
|
||||
|
||||
return new FunctionCall(
|
||||
ast.span, target, [new LiteralPrimitive(ast.span, slot), value, ...args]);
|
||||
}
|
||||
}
|
||||
|
||||
function invalid<T>(arg: o.Expression | o.Statement | TemplateAst): never {
|
||||
throw new Error(
|
||||
`Invalid state: Visitor ${this.constructor.name} doesn't handle ${o.constructor.name}`);
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {MissingTranslationStrategy} from '@angular/core';
|
||||
import {CompilerConfig} from '../src/config';
|
||||
import {CompilerConfig, preserveWhitespacesDefault} from '../src/config';
|
||||
|
||||
{
|
||||
describe('compiler config', () => {
|
||||
@ -16,4 +16,13 @@ import {CompilerConfig} from '../src/config';
|
||||
expect(config.missingTranslation).toEqual(MissingTranslationStrategy.Error);
|
||||
});
|
||||
});
|
||||
|
||||
describe('preserveWhitespacesDefault', () => {
|
||||
it('should return the default `false` setting when no preserveWhitespacesOption are provided',
|
||||
() => { expect(preserveWhitespacesDefault(null)).toEqual(false); });
|
||||
it('should return the preserveWhitespacesOption when provided as a parameter', () => {
|
||||
expect(preserveWhitespacesDefault(true)).toEqual(true);
|
||||
expect(preserveWhitespacesDefault(false)).toEqual(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -6,12 +6,13 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AotCompilerHost, AotCompilerOptions, AotSummaryResolver, CompileDirectiveMetadata, CompileMetadataResolver, CompilerConfig, DirectiveNormalizer, DirectiveResolver, DomElementSchemaRegistry, HtmlParser, I18NHtmlParser, Lexer, NgModuleResolver, Parser, PipeResolver, StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, TemplateParser, TypeScriptEmitter, analyzeNgModules, createAotUrlResolver} from '@angular/compiler';
|
||||
import {AotCompilerHost, AotCompilerOptions, AotSummaryResolver, CompileDirectiveMetadata, CompileMetadataResolver, CompilePipeSummary, CompilerConfig, DirectiveNormalizer, DirectiveResolver, DomElementSchemaRegistry, HtmlParser, I18NHtmlParser, Lexer, NgModuleResolver, Parser, PipeResolver, StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, TemplateParser, TypeScriptEmitter, analyzeNgModules, createAotUrlResolver} from '@angular/compiler';
|
||||
import {ViewEncapsulation} from '@angular/core';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ConstantPool} from '../../src/constant_pool';
|
||||
import * as o from '../../src/output/output_ast';
|
||||
import {compilePipe} from '../../src/render3/r3_pipe_compiler';
|
||||
import {compileComponent, compileDirective} from '../../src/render3/r3_view_compiler';
|
||||
import {OutputContext} from '../../src/util';
|
||||
import {MockAotCompilerHost, MockCompilerHost, MockData, MockDirectory, arrayToMockDir, expectNoDiagnostics, settings, setup, toMockFileArray} from '../aot/test_util';
|
||||
@ -173,7 +174,7 @@ export function compile(
|
||||
// to generate a template definition.
|
||||
const analyzedModules = analyzeNgModules(sourceFiles, compilerHost, symbolResolver, resolver);
|
||||
|
||||
const directives = Array.from(analyzedModules.ngModuleByPipeOrDirective.keys());
|
||||
const pipesOrDirectives = Array.from(analyzedModules.ngModuleByPipeOrDirective.keys());
|
||||
|
||||
const fakeOutputContext: OutputContext = {
|
||||
genFilePath: 'fakeFactory.ts',
|
||||
@ -193,20 +194,20 @@ export function compile(
|
||||
constantPool: new ConstantPool()
|
||||
};
|
||||
|
||||
// Load All directives
|
||||
for (const directive of directives) {
|
||||
const module = analyzedModules.ngModuleByPipeOrDirective.get(directive) !;
|
||||
// Load all directives and pipes
|
||||
for (const pipeOrDirective of pipesOrDirectives) {
|
||||
const module = analyzedModules.ngModuleByPipeOrDirective.get(pipeOrDirective) !;
|
||||
resolver.loadNgModuleDirectiveAndPipeMetadata(module.type.reference, true);
|
||||
}
|
||||
|
||||
// Compile the directives.
|
||||
for (const directive of directives) {
|
||||
const module = analyzedModules.ngModuleByPipeOrDirective.get(directive);
|
||||
for (const pipeOrDirective of pipesOrDirectives) {
|
||||
const module = analyzedModules.ngModuleByPipeOrDirective.get(pipeOrDirective);
|
||||
if (!module || !module.type.reference.filePath.startsWith('/app')) {
|
||||
continue;
|
||||
}
|
||||
if (resolver.isDirective(directive)) {
|
||||
const metadata = resolver.getDirectiveMetadata(directive);
|
||||
if (resolver.isDirective(pipeOrDirective)) {
|
||||
const metadata = resolver.getDirectiveMetadata(pipeOrDirective);
|
||||
if (metadata.isComponent) {
|
||||
const fakeUrl = 'ng://fake-template-url.html';
|
||||
const htmlAst = htmlParser.parse(metadata.template !.template !, fakeUrl);
|
||||
@ -217,11 +218,16 @@ export function compile(
|
||||
module.transitiveModule.pipes.map(pipe => resolver.getPipeSummary(pipe.reference));
|
||||
const parsedTemplate = templateParser.parse(
|
||||
metadata, htmlAst, directives, pipes, module.schemas, fakeUrl, false);
|
||||
|
||||
compileComponent(fakeOutputContext, metadata, parsedTemplate.template, staticReflector);
|
||||
compileComponent(
|
||||
fakeOutputContext, metadata, pipes, parsedTemplate.template, staticReflector);
|
||||
} else {
|
||||
compileDirective(fakeOutputContext, metadata, staticReflector);
|
||||
}
|
||||
} else if (resolver.isPipe(pipeOrDirective)) {
|
||||
const metadata = resolver.getPipeMetadata(pipeOrDirective);
|
||||
if (metadata) {
|
||||
compilePipe(fakeOutputContext, metadata, staticReflector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
716
packages/compiler/test/render3/r3_compiler_compliance_spec.ts
Normal file
716
packages/compiler/test/render3/r3_compiler_compliance_spec.ts
Normal file
@ -0,0 +1,716 @@
|
||||
/**
|
||||
* @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 {MockDirectory, setup} from '../aot/test_util';
|
||||
import {compile, expectEmit} from './mock_compile';
|
||||
|
||||
/* These tests are codified version of the tests in compiler_canonical_spec.ts. Every
|
||||
* test in compiler_canonical_spec.ts should have a corresponding test here.
|
||||
*/
|
||||
describe('compiler compliance', () => {
|
||||
const angularFiles = setup({
|
||||
compileAngular: true,
|
||||
compileAnimations: false,
|
||||
compileCommon: true,
|
||||
});
|
||||
|
||||
describe('elements', () => {
|
||||
it('should translate DOM structure', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: \`<div class="my-app" title="Hello">Hello <b>World</b>!</div>\`
|
||||
})
|
||||
export class MyComponent {}
|
||||
|
||||
@NgModule({declarations: [MyComponent]})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
// The factory should look like this:
|
||||
const factory = 'factory: function MyComponent_Factory() { return new MyComponent(); }';
|
||||
|
||||
// The template should look like this (where IDENT is a wild card for an identifier):
|
||||
const template = `
|
||||
const $c1$ = ['class', 'my-app', 'title', 'Hello'];
|
||||
…
|
||||
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
$r3$.ɵE(0, 'div', $c1$);
|
||||
$r3$.ɵT(1, 'Hello ');
|
||||
$r3$.ɵE(2, 'b');
|
||||
$r3$.ɵT(3, 'World');
|
||||
$r3$.ɵe();
|
||||
$r3$.ɵT(4, '!');
|
||||
$r3$.ɵe();
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
|
||||
expectEmit(result.source, factory, 'Incorrect factory');
|
||||
expectEmit(result.source, template, 'Incorrect template');
|
||||
});
|
||||
});
|
||||
|
||||
describe('components & directives', () => {
|
||||
it('should instantiate directives', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, Directive, NgModule} from '@angular/core';
|
||||
|
||||
@Component({selector: 'child', template: 'child-view'})
|
||||
export class ChildComponent {}
|
||||
|
||||
@Directive({selector: '[some-directive]'})
|
||||
export class SomeDirective {}
|
||||
|
||||
@Component({selector: 'my-component', template: '<child some-directive></child>!'})
|
||||
export class MyComponent {}
|
||||
|
||||
@NgModule({declarations: [ChildComponent, SomeDirective, MyComponent]})
|
||||
export class MyModule{}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
// ChildComponent definition should be:
|
||||
const ChildComponentDefinition = `
|
||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: ChildComponent,
|
||||
tag: 'child',
|
||||
factory: function ChildComponent_Factory() { return new ChildComponent(); },
|
||||
template: function ChildComponent_Template(ctx: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
$r3$.ɵT(0, 'child-view');
|
||||
}
|
||||
}
|
||||
});`;
|
||||
|
||||
// SomeDirective definition should be:
|
||||
const SomeDirectiveDefinition = `
|
||||
static ngDirectiveDef = $r3$.ɵdefineDirective({
|
||||
type: SomeDirective,
|
||||
factory: function SomeDirective_Factory() {return new SomeDirective(); }
|
||||
});
|
||||
`;
|
||||
|
||||
// MyComponent definition should be:
|
||||
const MyComponentDefinition = `
|
||||
const $c1$ = ['some-directive', ''];
|
||||
const $c2$ = [SomeDirective];
|
||||
…
|
||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: MyComponent,
|
||||
tag: 'my-component',
|
||||
factory: function MyComponent_Factory() { return new MyComponent(); },
|
||||
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
$r3$.ɵE(0, ChildComponent, IDENT, IDENT);
|
||||
$r3$.ɵe();
|
||||
$r3$.ɵT(3, '!');
|
||||
}
|
||||
ChildComponent.ngComponentDef.h(1, 0);
|
||||
SomeDirective.ngDirectiveDef.h(2, 0);
|
||||
$r3$.ɵr(1, 0);
|
||||
$r3$.ɵr(2, 0);
|
||||
}
|
||||
});
|
||||
`;
|
||||
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
const source = result.source;
|
||||
|
||||
expectEmit(source, ChildComponentDefinition, 'Incorrect ChildComponent.ngComponentDef');
|
||||
expectEmit(source, SomeDirectiveDefinition, 'Incorrect SomeDirective.ngDirectiveDef');
|
||||
expectEmit(source, MyComponentDefinition, 'Incorrect MyComponentDefinition.ngComponentDef');
|
||||
});
|
||||
|
||||
it('should support structural directives', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, Directive, NgModule, TemplateRef} from '@angular/core';
|
||||
|
||||
@Directive({selector: '[if]'})
|
||||
export class IfDirective {
|
||||
constructor(template: TemplateRef<any>) { }
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: '<ul #foo><li *if>{{salutation}} {{foo}}</li></ul>'
|
||||
})
|
||||
export class MyComponent {
|
||||
salutation = 'Hello';
|
||||
}
|
||||
|
||||
@NgModule({declarations: [IfDirective, MyComponent]})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const IfDirectiveDefinition = `
|
||||
static ngDirectiveDef = $r3$.ɵdefineDirective({
|
||||
type: IfDirective,
|
||||
factory: function IfDirective_Factory() { return new IfDirective($r3$.ɵinjectTemplateRef()); }
|
||||
});`;
|
||||
const MyComponentDefinition = `
|
||||
const $c1$ = ['foo', ''];
|
||||
const $c2$ = [IfDirective];
|
||||
…
|
||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: MyComponent,
|
||||
tag: 'my-component',
|
||||
factory: function MyComponent_Factory() { return new MyComponent(); },
|
||||
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
$r3$.ɵE(0, 'ul', null, null, $c1$);
|
||||
$r3$.ɵC(2, $c2$, MyComponent_IfDirective_Template_2);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
const $foo$ = $r3$.ɵld(1);
|
||||
IfDirective.ngDirectiveDef.h(3,2);
|
||||
$r3$.ɵcR(2);
|
||||
$r3$.ɵr(3,2);
|
||||
$r3$.ɵcr();
|
||||
|
||||
function MyComponent_IfDirective_Template_2(ctx0: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
$r3$.ɵE(0, 'li');
|
||||
$r3$.ɵT(1);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
$r3$.ɵt(1, $r3$.ɵi2('', ctx.salutation, ' ', $foo$, ''));
|
||||
}
|
||||
}
|
||||
});`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
const source = result.source;
|
||||
|
||||
expectEmit(source, IfDirectiveDefinition, 'Incorrect IfDirective.ngDirectiveDef');
|
||||
expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ngComponentDef');
|
||||
});
|
||||
|
||||
it('should support 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>'})
|
||||
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 MyApp {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const SimpleComponentDefinition = `
|
||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: SimpleComponent,
|
||||
tag: 'simple',
|
||||
factory: function SimpleComponent_Factory() { return new SimpleComponent(); },
|
||||
template: function SimpleComponent_Template(ctx: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
$r3$.ɵpD(0);
|
||||
$r3$.ɵE(1, 'div');
|
||||
$r3$.ɵP(2, 0);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
}
|
||||
});`;
|
||||
|
||||
const ComplexComponentDefinition = `
|
||||
const $c1$ = [[[['span', 'title', 'tofirst'], null]], [[['span', 'title', 'tosecond'], null]]];
|
||||
const $c2$ = ['id','first'];
|
||||
const $c3$ = ['id','second'];
|
||||
…
|
||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: ComplexComponent,
|
||||
tag: 'complex',
|
||||
factory: function ComplexComponent_Factory() { return new ComplexComponent(); },
|
||||
template: function ComplexComponent_Template(ctx: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
$r3$.ɵpD(0, $c1$);
|
||||
$r3$.ɵE(1, 'div', $c2$);
|
||||
$r3$.ɵP(2, 0, 1);
|
||||
$r3$.ɵe();
|
||||
$r3$.ɵE(3, 'div', $c3$);
|
||||
$r3$.ɵP(4, 0, 2);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
}
|
||||
});
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
const source = result.source;
|
||||
|
||||
expectEmit(result.source, SimpleComponentDefinition, 'Incorrect SimpleComponent definition');
|
||||
expectEmit(
|
||||
result.source, ComplexComponentDefinition, 'Incorrect ComplexComponent definition');
|
||||
});
|
||||
|
||||
describe('pipes', () => {
|
||||
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, NgModule, Pipe, PipeTransform, OnDestroy} from '@angular/core';
|
||||
|
||||
@Pipe({
|
||||
name: 'myPipe',
|
||||
pure: false
|
||||
})
|
||||
export class MyPipe implements PipeTransform,
|
||||
OnDestroy {
|
||||
transform(value: any, ...args: any[]) { return value; }
|
||||
ngOnDestroy(): void { }
|
||||
}
|
||||
|
||||
@Pipe({
|
||||
name: 'myPurePipe',
|
||||
pure: true,
|
||||
})
|
||||
export class MyPurePipe implements PipeTransform {
|
||||
transform(value: any, ...args: any[]) { return value; }
|
||||
}
|
||||
|
||||
@Component({selector: 'my-app', template: '{{name | myPipe:size | myPurePipe:size }}'})
|
||||
export class MyApp {
|
||||
name = 'World';
|
||||
size = 0;
|
||||
}
|
||||
|
||||
@NgModule({declarations:[MyPipe, MyPurePipe, MyApp]})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
it('should render pipes', () => {
|
||||
const MyPipeDefinition = `
|
||||
static ngPipeDef = $r3$.ɵdefinePipe(
|
||||
{type: MyPipe, factory: function MyPipe_Factory() { return new MyPipe(); }});
|
||||
`;
|
||||
|
||||
const MyPurePipeDefinition = `
|
||||
static ngPipeDef = $r3$.ɵdefinePipe({
|
||||
type: MyPurePipe,
|
||||
factory: function MyPurePipe_Factory() { return new MyPurePipe(); },
|
||||
pure: true
|
||||
});`;
|
||||
|
||||
const MyAppDefinition = `
|
||||
const $MyPurePipe_ngPipeDef$ = MyPurePipe.ngPipeDef;
|
||||
const $MyPipe_ngPipeDef$ = MyPipe.ngPipeDef;
|
||||
…
|
||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: MyApp,
|
||||
tag: 'my-app',
|
||||
factory: function MyApp_Factory() { return new MyApp(); },
|
||||
template: function MyApp_Template(ctx: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
$r3$.ɵT(0);
|
||||
$r3$.ɵPp(1, $MyPurePipe_ngPipeDef$, $MyPurePipe_ngPipeDef$.n());
|
||||
$r3$.ɵPp(2, $MyPipe_ngPipeDef$, $MyPipe_ngPipeDef$.n());
|
||||
}
|
||||
$r3$.ɵt(0, $r3$.ɵi1('', $r3$.ɵpb2(1, $r3$.ɵpb2(2,ctx.name, ctx.size), ctx.size), ''));
|
||||
}
|
||||
});`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
const source = result.source;
|
||||
|
||||
expectEmit(source, MyPipeDefinition, 'Invalid pipe definition');
|
||||
expectEmit(source, MyPurePipeDefinition, 'Invalid pure pipe definition');
|
||||
expectEmit(source, MyAppDefinition, 'Invalid MyApp definition');
|
||||
});
|
||||
});
|
||||
|
||||
it('local reference', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({selector: 'my-component', template: '<input #user>Hello {{user.value}}!'})
|
||||
export class MyComponent {}
|
||||
|
||||
@NgModule({declarations: [MyComponent]})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const MyComponentDefinition = `
|
||||
const $c1$ = ['user', ''];
|
||||
…
|
||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: MyComponent,
|
||||
tag: 'my-component',
|
||||
factory: function MyComponent_Factory() { return new MyComponent(); },
|
||||
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
$r3$.ɵE(0, 'input', null, null, $c1$);
|
||||
$r3$.ɵe();
|
||||
$r3$.ɵT(2);
|
||||
}
|
||||
const $user$ = $r3$.ɵld(1);
|
||||
$r3$.ɵt(2, $r3$.ɵi1('Hello ', $user$.value, '!'));
|
||||
}
|
||||
});
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
const source = result.source;
|
||||
|
||||
expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ngComponentDef');
|
||||
});
|
||||
|
||||
describe('lifecycle hooks', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, Input, NgModule} from '@angular/core';
|
||||
|
||||
let events: string[] = [];
|
||||
|
||||
@Component({selector: 'lifecycle-comp', template: ''})
|
||||
export class LifecycleComp {
|
||||
@Input('name') nameMin: string;
|
||||
|
||||
ngOnChanges() { events.push('changes' + this.nameMin); }
|
||||
|
||||
ngOnInit() { events.push('init' + this.nameMin); }
|
||||
ngDoCheck() { events.push('check' + this.nameMin); }
|
||||
|
||||
ngAfterContentInit() { events.push('content init' + this.nameMin); }
|
||||
ngAfterContentChecked() { events.push('content check' + this.nameMin); }
|
||||
|
||||
ngAfterViewInit() { events.push('view init' + this.nameMin); }
|
||||
ngAfterViewChecked() { events.push('view check' + this.nameMin); }
|
||||
|
||||
ngOnDestroy() { events.push(this.nameMin); }
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'simple-layout',
|
||||
template: \`
|
||||
<lifecycle-comp [name]="name1"></lifecycle-comp>
|
||||
<lifecycle-comp [name]="name2"></lifecycle-comp>
|
||||
\`
|
||||
})
|
||||
export class SimpleLayout {
|
||||
name1 = '1';
|
||||
name2 = '2';
|
||||
}
|
||||
|
||||
@NgModule({declarations: [LifecycleComp, SimpleLayout]})
|
||||
export class LifecycleModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
it('should gen hooks with a few simple components', () => {
|
||||
const LifecycleCompDefinition = `
|
||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: LifecycleComp,
|
||||
tag: 'lifecycle-comp',
|
||||
factory: function LifecycleComp_Factory() { return new LifecycleComp(); },
|
||||
template: function LifecycleComp_Template(ctx: IDENT, cm: IDENT) {},
|
||||
inputs: {nameMin: 'name'},
|
||||
features: [$r3$.ɵNgOnChangesFeature(LifecycleComp)]
|
||||
});`;
|
||||
|
||||
const SimpleLayoutDefinition = `
|
||||
const $c1$ = LifecycleComp.ngComponentDef;
|
||||
…
|
||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: SimpleLayout,
|
||||
tag: 'simple-layout',
|
||||
factory: function SimpleLayout_Factory() { return new SimpleLayout(); },
|
||||
template: function SimpleLayout_Template(ctx: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
$r3$.ɵE(0, LifecycleComp);
|
||||
$r3$.ɵe();
|
||||
$r3$.ɵE(2, LifecycleComp);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
$r3$.ɵp(0, 'name', $r3$.ɵb(ctx.name1));
|
||||
$r3$.ɵp(2, 'name', $r3$.ɵb(ctx.name2));
|
||||
$c1$.h(1, 0);
|
||||
$c1$.h(3, 2);
|
||||
$r3$.ɵr(1, 0);
|
||||
$r3$.ɵr(3, 2);
|
||||
}
|
||||
});`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
const source = result.source;
|
||||
|
||||
expectEmit(source, LifecycleCompDefinition, 'Invalid LifecycleComp definition');
|
||||
expectEmit(source, SimpleLayoutDefinition, 'Invalid SimpleLayout definition');
|
||||
});
|
||||
});
|
||||
|
||||
describe('template variables', () => {
|
||||
const shared = {
|
||||
shared: {
|
||||
'for_of.ts': `
|
||||
import {Directive, Input, SimpleChanges, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||
|
||||
export interface ForOfContext {
|
||||
$implicit: any;
|
||||
index: number;
|
||||
even: boolean;
|
||||
odd: boolean;
|
||||
}
|
||||
|
||||
@Directive({selector: '[forOf]'})
|
||||
export class ForOfDirective {
|
||||
private previous: any[];
|
||||
|
||||
constructor(private view: ViewContainerRef, private template: TemplateRef<any>) {}
|
||||
|
||||
@Input() forOf: any[];
|
||||
|
||||
ngOnChanges(simpleChanges: SimpleChanges) {
|
||||
if ('forOf' in simpleChanges) {
|
||||
this.update();
|
||||
}
|
||||
}
|
||||
|
||||
ngDoCheck(): void {
|
||||
const previous = this.previous;
|
||||
const current = this.forOf;
|
||||
if (!previous || previous.length != current.length ||
|
||||
previous.some((value: any, index: number) => current[index] !== previous[index])) {
|
||||
this.update();
|
||||
}
|
||||
}
|
||||
|
||||
private update() {
|
||||
// TODO(chuckj): Not implemented yet
|
||||
// this.view.clear();
|
||||
if (this.forOf) {
|
||||
const current = this.forOf;
|
||||
for (let i = 0; i < current.length; i++) {
|
||||
const context = {$implicit: current[i], index: i, even: i % 2 == 0, odd: i % 2 == 1};
|
||||
// TODO(chuckj): Not implemented yet
|
||||
// this.view.createEmbeddedView(this.template, context);
|
||||
}
|
||||
this.previous = [...this.forOf];
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
it('should support a let variable and reference', () => {
|
||||
const files = {
|
||||
app: {
|
||||
...shared,
|
||||
'spec.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
import {ForOfDirective} from './shared/for_of';
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: \`<ul><li *for="let item of items">{{item.name}}</li></ul>\`
|
||||
})
|
||||
export class MyComponent {
|
||||
items = [{name: 'one'}, {name: 'two'}];
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [MyComponent, ForOfDirective]
|
||||
})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
// TODO(chuckj): Enforce this when the directives are specified
|
||||
const ForDirectiveDefinition = `
|
||||
static ngDirectiveDef = $r3$.ɵdefineDirective({
|
||||
type: ForOfDirective,
|
||||
factory: function ForOfDirective_Factory() {
|
||||
return new ForOfDirective($r3$.ɵinjectViewContainerRef(), $r3$.ɵinjectTemplateRef());
|
||||
},
|
||||
features: [$r3$.ɵNgOnChangesFeature(NgForOf)],
|
||||
inputs: {forOf: 'forOf'}
|
||||
});
|
||||
`;
|
||||
|
||||
const MyComponentDefinition = `
|
||||
const $c1$ = [ForOfDirective];
|
||||
…
|
||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: MyComponent,
|
||||
tag: 'my-component',
|
||||
factory: function MyComponent_Factory() { return new MyComponent(); },
|
||||
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
$r3$.ɵE(0, 'ul');
|
||||
$r3$.ɵC(1, $c1$, MyComponent_ForOfDirective_Template_1);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
$r3$.ɵp(1, 'forOf', $r3$.ɵb(ctx.items));
|
||||
ForOfDirective.ngDirectiveDef.h(2, 1);
|
||||
$r3$.ɵcR(1);
|
||||
$r3$.ɵr(2, 1);
|
||||
$r3$.ɵcr();
|
||||
|
||||
function MyComponent_ForOfDirective_Template_1(ctx0: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
$r3$.ɵE(0, 'li');
|
||||
$r3$.ɵT(1);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
const $item$ = ctx0.$implicit;
|
||||
$r3$.ɵt(1, $r3$.ɵi1('', $item$.name, ''));
|
||||
}
|
||||
}
|
||||
});
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
const source = result.source;
|
||||
|
||||
// TODO(chuckj): Enforce this when the directives are specified
|
||||
// expectEmit(source, ForDirectiveDefinition, 'Invalid directive definition');
|
||||
expectEmit(source, MyComponentDefinition, 'Invalid component definition');
|
||||
});
|
||||
|
||||
it('should support accessing parent template variables', () => {
|
||||
const files = {
|
||||
app: {
|
||||
...shared,
|
||||
'spec.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
import {ForOfDirective} from './shared/for_of';
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: \`
|
||||
<ul>
|
||||
<li *for="let item of items">
|
||||
<div>{{item.name}}</div>
|
||||
<ul>
|
||||
<li *for="let info of item.infos">
|
||||
{{item.name}}: {{info.description}}
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>\`
|
||||
})
|
||||
export class MyComponent {
|
||||
items = [
|
||||
{name: 'one', infos: [{description: '11'}, {description: '12'}]},
|
||||
{name: 'two', infos: [{description: '21'}, {description: '22'}]}
|
||||
];
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [MyComponent, ForOfDirective]
|
||||
})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const MyComponentDefinition = `
|
||||
const $c1$ = [ForOfDirective];
|
||||
const $c2$ = ForOfDirective.ngDirectiveDef;
|
||||
…
|
||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: MyComponent,
|
||||
tag: 'my-component',
|
||||
factory: function MyComponent_Factory() { return new MyComponent(); },
|
||||
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
$r3$.ɵE(0, 'ul');
|
||||
$r3$.ɵC(1, $c1$, MyComponent_ForOfDirective_Template_1);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
$r3$.ɵp(1, 'forOf', $r3$.ɵb(ctx.items));
|
||||
$c2$.h(2,1);
|
||||
$r3$.ɵcR(1);
|
||||
$r3$.ɵr(2, 1);
|
||||
$r3$.ɵcr();
|
||||
|
||||
function MyComponent_ForOfDirective_Template_1(ctx0: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
$r3$.ɵE(0, 'li');
|
||||
$r3$.ɵE(1, 'div');
|
||||
$r3$.ɵT(2);
|
||||
$r3$.ɵe();
|
||||
$r3$.ɵE(3, 'ul');
|
||||
$r3$.ɵC(4, $c1$, MyComponent_ForOfDirective_ForOfDirective_Template_4);
|
||||
$r3$.ɵe();
|
||||
$r3$.ɵe();
|
||||
}
|
||||
const $item$ = ctx0.$implicit;
|
||||
$r3$.ɵp(4, 'forOf', $r3$.ɵb(IDENT.infos));
|
||||
$c2$.h(5,4);
|
||||
$r3$.ɵt(2, $r3$.ɵi1('', IDENT.name, ''));
|
||||
$r3$.ɵcR(4);
|
||||
$r3$.ɵr(5, 4);
|
||||
$r3$.ɵcr();
|
||||
|
||||
function MyComponent_ForOfDirective_ForOfDirective_Template_4(
|
||||
ctx1: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
$r3$.ɵE(0, 'li');
|
||||
$r3$.ɵT(1);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
const $info$ = ctx1.$implicit;
|
||||
$r3$.ɵt(1, $r3$.ɵi2(' ', $item$.name, ': ', $info$.description, ' '));
|
||||
}
|
||||
}
|
||||
}
|
||||
});`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
const source = result.source;
|
||||
expectEmit(source, MyComponentDefinition, 'Invalid component definition');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -114,7 +114,7 @@ describe('r3_view_compiler', () => {
|
||||
}
|
||||
};
|
||||
|
||||
const bV_call = `$r3$.ɵbV([' ',ctx.list[0],' ',ctx.list[1],' ',ctx.list[2],' ',ctx.list[3],
|
||||
const bV_call = `$r3$.ɵiV([' ',ctx.list[0],' ',ctx.list[1],' ',ctx.list[2],' ',ctx.list[3],
|
||||
' ',ctx.list[4],' ',ctx.list[5],' ',ctx.list[6],' ',ctx.list[7],' ',ctx.list[8],
|
||||
' '])`;
|
||||
const result = compile(files, angularFiles);
|
||||
@ -122,626 +122,4 @@ describe('r3_view_compiler', () => {
|
||||
});
|
||||
});
|
||||
|
||||
/* These tests are codified version of the tests in compiler_canonical_spec.ts. Every
|
||||
* test in compiler_canonical_spec.ts should have a corresponding test here.
|
||||
*/
|
||||
describe('compiler conformance', () => {
|
||||
describe('elements', () => {
|
||||
it('should translate DOM structure', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: \`<div class="my-app" title="Hello">Hello <b>World</b>!</div>\`
|
||||
})
|
||||
export class MyComponent {}
|
||||
|
||||
@NgModule({declarations: [MyComponent]})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
// The factory should look like this:
|
||||
const factory = 'factory: function MyComponent_Factory() { return new MyComponent(); }';
|
||||
|
||||
// The template should look like this (where IDENT is a wild card for an identifier):
|
||||
const template = `
|
||||
const $c1$ = ['class', 'my-app', 'title', 'Hello'];
|
||||
…
|
||||
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
$r3$.ɵE(0, 'div', $c1$);
|
||||
$r3$.ɵT(1, 'Hello ');
|
||||
$r3$.ɵE(2, 'b');
|
||||
$r3$.ɵT(3, 'World');
|
||||
$r3$.ɵe();
|
||||
$r3$.ɵT(4, '!');
|
||||
$r3$.ɵe();
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
|
||||
expectEmit(result.source, factory, 'Incorrect factory');
|
||||
expectEmit(result.source, template, 'Incorrect template');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('components & directives', () => {
|
||||
it('should instantiate directives', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, Directive, NgModule} from '@angular/core';
|
||||
|
||||
@Component({selector: 'child', template: 'child-view'})
|
||||
export class ChildComponent {}
|
||||
|
||||
@Directive({selector: '[some-directive]'})
|
||||
export class SomeDirective {}
|
||||
|
||||
@Component({selector: 'my-component', template: '<child some-directive></child>!'})
|
||||
export class MyComponent {}
|
||||
|
||||
@NgModule({declarations: [ChildComponent, SomeDirective, MyComponent]})
|
||||
export class MyModule{}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
// ChildComponent definition should be:
|
||||
const ChildComponentDefinition = `
|
||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: ChildComponent,
|
||||
tag: 'child',
|
||||
factory: function ChildComponent_Factory() { return new ChildComponent(); },
|
||||
template: function ChildComponent_Template(ctx: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
$r3$.ɵT(0, 'child-view');
|
||||
}
|
||||
}
|
||||
});`;
|
||||
|
||||
// SomeDirective definition should be:
|
||||
const SomeDirectiveDefinition = `
|
||||
static ngDirectiveDef = $r3$.ɵdefineDirective({
|
||||
type: SomeDirective,
|
||||
factory: function SomeDirective_Factory() {return new SomeDirective(); }
|
||||
});
|
||||
`;
|
||||
|
||||
// MyComponent definition should be:
|
||||
const MyComponentDefinition = `
|
||||
const $c1$ = ['some-directive', ''];
|
||||
const $c2$ = [SomeDirective];
|
||||
…
|
||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: MyComponent,
|
||||
tag: 'my-component',
|
||||
factory: function MyComponent_Factory() { return new MyComponent(); },
|
||||
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
$r3$.ɵE(0, ChildComponent, IDENT, IDENT);
|
||||
$r3$.ɵe();
|
||||
$r3$.ɵT(3, '!');
|
||||
}
|
||||
ChildComponent.ngComponentDef.h(1, 0);
|
||||
SomeDirective.ngDirectiveDef.h(2, 0);
|
||||
$r3$.ɵr(1, 0);
|
||||
$r3$.ɵr(2, 0);
|
||||
}
|
||||
});
|
||||
`;
|
||||
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
const source = result.source;
|
||||
|
||||
expectEmit(source, ChildComponentDefinition, 'Incorrect ChildComponent.ngComponentDef');
|
||||
expectEmit(source, SomeDirectiveDefinition, 'Incorrect SomeDirective.ngDirectiveDef');
|
||||
expectEmit(source, MyComponentDefinition, 'Incorrect MyComponentDefinition.ngComponentDef');
|
||||
});
|
||||
|
||||
it('should support structural directives', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, Directive, NgModule, TemplateRef} from '@angular/core';
|
||||
|
||||
@Directive({selector: '[if]'})
|
||||
export class IfDirective {
|
||||
constructor(template: TemplateRef<any>) { }
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: '<ul #foo><li *if>{{salutation}} {{foo}}</li></ul>'
|
||||
})
|
||||
export class MyComponent {
|
||||
salutation = 'Hello';
|
||||
}
|
||||
|
||||
@NgModule({declarations: [IfDirective, MyComponent]})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const IfDirectiveDefinition = `
|
||||
static ngDirectiveDef = $r3$.ɵdefineDirective({
|
||||
type: IfDirective,
|
||||
factory: function IfDirective_Factory() { return new IfDirective($r3$.ɵinjectTemplateRef()); }
|
||||
});`;
|
||||
const MyComponentDefinition = `
|
||||
const $c1$ = ['foo', ''];
|
||||
const $c2$ = [IfDirective];
|
||||
…
|
||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: MyComponent,
|
||||
tag: 'my-component',
|
||||
factory: function MyComponent_Factory() { return new MyComponent(); },
|
||||
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
$r3$.ɵE(0, 'ul', null, null, $c1$);
|
||||
$r3$.ɵC(2, $c2$, MyComponent_IfDirective_Template_2);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
const $foo$ = $r3$.ɵm(1);
|
||||
IfDirective.ngDirectiveDef.h(3,2);
|
||||
$r3$.ɵcR(2);
|
||||
$r3$.ɵr(3,2);
|
||||
$r3$.ɵcr();
|
||||
|
||||
function MyComponent_IfDirective_Template_2(ctx0: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
$r3$.ɵE(0, 'li');
|
||||
$r3$.ɵT(1);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
$r3$.ɵt(1, $r3$.ɵb2('', ctx.salutation, ' ', $foo$, ''));
|
||||
}
|
||||
}
|
||||
});`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
const source = result.source;
|
||||
|
||||
expectEmit(source, IfDirectiveDefinition, 'Incorrect IfDirective.ngDirectiveDef');
|
||||
expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ngComponentDef');
|
||||
});
|
||||
|
||||
it('should support 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>'})
|
||||
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 MyApp {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const SimpleComponentDefinition = `
|
||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: SimpleComponent,
|
||||
tag: 'simple',
|
||||
factory: function SimpleComponent_Factory() { return new SimpleComponent(); },
|
||||
template: function SimpleComponent_Template(ctx: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
$r3$.ɵpD(0);
|
||||
$r3$.ɵE(1, 'div');
|
||||
$r3$.ɵP(2, 0);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
}
|
||||
});`;
|
||||
|
||||
const ComplexComponentDefinition = `
|
||||
const $c1$ = [[[['span', 'title', 'tofirst'], null]], [[['span', 'title', 'tosecond'], null]]];
|
||||
const $c2$ = ['id','first'];
|
||||
const $c3$ = ['id','second'];
|
||||
…
|
||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: ComplexComponent,
|
||||
tag: 'complex',
|
||||
factory: function ComplexComponent_Factory() { return new ComplexComponent(); },
|
||||
template: function ComplexComponent_Template(ctx: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
$r3$.ɵpD(0, $c1$);
|
||||
$r3$.ɵE(1, 'div', $c2$);
|
||||
$r3$.ɵP(2, 0, 1);
|
||||
$r3$.ɵe();
|
||||
$r3$.ɵE(3, 'div', $c3$);
|
||||
$r3$.ɵP(4, 0, 2);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
}
|
||||
});
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
const source = result.source;
|
||||
|
||||
expectEmit(result.source, SimpleComponentDefinition, 'Incorrect SimpleComponent definition');
|
||||
expectEmit(
|
||||
result.source, ComplexComponentDefinition, 'Incorrect ComplexComponent definition');
|
||||
});
|
||||
|
||||
it('local reference', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({selector: 'my-component', template: '<input #user>Hello {{user.value}}!'})
|
||||
export class MyComponent {}
|
||||
|
||||
@NgModule({declarations: [MyComponent]})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const MyComponentDefinition = `
|
||||
const $c1$ = ['user', ''];
|
||||
…
|
||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: MyComponent,
|
||||
tag: 'my-component',
|
||||
factory: function MyComponent_Factory() { return new MyComponent(); },
|
||||
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
$r3$.ɵE(0, 'input', null, null, $c1$);
|
||||
$r3$.ɵe();
|
||||
$r3$.ɵT(2);
|
||||
}
|
||||
const $user$ = $r3$.ɵm(1);
|
||||
$r3$.ɵt(2, $r3$.ɵb1('Hello ', $user$.value, '!'));
|
||||
}
|
||||
});
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
const source = result.source;
|
||||
|
||||
expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ngComponentDef');
|
||||
});
|
||||
|
||||
describe('lifecycle hooks', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, Input, NgModule} from '@angular/core';
|
||||
|
||||
let events: string[] = [];
|
||||
|
||||
@Component({selector: 'lifecycle-comp', template: ''})
|
||||
export class LifecycleComp {
|
||||
@Input('name') nameMin: string;
|
||||
|
||||
ngOnChanges() { events.push('changes' + this.nameMin); }
|
||||
|
||||
ngOnInit() { events.push('init' + this.nameMin); }
|
||||
ngDoCheck() { events.push('check' + this.nameMin); }
|
||||
|
||||
ngAfterContentInit() { events.push('content init' + this.nameMin); }
|
||||
ngAfterContentChecked() { events.push('content check' + this.nameMin); }
|
||||
|
||||
ngAfterViewInit() { events.push('view init' + this.nameMin); }
|
||||
ngAfterViewChecked() { events.push('view check' + this.nameMin); }
|
||||
|
||||
ngOnDestroy() { events.push(this.nameMin); }
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'simple-layout',
|
||||
template: \`
|
||||
<lifecycle-comp [name]="name1"></lifecycle-comp>
|
||||
<lifecycle-comp [name]="name2"></lifecycle-comp>
|
||||
\`
|
||||
})
|
||||
export class SimpleLayout {
|
||||
name1 = '1';
|
||||
name2 = '2';
|
||||
}
|
||||
|
||||
@NgModule({declarations: [LifecycleComp, SimpleLayout]})
|
||||
export class LifecycleModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
it('should gen hooks with a few simple components', () => {
|
||||
const LifecycleCompDefinition = `
|
||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: LifecycleComp,
|
||||
tag: 'lifecycle-comp',
|
||||
factory: function LifecycleComp_Factory() { return new LifecycleComp(); },
|
||||
template: function LifecycleComp_Template(ctx: IDENT, cm: IDENT) {},
|
||||
inputs: {nameMin: 'name'},
|
||||
features: [$r3$.ɵNgOnChangesFeature(LifecycleComp)]
|
||||
});`;
|
||||
|
||||
const SimpleLayoutDefinition = `
|
||||
const $c1$ = LifecycleComp.ngComponentDef;
|
||||
…
|
||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: SimpleLayout,
|
||||
tag: 'simple-layout',
|
||||
factory: function SimpleLayout_Factory() { return new SimpleLayout(); },
|
||||
template: function SimpleLayout_Template(ctx: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
$r3$.ɵE(0, LifecycleComp);
|
||||
$r3$.ɵe();
|
||||
$r3$.ɵE(2, LifecycleComp);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
$r3$.ɵp(0, 'name', $r3$.ɵb(ctx.name1));
|
||||
$r3$.ɵp(2, 'name', $r3$.ɵb(ctx.name2));
|
||||
$c1$.h(1, 0);
|
||||
$c1$.h(3, 2);
|
||||
$r3$.ɵr(1, 0);
|
||||
$r3$.ɵr(3, 2);
|
||||
}
|
||||
});`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
const source = result.source;
|
||||
|
||||
expectEmit(source, LifecycleCompDefinition, 'Invalid LifecycleComp definition');
|
||||
expectEmit(source, SimpleLayoutDefinition, 'Invalid SimpleLayout definition');
|
||||
});
|
||||
});
|
||||
|
||||
describe('template variables', () => {
|
||||
const shared = {
|
||||
shared: {
|
||||
'for_of.ts': `
|
||||
import {Directive, Input, SimpleChanges, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||
|
||||
export interface ForOfContext {
|
||||
$implicit: any;
|
||||
index: number;
|
||||
even: boolean;
|
||||
odd: boolean;
|
||||
}
|
||||
|
||||
@Directive({selector: '[forOf]'})
|
||||
export class ForOfDirective {
|
||||
private previous: any[];
|
||||
|
||||
constructor(private view: ViewContainerRef, private template: TemplateRef<any>) {}
|
||||
|
||||
@Input() forOf: any[];
|
||||
|
||||
ngOnChanges(simpleChanges: SimpleChanges) {
|
||||
if ('forOf' in simpleChanges) {
|
||||
this.update();
|
||||
}
|
||||
}
|
||||
|
||||
ngDoCheck(): void {
|
||||
const previous = this.previous;
|
||||
const current = this.forOf;
|
||||
if (!previous || previous.length != current.length ||
|
||||
previous.some((value: any, index: number) => current[index] !== previous[index])) {
|
||||
this.update();
|
||||
}
|
||||
}
|
||||
|
||||
private update() {
|
||||
// TODO(chuckj): Not implemented yet
|
||||
// this.view.clear();
|
||||
if (this.forOf) {
|
||||
const current = this.forOf;
|
||||
for (let i = 0; i < current.length; i++) {
|
||||
const context = {$implicit: current[i], index: i, even: i % 2 == 0, odd: i % 2 == 1};
|
||||
// TODO(chuckj): Not implemented yet
|
||||
// this.view.createEmbeddedView(this.template, context);
|
||||
}
|
||||
this.previous = [...this.forOf];
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
it('should support a let variable and reference', () => {
|
||||
const files = {
|
||||
app: {
|
||||
...shared,
|
||||
'spec.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
import {ForOfDirective} from './shared/for_of';
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: \`<ul><li *for="let item of items">{{item.name}}</li></ul>\`
|
||||
})
|
||||
export class MyComponent {
|
||||
items = [{name: 'one'}, {name: 'two'}];
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [MyComponent, ForOfDirective]
|
||||
})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
// TODO(chuckj): Enforce this when the directives are specified
|
||||
const ForDirectiveDefinition = `
|
||||
static ngDirectiveDef = $r3$.ɵdefineDirective({
|
||||
type: ForOfDirective,
|
||||
factory: function ForOfDirective_Factory() {
|
||||
return new ForOfDirective($r3$.ɵinjectViewContainerRef(), $r3$.ɵinjectTemplateRef());
|
||||
},
|
||||
features: [$r3$.ɵNgOnChangesFeature(NgForOf)],
|
||||
inputs: {forOf: 'forOf'}
|
||||
});
|
||||
`;
|
||||
|
||||
const MyComponentDefinition = `
|
||||
const $c1$ = [ForOfDirective];
|
||||
…
|
||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: MyComponent,
|
||||
tag: 'my-component',
|
||||
factory: function MyComponent_Factory() { return new MyComponent(); },
|
||||
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
$r3$.ɵE(0, 'ul');
|
||||
$r3$.ɵC(1, $c1$, MyComponent_ForOfDirective_Template_1);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
$r3$.ɵp(1, 'forOf', $r3$.ɵb(ctx.items));
|
||||
ForOfDirective.ngDirectiveDef.h(2, 1);
|
||||
$r3$.ɵcR(1);
|
||||
$r3$.ɵr(2, 1);
|
||||
$r3$.ɵcr();
|
||||
|
||||
function MyComponent_ForOfDirective_Template_1(ctx0: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
$r3$.ɵE(0, 'li');
|
||||
$r3$.ɵT(1);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
const $item$ = ctx0.$implicit;
|
||||
$r3$.ɵt(1, $r3$.ɵb1('', $item$.name, ''));
|
||||
}
|
||||
}
|
||||
});
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
const source = result.source;
|
||||
|
||||
// TODO(chuckj): Enforce this when the directives are specified
|
||||
// expectEmit(source, ForDirectiveDefinition, 'Invalid directive definition');
|
||||
expectEmit(source, MyComponentDefinition, 'Invalid component definition');
|
||||
});
|
||||
|
||||
it('should support accessing parent template variables', () => {
|
||||
const files = {
|
||||
app: {
|
||||
...shared,
|
||||
'spec.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
import {ForOfDirective} from './shared/for_of';
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: \`
|
||||
<ul>
|
||||
<li *for="let item of items">
|
||||
<div>{{item.name}}</div>
|
||||
<ul>
|
||||
<li *for="let info of item.infos">
|
||||
{{item.name}}: {{info.description}}
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>\`
|
||||
})
|
||||
export class MyComponent {
|
||||
items = [
|
||||
{name: 'one', infos: [{description: '11'}, {description: '12'}]},
|
||||
{name: 'two', infos: [{description: '21'}, {description: '22'}]}
|
||||
];
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [MyComponent, ForOfDirective]
|
||||
})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const MyComponentDefinition = `
|
||||
const $c1$ = [ForOfDirective];
|
||||
const $c2$ = ForOfDirective.ngDirectiveDef;
|
||||
…
|
||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: MyComponent,
|
||||
tag: 'my-component',
|
||||
factory: function MyComponent_Factory() { return new MyComponent(); },
|
||||
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
$r3$.ɵE(0, 'ul');
|
||||
$r3$.ɵC(1, $c1$, MyComponent_ForOfDirective_Template_1);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
$r3$.ɵp(1, 'forOf', $r3$.ɵb(ctx.items));
|
||||
$c2$.h(2,1);
|
||||
$r3$.ɵcR(1);
|
||||
$r3$.ɵr(2, 1);
|
||||
$r3$.ɵcr();
|
||||
|
||||
function MyComponent_ForOfDirective_Template_1(ctx0: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
$r3$.ɵE(0, 'li');
|
||||
$r3$.ɵE(1, 'div');
|
||||
$r3$.ɵT(2);
|
||||
$r3$.ɵe();
|
||||
$r3$.ɵE(3, 'ul');
|
||||
$r3$.ɵC(4, $c1$, MyComponent_ForOfDirective_ForOfDirective_Template_4);
|
||||
$r3$.ɵe();
|
||||
$r3$.ɵe();
|
||||
}
|
||||
const $item$ = ctx0.$implicit;
|
||||
$r3$.ɵp(4, 'forOf', $r3$.ɵb(IDENT.infos));
|
||||
$c2$.h(5,4);
|
||||
$r3$.ɵt(2, $r3$.ɵb1('', IDENT.name, ''));
|
||||
$r3$.ɵcR(4);
|
||||
$r3$.ɵr(5, 4);
|
||||
$r3$.ɵcr();
|
||||
|
||||
function MyComponent_ForOfDirective_ForOfDirective_Template_4(
|
||||
ctx1: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
$r3$.ɵE(0, 'li');
|
||||
$r3$.ɵT(1);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
const $info$ = ctx1.$implicit;
|
||||
$r3$.ɵt(1, $r3$.ɵb2(' ', $item$.name, ': ', $info$.description, ' '));
|
||||
}
|
||||
}
|
||||
}
|
||||
});`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
const source = result.source;
|
||||
expectEmit(source, MyComponentDefinition, 'Invalid component definition');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -21,6 +21,7 @@ export {
|
||||
PublicFeature as ɵPublicFeature,
|
||||
NgOnChangesFeature as ɵNgOnChangesFeature,
|
||||
CssSelector as ɵCssSelector,
|
||||
NC as ɵNC,
|
||||
C as ɵC,
|
||||
E as ɵE,
|
||||
L as ɵL,
|
||||
@ -29,40 +30,43 @@ export {
|
||||
Q as ɵQ,
|
||||
P as ɵP,
|
||||
b as ɵb,
|
||||
b1 as ɵb1,
|
||||
b2 as ɵb2,
|
||||
b3 as ɵb3,
|
||||
b4 as ɵb4,
|
||||
b5 as ɵb5,
|
||||
b6 as ɵb6,
|
||||
b7 as ɵb7,
|
||||
b8 as ɵb8,
|
||||
bV as ɵbV,
|
||||
i1 as ɵi1,
|
||||
i2 as ɵi2,
|
||||
i3 as ɵi3,
|
||||
i4 as ɵi4,
|
||||
i5 as ɵi5,
|
||||
i6 as ɵi6,
|
||||
i7 as ɵi7,
|
||||
i8 as ɵi8,
|
||||
iV as ɵiV,
|
||||
pb1 as ɵpb1,
|
||||
pb2 as ɵpb2,
|
||||
pb3 as ɵpb3,
|
||||
pb4 as ɵpb4,
|
||||
pbV as ɵpbV,
|
||||
o1 as ɵo1,
|
||||
o2 as ɵo2,
|
||||
o3 as ɵo3,
|
||||
o4 as ɵo4,
|
||||
o5 as ɵo5,
|
||||
o6 as ɵo6,
|
||||
o7 as ɵo7,
|
||||
o8 as ɵo8,
|
||||
oV as ɵoV,
|
||||
f0 as ɵf0,
|
||||
f1 as ɵf1,
|
||||
f2 as ɵf2,
|
||||
f3 as ɵf3,
|
||||
f4 as ɵf4,
|
||||
f5 as ɵf5,
|
||||
f6 as ɵf6,
|
||||
f7 as ɵf7,
|
||||
f8 as ɵf8,
|
||||
fV as ɵfV,
|
||||
cR as ɵcR,
|
||||
cr as ɵcr,
|
||||
qR as ɵqR,
|
||||
e as ɵe,
|
||||
p as ɵp,
|
||||
pD as ɵpD,
|
||||
a as ɵa,
|
||||
s as ɵs,
|
||||
t as ɵt,
|
||||
v as ɵv,
|
||||
r as ɵr,
|
||||
m as ɵm,
|
||||
st as ɵst,
|
||||
ld as ɵld,
|
||||
Pp as ɵPp,
|
||||
} from './render3/index';
|
||||
// clang-format on
|
||||
|
@ -23,3 +23,4 @@ export {StaticProvider, ValueProvider, ExistingProvider, FactoryProvider, Provid
|
||||
export {ResolvedReflectiveFactory, ResolvedReflectiveProvider} from './di/reflective_provider';
|
||||
export {ReflectiveKey} from './di/reflective_key';
|
||||
export {InjectionToken} from './di/injection_token';
|
||||
export {APP_ROOT_SCOPE} from './di/scope';
|
||||
|
@ -8,11 +8,7 @@
|
||||
|
||||
import {Type} from '../type';
|
||||
|
||||
import {Injectable, convertInjectableProviderToFactory, defineInjectable} from './injectable';
|
||||
import {ClassSansProvider, ExistingSansProvider, FactorySansProvider, StaticClassSansProvider, ValueSansProvider} from './provider';
|
||||
|
||||
export type InjectionTokenProvider = ValueSansProvider | ExistingSansProvider |
|
||||
FactorySansProvider | ClassSansProvider | StaticClassSansProvider;
|
||||
import {Injectable, defineInjectable} from './injectable';
|
||||
|
||||
/**
|
||||
* Creates a token that can be used in a DI Provider.
|
||||
@ -42,11 +38,11 @@ export class InjectionToken<T> {
|
||||
|
||||
readonly ngInjectableDef: Injectable|undefined;
|
||||
|
||||
constructor(protected _desc: string, options?: {scope: Type<any>}&InjectionTokenProvider) {
|
||||
constructor(protected _desc: string, options?: {scope: Type<any>, factory: () => T}) {
|
||||
if (options !== undefined) {
|
||||
this.ngInjectableDef = defineInjectable({
|
||||
scope: options.scope,
|
||||
factory: convertInjectableProviderToFactory(this as any, options),
|
||||
factory: options.factory,
|
||||
});
|
||||
} else {
|
||||
this.ngInjectableDef = undefined;
|
||||
|
27
packages/core/src/di/scope.ts
Normal file
27
packages/core/src/di/scope.ts
Normal file
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* @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 {Type} from '../type';
|
||||
import {InjectionToken} from './injection_token';
|
||||
|
||||
|
||||
// APP_ROOT_SCOPE is cast as a Type to allow for its usage as the scope parameter of @Injectable().
|
||||
|
||||
/**
|
||||
* A scope which targets the root injector.
|
||||
*
|
||||
* When specified as the `scope` parameter to `@Injectable` or `InjectionToken`, this special
|
||||
* scope indicates the provider for the service or token being configured belongs in the root
|
||||
* injector. This is loosely equivalent to the convention of having a `forRoot()` static
|
||||
* function within a module that configures the provider, and expecting users to only import that
|
||||
* module via its `forRoot()` function in the root injector.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export const APP_ROOT_SCOPE: Type<any> = new InjectionToken<boolean>(
|
||||
'The presence of this token marks an injector as being the root injector.') as any;
|
@ -693,13 +693,13 @@ export interface Component extends Directive {
|
||||
* - text nodes are left as-is inside HTML tags where whitespaces are significant (ex. `<pre>`,
|
||||
* `<textarea>`).
|
||||
*
|
||||
* Described transformations can (potentially) influence DOM nodes layout so the
|
||||
* `preserveWhitespaces` option is `true` be default (no whitespace removal).
|
||||
* In Angular 5 you need to opt-in for whitespace removal (but we might revisit the default
|
||||
* setting in Angular 6 or later). If you want to change the default setting for all components
|
||||
* in your application you can use the `preserveWhitespaces` option of the AOT compiler.
|
||||
* Described transformations may (potentially) influence DOM nodes layout. However, the impact
|
||||
* should so be minimal. That's why starting from Angular 6, the
|
||||
* `preserveWhitespaces` option is `false` by default (whitespace removal).
|
||||
* If you want to change the default setting for all components in your application you can use
|
||||
* the `preserveWhitespaces` option of the AOT compiler.
|
||||
*
|
||||
* Even if you decide to opt-in for whitespace removal there are ways of preserving whitespaces
|
||||
* Even with the default behavior of whitespace removal, there are ways of preserving whitespaces
|
||||
* in certain fragments of a template. You can either exclude entire DOM sub-tree by using the
|
||||
* `ngPreserveWhitespaces` attribute, ex.:
|
||||
*
|
||||
|
@ -15,9 +15,12 @@ import {GetterFn, MethodFn, SetterFn} from './types';
|
||||
|
||||
|
||||
/**
|
||||
* Attention: This regex has to hold even if the code is minified!
|
||||
* Attention: These regex has to hold even if the code is minified!
|
||||
*/
|
||||
export const DELEGATE_CTOR = /^function\s+\S+\(\)\s*{[\s\S]+\.apply\(this,\s*arguments\)/;
|
||||
export const INHERITED_CLASS = /^class\s+[A-Za-z\d$_]*\s*extends\s+[A-Za-z\d$_]+\s*{/;
|
||||
export const INHERITED_CLASS_WITH_CTOR =
|
||||
/^class\s+[A-Za-z\d$_]*\s*extends\s+[A-Za-z\d$_]+\s*{[\s\S]*constructor\s*\(/;
|
||||
|
||||
export class ReflectionCapabilities implements PlatformReflectionCapabilities {
|
||||
private _reflect: any;
|
||||
@ -57,6 +60,7 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
|
||||
}
|
||||
|
||||
private _ownParameters(type: Type<any>, parentCtor: any): any[][]|null {
|
||||
const typeStr = type.toString();
|
||||
// If we have no decorators, we only have function.length as metadata.
|
||||
// In that case, to detect whether a child class declared an own constructor or not,
|
||||
// we need to look inside of that constructor to check whether it is
|
||||
@ -64,7 +68,8 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
|
||||
// This also helps to work around for https://github.com/Microsoft/TypeScript/issues/12439
|
||||
// that sets 'design:paramtypes' to []
|
||||
// if a class inherits from another class but has no ctor declared itself.
|
||||
if (DELEGATE_CTOR.exec(type.toString())) {
|
||||
if (DELEGATE_CTOR.exec(typeStr) ||
|
||||
(INHERITED_CLASS.exec(typeStr) && !INHERITED_CLASS_WITH_CTOR.exec(typeStr))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -252,7 +257,7 @@ function convertTsickleDecoratorIntoMetadata(decoratorInvocations: any[]): any[]
|
||||
}
|
||||
|
||||
function getParentCtor(ctor: Function): Type<any> {
|
||||
const parentProto = Object.getPrototypeOf(ctor.prototype);
|
||||
const parentProto = ctor.prototype ? Object.getPrototypeOf(ctor.prototype) : null;
|
||||
const parentCtor = parentProto ? parentProto.constructor : null;
|
||||
// Note: We always use `Object` as the null value
|
||||
// to simplify checking later on.
|
||||
|
53
packages/core/src/render3/PERF_NOTES.md
Normal file
53
packages/core/src/render3/PERF_NOTES.md
Normal file
@ -0,0 +1,53 @@
|
||||
|
||||
## General Notes
|
||||
|
||||
Each Array costs 70 bytes and is composed of `Array` and `(array)` object
|
||||
* `Array` javascript visible object: 32 bytes
|
||||
* `(array)` VM object where the array is actually stored in: 38 bytes
|
||||
|
||||
Each Object cost is 24 bytes plus 8 bytes per property.
|
||||
|
||||
For small arrays, it is more efficient to store the data as a linked list
|
||||
of items rather than small arrays. However, the array access is faster as
|
||||
shown here: https://jsperf.com/small-arrays-vs-linked-objects
|
||||
|
||||
## Monomorphic vs Megamorphic code
|
||||
|
||||
Great read: [What's up with monomorphism?](http://mrale.ph/blog/2015/01/11/whats-up-with-monomorphism.html)
|
||||
|
||||
1) Monomorphic prop access is 100 times faster then megamorphic.
|
||||
2) Monomorphic call is 4 times faster the megamorphic call.
|
||||
|
||||
See benchmark [here](https://jsperf.com/mono-vs-megamorphic-property-access).
|
||||
|
||||
## Exporting top level variables
|
||||
|
||||
Exporting top level variables should be avoided where possible where performance
|
||||
and code size matters:
|
||||
|
||||
```
|
||||
// Typescript
|
||||
export let exported = 0;
|
||||
let notExported = 0;
|
||||
|
||||
notExported = exported;
|
||||
|
||||
// Would be compiled to
|
||||
exports.exported = 0;
|
||||
var notExported = 0;
|
||||
|
||||
notExported = exports.exported;
|
||||
```
|
||||
|
||||
Most minifiers do not rename properties (closure is an exception here).
|
||||
|
||||
What could be done instead is:
|
||||
|
||||
```
|
||||
let exported = 0;
|
||||
|
||||
export function getExported() { return exported; }
|
||||
export function setExported(v) { exported = v; }
|
||||
```
|
||||
|
||||
Also writing to a property of `exports` might change its hidden class resulting in megamorphic access.
|
@ -10,58 +10,48 @@
|
||||
// about state in an instruction are correct before implementing any logic.
|
||||
// They are meant only to be called in dev mode as sanity checks.
|
||||
|
||||
/**
|
||||
* Stringifies values such that strings are wrapped in explicit quotation marks and
|
||||
* other types are stringified normally. Used in error messages (e.g. assertThrow)
|
||||
* to make it clear that certain values are of the string type when comparing.
|
||||
*
|
||||
* e.g. `expected "3" to be 3` is easier to understand than `expected 3 to be 3`.
|
||||
*
|
||||
* @param value The value to be stringified
|
||||
* @returns The stringified value
|
||||
*/
|
||||
function stringifyValueForError(value: any): string {
|
||||
if (value && value.native && value.native.outerHTML) {
|
||||
return value.native.outerHTML;
|
||||
export function assertNumber(actual: any, msg: string) {
|
||||
if (typeof actual != 'number') {
|
||||
throwError(msg);
|
||||
}
|
||||
return typeof value === 'string' ? `"${value}"` : value;
|
||||
}
|
||||
|
||||
export function assertNumber(actual: any, name: string) {
|
||||
(typeof actual != 'number') && assertThrow(actual, 'number', name, 'typeof ==');
|
||||
export function assertEqual<T>(actual: T, expected: T, msg: string) {
|
||||
if (actual != expected) {
|
||||
throwError(msg);
|
||||
}
|
||||
}
|
||||
|
||||
export function assertEqual<T>(
|
||||
actual: T, expected: T, name: string, serializer?: ((v: T) => string)) {
|
||||
(actual != expected) && assertThrow(actual, expected, name, '==', serializer);
|
||||
export function assertNotEqual<T>(actual: T, expected: T, msg: string) {
|
||||
if (actual == expected) {
|
||||
throwError(msg);
|
||||
}
|
||||
}
|
||||
|
||||
export function assertLessThan<T>(actual: T, expected: T, name: string) {
|
||||
(actual >= expected) && assertThrow(actual, expected, name, '<');
|
||||
export function assertSame<T>(actual: T, expected: T, msg: string) {
|
||||
if (actual !== expected) {
|
||||
throwError(msg);
|
||||
}
|
||||
}
|
||||
|
||||
export function assertNotNull<T>(actual: T, name: string) {
|
||||
assertNotEqual(actual, null, name);
|
||||
export function assertLessThan<T>(actual: T, expected: T, msg: string) {
|
||||
if (actual >= expected) {
|
||||
throwError(msg);
|
||||
}
|
||||
}
|
||||
|
||||
export function assertNotEqual<T>(actual: T, expected: T, name: string) {
|
||||
(actual == expected) && assertThrow(actual, expected, name, '!=');
|
||||
export function assertNull<T>(actual: T, msg: string) {
|
||||
if (actual != null) {
|
||||
throwError(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an error with a message constructed from the arguments.
|
||||
*
|
||||
* @param actual The actual value (e.g. 3)
|
||||
* @param expected The expected value (e.g. 5)
|
||||
* @param name The name of the value being checked (e.g. attrs.length)
|
||||
* @param operator The comparison operator (e.g. <, >, ==)
|
||||
* @param serializer Function that maps a value to its display value
|
||||
*/
|
||||
export function assertThrow<T>(
|
||||
actual: T, expected: T, name: string, operator: string,
|
||||
serializer: ((v: T) => string) = stringifyValueForError): never {
|
||||
const error =
|
||||
`ASSERT: expected ${name} ${operator} ${serializer(expected)} but was ${serializer(actual)}!`;
|
||||
debugger; // leave `debugger` here to aid in debugging.
|
||||
throw new Error(error);
|
||||
export function assertNotNull<T>(actual: T, msg: string) {
|
||||
if (actual == null) {
|
||||
throwError(msg);
|
||||
}
|
||||
}
|
||||
|
||||
function throwError(msg: string): never {
|
||||
throw new Error(`ASSERTION ERROR: ${msg}`);
|
||||
}
|
@ -17,6 +17,7 @@ import {NG_HOST_SYMBOL, createError, createLView, createTView, directiveCreate,
|
||||
import {ComponentDef, ComponentType} from './interfaces/definition';
|
||||
import {LElementNode} from './interfaces/node';
|
||||
import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
|
||||
import {RootContext} from './interfaces/view';
|
||||
import {notImplemented, stringify} from './util';
|
||||
|
||||
|
||||
@ -43,6 +44,19 @@ export interface CreateComponentOptions {
|
||||
* Example: PublicFeature is a function that makes the component public to the DI system.
|
||||
*/
|
||||
features?: (<T>(component: T, componentDef: ComponentDef<T>) => void)[];
|
||||
|
||||
/**
|
||||
* A function which is used to schedule change detection work in the future.
|
||||
*
|
||||
* When marking components as dirty, it is necessary to schedule the work of
|
||||
* change detection in the future. This is done to coalesce multiple
|
||||
* {@link markDirty} calls into a single changed detection processing.
|
||||
*
|
||||
* The default value of the scheduler is the `requestAnimationFrame` function.
|
||||
*
|
||||
* It is also useful to override this function for testing purposes.
|
||||
*/
|
||||
scheduler?: (work: () => void) => void;
|
||||
}
|
||||
|
||||
|
||||
@ -155,11 +169,22 @@ export const NULL_INJECTOR: Injector = {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A permanent marker promise which signifies that the current CD tree is
|
||||
* clean.
|
||||
*/
|
||||
const CLEAN_PROMISE = Promise.resolve(null);
|
||||
|
||||
/**
|
||||
* Bootstraps a Component into an existing host element and returns an instance
|
||||
* of the component.
|
||||
*
|
||||
* Use this function to bootstrap a component into the DOM tree. Each invocation
|
||||
* of this function will create a separate tree of components, injectors and
|
||||
* change detection cycles and lifetimes. To dynamically insert a new component
|
||||
* into an existing tree such that it shares the same injection, change detection
|
||||
* and object lifetime, use {@link ViewContainer#createComponent}.
|
||||
*
|
||||
* @param componentType Component to bootstrap
|
||||
* @param options Optional parameters which control bootstrapping
|
||||
*/
|
||||
@ -170,15 +195,23 @@ export function renderComponent<T>(
|
||||
if (componentDef.type != componentType) componentDef.type = componentType;
|
||||
let component: T;
|
||||
const hostNode = locateHostElement(rendererFactory, opts.host || componentDef.tag);
|
||||
const rootContext: RootContext = {
|
||||
// Incomplete initialization due to circular reference.
|
||||
component: null !,
|
||||
scheduler: opts.scheduler || requestAnimationFrame,
|
||||
clean: CLEAN_PROMISE,
|
||||
};
|
||||
const oldView = enterView(
|
||||
createLView(
|
||||
-1, rendererFactory.createRenderer(hostNode, componentDef.rendererType), createTView()),
|
||||
-1, rendererFactory.createRenderer(hostNode, componentDef.rendererType), createTView(),
|
||||
null, rootContext),
|
||||
null !);
|
||||
try {
|
||||
// Create element node at index 0 in data array
|
||||
hostElement(hostNode, componentDef);
|
||||
// Create directive instance with n() and store at index 1 in data array (el is 0)
|
||||
component = getDirectiveInstance(directiveCreate(1, componentDef.n(), componentDef));
|
||||
component = rootContext.component =
|
||||
getDirectiveInstance(directiveCreate(1, componentDef.n(), componentDef));
|
||||
} finally {
|
||||
leaveView(oldView);
|
||||
}
|
||||
@ -188,27 +221,120 @@ export function renderComponent<T>(
|
||||
return component;
|
||||
}
|
||||
|
||||
export function detectChanges<T>(component: T) {
|
||||
ngDevMode && assertNotNull(component, 'component');
|
||||
const hostNode = (component as any)[NG_HOST_SYMBOL] as LElementNode;
|
||||
if (ngDevMode && !hostNode) {
|
||||
createError('Not a directive instance', component);
|
||||
}
|
||||
ngDevMode && assertNotNull(hostNode.data, 'hostNode.data');
|
||||
/**
|
||||
* Synchronously perform change detection on a component (and possibly its sub-components).
|
||||
*
|
||||
* This function triggers change detection in a synchronous way on a component. There should
|
||||
* be very little reason to call this function directly since a preferred way to do change
|
||||
* detection is to {@link markDirty} the component and wait for the scheduler to call this method
|
||||
* at some future point in time. This is because a single user action often results in many
|
||||
* components being invalidated and calling change detection on each component synchronously
|
||||
* would be inefficient. It is better to wait until all components are marked as dirty and
|
||||
* then perform single change detection across all of the components
|
||||
*
|
||||
* @param component The component which the change detection should be performed on.
|
||||
*/
|
||||
export function detectChanges<T>(component: T): void {
|
||||
const hostNode = _getComponentHostLElementNode(component);
|
||||
ngDevMode && assertNotNull(hostNode.data, 'Component host node should be attached to an LView');
|
||||
renderComponentOrTemplate(hostNode, hostNode.view, component);
|
||||
isDirty = false;
|
||||
}
|
||||
|
||||
let isDirty = false;
|
||||
export function markDirty<T>(
|
||||
component: T, scheduler: (fn: () => void) => void = requestAnimationFrame) {
|
||||
ngDevMode && assertNotNull(component, 'component');
|
||||
if (!isDirty) {
|
||||
isDirty = true;
|
||||
scheduler(() => detectChanges(component));
|
||||
/**
|
||||
* Mark the component as dirty (needing change detection).
|
||||
*
|
||||
* Marking a component dirty will schedule a change detection on this
|
||||
* component at some point in the future. Marking an already dirty
|
||||
* component as dirty is a noop. Only one outstanding change detection
|
||||
* can be scheduled per component tree. (Two components bootstrapped with
|
||||
* separate `renderComponent` will have separate schedulers)
|
||||
*
|
||||
* When the root component is bootstrapped with `renderComponent` a scheduler
|
||||
* can be provided.
|
||||
*
|
||||
* @param component Component to mark as dirty.
|
||||
*/
|
||||
export function markDirty<T>(component: T) {
|
||||
const rootContext = getRootContext(component);
|
||||
if (rootContext.clean == CLEAN_PROMISE) {
|
||||
let res: null|((val: null) => void);
|
||||
rootContext.clean = new Promise<null>((r) => res = r);
|
||||
rootContext.scheduler(() => {
|
||||
detectChanges(rootContext.component);
|
||||
res !(null);
|
||||
rootContext.clean = CLEAN_PROMISE;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function getHostElement<T>(component: T): RElement {
|
||||
return ((component as any)[NG_HOST_SYMBOL] as LElementNode).native;
|
||||
/**
|
||||
* Retrieve the root component of any component by walking the parent `LView` until
|
||||
* reaching the root `LView`.
|
||||
*
|
||||
* @param component any component
|
||||
*/
|
||||
function getRootContext(component: any): RootContext {
|
||||
ngDevMode && assertNotNull(component, 'component');
|
||||
const lElementNode = _getComponentHostLElementNode(component);
|
||||
let lView = lElementNode.view;
|
||||
while (lView.parent) {
|
||||
lView = lView.parent;
|
||||
}
|
||||
const rootContext = lView.context as RootContext;
|
||||
ngDevMode && assertNotNull(rootContext, 'rootContext');
|
||||
return rootContext;
|
||||
}
|
||||
|
||||
function _getComponentHostLElementNode<T>(component: T): LElementNode {
|
||||
ngDevMode && assertNotNull(component, 'expecting component got null');
|
||||
const lElementNode = (component as any)[NG_HOST_SYMBOL] as LElementNode;
|
||||
ngDevMode && assertNotNull(component, 'object is not a component');
|
||||
return lElementNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the host element of the component.
|
||||
*
|
||||
* Use this function to retrieve the host element of the component. The host
|
||||
* element is the element which the component is associated with.
|
||||
*
|
||||
* @param component Component for which the host element should be retrieved.
|
||||
*/
|
||||
export function getHostElement<T>(component: T): HTMLElement {
|
||||
return _getComponentHostLElementNode(component).native as any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the rendered text for a given component.
|
||||
*
|
||||
* This function retrieves the host element of a component and
|
||||
* and then returns the `textContent` for that element. This implies
|
||||
* that the text returned will include re-projected content of
|
||||
* the component as well.
|
||||
*
|
||||
* @param component The component to return the content text for.
|
||||
*/
|
||||
export function getRenderedText(component: any): string {
|
||||
const hostElement = getHostElement(component);
|
||||
return hostElement.textContent || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait on component until it is rendered.
|
||||
*
|
||||
* This function returns a `Promise` which is resolved when the component's
|
||||
* change detection is executed. This is determined by finding the scheduler
|
||||
* associated with the `component`'s render tree and waiting until the scheduler
|
||||
* flushes. If nothing is scheduled, the function returns a resolved promise.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* await whenRendered(myComponent);
|
||||
* ```
|
||||
*
|
||||
* @param component Component to wait upon
|
||||
* @returns Promise which resolves when the component is rendered.
|
||||
*/
|
||||
export function whenRendered(component: any): Promise<null> {
|
||||
return getRootContext(component).clean;
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ export function defineComponent<T>(componentDefinition: ComponentDefArgs<T>): Co
|
||||
tag: (componentDefinition as ComponentDefArgs<T>).tag || null !,
|
||||
template: (componentDefinition as ComponentDefArgs<T>).template || null !,
|
||||
h: componentDefinition.hostBindings || noop,
|
||||
attributes: componentDefinition.attributes || null,
|
||||
inputs: invertObject(componentDefinition.inputs),
|
||||
outputs: invertObject(componentDefinition.outputs),
|
||||
methods: invertObject(componentDefinition.methods),
|
||||
@ -177,4 +178,4 @@ export function definePipe<T>(
|
||||
{type, factory, pure}: {type: Type<T>, factory: () => PipeTransform, pure?: boolean}):
|
||||
PipeDef<T> {
|
||||
throw new Error('TODO: implement!');
|
||||
}
|
||||
}
|
||||
|
@ -6,12 +6,14 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {createComponentRef, detectChanges, getHostElement, markDirty, renderComponent} from './component';
|
||||
import {createComponentRef, detectChanges, getHostElement, getRenderedText, markDirty, renderComponent, whenRendered} from './component';
|
||||
import {NgOnChangesFeature, PublicFeature, defineComponent, defineDirective, definePipe} from './definition';
|
||||
import {InjectFlags} from './di';
|
||||
import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFlags, DirectiveType} from './interfaces/definition';
|
||||
|
||||
export {InjectFlags, QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF, inject, injectElementRef, injectTemplateRef, injectViewContainerRef} from './di';
|
||||
export {CssSelector} from './interfaces/projection';
|
||||
|
||||
|
||||
// Naming scheme:
|
||||
// - Capital letters are for creating things: T(Text), E(Element), D(Directive), V(View),
|
||||
@ -27,15 +29,15 @@ export {
|
||||
NO_CHANGE as NC,
|
||||
|
||||
bind as b,
|
||||
bind1 as b1,
|
||||
bind2 as b2,
|
||||
bind3 as b3,
|
||||
bind4 as b4,
|
||||
bind5 as b5,
|
||||
bind6 as b6,
|
||||
bind7 as b7,
|
||||
bind8 as b8,
|
||||
bindV as bV,
|
||||
interpolation1 as i1,
|
||||
interpolation2 as i2,
|
||||
interpolation3 as i3,
|
||||
interpolation4 as i4,
|
||||
interpolation5 as i5,
|
||||
interpolation6 as i6,
|
||||
interpolation7 as i7,
|
||||
interpolation8 as i8,
|
||||
interpolationV as iV,
|
||||
|
||||
componentRefresh as r,
|
||||
|
||||
@ -51,7 +53,8 @@ export {
|
||||
elementStyle as s,
|
||||
|
||||
listener as L,
|
||||
memory as m,
|
||||
store as st,
|
||||
load as ld,
|
||||
|
||||
projection as P,
|
||||
projectionDef as pD,
|
||||
@ -79,16 +82,17 @@ export {
|
||||
queryRefresh as qR,
|
||||
} from './query';
|
||||
export {
|
||||
objectLiteral1 as o1,
|
||||
objectLiteral2 as o2,
|
||||
objectLiteral3 as o3,
|
||||
objectLiteral4 as o4,
|
||||
objectLiteral5 as o5,
|
||||
objectLiteral6 as o6,
|
||||
objectLiteral7 as o7,
|
||||
objectLiteral8 as o8,
|
||||
objectLiteralV as oV,
|
||||
} from './object_literal';
|
||||
pureFunction0 as f0,
|
||||
pureFunction1 as f1,
|
||||
pureFunction2 as f2,
|
||||
pureFunction3 as f3,
|
||||
pureFunction4 as f4,
|
||||
pureFunction5 as f5,
|
||||
pureFunction6 as f6,
|
||||
pureFunction7 as f7,
|
||||
pureFunction8 as f8,
|
||||
pureFunctionV as fV,
|
||||
} from './pure_function';
|
||||
|
||||
|
||||
// clang-format on
|
||||
@ -105,6 +109,11 @@ export {
|
||||
defineComponent,
|
||||
defineDirective,
|
||||
definePipe,
|
||||
detectChanges,
|
||||
createComponentRef,
|
||||
getHostElement,
|
||||
getRenderedText,
|
||||
markDirty,
|
||||
renderComponent,
|
||||
whenRendered,
|
||||
};
|
||||
export {createComponentRef, detectChanges, getHostElement, markDirty, renderComponent};
|
||||
export {CssSelector} from './interfaces/projection';
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user