浣溪沙茶业 發表於 2024-11-1 15:36:00

Angular 18 上手开发

<h1 id="0x01-概述">0x01 概述</h1>
<h2 id="1简介">(1)简介</h2>
<ul>
<li>官方网站:https://www.angular.cn/</li>
<li>Angular 由 Google 的专业团队维护,Angular 提供了广泛的工具、API 和库,简化和优化开发工作流程</li>
<li>Angular 提供了一个坚实的平台,可用于构建快速、可靠、能够随着团队规模和代码库规模扩展的应用程序</li>
<li>特点:
<ul>
<li>整合性高,降低技术决策成本</li>
<li>简化 DOM 操作,注重业务逻辑</li>
<li>采用后端的<strong>依赖注入</strong>系统</li>
</ul>
</li>
<li>与 React、Vue 对比,Angular:
<ul>
<li>在大型企业级应用中提供更成熟的框架和严格的架构模式,适合复杂项目的长期维护</li>
<li>初始加载时间较长,不够灵活和轻量</li>
<li>学习曲线较陡峭</li>
</ul>
</li>
</ul>
<h2 id="2创建项目">(2)创建项目</h2>
<blockquote>
<p>Angular 18 于 2024 年 5 月正式发布,以下内容均采用 18.2.10 / 18.2.9 版本,NodeJS 采用 23.0.0 版本,npm 采用 10.9.0</p>
</blockquote>
<ol>
<li>使用命令 <code>npm install -g @angular/cli</code> 安装脚手架</li>
<li>使用命令 <code>ng new angular-app</code> 创建名为 <em>angular-app</em> 的项目
<ol>
<li>不向 Google 发送数据</li>
<li>不使用 CSS 预编译</li>
<li>不使用 SSR</li>
</ol>
</li>
<li>使用命令 <code>cd angular-app</code> 进入项目目录</li>
<li>使用命令 <code>ng serve --open</code> 启动项目并自动开启浏览器</li>
</ol>
<blockquote>
<p>使用 <code>ng</code> 命令创建组件等:</p>
<table>
<thead>
<tr>
<th>脚手架</th>
<th>说明</th>
<th>命令</th>
</tr>
</thead>
<tbody>
<tr>
<td>Component</td>
<td>组件</td>
<td><code>ng generate component my-new-component</code></td>
</tr>
<tr>
<td>Directive</td>
<td>指令</td>
<td><code>ng generate directive my-new-directive</code></td>
</tr>
<tr>
<td>Pipe</td>
<td>管道</td>
<td><code>ng generate pipe my-new-pipe</code></td>
</tr>
<tr>
<td>Service</td>
<td>服务</td>
<td><code>ng generate service my-new-service</code></td>
</tr>
<tr>
<td>Class</td>
<td>类</td>
<td><code>ng generate class my-new-class</code></td>
</tr>
<tr>
<td>Interface</td>
<td>接口</td>
<td><code>ng generate interface my-new-interface</code></td>
</tr>
<tr>
<td>Enum</td>
<td>枚举</td>
<td><code>ng generate enum my-new-enum</code></td>
</tr>
<tr>
<td>Module</td>
<td>模块</td>
<td><code>ng generate module my-new-module</code></td>
</tr>
</tbody>
</table>
</blockquote>
<h2 id="3项目结构">(3)项目结构</h2>
<ul>
<li>
<p>.angular:编译缓存目录</p>
</li>
<li>
<p>node_modules:Node 包目录</p>
</li>
<li>
<p>public:公共资源目录</p>
</li>
<li>
<p>src:代码资源目录</p>
<ul>
<li>
<p>app:App 模块代码目录</p>
<ul>
<li>app.component.css:组件样式文件</li>
<li>app.component.html:组件模板文件</li>
<li>app.component.spec.ts:组件测试文件</li>
<li>app.component.ts:组件入口文件</li>
<li>app.config.ts:模块配置文件</li>
<li>app.routes.ts:模块路由文件</li>
</ul>
</li>
<li>
<p>index.html:模板文件</p>
<pre><code class="language-html">&lt;!doctype html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
&lt;meta charset="utf-8"&gt;
&lt;title&gt;AngularApp&lt;/title&gt;
&lt;base href="/"&gt;
&lt;meta name="viewport" content="width=device-width, initial-scale=1"&gt;
&lt;link rel="icon" type="image/x-icon" href="favicon.ico"&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;app-root&gt;&lt;/app-root&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
</li>
<li>
<p>main.ts:入口文件</p>
<pre><code class="language-ts">import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { AppComponent } from './app/app.component';

bootstrapApplication(AppComponent, appConfig)
.catch((err) =&gt; console.error(err));
</code></pre>
</li>
<li>
<p>style.css:全局样式文件</p>
</li>
</ul>
</li>
<li>
<p>.editorconfig:编辑器配置文件,详情参考 https://editorconfig.org</p>
</li>
<li>
<p>.gitignore:git 忽略文件</p>
</li>
<li>
<p>angular.json:Angular 配置文件</p>
</li>
<li>
<p>package.json:Node 包配置文件</p>
<ul>
<li>
<p>在 <code>"script"</code> 项中添加以下内容:</p>
<pre><code class="language-json">{
"scripts": {
    "dev": "ng serve --open",
    // ...
},
}
</code></pre>
<p>以后通过命令 <code>npm run dev</code> 来启动项目(代替命令 <code>ng serve --open</code>)</p>
</li>
</ul>
</li>
<li>
<p>tsconfig.json:TypeScript 配置文件</p>
</li>
</ul>
<div class="mermaid">flowchart TB
项目--&gt;模块1 &amp; 模块2 &amp; m[...] &amp; 模块n
模块1--&gt;组件1 &amp; 组件2 &amp; c[...] &amp; 组件n
</div><h1 id="0x02-组件">0x02 组件</h1>
<h2 id="1简介-1">(1)简介</h2>
<blockquote>
<p>以 app.component.ts 为例:</p>
<pre><code class="language-ts">import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';

