什么是 Angular?

# 什么是 Angular?

Angular 是一个 Web 框架,能够帮助开发者构建快速、可靠的应用

由 Google 的专业团队维护

# 基础

# 组件

构建 Angular 应用的基本构建块。

大驼峰命名 组件树

定义组件

核心属性:

  1. 一个包含一些配置的 @Component 装饰器 (opens new window)
  2. 控制将渲染到 DOM 中的 HTML 模板
  3. 定义组件在 HTML 中如何使用的 CSS 选择器 (opens new window)
  4. 一个包含管理状态、处理用户输入或从服务器获取数据等行为的 TypeScript 类。
// todo-list-item.component.ts
@Component({
  standalone: true,
  selector: 'todo-list-item',
  template: `
    <li>(TODO) Read Angular Essentials Guide</li>
  `,
  styles: `
    li {
      color: red;
      font-weight: 300;
    }
  `,
})
export class TodoListItem {
  /* Component behavior is defined in here */
}

包括的元数据

  • standalone: true ——— 推荐的简化组件编写体验的方法
  • styles ——— 包含任何你想应用到组件上的 CSS 样式的字符串或字符串数组

可以将html和css分离到单独的文件中

Angular 提供了两个额外的属性: templateUrlstyleUrl

// todo-list-item.component.ts
@Component({
  standalone: true,
  selector: 'todo-list-item',
  templateUrl: './todo-list-item.component.html',
  styleUrl: './todo-list-item.component.css',
})
export class TodoListItem {
  /* Component behavior is defined in here */
}

使用组件

要使用组件,你需要:

  1. 将组件导入到文件中
  2. 将其添加到组件的 imports 数组中
  3. template 中使用该组件的选择器
//  TodoList 组件导入之前的 TodoListItem 组件的示例
// todo-list.component.ts
import {TodoListItem} from './todo-list-item.component.ts';
@Component({
  standalone: true,
  imports: [TodoListItem],
  template: `
    <ul>
      <todo-list-item></todo-list-item>
    </ul>
  `,
})
export class TodoList {}

# 管理动态数据

定义组件的状态和行为来管理动态数据。

组件需要跟踪的各种属性通常被称为**「状态」**。

定义状态

要定义状态,你可以在组件内使用 类字段语法 (opens new window)

// todo-list-item.component.ts
@Component({ ... })
export class TodoListItem {
  taskTitle = '';
  isComplete = false;
}

更新状态

当你想更新状态时,通常通过在组件类中定义方法来实现,这些方法可以使用 this 关键字访问各种类字段。

// todo-list-item.component.ts
@Component({ ... })
export class TodoListItem {
  taskTitle = '';
  isComplete = false;
  completeTask() {
    this.isComplete = true;
  }
  updateTitle(newTitle: string) {
    this.taskTitle = newTitle;
  }
}

# 渲染动态模板

使用 Angular 的模板语法创建动态 HTML。

渲染动态属性

当你需要在模板中显示动态内容时,Angular 使用双花括号语法来区分静态和动态内容。

@Component({
  selector: 'todo-list-item',
  template: `
    <p>Title: {{ taskTitle }}</p>
  `,
})
export class TodoListItem {
  taskTitle = 'Read cup of coffee';
}

这种语法声明了 HTML 内部动态数据属性之间的 插值。因此,每当数据变化时,Angular 会自动更新 DOM,以反映该属性的新值。

# 动态属性

当你需要在 HTML 元素上动态设置标准 DOM 属性的值时,该属性会用方括号包裹,以通知 Angular 声明的值应被解释为类似 JavaScript 的语句( 带有一些 Angular 增强功能 (opens new window)),而不是纯字符串。

@Component({
  selector: 'sign-up-form',
  template: `
    <button type="submit" [disabled]="formIsInvalid">Submit</button>
  `,
})
export class SignUpForm {
  formIsInvalid = true;
}

动态绑定自定义的 HTML 属性(例如, aria-data- 等)需要用 attr. 前缀来修饰自定义的 HTML 属性

