Skip to content

Commit

Permalink
build: add rules for generating block/element API data (angular#52480)
Browse files Browse the repository at this point in the history
Adds build rules for "artificially" generating `DocEntry` collections for block and element APIs. The two rules are very similar, but _just_ different enough that it's worth having two separate implementations.

PR Close angular#52480
  • Loading branch information
jelbourn authored and alxhub committed Nov 2, 2023
1 parent a3abe16 commit 64db486
Show file tree
Hide file tree
Showing 18 changed files with 613 additions and 3 deletions.
3 changes: 2 additions & 1 deletion .pullapprove.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1168,10 +1168,11 @@ groups:
'tools/esm-interop/**/{*,.*}',
'tools/gulp-tasks/**/{*,.*}',
'tools/legacy-saucelabs/**/{*,.*}',
'tools/manual_api_docs/**/{*,.*}',
'tools/npm-patches/**/{*,.*}',
'tools/rxjs/**/{*,.*}',
'tools/saucelabs/**/{*,.*}',
'tools/saucelabs-daemon/**/{*,.*}',
'tools/saucelabs/**/{*,.*}',
'tools/symbol-extractor/**/{*,.*}',
'tools/testing/**/{*,.*}',
'tools/tslint/**/{*,.*}',
Expand Down
37 changes: 37 additions & 0 deletions tools/manual_api_docs/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
load("//tools:defaults.bzl", "nodejs_binary", "ts_library")

package(default_visibility = ["//visibility:public"])

ts_library(
name = "generate_element_api_json_lib",
srcs = ["generate_element_api_json.ts"],
deps = [
"//packages/compiler-cli",
"@npm//@types/node",
],
)

nodejs_binary(
name = "generate_element_api_json",
data = [
":generate_element_api_json_lib",
],
entry_point = ":generate_element_api_json.ts",
)

ts_library(
name = "generate_block_api_json_lib",
srcs = ["generate_block_api_json.ts"],
deps = [
"//packages/compiler-cli",
"@npm//@types/node",
],
)

nodejs_binary(
name = "generate_block_api_json",
data = [
":generate_block_api_json_lib",
],
entry_point = ":generate_block_api_json.ts",
)
6 changes: 6 additions & 0 deletions tools/manual_api_docs/blocks/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
load("//tools/manual_api_docs:generate_block_api_json.bzl", "generate_block_api_json")

generate_block_api_json(
name = "blocks",
srcs = glob(["*.md"]),
)
63 changes: 63 additions & 0 deletions tools/manual_api_docs/blocks/defer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
A type of [block](api/core/defer) that can be used to defer load the JavaScript for components,
directives and pipes used inside a component template.

## Syntax

```html
@defer ( on <trigger>; when <condition>; prefetch on <trigger>; prefetch when <condition> ) {
<!-- deferred template fragment -->
<calendar-cmp />
} @placeholder ( minimum? <duration> ) {
<!-- placeholder template fragment -->
<p>Placeholder</p>
} @loading ( minimum? <duration>; after? <duration> ) {
<!-- loading template fragment -->
<img alt="loading image" src="loading.gif" />
} @error {
<!-- error template fragment -->
<p>An loading error occured</p>
}
```

## Description

### Blocks

Supported sections of a defer block. Note: only the @defer block template fragment is deferred
loaded. The remaining optional blocks are eagerly loaded.

| block | Description |
|----------------|----------------------------------------------------------|
| `@defer` | The defer loaded block of content |
| `@placeholder` | Content shown prior to defer loading (Optional) |
| `@loading` | Content shown during defer loading (Optional) |
| `@error` | Content shown when defer loading errors occur (Optional) |

<h3>Triggers</h3>

Triggers provide conditions for when defer loading occurs. Some allow a template reference variable
as an optional parameter. Separate multiple triggers with a semicolon.

| trigger | Triggers... |
|---------------------------------|-----------------------------------------------|
| `on idle` | when the browser reports idle state (default) |
| `on viewport(<elementRef>?)` | when the element enters the viewport |
| `on interaction(<elementRef>?)` | when clicked, touched, or focused |
| `on hover(<elementRef>?)` | when element has been hovered |
| `on immediate` | when the page finishes rendering |
| `on timer(<duration>)` | after a specific timeout |
| `when <condition>` | on a custom condition |

<h2>Prefetch</h2>

Configures prefetching of the defer block used in the `@defer` parameters, but does not affect
rendering. Rendering is handled by the standard `on` and `when` conditions. Separate multiple
prefetch configurations with a semicolon.

```html
@defer (prefetch on <trigger>; prefetch when <condition>) {
<!-- deferred template fragment -->
}
```

Learn more in the [defer loading guide](guide/defer).
54 changes: 54 additions & 0 deletions tools/manual_api_docs/blocks/for.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
The `@for` block repeatedly renders content of a block for each item in a collection.

## Syntax

```html
@for (item of items; track item.name) {
<li> {{ item.name }} </li>
} @empty {
<li> There are no items. </li>
}
```

## Description

The `@for` block renders its content in response to changes in a collection. Collections can be any
JavaScript [iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols),
but there are performance advantages of using a regular `Array`.

You can optionally include an `@empty` section immediately after the `@for` block content. The
content of the `@empty` block displays when there are no items.

<h3> track and objects identity </h3>

The value of the `track` expression determines a key used to associate array items with the views in
the DOM. Having clear indication of the item identity allows Angular to execute a minimal set of DOM
operations as items are added, removed or moved in a collection.

Loops over immutable data without `trackBy` as one of the most common causes for performance issues
across Angular applications. Because of the potential for poor performance, the `track` expression
is required for the `@for` loops. When in doubt, using `track $index` is a good default.

<h3> `$index` and other contextual variables </h3>

Inside `@for` contents, several implicit variables are always available:

| Variable | Meaning |
| -------- | ------- |
| `$count` | Number of items in a collection iterated over |
| `$index` | Index of the current row |
| `$first` | Whether the current row is the first row |
| `$last` | Whether the current row is the last row |
| `$even` | Whether the current row index is even |
| `$odd` | Whether the current row index is odd |

These variables are always available with these names, but can be aliased via a `let` segment:

```html
@for (item of items; track item.id; let idx = $index, e = $even) {
Item #{{ idx }}: {{ item.name }}
}
```

The aliasing is especially useful in case of using nested `@for` blocks where contextual variable
names could collide.
27 changes: 27 additions & 0 deletions tools/manual_api_docs/blocks/if.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
The `@if` block conditionally displays its content when its condition expression is truthy.

## Syntax

```html
@if (a > b) {
{{a}} is greater than {{b}}
} @else if (b > a) {
{{a}} is less than {{b}}
} @else {
{{a}} is equal to {{b}}
}
```

## Description

Content is added and removed from the DOM based on the evaluation of conditional expressions in
the `@if` and `@else` blocks.

The built-in `@if` supports referencing of expression results to keep a solution for common coding
patterns:

```html
@if (users$ | async; as users) {
{{ users.length }}
}
```
29 changes: 29 additions & 0 deletions tools/manual_api_docs/blocks/switch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
The `@switch` block is inspired by the JavaScript `switch` statement:

## Syntax

```html
@switch (condition) {
@case (caseA) {
Case A.
}
@case (caseB) {
Case B.
}
@default {
Default case.
}
}
```

## Description

The `@switch` blocks displays content selected by one of the cases matching against the conditional
expression. The value of the conditional expression is compared to the case expression using
the `===` operator.

The `@default` block is optional and can be omitted. If no `@case` matches the expression and there
is no `@default` block, nothing is shown.

**`@switch` does not have fallthrough**, so you do not need an equivalent to a `break` or `return`
statement.
6 changes: 6 additions & 0 deletions tools/manual_api_docs/elements/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
load("//tools/manual_api_docs:generate_element_api_json.bzl", "generate_element_api_json")

generate_element_api_json(
name = "elements",
srcs = glob(["*.md"]),
)
119 changes: 119 additions & 0 deletions tools/manual_api_docs/elements/ng-container.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
A special element that can hold structural directives without adding new elements to the DOM.

The `<ng-container>` allows us to use structural directives without any extra element, making sure
that the only DOM changes being applied are those dictated by the directives themselves.

This not only increases performance \(even so slightly\) since the browser ends up rendering less
elements but can also be a valuable asset in having cleaner DOMs and styles alike.

It can for example enable us to use structural directives without breaking styling dependent on a
precise DOM structure \(as for example the ones we get when using flex containers, margins, the
child combinator selector, etc.\).

## Usage notes

### With `*NgIf`s

One common use case of `<ng-container>` is alongside the `*ngIf` structural directive. By using the
special element we can produce very clean templates easy to understand and work with.

For example, we may want to have a number of elements shown conditionally but they do not need to be
all under the same root element. That can be easily done by wrapping them in such a block:

<code-example format="html" language="html">

&lt;ng-container *ngIf="condition"&gt;
&hellip;
&lt;/ng-container&gt;

</code-example>

This can also be augmented with an `else` statement alongside an `<ng-template>` as:

<code-example format="html" language="html">

&lt;ng-container *ngIf="condition; else templateA"&gt;
&hellip;
&lt;/ng-container&gt;
&lt;ng-template #templateA&gt;
&hellip;
&lt;/ng-template&gt;

</code-example>

### Combination of multiple structural directives

Multiple structural directives cannot be used on the same element; if you need to take advantage of
more than one structural directive, it is advised to use an `<ng-container>` per structural
directive.

The most common scenario is with `*ngIf` and `*ngFor`. For example, let's imagine that we have a
list of items but each item needs to be displayed only if a certain condition is true. We could be
tempted to try something like:

<code-example format="html" language="html">

&lt;ul&gt;
&lt;li *ngFor="let item of items" *ngIf="item.isValid"&gt;
{{ item.name }}
&lt;/li&gt;
&lt;/ul&gt;

</code-example>

As we said that would not work, what we can do is to simply move one of the structural directives to
an `<ng-container>` element, which would then wrap the other one, like so:

<code-example format="html" language="html">

&lt;ul&gt;
&lt;ng-container *ngFor="let item of items"&gt;
&lt;li *ngIf="item.isValid"&gt;
{{ item.name }}
&lt;/li&gt;
&lt;/ng-container&gt;
&lt;/ul&gt;

</code-example>

This would work as intended without introducing any new unnecessary elements in the DOM.

For more information see [one structural directive per element](guide/structural-directives#one-per-element).

### Use alongside ngTemplateOutlet

The `NgTemplateOutlet` directive can be applied to any element but most of the time it's applied
to `<ng-container>` ones. By combining the two, we get a very clear and easy to follow HTML and DOM
structure in which no extra elements are necessary and template views are instantiated where
requested.

For example, imagine a situation in which we have a large HTML, in which a small portion needs to be
repeated in different places. A simple solution is to define an `<ng-template>` containing our
repeating HTML and render that where necessary by using `<ng-container>` alongside
an `NgTemplateOutlet`.

Like so:

<code-example format="html" language="html">

&lt;!-- &hellip; --&gt;

&lt;ng-container *ngTemplateOutlet="tmpl; context: {&dollar;implicit: 'Hello'}"&gt;
&lt;/ng-container&gt;

&lt;!-- &hellip; --&gt;

&lt;ng-container *ngTemplateOutlet="tmpl; context: {&dollar;implicit: 'World'}"&gt;
&lt;/ng-container&gt;

&lt;!-- &hellip; --&gt;

&lt;ng-template #tmpl let-text&gt;
&lt;h1&gt;{{ text }}&lt;/h1&gt;
&lt;/ng-template&gt;

</code-example>

For more information regarding `NgTemplateOutlet`, see
the [`NgTemplateOutlet`s api documentation page](api/common/NgTemplateOutlet).

12 changes: 12 additions & 0 deletions tools/manual_api_docs/elements/ng-content.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
The `<ng-content>` element specifies where to project content inside a component template.

## Attributes

| Attribute | Description |
|---------------|-------------------------------------------------------------------------|
| `select` | CSS selector. Matching elements are projected into this `<ng-content>`. |

Only select elements from the projected content that match the given CSS `selector`.

Angular supports [selectors](https://developer.mozilla.org/docs/Web/CSS/CSS_Selectors) for any
combination of tag name, attribute, CSS class, and the `:not` pseudo-class.
Loading

0 comments on commit 64db486

Please sign in to comment.