@Component({
selector: 'app-root',
standalone: true,
imports: ,
templateUrl: './app.component.html',
styleUrl: './app.component.css'
})
export class AppComponent {
title = 'angular-app';
}
</code></pre>
</blockquote>
<ul>
<li>
<p><code>@Component</code> 是一个装饰器,用于加载一些配置信息,其中</p>
<ul>
<li>
<p><code>selector</code>:选择器,用于选择模板中的 DOM 标签,并将该组件渲染至该标签中</p>
<pre><code class="language-html">&lt;!-- filename: index.html --&gt;
&lt;body&gt;
&lt;app-root&gt;&lt;/app-root&gt;
&lt;/body&gt;
</code></pre>
</li>
<li>
<p><code>standalone</code>:独立模式,<code>true</code>表示该组件不依赖于任何模块,可以直接在应用中使用,而不需要通过模块进行声明</p>
</li>
<li>
<p><code>imports</code>:导入组件,此处导入了 <code>RouterOutlet</code> 组件</p>
<ul>
<li>
<p><code>RouterOutlet</code> 是 Angular 路由模块中的一个重要组件,用于在应用中定义路由视图的占位符</p>
<blockquote>
<p>类似 React Router 中的 <code>&lt;Outlet /&gt;</code></p>
</blockquote>
</li>
</ul>
</li>
<li>
<p><code>templateUrl</code>:模板文件路径</p>
</li>
<li>
<p><code>styleUrl</code>:样式文件路径</p>
</li>
</ul>
</li>
<li>
<p>导出经过装饰器声明的类,类中可以传值</p>
</li>
</ul>
<h2 id="2创建组件">(2)创建组件</h2>
<ol>
<li>
<p>使用命令 <code>ng generate component hello</code>(或 <code>ng g c hello</code>)创建一个在 app 目录下,名为 hello 的新组件</p>
</li>
<li>
<p>app 目录结构</p>
<div class="mermaid">flowchart TB
app--&gt;hello &amp; app.component.css &amp; app.component.html &amp; ...
hello--&gt;hello.component.css &amp; hello.component.html &amp; hello.component.spec.ts &amp; hello.component.ts
</div></li>
<li>
<p>修改 hello.component.html</p>
<pre><code class="language-html">&lt;div style="width: 200px; height: 200px; background-color: red"&gt;
这里是 hello 组件
&lt;/div&gt;
</code></pre>
</li>
<li>
<p>修改 hello.component.ts</p>
<pre><code class="language-ts">import { Component } from '@angular/core';

@Component({
selector: 'app-hello',
standalone: true,
imports: [],
templateUrl: './hello.component.html',
styleUrl: './hello.component.css'
})
export class HelloComponent {

}
</code></pre>
</li>
<li>
<p>修改 app.component.ts</p>
<pre><code class="language-ts">import { HelloComponent } from "./hello/hello.component";
</code></pre>
</li>
<li>
<p>修改 app.component.html</p>
<pre><code class="language-html">&lt;app-hello&gt;&lt;/app-hello&gt;
</code></pre>
</li>
<li>
<p>启动项目,访问 http://localhost:4200/,此时可以发现页面中存在 hello 组件</p>
</li>
</ol>
<h2 id="3生命周期">(3)生命周期</h2>
<ul>
<li>
<p>生命周期钩子及其执行顺序如下:</p>
<table>
<thead>
<tr>
<th>钩子</th>
<th>作用</th>
<th>时机</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>ngOnChanges()</code></td>
<td>每当 Angular 设置或重新设置数据绑定的输入属性时响应</td>
<td>在 <code>ngOnInit()</code> 之前以及所绑定的一个或多个输入属性方式变化时调用</td>
</tr>
<tr>
<td><code>ngOnInit()</code></td>
<td>在 Angular 第一次显示数据绑定和设置指令或组件的输入属性后,初始化指令或组件</td>
<td>首次 <code>ngOnChanges()</code> 之后调用,且<strong>仅调用一次</strong></td>
</tr>
<tr>
<td><code>ngDoCheck()</code></td>
<td>每当发生 Angular 无法或不愿自检的变化时,进行检测</td>
<td>每次执行变更检测时的 <code>ngOnChanges()</code> 之后调用<br>首次执行变更检测的 <code>ngOnInit()</code> 之后调用</td>
</tr>
<tr>
<td><code>ngAfterContentInit()</code></td>
<td>当 Angular 将外部内容投影至组件或指令中的内容之后调用</td>
<td>首次 <code>ngDoCheck()</code> 之后调用,且<strong>仅调用一次</strong></td>
</tr>
<tr>
<td><code>ngAfterContentChecked()</code></td>
<td>每当 Angular 检测完被投影至组件或指令中的内容之后调用</td>
<td>每次 <code>ngDoCheck()</code> 之后调用<br>首次 <code>ngAfterContentInit()</code> 之后调用</td>
</tr>
<tr>
<td><code>ngAfterViewInit()</code></td>
<td>当 Angular 初始化完组件视图、其子视图、或包含该指令的视图之后调用</td>
<td>首次 <code>ngAfterContentChecked()</code> 之后调用,且<strong>仅调用一次</strong></td>
</tr>
<tr>
<td><code>ngAfterViewChecked()</code></td>
<td>每当 Angular 检测完组件视图、其子视图、或包含该指令的视图的变更之后调用</td>
<td>每次 <code>ngAfterContentChecked()</code> 之后调用<br>首次 <code>ngAfterViewInit()</code> 之后调用</td>
</tr>
<tr>
<td><code>ngOnDestroy()</code></td>
<td>每当 Angular 销毁指令或组件之前调用</td>
<td>在 Angular 销毁指令或组件之前立即调用</td>
</tr>
</tbody>
</table>
<div class="mermaid">graph LR
ngOnChanges--首次--&gt;ngOnInit--&gt;ngDoCheck--首次--&gt;ngAfterContentInit--&gt;ngAfterContentChecked--首次--&gt;ngAfterViewInit--&gt;ngAfterViewChecked-.-&gt;ngOnDestroy