@Component({
  standalone: true,
  template: `
    <button [attr.data-test-id]="testId">Primary CTA</button>
  `,
})
export class AppBanner {
  testId = 'main-cta';
}

# 条件与渲染

@if 控制块

类似于 JavaScript 的 if 语句,Angular 使用 @if 控制块有条件地隐藏和显示模板及其内容的部分

@else 控制块

// user-controls.component.ts
@Component({
  standalone: true,
  selector: 'user-controls',
  template: `
    @if (isAdmin) {
      <button>Erase database</button>
    } @else {
      <p>You are not authorized.</p>
    }
  `,
})
export class UserControls {
  isAdmin = true;
}

@for 控制块

类似于 JavaScript 的 for...of 循环,Angular 提供了 @for 控制块来渲染重复的元素。

track 属性

当 Angular 使用 @for 渲染元素列表时,这些条目可能会在后续更改或移动。Angular 需要通过任何重新排序跟踪每个元素,通常是将条目的某个属性作为唯一标识符或键

<!-- ingredient-list.component.html -->
<ul>
  @for (ingredient of ingredientList; track ingredient.name) {
    <li>{{ ingredient.quantity }} - {{ ingredient.name }}</li>
  }
</ul>
// ingredient-list.component.ts
@Component({
  standalone: true,
  selector: 'ingredient-list',
  templateUrl: './ingredient-list.component.html',
})
export class IngredientList {
  ingredientList = [
    {name: 'noodles', quantity: 1},
    {name: 'miso broth', quantity: 1},
    {name: 'egg', quantity: 2},
  ];
}

# 处理用户交互

在你的应用中处理用户交互。

事件处理

  1. 在圆括号内添加一个带有事件名称的属性
  2. 指定当事件触发时你想运行的 JavaScript 语句
<button (click)="save()">Save</button>

例如,如果我们想创建一个按钮,当 click 事件触发时运行 transformText 函数,它看起来如下所示:

// text-transformer.component.ts
@Component({
  standalone: true,
  selector: 'text-transformer',
  template: `
    <p>{{ announcement }}</p>
    <button (click)="transformText()">Abracadabra!</button>
  `,
})
export class TextTransformer {
  announcement = 'Hello again Angular!';

  transformText() {
    this.announcement = this.announcement.toUpperCase();
  }
}

其他常见的事件监听器示例包括:

  • <input (keyup)="validateInput()" />
  • <input (keydown)="updateInput()" />

$event

如果你需要访问 事件 (opens new window) 对象,Angular 提供了一个隐式的 $event 变量,你可以将其传递给一个函数:

<button (click)="createUser($event)">Submit</button>

# 共享代码

依赖注入允许你共享代码。

当你需要在组件之间共享逻辑时,Angular 利用 依赖注入 (opens new window)的设计模式,允许你创建一个“服务”,从而允许你将代码注入组件,并从单一事实之源管理它。

什么是服务?

服务是可重用的代码片段,可以被注入

类似于定义组件,服务由以下内容组成:

  • 一个 TypeScript 装饰器,通过 @Injectable 声明该类为 Angular 服务,并允许你通过 providedIn 属性(通常为 'root')定义应用程序中哪些部分可以访问该服务,从而允许服务在应用程序中的任何地方被访问。
  • 一个 TypeScript 类,定义当服务被注入时将可访问的代码
import {Injectable} from '@angular/core';
@Injectable({
  providedIn: 'root',
})
export class CalculatorService {
  add(x: number, y: number) {
    return x + y;
  }
}

如何使用服务

  1. 导入服务
  2. 声明一个类字段,服务被注入到其中。将类字段赋值为调用内置函数 inject 创建服务的结果

这是在 Receipt组件中的可能样子:

import { Component } from '@angular/core';
import { CalculatorService } from './calculator.service';

@Component({
  selector: 'app-receipt',
  template: `<h1>The total is {{ totalCost }}</h1>`,
})

export class Receipt {
  private calculatorService = inject(CalculatorService);
  totalCost = this.calculatorService.add(50, 25);
}

