JavaScript 第26章:Angular 基础

Angular是由Google维护的一个开源框架,用于构建Web应用程序。Angular提供了一套完整的解决方案来构建动态Web应用,它利用TypeScript语言来编写组件,并提供了一系列内置指令和管道来简化前端开发工作。

Angular 概述

Angular是一个完整的MVC(Model-View-Controller)框架,它旨在简化Web应用的开发流程,通过模块化的方式组织代码,提供依赖注入系统,以及丰富的指令和管道等功能。Angular支持组件化开发,这意味着你可以把应用分割成小的、可重用的组件。

组件与服务

在Angular中,组件是构成应用的基本单元。每个组件都由三个部分组成:一个类(组件类),一个HTML模板,以及一个样式表。组件类负责业务逻辑,模板定义了组件的视图,样式表则负责组件的外观。

服务则是封装了应用逻辑的部分,通常用来处理数据获取、认证等跨组件的工作。服务可以通过依赖注入(DI)机制在组件中使用。

模板语法

Angular模板语法允许你在HTML中嵌入TypeScript代码,以便实现动态内容。你可以使用*ngFor来遍历数据集合,使用*ngIf来控制元素的显示隐藏,以及其他内置指令来控制DOM元素的行为。

数据绑定

Angular支持多种类型的数据绑定:

  • 属性绑定 :将组件类中的属性值绑定到HTML元素的属性上,如[attr.class]="className"
  • 事件绑定 :将事件(如点击)与组件类中的方法绑定起来,如(click)="onClick($event)"
  • 双向数据绑定 :通过[(ngModel)]指令来同步HTML控件和组件类中的属性。

实战案例:Angular入门应用

下面是一个简单的Angular入门应用,该应用将展示一个待办事项列表,并允许用户添加和删除待办事项。

创建Angular应用

首先,你需要安装Angular CLI来创建一个新的Angular项目:

bash 复制代码
npm install -g @angular/cli
ng new my-todo-app
cd my-todo-app
修改AppComponent

接下来,修改src/app/app.component.ts文件,创建一个简单的待办事项列表。

typescript 复制代码
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'my-todo-app';
  todos: string[] = [];

  addTodo(todo: string) {
    this.todos.push(todo);
  }

  removeTodo(index: number) {
    this.todos.splice(index, 1);
  }
}
修改AppComponent模板

接着,在src/app/app.component.html中添加模板代码:

html 复制代码
<h1>{{ title }}</h1>
<input [(ngModel)]="newTodo" placeholder="输入待办事项">
<button (click)="addTodo(newTodo)">添加</button>
<ul>
  <li *ngFor="let todo of todos; let i = index">
    {{ todo }}
    <button (click)="removeTodo(i)">删除</button>
  </li>
</ul>

注意,这里使用了ngModel来进行双向数据绑定,你需要导入FormsModule来启用模板驱动的表单。

导入FormsModule

src/app/app.module.ts中导入FormsModule

typescript 复制代码
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms'; // 导入FormsModule

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule // 添加FormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
启动应用

最后,运行以下命令启动应用:

bash 复制代码
ng serve

打开浏览器访问 http://localhost:4200,你应该可以看到一个简单的待办事项列表应用。

这个例子展示了如何使用Angular创建一个简单的Web应用,包括如何定义组件、使用模板语法进行数据绑定以及处理用户输入。通过这些基础知识,你可以开始构建更复杂的应用程序了。

接下来,让我们继续深入探讨Angular的基础知识,并通过扩展之前的待办事项应用来进一步演示Angular的一些核心特性。

更深入地了解Angular特性

依赖注入(Dependency Injection, DI)

Angular的依赖注入系统允许你轻松地管理和传递依赖项。依赖注入可以让你在组件内部声明它所需要的服务或其他对象,而不必关心这些对象是如何被创建的。Angular会负责创建这些依赖并将它们注入到需要的地方。

指令(Directives)

除了Angular内置的指令(如*ngFor, *ngIf等),你还可以创建自定义指令来扩展HTML的行为。自定义指令可以用来改变DOM元素的行为、样式,甚至可以响应用户交互。

管道(Pipes)

管道是在Angular模板中使用的特殊函数,用于转换一个值。例如,可以使用内置的日期管道{``{ date | date:'short' }}来格式化日期字符串。

扩展待办事项应用

现在,让我们通过添加一些新的功能来扩展之前的待办事项应用,比如使用服务来管理待办事项列表、使用自定义指令来高亮显示完成的任务、以及使用管道来格式化日期。