ngOnChanges--&gt;ngDoCheck--&gt;ngAfterContentChecked--&gt;ngAfterViewChecked
</div></li>
<li>
<p>举例:app.component.ts</p>
<pre><code class="language-ts">export class AppComponent {
ngOnChanges() {
    console.log('ngOnChanges');
}
ngOnInit() {
    console.log('ngOnInit');
}
ngDoCheck() {
    console.log('ngDoCheck');
}
ngAfterContentInit() {
    console.log('ngAfterContentInit');
}
ngAfterContentChecked() {
    console.log('ngAfterContentChecked');
}
ngAfterViewInit() {
    console.log('ngAfterViewInit');
}
ngAfterViewChecked() {
    console.log('ngAfterViewChecked');
}
ngOnDestroy() {
    console.log('ngOnDestroy');
}
}
</code></pre>
</li>
</ul>
<h2 id="4交互">(4)交互</h2>
<blockquote>
<p>组件间传参</p>
</blockquote>
<ul>
<li>
<p><code>@Input</code>:用于父组件给子组件绑定属性,设置输入类数据</p>
<ol>
<li>
<p>hello.component.ts</p>
<pre><code class="language-ts">import { Component, Input } from '@angular/core';

@Component({/* ... */})
export class HelloComponent {
@Input() username!: string;

ngOnInit() {
    console.log(this.username);
}
}

</code></pre>
</li>
<li>
<p>app.component.html</p>
<pre><code class="language-html">&lt;app-hello ="'SRIGT'"&gt;&lt;/app-hello&gt;
</code></pre>
</li>
</ol>
</li>
<li>
<p><code>@Output</code>:用于子组件弹射触发事件,该事件来自父组件给子组件的传递</p>
<ol>
<li>
<p>app.component.ts</p>
<pre><code class="language-ts">export class AppComponent {
list = ;
add(newNumber: number) {
    this.list.push(newNumber);
}
}
</code></pre>
</li>
<li>
<p>hello.component.ts</p>
<pre><code class="language-ts">import { Component, EventEmitter, Output } from '@angular/core';

@Component({
selector: 'app-hello',
standalone: true,
imports: [],
templateUrl: './hello.component.html',
styleUrl: './hello.component.css',
})
export class HelloComponent {
@Output() add = new EventEmitter();

clickHandler() {
    this.add.emit(4);
}
}
</code></pre>
</li>
<li>
<p>app.component.html</p>
<pre><code class="language-html">&lt;app-hello (add)="add($event)"&gt;&lt;/app-hello&gt;
&lt;p&gt;{{ list.toString() }}&lt;/p&gt;
</code></pre>
</li>
<li>
<p>hello.component.html</p>
<pre><code class="language-html">&lt;button (click)="clickHandler()"&gt;+4&lt;/button&gt;
</code></pre>
</li>
</ol>
</li>
<li>
<p><code>@ViewChild</code>:获取子组件实例与数据</p>
<ol>
<li>
<p>app.component.ts</p>
<pre><code class="language-ts">import { Component, ViewChild } from '@angular/core';
// ...
@Component({/* ... */})
export class AppComponent {
@ViewChild('childComp') child: any;

clickHandler() {
    console.log(this.child);
}
}
</code></pre>
</li>
<li>
<p>app.component.html</p>
<pre><code class="language-html">&lt;app-hello #childComp&gt;&lt;/app-hello&gt;
&lt;button (click)="clickHandler()"&gt;Click&lt;/button&gt;
</code></pre>
</li>
</ol>
</li>
</ul>
<h1 id="0x03-模板">0x03 模板</h1>
<h2 id="1插值">(1)插值</h2>
<ul>
<li>
<p>“插值”指将表达式嵌入到 HTML 文档中,通过 <code>{{ }}</code> 实现</p>
<pre><code class="language-html">&lt;!-- filename: app.component.html --&gt;
&lt;p&gt;1 + 1 = {{ 1 + 1 }}&lt;/p&gt;
</code></pre>
</li>
<li>
<p>在 app.component.ts 导出的类中包含的变量值,也可以通过插值语法嵌入到 HTML 文档中</p>
<ul>
<li>
<p>app.component.ts</p>
<pre><code class="language-ts">// import ...