在这个例子中,通过调用 Angular 函数 inject并将服务传递给它来使用 CalculatorService

# 进入开发

开始来正式学一下怎么开发Angular项目

  1. 安装node.js node --version
  2. 安装npm
  3. 安装Angular
    • npm install -g @angular/cli
  4. 安装集成开发工具:VsCode

# 创建

创建一个新的 Angular 项目:

npx @angular/cli new project1

启动服务

ng serve

创建一个新组件

ng generate component home

你创建 HomeComponent 时,会用到这些属性:

  • selector:描述了 Angular 要如何在模板中引用本组件。
  • standalone:描述组件是否需要一个 NgModule
  • imports:描述组件的依赖关系。
  • template:描述组件的 HTML 标记和布局。
  • styleUrls:以数组形式列出组件使用的 CSS 文件的 URL。
image-20250110221353958

将新组件添加到应用的布局

将新组件 HomeComponent 添加到应用的根组件 AppComponent

  1. 打开 app.component.ts

  2. app.component.ts 中使用import 来导入 HomeComponent

  3. app.component.ts中,在 @Component中,更新 imports数组属性,并添加 HomeComponent

  4. 更新 template属性,下面代码:

    template: `
        <main>
          <header class="brand-name">
            <img class="brand-logo" src="/assets/logo.svg" alt="logo" aria-hidden="true" />
          </header>
          <section class="content">
            <app-home></app-home>
          </section>
        </main>
      `,
    
  5. 保存并检查保持 ng serve运行

应用中的 Hello world应该从 HomeComponent更改为 home works!

image-20250110221848229

HomeComponent 添加特性

需求:添加一个搜索过滤器和按钮

  1. 打开 home.component.ts

  2. home.component.ts中,在 @Component中,使用此代码更新 template属性。

    template: `
        <section>
          <form>
            <input type="text" placeholder="Filter by city" />
            <button class="primary" type="button">Search</button>
          </form>
        </section>
      `,
    
  3. 打开 home.component.css 并使用如下样式更新其内容

    注意:在浏览器中,这些内容可以放在 src/app/home/home.component.ts中的 styles数组中。

    .results {
      display: grid;
      column-gap: 14px;
      row-gap: 14px;
      grid-template-columns: repeat(auto-fill, minmax(400px, 400px));
      margin-top: 50px;
      justify-content: space-around;
    }
    
    input[type="text"] {
      border: solid 1px #ccc;
      padding: 10px;
      border-radius: 8px;
      margin-right: 4px;
      display: inline-block;
      width: 30%;
    }
    
    button {
      padding: 10px;
      border: solid 1px #ccc;
      background: #ccc;
      color: white;
      border-radius: 8px;
    }
    
    @media (min-width: 500px) and (max-width: 768px) {
      .results {
        grid-template-columns: repeat(2, 1fr);
      }
    
      input[type="text"] {
        width: 70%;
      }
    }
    
    @media (max-width: 499px) {
      .results {
        grid-template-columns: 1fr;
      }
    }
    
    
    
  4. 保存运行检查

image-20250110222536825

需求:创建一个新组件:住房位置组件,新组件 HousingLocationComponent 添加到应用的 HomeComponent

  1. 运行此命令以创建一个新的 HousingLocationComponent

    ng generate component housingLocation
    
    
  2. 打开home.component.ts,导入组件

    import {HousingLocationComponent} from '../housing-location/housing-location.component';
    
  3. 通过将 HousingLocationComponent 添加到数组中,更新 @Component 元数据的 imports 属性

  4. 更新 @Component 元数据的 template 属性,包含对 <app-housing-location> 标签的引用。

image-20250111155912207

  1. 为组件添加样式,打开housing-location.component.css,将以下代码放进去

    .listing {
      background: var(--accent-color);
      border-radius: 30px;
      padding-bottom: 30px;
    }
    .listing-heading {
      color: var(--primary-color);
      padding: 10px 20px 0 20px;
    }
    .listing-photo {
      height: 250px;
      width: 100%;
      object-fit: cover;
      border-radius: 30px 30px 0 0;
    }
    .listing-location {
      padding: 10px 20px 20px 20px;
    }
    .listing-location::before {
      content: url("/assets/location-pin.svg") / "";
    }
    section.listing a {
      padding-left: 20px;
      text-decoration: none;
      color: var(--primary-color);
    }
    section.listing a::after {
      content: "\203A";
      margin-left: 5px;
    }
    