创建服务来管理待办事项

首先,我们需要创建一个服务来管理待办事项列表。使用Angular CLI来创建一个服务:

bash 复制代码
ng generate service todo

然后编辑src/app/todo.service.ts

typescript 复制代码
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class TodoService {
  private todos: { text: string, completed: boolean, createdAt: Date }[] = [];

  constructor() { }

  addTodo(text: string): void {
    this.todos.push({ text, completed: false, createdAt: new Date() });
  }

  removeTodo(index: number): void {
    this.todos.splice(index, 1);
  }

  getTodos(): { text: string, completed: boolean, createdAt: Date }[] {
    return this.todos;
  }

  toggleTodo(index: number): void {
    this.todos[index].completed = !this.todos[index].completed;
  }
}
在组件中使用服务

接下来,我们需要在AppComponent中使用TodoService来管理待办事项列表:

typescript 复制代码
import { Component } from '@angular/core';
import { TodoService } from './todo.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'my-todo-app';
  newTodo = '';

  constructor(private todoService: TodoService) { }

  addTodo() {
    this.todoService.addTodo(this.newTodo);
    this.newTodo = '';
  }

  removeTodo(index: number) {
    this.todoService.removeTodo(index);
  }

  toggleTodo(index: number) {
    this.todoService.toggleTodo(index);
  }

  getTodos() {
    return this.todoService.getTodos();
  }
}
创建自定义指令

我们可以创建一个自定义指令来高亮显示已完成的任务:

typescript 复制代码
import { Directive, HostBinding, Input } from '@angular/core';

@Directive({
  selector: '[appHighlight]'
})
export class HighlightDirective {
  @Input() appHighlight: boolean;

  @HostBinding('class.completed') get highlight() {
    return this.appHighlight;
  }
}

注册自定义指令:

typescript 复制代码
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { HighlightDirective } from './highlight.directive';
import { TodoService } from './todo.service';

@NgModule({
  declarations: [
    AppComponent,
    HighlightDirective
  ],
  imports: [
    BrowserModule,
    FormsModule
  ],
  providers: [TodoService],
  bootstrap: [AppComponent]
})
export class AppModule { }
更新模板

更新app.component.html来使用自定义指令和管道:

html 复制代码
<h1>{{ title }}</h1>
<input [(ngModel)]="newTodo" placeholder="输入待办事项">
<button (click)="addTodo()">添加</button>
<ul>
  <li *ngFor="let todo of getTodos(); let i = index" appHighlight="todo.completed">
    <input type="checkbox" [(ngModel)]="todo.completed">
    <span [ngClass]="{ completed: todo.completed }">{{ todo.text }}</span>
    <small>{{ todo.createdAt | date }}</small>
    <button (click)="removeTodo(i)">删除</button>
  </li>
</ul>

总结

通过这些扩展,我们不仅增强了待办事项应用的功能,还引入了Angular的服务、自定义指令以及管道等特性。这些特性使得Angular成为构建复杂Web应用程序的理想选择。希望这些示例能够帮助你更好地理解Angular,并鼓励你在自己的项目中运用这些知识。

我们将继续扩展待办事项应用,并探讨Angular的一些其他重要特性,如路由管理、表单处理、以及如何使用Angular Material来美化应用界面。

Angular路由管理

为了使待办事项应用更加实用,我们可以添加一些额外的页面,如登录页面、用户信息页面等,并使用Angular路由来管理这些页面之间的导航。

创建路由

首先,我们需要创建一个新的组件作为登录页面,并配置路由。

  1. 使用Angular CLI创建一个新的组件:

    bash 复制代码
    ng generate component login
  2. 编辑login.component.html,创建一个简单的登录表单:

    html 复制代码
    <h1>登录</h1>
    <form (ngSubmit)="onLogin()">
      <div>
        <label for="username">用户名:</label>
        <input id="username" type="text" [(ngModel)]="username" name="username" required>
      </div>
      <div>
        <label for="password">密码:</label>
        <input id="password" type="password" [(ngModel)]="password" name="password" required>
      </div>
      <button type="submit">登录</button>
    </form>
  3. 编辑login.component.ts

    typescript 复制代码
    import { Component } from '@angular/core';
    
    @Component({
      selector: 'app-login',
      templateUrl: './login.component.html',
      styleUrls: ['./login.component.css']
    })
    export class LoginComponent {
      username = '';
      password = '';
    
      onLogin() {
        // 登录逻辑
      }
    }
  4. 配置路由:

    编辑app-routing.module.ts

    typescript 复制代码
    import { NgModule } from '@angular/core';
    import { RouterModule, Routes } from '@angular/router';
    import { AppComponent } from './app.component';
    import { LoginComponent } from './login/login.component';
    
    const routes: Routes = [
      { path: '', component: AppComponent },
      { path: 'login', component: LoginComponent }
    ];
    
    @NgModule({
      imports: [RouterModule.forRoot(routes)],
      exports: [RouterModule]
    })
    export class AppRoutingModule { }
  5. 更新app.component.html以包含路由出口:

    html 复制代码
    <router-outlet></router-outlet>