@Component({/* ... */})
export class AppComponent {
expression = `1 + 1 = ${1 + 1}`;
}
</code></pre>
</li>
<li>
<p>app.component.html</p>
<pre><code class="language-html">&lt;p&gt;{{ expression }}&lt;/p&gt;
</code></pre>
</li>
</ul>
</li>
</ul>
<h2 id="2绑定">(2)绑定</h2>
<h3 id="a-属性绑定">a. 属性绑定</h3>
<ul>
<li>
<p>一般地,属性绑定使用 <code>[ ]</code> 实现,如:</p>
<pre><code class="language-html">&lt;h1 ="'h1-title'"&gt;Title&lt;/h1&gt;
</code></pre>
<p>渲染后变成了</p>
<pre><code class="language-html">&lt;h1 _ngcontent-ng-xxxxxxxxxxx id="h1-title"&gt;Title&lt;/h1&gt;
</code></pre>
</li>
<li>
<p>类绑定</p>
<ul>
<li>
<p>单一类绑定:</p>
<pre><code class="language-html">&lt;h1 ="'h1-title'"&gt;Title&lt;/h1&gt;
&lt;!-- 或 --&gt;
&lt;h1 ="true"&gt;Title&lt;/h1&gt;
</code></pre>
</li>
<li>
<p>多个类绑定:</p>
<pre><code class="language-html">&lt;h1 ="'h1-title text-4xl font-bold'"&gt;Title&lt;/h1&gt;
&lt;!-- 或 --&gt;
&lt;h1 ="{ 'h1-title': true, 'text-4xl': false }"&gt;Title&lt;/h1&gt;
&lt;!-- 或 --&gt;
&lt;h1 ="[ 'h1-title', 'text-4xl' ]"&gt;Title&lt;/h1&gt;
</code></pre>
</li>
</ul>
</li>
<li>
<p>样式绑定</p>
<ul>
<li>
<p>单一样式绑定:</p>
<pre><code class="language-html">&lt;h1 ="'font-size:1em'"&gt;Title&lt;/h1&gt;
&lt;!-- 或 --&gt;
&lt;h1 ="'1em'"&gt;Title&lt;/h1&gt;
&lt;!-- 或 --&gt;
&lt;h1 ="'1'"&gt;Title&lt;/h1&gt;
</code></pre>
</li>
<li>
<p>多重样式绑定:</p>
<pre><code class="language-html">&lt;h1 ="'font-size:1em;font-weight:900;'"&gt;Title&lt;/h1&gt;
&lt;!-- 或 --&gt;
&lt;h1 ="{ 'font-size': '1em', 'font-weight': 900 }"&gt;Title&lt;/h1&gt;
</code></pre>
</li>
</ul>
</li>
</ul>
<h3 id="b-事件绑定">b. 事件绑定</h3>
<ul>
<li>
<p>一般地,事件绑定使用 <code>( )</code> 实现,如:</p>
<ul>
<li>
<p>app.component.ts</p>
<pre><code class="language-ts">export class AppComponent {
clickHandler() {
    alert("Button clicked")
}
}
</code></pre>
</li>
<li>
<p>app.component.html</p>
<pre><code class="language-html">&lt;button (click)="clickHandler()"&gt;Click&lt;/button&gt;
</code></pre>
</li>
</ul>
</li>
<li>
<p>对于事件对象,使用 <code>$event</code> 传递</p>
<ul>
<li>
<p>app.component.ts</p>
<pre><code class="language-ts">export class AppComponent {
clickHandler(event: Event) {
    console.log(event)
}
}
</code></pre>
</li>
<li>
<p>app.component.html</p>
<pre><code class="language-html"> &lt;button (click)="clickHandler($event)"&gt;Click&lt;/button&gt;
</code></pre>
</li>
</ul>
</li>
</ul>
<h3 id="c-双向绑定">c. 双向绑定</h3>
<ul>
<li>
<p>双向绑定是应用内组件共享数据的方式</p>
</li>
<li>
<p>一般地,双向绑定使用 <code>[( )]</code> 实现</p>
<ul>
<li>
<p>app.component.ts</p>
<pre><code class="language-ts">export class AppComponent {
number: number = 10;

add() {
    this.number += 1;
}

sub() {
    this.number -= 1;
}
}
</code></pre>
</li>
<li>
<p>app.component.html</p>
<pre><code class="language-html">&lt;button (click)="sub()"&gt;-1&lt;/button&gt;
&lt;button (click)="add()"&gt;+1&lt;/button&gt;
&lt;span&gt;Current number is {{ number }}&lt;/span&gt;
</code></pre>
</li>
</ul>
</li>
<li>
<p>表单元素的双向绑定需要 <code>@NgModule</code> 装饰器,使用前需要从 <code>@angular/forms</code> 中导入 <code>FormsModule</code></p>
<ul>
<li>
<p>app.component.ts</p>
<pre><code class="language-ts">// ...
import { FormsModule } from '@angular/forms';

@Component({
// ...
imports: [/*...*/, FormsModule],
})
export class AppComponent {
text = '';
}
</code></pre>
</li>
<li>
<p>app.component.html</p>
<pre><code class="language-html">&lt;input [(ngModel)]="text" /&gt;
&lt;p&gt;Input text: {{ text }}&lt;/p&gt;
</code></pre>
</li>
</ul>
</li>
</ul>
<h3 id="d-模板变量">d. 模板变量</h3>
<ul>
<li>
<p>模板变量使用 <code>#</code> 声明</p>
<ul>
<li>
<p>app.component.ts</p>
<pre><code class="language-ts">export class AppComponent {
clickHandler(value: string) {
    alert(value);
}
}
</code></pre>
</li>
<li>
<p>app.component.html</p>
<pre><code class="language-html">&lt;input #text /&gt;
&lt;button (click)="clickHandler(text.value)"&gt;Submit&lt;/button&gt;
</code></pre>
</li>
</ul>
</li>
<li>
<p>Angular 根据声明模板变量的位置进行赋值</p>
<ul>
<li>在标准 HTML 元素上,则引用该元素</li>
<li>在组件上,则引用该组件实例</li>
<li>在 <code>&lt;ng-template&gt;</code> 元素上,则引用一个 <code>TemplateRef</code> 实例来代表模板</li>
</ul>
</li>
</ul>
<h2 id="3渲染">(3)渲染</h2>
<h3 id="a-条件">a. 条件</h3>
<h4 id="i-if">I. if</h4>
<p>使用 <code>*ngIF</code> 实现,使用前需要从 <code>@angular/common</code> 导入 <code>NgIf</code></p>
<blockquote>
<p>注意:<strong>仅用于元素是否被渲染,而非控制元素的显隐</strong></p>
</blockquote>
<ul>
<li>
<p>app.component.ts</p>
<pre><code class="language-ts">// ...
import { NgIf } from '@angular/common';