# 接口

接口 (opens new window) 是应用程序的自定义数据类型。

Angular就是使用typeScript来充分发挥在强类型编程环境中工作的优势

强类型检查降低了应用程序中的一个元素向另一个元素发送错误格式数据的可能性。这样接口的话就要好好的弄以下,创建出来使TypeScript 编译器捕获得更好

创建一个新接口

  1. 运行此命令以创建新接口

    ng generate interface housinglocation
    
  2. 打开 src/app/housinglocation.ts 文件,写下面代码

    export interface HousingLocation {
      id: number;
      name: string;
      city: string;
      state: string;
      photo: string;
      availableUnits: number;
      wifi: boolean;
      laundry: boolean;
    }
    
  3. 下面来在home.component.ts 进行导入接口

    import {HousingLocation} from '../housinglocation';
    
  4. 用如下代码替换空定义 export class HomeComponent {},以在组件中创建新接口的单个实

    export class HomeComponent {
      readonly baseUrl = 'https://angular.dev/assets/tutorials/common';
      housingLocation: HousingLocation = {
        id: 9999,
        name: 'Test Home',
        city: 'Test city',
        state: 'ST',
        photo: `${this.baseUrl}/example-house.jpg`,
        availableUnits: 99,
        wifi: true,
        laundry: false,
      };
    }
    
  5. 通过将类型为 HousingLocationhousingLocation 属性添加到 HomeComponent 类中,我们可以确认数据是否与接口的描述匹配。如果数据不符合接口的描述,IDE 将有足够的信息为我们提供有用的错误提示

image-20250111162611174

感觉是不是这样进度会相对比较慢一点

那就速通,下面会集中精华来记录知识点,案例也跟着敲,不过不会有笔记记录了

# 为组件添加一个输入参数

输入属性 (opens new window)允许组件共享数据。数据共享的方向是从父组件到子组件。

  1. 要导入Input装饰器
import {Component, Input} from '@angular/core';
import {CommonModule} from '@angular/common';
  1. 添加Input属性

    在属性名称后面添加一个 !,并在前面加上 @Input() 装饰器

    export class HousingLocationComponent {
      @Input() housingLocation!: HousingLocation;
    }
    

    必须添加 !,因为输入框期望传递一个值。在这种情况下,没有默认值。

image-20250112115139616

向组件模板添加一个属性绑定

属性绑定使你能够将一个变量连接到 Angular 模板中的一个 Input。然后,数据会动态绑定到 Input

image-20250112115428017

当向组件标签添加属性绑定时,我们使用 [attribute] = "value" 语法来通知 Angular 所赋的值应该视为来自组件类的属性,而不是字符串值。

右侧的值是从 HomeComponent中属性的名称。

# 向组件模板添加插值

使用插值在模板中显示值(属性和 Input值)。

在 Angular 模板中使用 ,可以从属性、 Inputs 和有效的 JavaScript 表达式中渲染值。

image-20250112115726445

# 使用*ngFor在组件中列出对象

在 Angular 中, ngFor 是一种特定类型的 指令 (opens new window),用于在模板中动态重复数据。在纯 JavaScript 中,你会使用 for 循环 — ngFor 为 Angular 模板提供了类似的功能。

你可以利用 ngFor 迭代数组,甚至是异步值。

image-20250112152056256

# Angular 服务

Angular 服务为你提供了一种分离 Angular 应用程序数据和功能的方法,这些数据和功能可由应用程序中的多个组件使用。要被多个组件使用,服务必须是可注入的。由组件注入和使用的服务就成了该组件的依赖项。该组件依赖于这些服务,没有它们就无法运行。

依赖注入