表单处理

在上面的登录页面中,我们使用了模板驱动的表单。Angular还支持反应式的表单处理,这对于更复杂的表单场景非常有用。下面是一个使用反应式表单的例子:

使用反应式表单
  1. 更新login.component.ts以使用反应式表单:

    typescript 复制代码
    import { Component, OnInit } from '@angular/core';
    import { FormBuilder, FormGroup, Validators } from '@angular/forms';
    
    @Component({
      selector: 'app-login',
      templateUrl: './login.component.html',
      styleUrls: ['./login.component.css']
    })
    export class LoginComponent implements OnInit {
      loginForm: FormGroup;
    
      constructor(private fb: FormBuilder) { }
    
      ngOnInit() {
        this.loginForm = this.fb.group({
          username: ['', Validators.required],
          password: ['', Validators.required]
        });
      }
    
      onLogin() {
        if (this.loginForm.valid) {
          // 登录逻辑
        }
      }
    }
  2. 更新login.component.html以使用formControlName

    html 复制代码
    <form [formGroup]="loginForm" (ngSubmit)="onLogin()">
      <div>
        <label for="username">用户名:</label>
        <input id="username" type="text" formControlName="username" required>
      </div>
      <div>
        <label for="password">密码:</label>
        <input id="password" type="password" formControlName="password" required>
      </div>
      <button type="submit">登录</button>
    </form>

使用Angular Material

Angular Material 提供了许多预构建的组件,可以快速美化你的应用界面。下面我们来添加一些Material组件来改善待办事项应用的外观。

  1. 安装Angular Material:

    bash 复制代码
    ng add @angular/material
  2. 选择一个合适的主题颜色方案,并在全局样式文件中导入:

    css 复制代码
    /* styles.css */
    @import '~@angular/material/prebuilt-themes/deeppurple-amber.css';
  3. app.module.ts中导入所需的Material模块:

    typescript 复制代码
    import { NgModule } from '@angular/core';
    import { BrowserModule } from '@angular/platform-browser';
    import { FormsModule } from '@angular/forms';
    import { AppComponent } from './app.component';
    import { HighlightDirective } from './highlight.directive';
    import { TodoService } from './todo.service';
    import { RouterModule } from '@angular/router';
    import { AppRoutingModule } from './app-routing.module';
    import { LoginComponent } from './login/login.component';
    import { MatInputModule } from '@angular/material/input';
    import { MatButtonModule } from '@angular/material/button';
    import { MatCheckboxModule } from '@angular/material/checkbox';
    import { MatListModule } from '@angular/material/list';
    
    @NgModule({
      declarations: [
        AppComponent,
        HighlightDirective,
        LoginComponent
      ],
      imports: [
        BrowserModule,
        FormsModule,
        RouterModule,
        AppRoutingModule,
        MatInputModule,
        MatButtonModule,
        MatCheckboxModule,
        MatListModule
      ],
      providers: [TodoService],
      bootstrap: [AppComponent]
    })
    export class AppModule { }
  4. 更新app.component.html以使用Material组件:

    html 复制代码
    <mat-toolbar color="primary">
      <span>{{ title }}</span>
    </mat-toolbar>
    <mat-list>
      <mat-list-item *ngFor="let todo of getTodos(); let i = index">
        <mat-checkbox [(ngModel)]="todo.completed"></mat-checkbox>
        <mat-list-item-content>
          <span [ngClass]="{ completed: todo.completed }">{{ todo.text }}</span>
          <small>{{ todo.createdAt | date }}</small>
        </mat-list-item-content>
        <button mat-button (click)="removeTodo(i)">删除</button>
      </mat-list-item>
    </mat-list>
  5. 更新login.component.html以使用Material组件:

    html 复制代码
    <mat-toolbar color="primary">
      <span>登录</span>
    </mat-toolbar>
    <form [formGroup]="loginForm" (ngSubmit)="onLogin()">
      <mat-form-field appearance="fill">
        <mat-label>用户名</mat-label>
        <input matInput formControlName="username" required>
      </mat-form-field>
      <mat-form-field appearance="fill">
        <mat-label>密码</mat-label>
        <input matInput type="password" formControlName="password" required>
      </mat-form-field>
      <button mat-raised-button color="primary" type="submit">登录</button>
    </form>