@Component({
// ...
imports: [/* ... */, NgIf],
})
export class AppComponent {
isRender = false;
}
</code></pre>
</li>
<li>
<p>app.component.html</p>
<pre><code class="language-html">&lt;p&gt;
My name is
&lt;span *ngIf="isRender"&gt;John&lt;/span&gt;
&lt;span *ngIf="!isRender"&gt;SRIGT&lt;/span&gt;
.
&lt;/p&gt;
&lt;!-- My name is SRIGT . --&gt;
</code></pre>
</li>
</ul>
<h4 id="ii-switch">II. switch</h4>
<p>使用 <code>*ngSwitch</code> 实现,使用前需要从 <code>@angular/common</code> 导入 <code>NgSwitch</code></p>
<ul>
<li>
<p>app.component.ts</p>
<pre><code class="language-ts">// ...
import { NgSwitch, NgSwitchCase, NgSwitchDefault } from '@angular/common';

@Component({
// ...
imports: [/* ... */, NgSwitch, NgSwitchCase, NgSwitchDefault],
})
export class AppComponent {
nameType = "nickname"
}
</code></pre>
</li>
<li>
<p>app.component.html</p>
<pre><code class="language-html">&lt;p ="nameType"&gt;
My name is
&lt;span *ngSwitchCase="'realname'"&gt;John&lt;/span&gt;
&lt;span *ngSwitchCase="'nickname'"&gt;SRIGT&lt;/span&gt;
&lt;span *ngSwitchDefault&gt;Someone&lt;/span&gt;
.
&lt;/p&gt;
&lt;!-- My name is SRIGT . --&gt;
</code></pre>
</li>
</ul>
<h3 id="b-循环">b. 循环</h3>
<p>循环渲染使用 <code>*ngFor</code> 实现,使用前需要从 <code>@angular/common</code> 导入 <code>NgFor</code></p>
<ul>
<li>
<p>app.component.ts</p>
<pre><code class="language-ts">// ...
import { NgFor } from '@angular/common';

@Component({
// ...
imports: [/* ... */, NgFor],
})
export class AppComponent {
names = ['Alex', 'Bob', 'Charlie'];
}
</code></pre>
</li>
<li>
<p>app.component.html</p>
<pre><code class="language-html">&lt;div *ngFor="let name of names; let i = index; let odd = odd"&gt;
{{ odd }} {{ i }} {{ name }}
&lt;/div&gt;
&lt;!--
false 0 Alex
true 1 Bob
false 2 Charlie
--&gt;
</code></pre>
</li>
</ul>
<h3 id="c-控制流">c. 控制流</h3>
<blockquote>
<p>Angular 16 新特性</p>
</blockquote>
<ul>
<li>
<p><code>@if</code>、<code>@else if</code>、<code>@else</code></p>
<ul>
<li>
<p>app.component.ts</p>
<pre><code class="language-ts">export class AppComponent {
isRender = false;
}
</code></pre>
</li>
<li>
<p>app.component.html</p>
<pre><code class="language-html">&lt;p&gt;
My name is
@if (isRender) { {{ "John" }} }
@else { {{ "SRIGT" }} }
.
&lt;/p&gt;
&lt;!-- My name is SRIGT . --&gt;
</code></pre>
</li>
</ul>
</li>
<li>
<p><code>@switch</code>、<code>@case</code>、<code>@default</code></p>
<ul>
<li>
<p>app.component.ts</p>
<pre><code class="language-ts">export class AppComponent {
nameType = "nickname"
}
</code></pre>
</li>
<li>
<p>app.component.html</p>
<pre><code class="language-html">&lt;p&gt;
My name is
@switch (nameType) {
    @case ('realname') {
      {{ "John" }}
    }
    @case ('nickname') {
      {{ "SRIGT" }}
    }
    @default {
      {{ "Someone" }}
    }
}
.
&lt;/p&gt;
&lt;!-- My name is SRIGT . --&gt;
</code></pre>
</li>
</ul>
</li>
<li>
<p><code>@for</code></p>
<ul>
<li>
<p>app.component.ts</p>
<pre><code class="language-ts">export class AppComponent {
items = [
    { id: 1, name: 'Alex' },
    { id: 2, name: 'Bob' },
    { id: 3, name: 'Charlie' },
];
}
</code></pre>
</li>
<li>
<p>app.component.html</p>
<pre><code class="language-html">&lt;ul&gt;
@for (item of items; track item.id) {
&lt;li&gt;{{ item.name }}&lt;/li&gt;
} @empty {
&lt;li&gt;There are no items.&lt;/li&gt;
}
&lt;/ul&gt;
</code></pre>
</li>
</ul>
</li>
</ul>
<h2 id="4表单">(4)表单</h2>
<h3 id="a-单个表单控件">a. 单个表单控件</h3>
<ol>
<li>
<p>注册响应式表单模块,需要从 <code>@angular/forms</code> 导入 <code>ReactiveFormsModule</code></p>
<pre><code class="language-ts">// ...
import { ReactiveFormsModule } from '@angular/forms';