依赖注入是一种用于管理应用程序组件和其他组件可以使用的服务的依赖关系的机制。

  1. 行此命令以创建新服务

    ng generate service housing --skip-tests
    
  2. 新的服务添加静态数据,从 HomeComponent 复制 housingLocationList 变量及其数组值。

  3. 写如下的函数允许依赖项访问服务的数据。

    getAllHousingLocations(): HousingLocation[] {
        return this.housingLocationList;
      }
      getHousingLocationById(id: number): HousingLocation | undefined {
        return this.housingLocationList.find((housingLocation) => housingLocation.id === id);
      }
    
  4. 别忘了导入housinglocation.ts的接口

    image-20250112153556512

    image-20250112153624747

  5. 将新服务注入到 HomeComponent

    • home.component.ts添加 inject 到从 @angular/core 导入的条目中。

      import {Component, inject} from '@angular/core';
      
    • HousingService 添加一个新的文件级导入

    • HomeComponent 中删除 housingLocationList 数组条目,并将 housingLocationList 赋值为空数组 ( []),后面更新代码以从 HousingService 中获取数据。

    • HomeComponent 中,添加以下代码以注入新的服务并初始化应用的数据。constructor 是创建此组件时运行的第一个函数。 constructor 中的代码将 housingLocationList 赋值为调用 getAllHousingLocations 返回的值。

      housingService: HousingService = inject(HousingService);
        constructor() {
          this.housingLocationList = this.housingService.getAllHousingLocations();
        }
      

    image-20250112154037272

# 向应用添加路由

路由是从应用中的一个组件导航到另一个组件的能力。在 单页应用(SPA) (opens new window) 中,只有页面的部分内容会更新,以呈现用户请求的视图。

Angular 路由器 (opens new window)允许用户声明路由,并指定当应用请求该路由时应在屏幕上显示的组件。

  1. 创建默认的详情组件

    ng generate component details
    
  2. 为应用添加路由

    1. src/app 目录中,创建一个名为 routes.ts 的文件。该文件是我们将在应用程序中定义路由的地方

    2. 添加文件级别的导入。定义一个名为 routeConfig 的变量,其类型为 Routes,并为应用定义两个路由:

      import {Routes} from '@angular/router';
      import {HomeComponent} from './home/home.component';
      import {DetailsComponent} from './details/details.component';
      
      const routeConfig: Routes = [
        {
          path: '',
          component: HomeComponent,
          title: 'Home page',
        },
        {
          path: 'details/:id',
          component: DetailsComponent,
          title: 'Home details',
        },
      ];
      export default routeConfig;
      
    3. main.ts 中,进行以下更新以在应用程序中启用路由:

      1. 导入路由文件和 provideRouter 函数:

        import {provideRouter} from '@angular/router';
        import routeConfig from './app/routes';
        
      2. 更新对 bootstrapApplication 的调用以包含路由配置:

        bootstrapApplication(AppComponent, {
          providers: [provideProtractorTestingSupport(), provideRouter(routeConfig)],
        }).catch((err) => console.error(err));
        

        image-20250112160059491

  3. src/app/app.component.ts 中,更新组件以使用路由:

    1. RoutingModule 添加文件级导入:

      import {RouterModule} from '@angular/router';
      
    2. @Component 元数据的 imports 中添加 RouterModule

      imports: [HomeComponent, RouterModule],
      
    3. template 属性中,用 <router-outlet> 指令替换 <app-home></app-home> 标签,并添加一个返回主页的链接

      template: `
          <main>
            <a [routerLink]="['/']">
              <header class="brand-name">
                <img class="brand-logo" src="/assets/logo.svg" alt="logo" aria-hidden="true" />
              </header>
            </a>
            <section class="content">
              <router-outlet></router-outlet>
            </section>
          </main>
          `
      

      image-20250112160314460

# 带参数路由

路由参数使你能够将动态信息作为路由 URL 的一部分。