总结

通过上述步骤,我们不仅增强了待办事项应用的功能,还引入了Angular的路由管理、表单处理以及Angular Material来提升应用的用户体验。这些特性使得Angular成为一个强大的框架,适用于构建高度交互性的Web应用程序。

好的,我们继续扩展待办事项应用,并进一步探讨Angular的一些其他特性,如状态管理、单元测试等。此外,我们还将讨论如何优化和重构代码,以确保应用的可维护性和可扩展性。

状态管理:使用NgRx

对于较大的应用来说,状态管理变得非常重要,特别是当多个组件需要共享状态时。Angular生态系统中,NgRx是一个流行的库,用于实现Redux模式的状态管理。下面我们将使用NgRx来管理待办事项的状态。

安装NgRx

首先,安装NgRx相关的包:

bash 复制代码
ng add @ngrx/store
ng add @ngrx/entity
创建State和Reducer

创建一个状态来存储待办事项列表,并定义一个reducer来处理状态更新。

  1. 创建一个store模块:

    bash 复制代码
    ng generate module store --flat --module=app
  2. store模块中创建状态和reducer:

    typescript 复制代码
    // store/todo.state.ts
    export interface Todo {
      id: number;
      text: string;
      completed: boolean;
      createdAt: Date;
    }
    
    export interface State {
      todos: Todo[];
    }
    
    export const initialState: State = {
      todos: []
    };
    
    // store/todo.reducer.ts
    import { createReducer, on } from '@ngrx/store';
    import { addTodo, removeTodo, toggleTodo } from './todo.actions';
    
    export const todoReducer = createReducer(
      initialState,
      on(addTodo, (state, { todo }) => ({
        ...state,
        todos: [...state.todos, todo]
      })),
      on(removeTodo, (state, { id }) => ({
        ...state,
        todos: state.todos.filter(todo => todo.id !== id)
      })),
      on(toggleTodo, (state, { id }) => ({
        ...state,
        todos: state.todos.map(todo =>
          todo.id === id ? { ...todo, completed: !todo.completed } : todo
        )
      }))
    );
  3. 创建Action文件:

    typescript 复制代码
    // store/todo.actions.ts
    import { createAction, props } from '@ngrx/store';
    
    export const addTodo = createAction(
      '[Todo] Add Todo',
      props<{ todo: Todo }>()
    );
    
    export const removeTodo = createAction(
      '[Todo] Remove Todo',
      props<{ id: number }>()
    );
    
    export const toggleTodo = createAction(
      '[Todo] Toggle Todo',
      props<{ id: number }>()
    );
  4. store.module.ts中注册reducers:

    typescript 复制代码
    // store.module.ts
    import { NgModule } from '@angular/core';
    import { StoreModule } from '@ngrx/store';
    import { todoReducer } from './todo.reducer';
    
    @NgModule({
      imports: [
        StoreModule.forRoot({ todos: todoReducer })
      ]
    })
    export class StoreModule { }
在组件中使用NgRx

接下来,我们需要在组件中使用NgRx来获取和更新状态。

  1. app.component.ts中使用select来获取状态:

    typescript 复制代码
    import { Component, OnInit } from '@angular/core';
    import { Store } from '@ngrx/store';
    import { addTodo, removeTodo, toggleTodo } from './store/todo.actions';
    import { selectTodos } from './store/todo.selectors';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css']
    })
    export class AppComponent implements OnInit {
      todos$ = this.store.select(selectTodos);
      newTodo = '';
    
      constructor(private store: Store) { }
    
      ngOnInit() {
        // 初始化逻辑
      }
    
      addTodo() {
        if (this.newTodo.trim()) {
          const newTodo = {
            id: Date.now(),
            text: this.newTodo,
            completed: false,
            createdAt: new Date()
          };
          this.store.dispatch(addTodo({ todo: newTodo }));
          this.newTodo = '';
        }
      }
    
      removeTodo(id: number) {
        this.store.dispatch(removeTodo({ id }));
      }
    
      toggleTodo(id: number) {
        this.store.dispatch(toggleTodo({ id }));
      }
    }
  2. 创建选择器来获取状态:

    typescript 复制代码
    // store/todo.selectors.ts
    import { createFeatureSelector, createSelector } from '@ngrx/store';
    import { State } from './todo.state';
    
    const selectTodoFeature = createFeatureSelector<State>('todos');
    
    export const selectTodos = createSelector(
      selectTodoFeature,
      state => state.todos
    );