@Component({
// ...
imports: [/* ... */, ReactiveFormsModule],
})
export class AppComponent {}
</code></pre>
</li>
<li>
<p>生成一个新的 <code>FormControl</code> 实例并保存在组件中,需要从 <code>@angular/forms</code> 导入 <code>FormControl</code></p>
<pre><code class="language-ts">// ...
import { FormControl, ReactiveFormsModule } from '@angular/forms';

@Component({/* ... */})
export class AppComponent {
username = new FormControl('');

reset() {
    this.username.setValue('');
}
}
</code></pre>
</li>
<li>
<p>在模板中注册该实例</p>
<pre><code class="language-html">&lt;label&gt;
username:
&lt;input type="text" ="username" /&gt;
&lt;/label&gt;
&lt;p&gt;{{ username.value }}&lt;/p&gt;
&lt;button (click)="reset()"&gt;reset&lt;/button&gt;
</code></pre>
</li>
</ol>
<h3 id="b-多个表单控件">b. 多个表单控件</h3>
<ol>
<li>
<p>创建 <code>FormGroup</code> 实例,需要从 <code>@angular/forms</code> 导入 <code>FormGroup</code></p>
<pre><code class="language-ts">// ...
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';

@Component({/* ... */})
export class AppComponent {
loginForm = new FormGroup({
    username: new FormControl(''),
    password: new FormControl(''),
});

onSubmit() {
    alert(
      `username: ${this.loginForm.value.username}\npassword: ${this.loginForm.value.password}`
    );
}
}
</code></pre>
</li>
<li>
<p>在模板中注册该实例</p>
<pre><code class="language-html">&lt;form ="loginForm"&gt;
&lt;label&gt;
    username:
    &lt;input type="text" formControlName="username" /&gt;
&lt;/label&gt;
&lt;br /&gt;
&lt;label&gt;
    password:
    &lt;input type="password" formControlName="password" /&gt;
&lt;/label&gt;
&lt;br /&gt;
&lt;button (click)="onSubmit()"&gt;Login&lt;/button&gt;
&lt;/form&gt;
</code></pre>
</li>
</ol>
<h3 id="c-表单验证">c. 表单验证</h3>
<ul>
<li>
<p>Angular 支持 HTML5 提供的表单验证关键字(如 <code>required</code> 等),通过双向绑定可以获取组件信息,从得到验证结果</p>
<ul>
<li>
<p>app.component.ts</p>
<pre><code class="language-ts">// ...
import {
FormControl,
FormGroup,
ReactiveFormsModule,
ValidationErrors,
Validators,
} from '@angular/forms';

@Component({
// ...
imports: [/* ... */, ReactiveFormsModule],
})
export class AppComponent {
// 表单数据
formData = { username: '', password: '' };

// 自定义验证方法
passwordValidator(control: FormControl): ValidationErrors | null {
    return control.value.length &lt; 5 ? { password: true } : null;
}

// 表单控件组
loginForm = new FormGroup({
    username: new FormControl(this.formData.username, [
      Validators.maxLength(10),
    ]),
    password: new FormControl(this.formData.password, [
      Validators.required,
      this.passwordValidator,
    ]),
});

// 提交方法
onSubmit() {
    alert(
      `username: ${this.loginForm.value.username}\npassword: ${this.loginForm.value.password}`
    );
}
}
</code></pre>
</li>
<li>
<p>app.component.html</p>
<pre><code class="language-html">&lt;form ="loginForm"&gt;
&lt;label&gt;
    username:
    &lt;input type="text" formControlName="username" required /&gt;
&lt;/label&gt;
&lt;br /&gt;
&lt;span&gt;{{ loginForm.get("username")?.valid }}&lt;/span&gt;
&lt;br /&gt;
&lt;label&gt;
    password:
    &lt;input type="password" formControlName="password" /&gt;
&lt;/label&gt;
&lt;br /&gt;
&lt;span&gt;{{ loginForm.get("password")?.valid }}&lt;/span&gt;
&lt;br /&gt;
&lt;button (click)="onSubmit()"&gt;Login&lt;/button&gt;
&lt;/form&gt;
</code></pre>
<p>其中,<code>.valid</code> 是布尔类型</p>
</li>
</ul>
</li>
<li>
<p><code>ngModel</code> 可用于跟踪修改状态与有效性验证,通过三个 CSS 类来更新控件:</p>
<table>
<thead>
<tr>
<th>状态</th>
<th>为真</th>
<th>为假</th>
</tr>
</thead>
<tbody>
<tr>
<td>已被访问</td>
<td><code>ng-touched</code></td>
<td><code>ug-untouched</code></td>
</tr>
<tr>
<td>已变化</td>
<td><code>ng-dirty</code></td>
<td><code>ng-pristine</code></td>
</tr>
<tr>
<td>有效</td>
<td><code>ng-valid</code></td>
<td><code>ng-invalid</code></td>
</tr>
</tbody>
</table>
<pre><code class="language-css">input {
outline: none;
}