:id 是动态的,并且会根据代码请求路由的方式而变化。

  1. src/app/housing-location/housing-location.component.ts 中,向 section 元素添加一个锚标签,并包含 routerLink 指令:

    <a [routerLink]="['/details', housingLocation.id]">Learn More</a>
    

    routerLink 指令使 Angular 的路由器能够在应用中创建动态链接.赋予 routerLink 的值是一个包含两部分的数组:路径的静态部分和动态数据。

    注意导入模块:

    image-20250112214716316

  2. 获取路由参数

    1. src/app/details/details.component.ts 中更新模板以导入你需要在 DetailsComponent 中使用的函数、类和服务:

      import {Component, inject} from '@angular/core';
      import {CommonModule} from '@angular/common';
      import {ActivatedRoute} from '@angular/router';
      import {HousingService} from '../housing.service';
      import {HousingLocation} from '../housinglocation';
      
    2. 更新以下代码:

      image-20250112215041612

  3. 自定义DetailComponent 添加对 HousingService 的调用

    1. 把模板代码更新成这样:

      template: `
          <article>
            <img
              class="listing-photo"
              [src]="housingLocation?.photo"
              alt="Exterior photo of {{ housingLocation?.name }}"
              crossorigin
            />
            <section class="listing-description">
              <h2 class="listing-heading">{{ housingLocation?.name }}</h2>
              <p class="listing-location">{{ housingLocation?.city }}, {{ housingLocation?.state }}</p>
            </section>
            <section class="listing-features">
              <h2 class="section-heading">About this housing location</h2>
              <ul>
                <li>Units available: {{ housingLocation?.availableUnits }}</li>
                <li>Does this location have wifi: {{ housingLocation?.wifi }}</li>
                <li>Does this location have laundry: {{ housingLocation?.laundry }}</li>
              </ul>
            </section>
          </article>
        `,
      
    2. DetailsComponent 类的主体更新成这样:

      export class DetailsComponent {
        route: ActivatedRoute = inject(ActivatedRoute);
        housingService = inject(HousingService);
        housingLocation: HousingLocation | undefined;
        constructor() {
          const housingLocationId = Number(this.route.snapshot.params['id']);
          this.housingLocation = this.housingService.getHousingLocationById(housingLocationId);
        }
      }
      
    3. 样式代码:

      .listing-photo {
        height: 600px;
        width: 50%;
        object-fit: cover;
        border-radius: 30px;
        float: right;
      }
      .listing-heading {
        font-size: 48pt;
        font-weight: bold;
        margin-bottom: 15px;
      }
      .listing-location::before {
        content: url('/assets/location-pin.svg') / '';
      }
      .listing-location {
        font-size: 24pt;
        margin-bottom: 15px;
      }
      .listing-features > .section-heading {
        color: var(--secondary-color);
        font-size: 24pt;
        margin-bottom: 15px;
      }
      .listing-features {
        margin-bottom: 20px;
      }
      .listing-features li {
        font-size: 14pt;
      }
      li {
        list-style-type: none;
      }
      .listing-apply .section-heading {
        font-size: 18pt;
        margin-bottom: 15px;
      }
      label, input {
        display: block;
      }
      label {
        color: var(--secondary-color);
        font-weight: bold;
        text-transform: uppercase;
        font-size: 12pt;
      }
      input {
        font-size: 16pt;
        margin-bottom: 15px;
        padding: 10px;
        width: 400px;
        border-top: none;
        border-right: none;
        border-left: none;
        border-bottom: solid .3px;
      }
      @media (max-width: 1024px) {
        .listing-photo {
          width: 100%;
          height: 400px;
        }
      }
      

      image-20250112215521574

  4. 为HomeComponent添加导航

    确认你的AppComponent 的模板代码与以下内容一致

    imports: [HomeComponent, RouterLink, RouterOutlet],
      template: `
        <main>
          <a [routerLink]="['/']">
            <header class="brand-name">
              <img class="brand-logo" src="/assets/logo.svg" alt="logo" aria-hidden="true" />
            </header>
          </a>
          <section class="content">
            <router-outlet></router-outlet>
          </section>
        </main>
      `,
    