更新模板

最后,更新app.component.html以反映状态变化:

html 复制代码
<h1>{{ title }}</h1>
<input [(ngModel)]="newTodo" placeholder="输入待办事项">
<button (click)="addTodo()">添加</button>
<ul>
  <li *ngFor="let todo of todos$ | async; let i = index">
    <mat-checkbox [(ngModel)]="todo.completed" (change)="toggleTodo(todo.id)"></mat-checkbox>
    <span [ngClass]="{ completed: todo.completed }">{{ todo.text }}</span>
    <small>{{ todo.createdAt | date }}</small>
    <button mat-button (click)="removeTodo(todo.id)">删除</button>
  </li>
</ul>

单元测试

Angular提供了Angular CLI工具来帮助编写单元测试。我们可以为组件和服务编写单元测试,以确保它们按预期工作。

创建单元测试
  1. AppComponent编写单元测试:

    bash 复制代码
    ng generate component app --spec=false
  2. 编写测试代码:

    typescript 复制代码
    // app.component.spec.ts
    import { ComponentFixture, TestBed } from '@angular/core/testing';
    import { AppComponent } from './app.component';
    import { StoreModule, combineReducers } from '@ngrx/store';
    import { provideMockStore } from '@ngrx/store/testing';
    import { todoReducer } from './store/todo.reducer';
    import { addTodo } from './store/todo.actions';
    
    describe('AppComponent', () => {
      let component: AppComponent;
      let fixture: ComponentFixture<AppComponent>;
    
      beforeEach(async () => {
        await TestBed.configureTestingModule({
          declarations: [AppComponent],
          imports: [
            StoreModule.forRoot({ todos: todoReducer }),
          ],
          providers: [provideMockStore()]
        }).compileComponents();
      });
    
      beforeEach(() => {
        fixture = TestBed.createComponent(AppComponent);
        component = fixture.componentInstance;
        fixture.detectChanges();
      });
    
      it('should create the app', () => {
        expect(component).toBeTruthy();
      });
    
      it('should dispatch addTodo action when addTodo is called', () => {
        const storeSpy = jasmine.createSpyObj('Store', ['dispatch']);
        component['store'] = storeSpy;
    
        component.addTodo();
    
        expect(storeSpy.dispatch).toHaveBeenCalledWith(addTodo({ todo: { id: expect.any(Number), text: '', completed: false, createdAt: expect.any(Date) } }));
      });
    });

总结

通过这些步骤,我们不仅增强了待办事项应用的功能,还引入了状态管理(NgRx)、单元测试等高级特性。这些特性使得Angular成为一个强大且灵活的框架,适合构建复杂的企业级Web应用程序。

相关推荐
foxhuli22921 分钟前
禁止ifrmare标签上的文件,实现自动下载功能,并且隐藏工具栏
前端
青皮桔1 小时前
CSS实现百分比水柱图
前端·css
失落的多巴胺1 小时前
使用deepseek制作“喝什么奶茶”随机抽签小网页
javascript·css·css3·html5
DataGear1 小时前
如何在DataGear 5.4.1 中快速制作SQL服务端分页的数据表格看板
javascript·数据库·sql·信息可视化·数据分析·echarts·数据可视化
影子信息1 小时前
vue 前端动态导入文件 import.meta.glob
前端·javascript·vue.js
青阳流月1 小时前
1.vue权衡的艺术
前端·vue.js·开源
样子20181 小时前
Vue3 之dialog弹框简单制作
前端·javascript·vue.js·前端框架·ecmascript
kevin_水滴石穿1 小时前
Vue 中报错 TypeError: crypto$2.getRandomValues is not a function
前端·javascript·vue.js
翻滚吧键盘1 小时前
vue文本插值
javascript·vue.js·ecmascript
孤水寒月2 小时前
给自己网站增加一个免费的AI助手,纯HTML
前端·人工智能·html