input.ng-invalid {
border: 1px solid red;
}
</code></pre>
</li>
</ul>
<h2 id="5管道">(5)管道</h2>
<ul>
<li>管道用于传输,即数据处理</li>
<li>管道采用<strong>链式</strong>连接,自左向右,依次执行</li>
<li>语法格式:<code>{{ 输入数据 | 管道1:参数 | 管道2... }}</code></li>
</ul>
<blockquote>
<p>注意:<strong>管道操作符的优先级高于 JavaScript 三元运算符 <code>?:</code>,同时使用时需要使用 <code>( )</code> 包裹三元运算符部分</strong></p>
</blockquote>
<h3 id="a-内置管道">a. 内置管道</h3>
<ul>
<li>
<p>内置管道包括</p>
<table>
<thead>
<tr>
<th>管道</th>
<th>功能</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>date</code></td>
<td>格式化日期</td>
</tr>
<tr>
<td><code>json</code></td>
<td>经过 <code>JSON.stringify()</code></td>
</tr>
<tr>
<td><code>uppercase</code></td>
<td>字母大写转换</td>
</tr>
<tr>
<td><code>lowercase</code></td>
<td>字母小写转换</td>
</tr>
<tr>
<td><code>decimal</code></td>
<td>数值特定格式</td>
</tr>
<tr>
<td><code>currectcy</code></td>
<td>数值货币格式</td>
</tr>
<tr>
<td><code>percent</code></td>
<td>数值百分比格式</td>
</tr>
<tr>
<td><code>slice</code></td>
<td>切片成新子集</td>
</tr>
</tbody>
</table>
</li>
<li>
<p>举例:</p>
<ul>
<li>
<p>app.component.ts</p>
<pre><code class="language-ts">// ...
import { CommonModule } from '@angular/common';

@Component({
// ...
imports: [/* ... */, CommonModule],
})
export class AppComponent {
today: Date = new Date();
}
</code></pre>
</li>
<li>
<p>app.component.html</p>
<pre><code class="language-html">&lt;p&gt;{{ today | date: 'yyyy-MM-dd HH:mm:ss'}}&lt;/p&gt;
</code></pre>
</li>
</ul>
</li>
</ul>
<h3 id="b-自定义管道">b. 自定义管道</h3>
<p>使用命令 <code>ng g p newPipe</code> 生成名为 newPipe 的自定义管道,通过 <code>@Pipe</code> 装饰器标识管道</p>
<ul>
<li>
<p>prefix.pipe.ts</p>
<pre><code class="language-ts">import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
name: 'prefix',
standalone: true,
})
export class PrefixPipe implements PipeTransform {
transform(value: string, ...args: unknown[]): unknown {
    return `angular-${value}`;
}
}
</code></pre>
</li>
<li>
<p>app.component.ts</p>
<pre><code class="language-ts">// ...
import { CommonModule } from '@angular/common';
import { PrefixPipe } from './prefix.pipe';

@Component({
// ...
imports: [/* ... */, CommonModule, PrefixPipe],
})
export class AppComponent {}
</code></pre>
</li>
<li>
<p>app.component.html</p>
<pre><code class="language-html">&lt;p&gt;{{ "SRIGT" | prefix }}&lt;/p&gt;
</code></pre>
</li>
</ul>
<h1 id="0x04-依赖注入">0x04 依赖注入</h1>
<h2 id="1服务">(1)服务</h2>
<ul>
<li>
<p>从组件中抽离出来的代码成为服务,其本质上是函数</p>
<ul>
<li>主要将<strong>数据访问</strong>的职责从组件中独立成服务,使组件专注于数据展示</li>
</ul>
</li>
<li>
<p>使用命令 <code>ng g s list</code> 生成一个名为 list 的服务,通过 <code>@Injectable</code> 装饰器标识服务</p>
<pre><code class="language-ts">import { Injectable } from '@angular/core';

@Injectable({
providedIn: 'root',
})
export class ListService {
constructor() {}
}
</code></pre>
</li>
<li>
<p>服务需要作为依赖,注入到系统、组件或模块才能使用,通过注册提供商和根注入器实现</p>
</li>
</ul>
<h2 id="2依赖注入">(2)依赖注入</h2>
<ul>
<li>
<p>在上述新建的服务中,<code>providedIn: 'root'</code> 指定 Angular 应该在根注入器中提供该服务</p>
</li>
<li>
<p><code>providedIn</code> 取值包括:</p>
<ul>
<li><code>'root'</code>:注入到 App Module,所有子组件都可以使用</li>
<li><code>null</code>:不设定服务在作用域</li>
<li>组件名:只作用于该组件,常用于<strong>懒加载</strong></li>
</ul>
</li>
<li>
<p>完善上述服务:</p>
<ul>
<li>
<p>list.service.ts</p>
<pre><code class="language-ts">import { Injectable } from '@angular/core';

@Injectable({
providedIn: 'root',
})
export class ListService {
constructor() {}

names = ['Alex', 'Bob', 'Charlie'];

getNames() {
    return this.names;
}
}
</code></pre>
</li>
<li>
<p>app.component.ts</p>
<pre><code class="language-ts">// ...
import { ListService } from './list.service';