# 集成Angular表单

  1. 向服务housing.service.ts添加一个方法来接收数据和打印数据到控制台,将此方法粘贴到类定义的底部

    submitApplication(firstName: string, lastName: string, email: string) {
        console.log(
          `Homes application received: firstName: ${firstName}, lastName: ${lastName}, email: ${email}.`,
        );
      }
    
  2. 在详情页添加表单功能

    1. 在文件顶部的 import 语句之后,添加以下代码以导入 Angular 表单类

      import {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms';
      
    2. DetailsComponent 装饰器的元数据中,使用以下代码更新 imports 属性:

      imports: [CommonModule, ReactiveFormsModule],
      
    3. DetailsComponent 类中的 constructor() 方法之前,添加以下代码以创建表单对象

      applyForm = new FormGroup({
          firstName: new FormControl(''),
          lastName: new FormControl(''),
          email: new FormControl(''),
        });
      

      在 Angular 中, FormGroupFormControl 是用于构建表单的类型。 FormControl 类型可以提供默认值并形成表单数据。在此示例中, firstName 是一个 string,默认值为空字符串。

    4. DetailsComponent 类中的 constructor() 方法之后,添加以下代码来处理 Apply now 的点击事件

      submitApplication() {
          this.housingService.submitApplication(
            this.applyForm.value.firstName ?? '',
            this.applyForm.value.lastName ?? '',
            this.applyForm.value.email ?? '',
          );
        }
      

      此代码使用空值合并运算符,如果值为 null,则默认为空字符串。

  3. 将表单的标记添加到详情页

    DetailsComponent 装饰器的元数据中,把 template 的 HTML 更新成如下代码,以添加表单的标记

    template: `
        <article>
          <img
            class="listing-photo"
            [src]="housingLocation?.photo"
            alt="Exterior photo of {{ housingLocation?.name }}"
            crossorigin
          />
          <section class="listing-description">
            <h2 class="listing-heading">{{ housingLocation?.name }}</h2>
            <p class="listing-location">{{ housingLocation?.city }}, {{ housingLocation?.state }}</p>
          </section>
          <section class="listing-features">
            <h2 class="section-heading">About this housing location</h2>
            <ul>
              <li>Units available: {{ housingLocation?.availableUnits }}</li>
              <li>Does this location have wifi: {{ housingLocation?.wifi }}</li>
              <li>Does this location have laundry: {{ housingLocation?.laundry }}</li>
            </ul>
          </section>
          <section class="listing-apply">
            <h2 class="section-heading">Apply now to live here</h2>
            <form [formGroup]="applyForm" (submit)="submitApplication()">
              <label for="first-name">First Name</label>
              <input id="first-name" type="text" formControlName="firstName" />
              <label for="last-name">Last Name</label>
              <input id="last-name" type="text" formControlName="lastName" />
              <label for="email">Email</label>
              <input id="email" type="email" formControlName="email" />
              <button type="submit" class="primary">Apply now</button>
            </form>
          </section>
        </article>
      `,
    

# 表单搜索

input 元素中包含一个名为 #filter 的模板变量

image-20250116113418274

# 添加HTTP通信

将 HTTP 和 API 集成到应用程序中,让应用程序通过 HTTP 与 JSON 服务器通信

  1. 配置JSON服务器

    JSON Server 是一种用于创建模拟 REST API 的开源工具

    1. 使用以下命令从 npm 安装 json-server

      npm install -g json-server
      
    2. 在项目的根目录中,创建一个名为 db.json 的文件。这是你将存储 json-server 数据的地方。

    3. 打开 db.json 并将以下代码复制到文件中

      {
               "locations": [
                   {
                       "id": 0,
                       "name": "Acme Fresh Start Housing",
                       "city": "Chicago",
                       "state": "IL",
                       "photo": "${this.baseUrl}/bernard-hermant-CLKGGwIBTaY-unsplash.jpg",
                       "availableUnits": 4,
                       "wifi": true,
                       "laundry": true
                   },
                   {
                       "id": 1,
                       "name": "A113 Transitional Housing",
                       "city": "Santa Monica",
                       "state": "CA",
                       "photo": "${this.baseUrl}/brandon-griggs-wR11KBaB86U-unsplash.jpg",
                       "availableUnits": 0,
                       "wifi": false,
                       "laundry": true
                   },
                   {
                       "id": 2,
                       "name": "Warm Beds Housing Support",
                       "city": "Juneau",
                       "state": "AK",
                       "photo": "${this.baseUrl}/i-do-nothing-but-love-lAyXdl1-Wmc-unsplash.jpg",
                       "availableUnits": 1,
                       "wifi": false,
                       "laundry": false
                   },
                   {
                       "id": 3,
                       "name": "Homesteady Housing",
                       "city": "Chicago",
                       "state": "IL",
                       "photo": "${this.baseUrl}/ian-macdonald-W8z6aiwfi1E-unsplash.jpg",
                       "availableUnits": 1,
                       "wifi": true,
                       "laundry": false
                   },
                   {
                       "id": 4,
                       "name": "Happy Homes Group",
                       "city": "Gary",
                       "state": "IN",
                       "photo": "${this.baseUrl}/krzysztof-hepner-978RAXoXnH4-unsplash.jpg",
                       "availableUnits": 1,
                       "wifi": true,
                       "laundry": false
                   },
                   {
                       "id": 5,
                       "name": "Hopeful Apartment Group",
                       "city": "Oakland",
                       "state": "CA",
                       "photo": "${this.baseUrl}/r-architecture-JvQ0Q5IkeMM-unsplash.jpg",
                       "availableUnits": 2,
                       "wifi": true,
                       "laundry": true
                   },
                   {
                       "id": 6,
                       "name": "Seriously Safe Towns",
                       "city": "Oakland",
                       "state": "CA",
                       "photo": "${this.baseUrl}/phil-hearing-IYfp2Ixe9nM-unsplash.jpg",
                       "availableUnits": 5,
                       "wifi": true,
                       "laundry": true
                   },
                   {
                       "id": 7,
                       "name": "Hopeful Housing Solutions",
                       "city": "Oakland",
                       "state": "CA",
                       "photo": "${this.baseUrl}/r-architecture-GGupkreKwxA-unsplash.jpg",
                       "availableUnits": 2,
                       "wifi": true,
                       "laundry": true
                   },
                   {
                       "id": 8,
                       "name": "Seriously Safe Towns",
                       "city": "Oakland",
                       "state": "CA",
                       "photo": "${this.baseUrl}/saru-robert-9rP3mxf8qWI-unsplash.jpg",
                       "availableUnits": 10,
                       "wifi": false,
                       "laundry": false
                   },
                   {
                       "id": 9,
                       "name": "Capital Safe Towns",
                       "city": "Portland",
                       "state": "OR",
                       "photo": "${this.baseUrl}/webaliser-_TPTXZd9mOo-unsplash.jpg",
                       "availableUnits": 6,
                       "wifi": true,
                       "laundry": true
                   }
               ]
           }
      
    4. 测试配置,在项目的根目录下运行以下命令

      json-server --watch db.json
      
    5. 在你的 Web 浏览器中,导航到 http://localhost:3000/locations 并确认响应里包括存储在 db.json 中的数据

  2. 更新服务以及组件以使用 Web 服务器而不是本地数组

    数据源已配置,下一步是更新你的 Web 应用程序以连接到它来使用数据。

    1. 更新serve.ts

      image-20250116114207111

    2. 更新home.component.ts的constructor

      constructor() {
          this.housingService.getAllHousingLocations().then((housingLocationList: HousingLocation[]) => {
            this.housingLocationList = housingLocationList;
            this.filteredLocationList = housingLocationList;
          });
        }
      
    3. 更新details.component.ts的constructor

      constructor() {
          const housingLocationId = parseInt(this.route.snapshot.params['id'], 10);
          this.housingService.getHousingLocationById(housingLocationId).then((housingLocation) => {
            this.housingLocation = housingLocation;
          });
        }