docs: add deferrable views tutorial (#57934)

Add a new tutorial "Deferrable Views" to the tutorials page.

PR Close #57934
This commit is contained in:
Thanh Truong 2024-09-23 14:29:00 -05:00 committed by Paul Gschwendtner
parent b9d846dad7
commit b84e2d3338
43 changed files with 7362 additions and 0 deletions

View file

@ -11,6 +11,7 @@ import {NavigationItem} from '@angular/docs';
// These 2 imports are expected to be red because they are generated a build time
import FIRST_APP_TUTORIAL_NAV_DATA from '../../src/assets/tutorials/first-app/routes.json';
import LEARN_ANGULAR_TUTORIAL_NAV_DATA from '../../src/assets/tutorials/learn-angular/routes.json';
import DEFERRABLE_VIEWS_TUTORIAL_NAV_DATA from '../../src/assets/tutorials/deferrable-views/routes.json';
import {DefaultPage} from './core/enums/pages';
import {getApiNavigationItems} from './features/references/helpers/manifest.helper';
@ -872,6 +873,7 @@ const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
export const TUTORIALS_SUB_NAVIGATION_DATA: NavigationItem[] = [
FIRST_APP_TUTORIAL_NAV_DATA,
LEARN_ANGULAR_TUTORIAL_NAV_DATA,
DEFERRABLE_VIEWS_TUTORIAL_NAV_DATA,
{
path: DefaultPage.TUTORIALS,
contentPath: 'tutorials/home',

View file

@ -37,6 +37,7 @@ copy_to_directory(
"//adev/src/content/tools/cli",
"//adev/src/content/tools/libraries",
"//adev/src/content/tutorials",
"//adev/src/content/tutorials/deferrable-views:deferrable-views-guides",
"//adev/src/content/tutorials/first-app:first-app-guides",
"//adev/src/content/tutorials/learn-angular:learn-angular-guides",
"//packages/animations:animations_docs",
@ -84,6 +85,7 @@ copy_to_directory(
copy_to_directory(
name = "tutorials",
srcs = [
"//adev/src/content/tutorials/deferrable-views",
"//adev/src/content/tutorials/first-app",
"//adev/src/content/tutorials/homepage",
"//adev/src/content/tutorials/learn-angular",

View file

@ -0,0 +1,107 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1920 960.4">
<defs>
<style>
.cls-1, .cls-2 {
fill: none;
}
.cls-1, .cls-3, .cls-4, .cls-5, .cls-6, .cls-7, .cls-8, .cls-9 {
stroke-width: 0px;
}
.cls-10, .cls-11, .cls-12, .cls-13, .cls-2 {
stroke-width: 4px;
}
.cls-10, .cls-11, .cls-12, .cls-13, .cls-2, .cls-14 {
stroke-miterlimit: 10;
}
.cls-10, .cls-11, .cls-13, .cls-2, .cls-14 {
stroke: #000;
}
.cls-10, .cls-14, .cls-6 {
fill: #fff;
}
.cls-11, .cls-9 {
fill: #e7e7e7;
}
.cls-12 {
stroke: #232428;
}
.cls-12, .cls-13, .cls-5 {
fill: #8514f5;
}
.cls-14 {
stroke-dasharray: 0 0 40 27;
stroke-width: 2px;
}
.cls-4 {
fill: #272428;
}
.cls-7 {
fill: #e4e4e4;
}
.cls-8 {
fill: #e90464;
}
</style>
</defs>
<rect class="cls-4" x="-36.05" y="-27.87" width="1982.09" height="1016.14"/>
<g>
<g>
<path class="cls-5" d="m338.09,289.42h706.06c24.29,0,44.01,19.72,44.01,44.01v658.41c0,24.29-19.72,44.01-44.01,44.01H338.09c-24.29,0-44.01-19.72-44.01-44.01V333.43c0-24.29,19.72-44.01,44.01-44.01Z"/>
<path class="cls-3" d="m1044.15,291.42c23.2,0,42.01,18.81,42.01,42.01v658.41c0,23.2-18.81,42.01-42.01,42.01H338.09c-23.2,0-42.01-18.81-42.01-42.01V333.43c0-23.2,18.81-42.01,42.01-42.01h706.06m0-4H338.09c-25.37,0-46.01,20.64-46.01,46.01v658.41c0,25.37,20.64,46.01,46.01,46.01h706.06c25.37,0,46.01-20.64,46.01-46.01V333.43c0-25.37-20.64-46.01-46.01-46.01h0Z"/>
</g>
<path class="cls-10" d="m457.15,126.61h1468.96c2.56,0,4.63,2.08,4.63,4.63v841.29c0,8.42-6.83,15.25-15.25,15.25H409.82c-2.56,0-4.63-2.08-4.63-4.63V178.57c0-28.68,23.28-51.96,51.96-51.96Z"/>
<g>
<polyline class="cls-11" points="1145.89 288.64 1145.89 956.86 405 956.86 405 288.64"/>
<path class="cls-11" d="m405,288.64h85.87s13.1-4.18,13.1-13.98v-48.56s1.76-13.43,15.64-13.43h389.14c2.41,0,14.53.11,14.53,14.09v48.89s2.97,13.29,15.42,13.29h980.87"/>
<rect class="cls-9" x="1291.4" y="427.76" width="547.94" height="238" rx="29.59" ry="29.59"/>
<rect class="cls-13" x="1267.68" y="401.87" width="547.94" height="239.62" rx="29.59" ry="29.59"/>
</g>
<rect class="cls-12" x="536.44" y="235.79" width="184.97" height="45.02" rx="22.51" ry="22.51"/>
<g>
<line class="cls-2" x1="886.49" y1="241.45" x2="852.8" y2="275.15"/>
<line class="cls-2" x1="886.49" y1="275.15" x2="852.8" y2="241.45"/>
</g>
<rect class="cls-12" x="644.27" y="433.64" width="131.87" height="44.02" rx="22.01" ry="22.01"/>
<rect class="cls-10" x="805.21" y="433.64" width="211.92" height="44.02" rx="22.01" ry="22.01"/>
<rect class="cls-10" x="710.21" y="505.35" width="211.92" height="44.02" rx="22.01" ry="22.01"/>
<g>
<path class="cls-8" d="m732.22,623.09c-13.24,0-24.01-10.77-24.01-24.01s10.77-24.01,24.01-24.01h240.03c13.24,0,24.01,10.77,24.01,24.01s-10.77,24.01-24.01,24.01h-240.03Z"/>
<path class="cls-3" d="m972.25,577.07c12.16,0,22.01,9.85,22.01,22.01h0c0,12.16-9.85,22.01-22.01,22.01h-240.03c-12.16,0-22.01-9.85-22.01-22.01h0c0-12.16,9.85-22.01,22.01-22.01h240.03m0-4h-240.03c-14.34,0-26.01,11.67-26.01,26.01s11.67,26.01,26.01,26.01h240.03c14.34,0,26.01-11.67,26.01-26.01s-11.67-26.01-26.01-26.01h0Z"/>
</g>
<rect class="cls-6" x="483.75" y="361.92" width="89.99" height="44.02" rx="22.01" ry="22.01"/>
<rect class="cls-10" x="483.75" y="432.92" width="89.99" height="44.02" rx="22.01" ry="22.01"/>
<rect class="cls-10" x="483.75" y="504.92" width="89.99" height="44.02" rx="22.01" ry="22.01"/>
<rect class="cls-10" x="483.75" y="576.92" width="89.99" height="44.02" rx="22.01" ry="22.01"/>
<rect class="cls-6" x="483.75" y="651.55" width="89.99" height="44.02" rx="22.01" ry="22.01"/>
<rect class="cls-6" x="483.75" y="723.55" width="89.99" height="44.02" rx="22.01" ry="22.01"/>
<rect class="cls-6" x="483.75" y="795.55" width="89.99" height="44.02" rx="22.01" ry="22.01"/>
<rect class="cls-6" x="483.75" y="867.55" width="89.99" height="44.02" rx="22.01" ry="22.01"/>
<rect class="cls-6" x="648.64" y="723.55" width="127.09" height="44.02" rx="22.01" ry="22.01"/>
<rect class="cls-6" x="805.25" y="723.55" width="270.7" height="44.02" rx="22.01" ry="22.01"/>
<rect class="cls-6" x="710.21" y="795.55" width="190.26" height="44.02" rx="22.01" ry="22.01"/>
<rect class="cls-6" x="928.46" y="795.55" width="147.49" height="44.02" rx="22.01" ry="22.01"/>
<rect class="cls-6" x="710.21" y="867.55" width="262.23" height="44.02" rx="22.01" ry="22.01"/>
<rect class="cls-7" x="1289.13" y="716.84" width="550.44" height="129" rx="32.39" ry="32.39"/>
<rect class="cls-14" x="1266.54" y="693.12" width="550.44" height="129" rx="32.39" ry="32.39"/>
<rect class="cls-13" x="1294.68" y="734.42" width="52.14" height="50.42"/>
<rect class="cls-10" x="1373.19" y="734.42" width="400.36" height="47.8" rx="23.9" ry="23.9"/>
<g>
<polygon class="cls-6" points="1677.74 739.07 1847.5 833.39 1753.87 840.47 1724.85 927.13 1677.74 739.07"/>
<path class="cls-3" d="m1680.8,743.06l159.9,88.83-85.66,6.48-2.65.2-.84,2.52-26.42,78.91-44.33-176.94m-6.12-7.98l49.9,199.17,30.77-91.89,98.96-7.48-179.63-99.79h0Z"/>
</g>
</g>
<rect class="cls-1" y="0" width="1920" height="960.4"/>
</svg>

After

Width:  |  Height:  |  Size: 5.4 KiB

View file

@ -11,6 +11,7 @@ generate_guides(
],
),
data = [
"//adev/src/assets/images:ang_illustrations-04.svg",
"//adev/src/assets/images:learn-angular-browser.svg",
"//adev/src/assets/images:learn-angular-local.svg",
],

View file

@ -0,0 +1,21 @@
load("//adev/shared-docs:index.bzl", "generate_guides", "generate_tutorial")
package(default_visibility = ["//adev:__subpackages__"])
generate_guides(
name = "deferrable-views-guides",
srcs = glob(["**/*.md"]),
)
filegroup(
name = "files",
srcs = glob(
["**/*"],
exclude = ["**/*.md"],
),
)
generate_tutorial(
name = "deferrable-views",
tutorial_srcs = ":files",
)

View file

@ -0,0 +1,101 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"first-app": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"inlineTemplate": true,
"inlineStyle": true,
"style": "scss",
"skipTests": true
},
"@schematics/angular:class": {
"skipTests": true
},
"@schematics/angular:directive": {
"skipTests": true
},
"@schematics/angular:guard": {
"skipTests": true
},
"@schematics/angular:interceptor": {
"skipTests": true
},
"@schematics/angular:pipe": {
"skipTests": true
},
"@schematics/angular:resolver": {
"skipTests": true
},
"@schematics/angular:service": {
"skipTests": true
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular/build:application",
"options": {
"outputPath": "dist/first-app",
"index": "src/index.html",
"browser": "src/main.ts",
"polyfills": ["zone.js"],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
{
"glob": "**/*",
"input": "src/public"
}
],
"styles": ["src/styles.css"],
"scripts": []
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kB",
"maximumError": "1MB"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kB",
"maximumError": "4kB"
}
],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular/build:dev-server",
"configurations": {
"production": {
"buildTarget": "first-app:build:production"
},
"development": {
"buildTarget": "first-app:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular/build:extract-i18n"
}
}
}
}
}

View file

@ -0,0 +1,35 @@
# To learn more about how to use Nix to configure your environment
# see: https://developers.google.com/idx/guides/customize-idx-env
{ pkgs, ... }: {
# Which nixpkgs channel to use.
channel = "stable-23.11"; # or "unstable"
# Use https://search.nixos.org/packages to find packages
packages = [
pkgs.nodejs_18
];
# Sets environment variables in the workspace
env = {};
idx = {
# Search for the extensions you want on https://open-vsx.org/ and use "publisher.id"
extensions = [
"angular.ng-template"
];
workspace = {
# Runs when a workspace is first created with this \`dev.nix\` file
onCreate = {
npm-install = "npm install --no-audit --prefer-offline";
};
# To run something each time the environment is rebuilt, use the \`onStart\` hook
};
# Enable previews and customize configuration
previews = {
enable = true;
previews = {
web = {
command = ["npm" "run" "start" "--" "--port" "$PORT" "--host" "0.0.0.0"];
manager = "web";
};
};
};
};
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,28 @@
{
"name": "angular.dev",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "NG_BUILD_PARALLEL_TS=0 ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development"
},
"private": true,
"dependencies": {
"@angular/common": "^18.0.0",
"@angular/compiler": "^18.0.0",
"@angular/core": "^18.0.0",
"@angular/forms": "^18.0.0",
"@angular/platform-browser": "^18.0.0",
"@angular/router": "^18.0.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.14.0"
},
"devDependencies": {
"@angular/build": "^18.0.1",
"@angular/cli": "^18.0.0",
"@angular/compiler-cli": "^18.0.0",
"typescript": "~5.5.0"
}
}

View file

@ -0,0 +1,11 @@
# Deferrable views tutorial
This interactive tutorial consists of lessons that introduce the Angular deferrable views concepts.
## How to use this tutorial
Each step represents a concept in Angular deferrable views. You can do one, or all of them.
If you get stuck, click "Reveal answer" at the top.
Alright, let's [get started](/tutorials/deferrable-views/1-what-are-deferrable-views).

View file

@ -0,0 +1,6 @@
{
"title": "Deferrable Views",
"type": "editor",
"nextTutorial": "first-app",
"openFiles": ["src/app/app.component.ts"]
}

View file

@ -0,0 +1,10 @@
import {Component} from '@angular/core';
@Component({
selector: 'app-root',
template: `
Welcome to Angular!
`,
standalone: true,
})
export class AppComponent {}

View file

@ -0,0 +1,38 @@
# What are deferrable views?
A fully rendered Angular page may contain many different components, directives, and pipes. While certain parts of the page should be shown to the user immediately, there may be portions that can wait to display until later.
Angular's *deferrable views*, using the `@defer` syntax, can help you speed up your application by telling Angular to wait to download the JavaScript for the parts of the page that don't need to be shown right away.
In this activity, you'll learn how to use deferrable views to defer load a section of your component template.
<hr>
<docs-workflow>
<docs-step title="Add a `@defer` block to a section of a template">
In your `app.component.ts`, wrap the `article-comments` component with a `@defer` block to defer load it.
<docs-code language="angular-html">
@defer {
<article-comments />
}
</docs-code>
By default, `@defer` loads the `article-comments` component when the browser is idle.
In your browser's developer console, you can see that the `article-comments-component` lazy chunk file is loaded separately (The specific file names may change from run to run):
<docs-code language="markdown">
Initial chunk files | Names | Raw size
chunk-NNSQHFIE.js | - | 769.00 kB |
main.js | main | 229.25 kB |
Lazy chunk files | Names | Raw size
chunk-T5UYXUSI.js | article-comments-component | 1.84 kB |
</docs-code>
</docs-step>
</docs-workflow>
Great work! Youve learned the basics of deferrable views.

View file

@ -0,0 +1,28 @@
import {Component} from '@angular/core';
import {ArticleCommentsComponent} from './article-comments.component';
@Component({
selector: 'app-root',
template: `
<div>
<h1>How I feel about Angular</h1>
<article>
<p>
Angular is my favorite framework, and
this is why. Angular has the coolest
deferrable view feature that makes defer
loading content the easiest and most
ergonomic it could possibly be.
</p>
</article>
@defer {
<article-comments />
}
</div>
`,
standalone: true,
imports: [ArticleCommentsComponent],
})
export class AppComponent {}

View file

@ -0,0 +1,5 @@
import {ApplicationConfig} from '@angular/core';
export const appConfig: ApplicationConfig = {
providers: [],
};

View file

@ -0,0 +1,29 @@
import {Component} from '@angular/core';
@Component({
selector: 'article-comments',
template: `
<h2>Comments</h2>
<p class="comment">
Building for the web is fantastic!
</p>
<p class="comment">
The new template syntax is great
</p>
<p class="comment">
I agree with the other comments!
</p>
`,
styles: [
`
.comment {
padding: 15px;
margin-left: 30px;
background-color: paleturquoise;
border-radius: 20px;
}
`,
],
standalone: true,
})
export class ArticleCommentsComponent {}

View file

@ -0,0 +1,5 @@
import {bootstrapApplication} from '@angular/platform-browser';
import {AppComponent} from './app/app.component';
import {appConfig} from './app/app.config';
bootstrapApplication(AppComponent, appConfig).catch((err) => console.error(err));

View file

@ -0,0 +1,5 @@
{
"openFiles": ["src/app/app.component.ts", "src/app/article-comments.component.ts"],
"title": "What are deferrable views",
"type": "editor"
}

View file

@ -0,0 +1,26 @@
import {Component} from '@angular/core';
import {ArticleCommentsComponent} from './article-comments.component';
@Component({
selector: 'app-root',
template: `
<div>
<h1>How I feel about Angular</h1>
<article>
<p>
Angular is my favorite framework, and
this is why. Angular has the coolest
deferrable view feature that makes defer
loading content the easiest and most
ergonomic it could possibly be.
</p>
</article>
<article-comments />
</div>
`,
standalone: true,
imports: [ArticleCommentsComponent],
})
export class AppComponent {}

View file

@ -0,0 +1,5 @@
import {ApplicationConfig} from '@angular/core';
export const appConfig: ApplicationConfig = {
providers: [],
};

View file

@ -0,0 +1,29 @@
import {Component} from '@angular/core';
@Component({
selector: 'article-comments',
template: `
<h2>Comments</h2>
<p class="comment">
Building for the web is fantastic!
</p>
<p class="comment">
The new template syntax is great
</p>
<p class="comment">
I agree with the other comments!
</p>
`,
styles: [
`
.comment {
padding: 15px;
margin-left: 30px;
background-color: paleturquoise;
border-radius: 20px;
}
`,
],
standalone: true,
})
export class ArticleCommentsComponent {}

View file

@ -0,0 +1,5 @@
import {bootstrapApplication} from '@angular/platform-browser';
import {AppComponent} from './app/app.component';
import {appConfig} from './app/app.config';
bootstrapApplication(AppComponent, appConfig).catch((err) => console.error(err));

View file

@ -0,0 +1,106 @@
# @loading, @error and @placeholder blocks
Deferrable views let you define content to be shown in different loading states.
<div class="docs-table docs-scroll-track-transparent">
<table>
<tr>
<td><code>@placeholder</code></td>
<td>
By default, defer blocks do not render any content before they are triggered. The <code>@placeholder</code> is an optional block that declares content to show before the deferred content loads. Angular replaces the placeholder with the deferred content after loading completes. While this block is optional, the Angular team recommends always including a placeholder.
<a href="https://angular.dev/guide/defer#triggers" target="_blank">
Learn more in the full deferrable views documentation
</a>
</td>
</tr>
<tr>
<td><code>@loading</code></td>
<td>
This optional block allows you to declare content to be shown during the loading of any deferred dependencies.
</td>
</tr>
<tr>
<td><code>@error</code></td>
<td>
This block allows you to declare content which is shown if deferred loading fails.
</td>
</tr>
</table>
</div>
The contents of all the above sub-blocks are eagerly loaded. Additionally, some features require a `@placeholder` block.
In this activity, you'll learn how to use the `@loading`, `@error` and `@placeholder` blocks to manage the states of deferrable views.
<hr>
<docs-workflow>
<docs-step title="Add `@placeholder` block">
In your `app.component.ts`, add a `@placeholder` block to the `@defer` block.
<docs-code language="angular-html" highlight="[3,4,5]">
@defer {
<article-comments />
} @placeholder {
<p>Placeholder for comments</p>
}
</docs-code>
</docs-step>
<docs-step title="Configure the `@placeholder` block">
The `@placeholder` block accepts an optional parameter to specify the `minimum` amount of time that this placeholder should be shown. This `minimum` parameter is specified in time increments of milliseconds (ms) or seconds (s). This parameter exists to prevent fast flickering of placeholder content in the case that the deferred dependencies are fetched quickly.
<docs-code language="angular-html" highlight="[3,4,5]">
@defer {
<article-comments />
} @placeholder (minimum 1s) {
<p>Placeholder for comments</p>
}
</docs-code>
</docs-step>
<docs-step title="Add `@loading` block">
Next add a `@loading` block to the component template.
The `@loading` block accepts two optional parameters:
* `minimum`: the amount of time that this block should be shown
* `after`: the amount of time to wait after loading begins before showing the loading template
Both parameters are specified in time increments of milliseconds (ms) or seconds (s).
Update `app.component.ts` to include a `@loading` block with a minimum parameter of `1s` as well as an after parameter with the value 500ms to the @loading block.
<docs-code language="angular-html" highlight="[5,6,7]">
@defer {
<article-comments />
} @placeholder (minimum 1s) {
<p>Placeholder for comments</p>
} @loading (minimum 1s; after 500ms) {
<p>Loading comments...</p>
}
</docs-code>
Note: this example uses two parameters, separated by the ; character.
</docs-step>
<docs-step title="Add `@error` block">
Finally, add an `@error` block to the `@defer` block.
<docs-code language="angular-html" highlight="[7,8,9]">
@defer {
<article-comments />
} @placeholder (minimum 1s) {
<p>Placeholder for comments</p>
} @loading (minimum 1s; after 500ms) {
<p>Loading comments...</p>
} @error {
<p>Failed to load comments</p>
}
</docs-code>
</docs-step>
</docs-workflow>
Congratulations! At this point, you have a good understanding about deferrable views. Keep up the great work and let's learn about triggers next.

View file

@ -0,0 +1,34 @@
import {Component} from '@angular/core';
import {ArticleCommentsComponent} from './article-comments.component';
@Component({
selector: 'app-root',
template: `
<div>
<h1>How I feel about Angular</h1>
<article>
<p>
Angular is my favorite framework, and
this is why. Angular has the coolest
deferrable view feature that makes defer
loading content the easiest and most
ergonomic it could possibly be.
</p>
</article>
@defer {
<article-comments />
} @placeholder (minimum 1s) {
<p>Placeholder for comments</p>
} @loading (minimum 1s; after 500ms) {
<p>Loading comments...</p>
} @error {
<p>Failed to load comments</p>
}
</div>
`,
standalone: true,
imports: [ArticleCommentsComponent],
})
export class AppComponent {}

View file

@ -0,0 +1,5 @@
import {ApplicationConfig} from '@angular/core';
export const appConfig: ApplicationConfig = {
providers: [],
};

View file

@ -0,0 +1,29 @@
import {Component} from '@angular/core';
@Component({
selector: 'article-comments',
template: `
<h2>Comments</h2>
<p class="comment">
Building for the web is fantastic!
</p>
<p class="comment">
The new template syntax is great
</p>
<p class="comment">
I agree with the other comments!
</p>
`,
styles: [
`
.comment {
padding: 15px;
margin-left: 30px;
background-color: paleturquoise;
border-radius: 20px;
}
`,
],
standalone: true,
})
export class ArticleCommentsComponent {}

View file

@ -0,0 +1,5 @@
import {bootstrapApplication} from '@angular/platform-browser';
import {AppComponent} from './app/app.component';
import {appConfig} from './app/app.config';
bootstrapApplication(AppComponent, appConfig).catch((err) => console.error(err));

View file

@ -0,0 +1,5 @@
{
"openFiles": ["src/app/app.component.ts", "src/app/article-comments.component.ts"],
"title": "@loading, @error and @placeholder blocks",
"type": "editor"
}

View file

@ -0,0 +1,28 @@
import {Component} from '@angular/core';
import {ArticleCommentsComponent} from './article-comments.component';
@Component({
selector: 'app-root',
template: `
<div>
<h1>How I feel about Angular</h1>
<article>
<p>
Angular is my favorite framework, and
this is why. Angular has the coolest
deferrable view feature that makes defer
loading content the easiest and most
ergonomic it could possibly be.
</p>
</article>
@defer {
<article-comments />
}
</div>
`,
standalone: true,
imports: [ArticleCommentsComponent],
})
export class AppComponent {}

View file

@ -0,0 +1,5 @@
import {ApplicationConfig} from '@angular/core';
export const appConfig: ApplicationConfig = {
providers: [],
};

View file

@ -0,0 +1,29 @@
import {Component} from '@angular/core';
@Component({
selector: 'article-comments',
template: `
<h2>Comments</h2>
<p class="comment">
Building for the web is fantastic!
</p>
<p class="comment">
The new template syntax is great
</p>
<p class="comment">
I agree with the other comments!
</p>
`,
styles: [
`
.comment {
padding: 15px;
margin-left: 30px;
background-color: paleturquoise;
border-radius: 20px;
}
`,
],
standalone: true,
})
export class ArticleCommentsComponent {}

View file

@ -0,0 +1,5 @@
import {bootstrapApplication} from '@angular/platform-browser';
import {AppComponent} from './app/app.component';
import {appConfig} from './app/app.config';
bootstrapApplication(AppComponent, appConfig).catch((err) => console.error(err));

View file

@ -0,0 +1,106 @@
# Defer triggers
While the default options for `@defer` offer great options for lazy loading parts of your components it may still be desirable to further customize the deferred loading experience.
By default, deferred content loads when the browser is idle. You can, however, customize when this loading occurs by specifying a **trigger**. This lets you pick the loading behavior best suited to your component.
Deferrable views offer two types of loading trigger:
<div class="docs-table docs-scroll-track-transparent">
<table>
<tr>
<td><code>on</code></td>
<td>
A trigger condition using a trigger from the list of built-in triggers.<br/>
For example: <code>@defer (on viewport)</code>
</td>
</tr>
<tr>
<td><code>when</code></td>
<td>
A condition as an expression which is evaluated for truthiness. When the expression is truthy, the placeholder is swapped with the lazily loaded content.<br/>
For example: <code>@defer (when customizedCondition)</code>
</td>
</tr>
</table>
</div>
If the `when` condition evaluates to `false`, the `defer` block is not reverted back to the placeholder. The swap is a one-time operation.
You can define multiple event triggers at once, these triggers will be evaluated as OR conditions.
* Ex: `@defer (on viewport; on timer(2s))`
* Ex: `@defer (on viewport; when customizedCondition)`
In this activity, you'll learn how to use triggers to specify the condition to load the deferrable views.
<hr>
<docs-workflow>
<docs-step title="Add `on hover` trigger">
In your `app.component.ts`, add an `on hover` trigger to the `@defer` block.
<docs-code language="angular-html" hightlight="[1]">
@defer (on hover) {
<article-comments />
} @placeholder (minimum 1s) {
<p>Placeholder for comments</p>
} @loading (minimum 1s; after 500ms) {
<p>Loading comments...</p>
} @error {
<p>Failed to load comments</p>
}
</docs-code>
Now, the page will not render the comments section until you hover its placeholder.
</docs-step>
<docs-step title="Add a 'Show all comments' button">
Next, update the template to include a button with the label "Show all comments". Include a template variable called `#showComments` with the button.
<docs-code language="angular-html" hightlight="[1]">
<button type="button" #showComments>Show all comments</button>
@defer (on hover) {
<article-comments />
} @placeholder (minimum 1s) {
<p>Placeholder for comments</p>
} @loading (minimum 1s; after 500ms) {
<p>Loading comments...</p>
} @error {
<p>Failed to load comments</p>
}
</docs-code>
Note: for more information on [template variables check the documentation](https://angular.dev/guide/templates/reference-variables#).
</docs-step>
<docs-step title="Add `on interaction` trigger">
Update the `@defer` block in the template to use the `on interaction` trigger. Provide the `showComments` template variable as the parameter to `interaction`.
<docs-code language="angular-html" hightlight="[3]">
<button type="button" #showComments>Show all comments</button>
@defer (on hover; on interaction(showComments)) {
<article-comments />
} @placeholder (minimum 1s) {
<p>Placeholder for comments</p>
} @loading (minimum 1s; after 500ms) {
<p>Loading comments...</p>
} @error {
<p>Failed to load comments</p>
}
</docs-code>
With these changes, the page will wait for one of the following conditions before rendering the comments section:
* User hovers the comments sections placeholder
* User clicks on the “Show all comments" button
You can reload the page to try out different triggers to render the comments section.
</docs-step>
</docs-workflow>
If you would like to learn more, check out the documentation for [Deferrable View](https://angular.dev/guide/defer).
Keep learning to unlock more of Angular's great features.

View file

@ -0,0 +1,36 @@
import {Component} from '@angular/core';
import {ArticleCommentsComponent} from './article-comments.component';
@Component({
selector: 'app-root',
template: `
<div>
<h1>How I feel about Angular</h1>
<article>
<p>
Angular is my favorite framework, and
this is why. Angular has the coolest
deferrable view feature that makes defer
loading content the easiest and most
ergonomic it could possibly be.
</p>
</article>
<button type="button" #showComments>Show all comments</button>
@defer (on hover; on interaction(showComments)) {
<article-comments />
} @placeholder (minimum 1s) {
<p>Placeholder for comments</p>
} @loading (minimum 1s; after 500ms) {
<p>Loading comments...</p>
} @error {
<p>Failed to load comments</p>
}
</div>
`,
standalone: true,
imports: [ArticleCommentsComponent],
})
export class AppComponent {}

View file

@ -0,0 +1,5 @@
import {ApplicationConfig} from '@angular/core';
export const appConfig: ApplicationConfig = {
providers: [],
};

View file

@ -0,0 +1,29 @@
import {Component} from '@angular/core';
@Component({
selector: 'article-comments',
template: `
<h2>Comments</h2>
<p class="comment">
Building for the web is fantastic!
</p>
<p class="comment">
The new template syntax is great
</p>
<p class="comment">
I agree with the other comments!
</p>
`,
styles: [
`
.comment {
padding: 15px;
margin-left: 30px;
background-color: paleturquoise;
border-radius: 20px;
}
`,
],
standalone: true,
})
export class ArticleCommentsComponent {}

View file

@ -0,0 +1,5 @@
import {bootstrapApplication} from '@angular/platform-browser';
import {AppComponent} from './app/app.component';
import {appConfig} from './app/app.config';
bootstrapApplication(AppComponent, appConfig).catch((err) => console.error(err));

View file

@ -0,0 +1,5 @@
{
"openFiles": ["src/app/app.component.ts", "src/app/article-comments.component.ts"],
"title": "Defer triggers",
"type": "editor"
}

View file

@ -0,0 +1,34 @@
import {Component} from '@angular/core';
import {ArticleCommentsComponent} from './article-comments.component';
@Component({
selector: 'app-root',
template: `
<div>
<h1>How I feel about Angular</h1>
<article>
<p>
Angular is my favorite framework, and
this is why. Angular has the coolest
deferrable view feature that makes defer
loading content the easiest and most
ergonomic it could possibly be.
</p>
</article>
@defer {
<article-comments />
} @placeholder (minimum 1s) {
<p>Placeholder for comments</p>
} @loading (minimum 1s; after 500ms) {
<p>Loading comments...</p>
} @error {
<p>Failed to load comments</p>
}
</div>
`,
standalone: true,
imports: [ArticleCommentsComponent],
})
export class AppComponent {}

View file

@ -0,0 +1,5 @@
import {ApplicationConfig} from '@angular/core';
export const appConfig: ApplicationConfig = {
providers: [],
};

View file

@ -0,0 +1,29 @@
import {Component} from '@angular/core';
@Component({
selector: 'article-comments',
template: `
<h2>Comments</h2>
<p class="comment">
Building for the web is fantastic!
</p>
<p class="comment">
The new template syntax is great
</p>
<p class="comment">
I agree with the other comments!
</p>
`,
styles: [
`
.comment {
padding: 15px;
margin-left: 30px;
background-color: paleturquoise;
border-radius: 20px;
}
`,
],
standalone: true,
})
export class ArticleCommentsComponent {}

View file

@ -0,0 +1,5 @@
import {bootstrapApplication} from '@angular/platform-browser';
import {AppComponent} from './app/app.component';
import {appConfig} from './app/app.config';
bootstrapApplication(AppComponent, appConfig).catch((err) => console.error(err));

View file

@ -9,4 +9,7 @@ Welcome to the Angular tutorials! These tutorials will guide you through the cor
<docs-card title="Build your first Angular app locally" link="Start coding" href="tutorials/first-app" imgSrc="adev/src/assets/images/learn-angular-local.svg">
via npm
</docs-card>
<docs-card title="Deferrable views" link="Start coding" href="tutorials/deferrable-views" imgSrc="adev/src/assets/images/ang_illustrations-04.svg">
via the Playground
</docs-card>
</docs-card-container>