@Component({/* ... */})
export class AppComponent {
constructor(private listService: ListService) {}

names: Array&lt;string&gt; | undefined;

ngOnInit() {
    this.names = this.listService.getNames();
}
}
</code></pre>
</li>
<li>
<p>app.component.html</p>
<pre><code class="language-html">&lt;ul&gt;
@for (name of names; track $index) {
&lt;li&gt;{{ name }}&lt;/li&gt;
}
&lt;/ul&gt;
</code></pre>
</li>
</ul>
</li>
</ul>
<h1 id="0x05-路由">0x05 路由</h1>
<h2 id="1配置">(1)配置</h2>
<ul>
<li>
<p>路由器(Router)是一套规则列表,可用于查询 URL 对应的视图规则</p>
</li>
<li>
<p>路由(Route)是列表中的一条规则</p>
<ul>
<li><code>path</code>:URL 目标</li>
<li><code>component</code>:对应视图(组件)</li>
</ul>
</li>
<li>
<p>一般地,路由规则配置在 .routes.ts 文件中,如 app.routes.ts:</p>
<pre><code class="language-ts">import { Routes } from '@angular/router';

export const routes: Routes = [];
</code></pre>
</li>
<li>
<p>举例,设置链接,从 <code>/</code> 跳转至 <code>/hello</code>:</p>
<ol>
<li>
<p>app.routes.ts</p>
<pre><code class="language-ts">import { Routes } from '@angular/router';
import { HelloComponent } from './hello/hello.component';

export const routes: Routes = [
{
    path: 'hello',
    component: HelloComponent,
},
];
</code></pre>
</li>
<li>
<p>app.component.ts</p>
<pre><code class="language-ts">import { Component } from '@angular/core';
import { RouterLink, RouterOutlet } from '@angular/router';

@Component({
selector: 'app-root',
standalone: true,
imports: ,
templateUrl: './app.component.html',
styleUrl: './app.component.css',
})
export class AppComponent {}
</code></pre>
</li>
<li>
<p>app.component.html</p>
<pre><code class="language-html">&lt;a ="['/hello']"&gt;Hello&lt;/a&gt;
&lt;router-outlet&gt;&lt;/router-outlet&gt;
</code></pre>
</li>
<li>
<p>hello.component.html</p>
<pre><code class="language-html">&lt;p&gt;Hello, Angular&lt;/p&gt;
</code></pre>
</li>
</ol>
</li>
<li>
<p>使用通配路由实现 404 页面</p>
<pre><code class="language-ts">export const routes: Routes = [
// ...
{
    path: '**',
    component: NotFoundComponent,
},
];
</code></pre>
</li>
</ul>
<h2 id="2嵌套">(2)嵌套</h2>
<ul>
<li>
<p>路由规则的 <code>children</code> 属性可用于实现路由嵌套</p>
<pre><code class="language-ts">// filename: app.routes.ts
import { Routes } from '@angular/router';
import { HelloComponent } from './hello/hello.component';

export const routes: Routes = [
{
    path: 'hello',
    component: HelloComponent,
    children: [
      {
      path: 'angular',
      component: HelloComponent,
      },
    ],
},
];
</code></pre>
</li>
<li>
<p>在非父级路由 <code>/hello</code> 需要跳转到 <code>/hello/angular</code> 时,需要些完整路径</p>
<pre><code class="language-html">&lt;!-- filename: app.component.html --&gt;
&lt;a ="['/hello/angular']"&gt;Hello&lt;/a&gt;
&lt;router-outlet&gt;&lt;/router-outlet&gt;
</code></pre>
</li>
</ul>
<h2 id="3传参">(3)传参</h2>
<ul>
<li>
<p>query:通过 <code>&lt;a&gt;</code> 的 <code>queryParams</code> 参数传递参数,通过 <code>ActivatedRoute.snapshot.queryParams</code> 获取参数</p>
<ol>
<li>
<p>app.component.html</p>
<pre><code class="language-html">&lt;a ="['/hello']" ="{ name: 'Angular' }"&gt;Hello&lt;/a&gt;
&lt;router-outlet&gt;&lt;/router-outlet&gt;
</code></pre>
</li>
<li>
<p>hello.component.ts</p>
<pre><code class="language-ts">import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({/* ... */})
export class HelloComponent {
constructor(private activatedRoute: ActivatedRoute) {}

name = ""

ngOnInit() {
    this.name = this.activatedRoute.snapshot.queryParams["name"]
}
}
</code></pre>
</li>
<li>
<p>hello.component.html</p>
<pre><code class="language-html">&lt;p&gt;Hello, {{ name }}&lt;/p&gt;
</code></pre>
</li>
</ol>
</li>
<li>
<p>params:通过在路由配置中加入参数名来传递参数,通过 `` 获取参数</p>
<ol>
<li>
<p>app.routes.ts</p>
<pre><code class="language-ts">import { Routes } from '@angular/router';
import { HelloComponent } from './hello/hello.component';

export const routes: Routes = [
{
    path: 'hello/:name',
    component: HelloComponent,
},
];
</code></pre>
</li>
<li>
<p>hello.component.ts</p>
<pre><code class="language-ts">import { Component } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router';

@Component({
selector: 'app-hello',
standalone: true,
imports: [],
templateUrl: './hello.component.html',
styleUrl: './hello.component.css',
})
export class HelloComponent {
constructor(private activatedRoute: ActivatedRoute) {}

name = '';

ngOnInit() {
    this.activatedRoute.params.subscribe(
      (params: Params) =&gt; (this.name = params['name'])
    );
}
}
</code></pre>
</li>
<li>
<p>hello.component.html</p>
<pre><code class="language-html">&lt;p&gt;Hello, {{ name }}&lt;/p&gt;
</code></pre>
</li>
<li>
<p>访问 http://localhost:4200/hello/angular</p>
</li>
</ol>
</li>
</ul>
<blockquote>
<p>更多详细内容参考官方文档</p>
</blockquote>
<p>-End-</p><br><br>
来源:https://www.cnblogs.com/SRIGT/p/18520352
頁: [1]
查看完整版本: Angular 18 上手开发