由于项目需要,最近学习了angular框架,相比于vue,angular还是有一定的难度,所以做一个记录与总结,方便以后学习!
一、初次使用
// 安装angular
npm install -g angular-cli
// 查看版本
ng --version
常用快捷命令
// 创建项目
ng new [project_name]
// 启动项目
ng serve
// 创建组件
ng generate component [component_name]
// 简写:ng g c [component_name]
// 创建服务
ng generate service [service_name]
// 创建路由守卫
ng generate guard [guard_name]
// 构建项目(angular 14版本以上)
ng build --configuration production
二、组件
组件生命周期
Constructor | 实例化组件类时执行 |
ngOnInit | 在首次接收到输入属性值后执行,在此处可以执行请求操作 |
ngDoCheck | 主要用于调试,只要输入属性发生变化,不论是基本数据类型还是引用数据类型还是引用数据类型中的属性变化,都会执行 |
ngAfterContentInit | 内容投影初始渲染完成后调用 |
ngAfterContentChecked | 内容投影更新完成后执行 |
ngAfterViewInit | 当组件视图渲染完成后调用 |
ngAfterViewChecked | 组件视图更新完成后执行 |
ngOnDestroy | 当组件被销毁之前调用, 用于清理操作 |
ngOnChanges | 当输入属性值发生变化时执行,初始设置时也会执行一次,顺序优于 ngOnInit 方法接收参数类型为 SimpleChanges 不论多少输入属性同时变化,钩子函数只会执行一次,变化的值会同时存储在参数中。对于基本数据类型来说, 只要值发生变化就可以被检测到,对于引用数据类型来说, 可以检测从一个对象变成另一个对象, 但是检测不到同一个对象中属性值的变化,但是不影响组件模板更新数据 |
创建组件
import { Component } from '@angular/core'
@Component({
// 指定组件的使用方式,当前为标记形式
// app-root => <app-root></app-root>
// [app-root] => <div app-root></div>
// .app-root => <div class="app-root" ></div>
selector: 'app-root',
// 导入所需的其他组件
imports: [],
// 关联的组件文件路径
// templateUrl: './app.component.html',
// 如果不使用路径,可以直接写组件模板
template:`<div></div>`,
// 关联的样式文件路径
// styleUrl: './app.component.scss',
// 如果不使用路径,可以直接写组件样式
styles:`div{ background-color:'red' }`
})
export class AppComponent {}
使用组件
//根据selector使用相应方式
<app-root></app-root>
父子组件通信
// 子组件定义@Input和@Output
import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector:'app-child',
...
})
export class ChildComponent {
//子组件声明输入字段,父组件使用子组件时可通过name传入数据(@Input 可选参数required)
@Input({required:true}) name:string;
//子组件声明事件,父组件使用子组件时可通过nameChange订阅事件
@Output() nameChange:EventEmitter<string> = new EventEmitter<string>()
//子组件调用事件触发父组件方法
invokeParent() {
this.nameChange.emit('child');
}
}
// 父组件通过标签注入
<app-child [name]="name" (nameChange)="print($event)" ></app-child>
//@Input 和 @Output 命名符合 input_name + Change 可以简写
<app-child [(name)]="name" ></app-child>
import { Component } from '@angular/core';
@Component(...)
export class AppComponent {
name:string = "hello";
print(str:string):void{
console.log(str);
}
}
三、服务
创建服务
import { Injectable } from '@angular/core';
@Injectable({
//服务作用域
// 'root':注入到AppModule,提供该服务,所有子组件都可以使用
// 组件名:只作用于该组件
// null:不设定服务作用域
providedIn:'root'
})
export class TestService {}
使用服务
import { Component, inject } from '@angular/core';
import {TestService} from './test.service';
@Component({
//如果需要为该组件创建一个局部的服务实例,而不会影响应用中其他组件或模块中该服务的实例,可以通过 @Component 或 @Directive 内部的 providers: []
providers:[TestService]
})
export class AppComponent {
//注入服务
//方式一 通过constructor实现依赖注入
constructor(private testService:TestService){}
//方式二 使用inject注入
testService:TestService = inject(TestService);
}
利用服务实现非父子组件通信
创建公共服务
import { EventEmitter, Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class CommunicationService {
event = new EventEmitter<void>();
constructor() { }
trigger(){
// console.log('invoke');
this.event.emit();
}
}
A组件订阅
//注入服务
constructor(private communication:CommunicationService) {
//订阅服务中事件
communication.event.subscribe(()=>{
console.log('A组件订阅');
})
}
B组件调用方法触发
//注入服务
constructor(private communication:CommunicationService) {
}
trigger() {
//调用服务中trigger方法触发A组件的订阅
this.communication.trigger();
}
四、指令
@NgModel
创建模块
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
const routes: Routes = [];
@NgModule({
// 该数组列出了模块依赖的其他模块
imports: [RouterModule.forRoot(routes)],
// 该数组列出了需要暴露给外部模块的内容。这里暴露了 RouterModule,因此在其他模块中也能使用路由功能
exports: [RouterModule]
})
export class AppRoutingModule { }
使用模块
import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
@NgModule({
// 声明在该模块中使用的组件、指令和管道
declarations: [
AppComponent
],
// 模块所依赖的其他模块
imports: [
BrowserModule, HttpClientModule,
AppRoutingModule ],
// 定义该模块的服务提供者
providers: [
provideAnimationsAsync()
],
// 指定应用程序启动时要加载的根组件
bootstrap: [AppComponent]
})
export class AppModule { }
常用指令
*ngIf&*ngFor&*ngSwitch
// 组件需导入CommonModule模块
import { CommonModule } from '@angular/common';
// *ngIf,*ngFor,*ngSwitch用法类似Vue
<div *ngIf="false" ></div>
<ul>
<li *ngFor = "let item of items">
{{ item }}
</li>
</ul>
@if&@for@switch(angular18新增)
@if (value === 'jake') {
...
}@else{
...
}
@for (item of items; track $index) {
<ol>{{ item }}</ol>
}@empty {
//items为空时执行的代码
}
@switch (value) {
@case ('hello') {
...
}
@case ('world') {
...
}
@default {
...
}
}
创建指令
import { Directive, ElementRef, HostListener, Input } from '@angular/core';
@Directive({
standalone: true,
selector: '[appMyDirective]'
})
export class MyDirective {
@Input() color = '';
// 注入ElementRef
constructor(private el:ElementRef) {
// 通过ElementRef获取dom元素
el.nativeElement.style.backgroundColor = this.color;
}
// 监听鼠标行为
@HostListener('mouseenter') onMouseEnter(){
this.highlight('yellow');
}
private highlight(color: string){
this.el.nativeElement.style.backgroundColor = color;
}
}
使用指令
<div appMyDirective color="red" ></div>
import { MyDirective } from './my.directive';
@Component({
imports:[MyDirective]
...
})
双向数据绑定
// 只对表单元素有效
<input type="text" [(NgModule)]="name" >
import { FormsModule } from '@angular/forms';
@Component({
imports:[FormsModule]
...
})
export class AppComponent {
name:string = "";
}
五、管道
常用管道
//格式化日期
{{ current|date }}
//将输入数据对象经过JSON.stringify()方法转换后输出对象的字符串
{{ data|JsonPipe }}
//异步
{{ time|async }}
import { Component } from '@angular/core';
@Component({
//导入管道
imports:[DatePipe,JsonPipe,AsyncPipe]
})
export class AppComponent implements AfterViewInit{
current:Date = new Date();
date:object = {name:'jake',age:18};
time = new Observable<Date>();
constructor(){
this.time = interval(1000).pipe(map(()=>new Date()));
}
}
创建管道
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'MyPipe'
})
export class MyPipe implements PipeTransform {
// 传入参数,操作数(可指定默认值)
transform(value: number, exponent: number = 1 ): number {
return Math.pow(value,exponent);
}
}
使用管道
{{10|MyPipe:2}}
import { MyPipe } from './my.pipe';
@Component({
imports:[MyPipe]
...
})
六、模板
局部变量
@let name = 'hello';
<h1>{{name}}</h1>
//注意:@let声明的变量不能在模板中重新赋值
// <button (click)="name = 'world'">change</button> 错误写法
引用变量
//使用#获取控件对象
<input #input type="text" >
//控件是否被触碰
{{input.touched}}
//控件的值是否变化
{{input.dirty}}
//控件是否有效
{{input.valid}}
@ViewChild
//使用'#'标记组件实例
<app-child #child ></app-child>
//直接在html中显示
{{child.name}}
import { Component, AfterViewInit, ViewChild, ViewChildren } from '@angular/core';
import { ChildComponent } from './components/child.component';
@Component({
imports: [ChildComponent]
...
})
//实现AfterViewInit接口
export class AppComponent implements AfterViewInit{
// 使用@ViewChild查询#child所标记的组件
@ViewChild('child') child:any;
// 使用@ViewChild查询ChildComponent组件
@ViewChild(ChildComponent)child2!:ChildComponent;
// 设置 static: true,此查询的目标总是存在且不是有条件的渲染。这使得结果在 ngOnInit 生命周期方法中更早可用
// @ViewChild(ChildComponent,{static: true})child2!:ChildComponent;
// 使用@ViewChildren查询多个结果
@ViewChildren(ChildComponent)childs!:QueryList<ChildComponent>;
// 重写ngAfterViewInit方法,在该方法中才可以拿到组件实例
ngAfterViewInit():void{
//结果为true
console.log(this.child == this.child2);
this.childs.forEach(item => {
console.log(item);
});
}
}
@ContentChild
<app-home >
<app-child></app-child>
</app-home>
import { Component, AfterContentInit, ContentChild} from '@angular/core';
import { ChildComponent } from './components/child.component';
@Component({
imports: [ChildComponent]
...
})
//实现AfterViewInit接口
export class HomeComponent implements AfterContentInit{
//使用@ContentChild查询HomeComponent组件内嵌套的ChildComponent组件
@ContentChild(ChildComponent) child!:ChildComponent;
//重写ngAfterContentInit方法,在该方法中才可以拿到组件实例
ngAfterContentInit():void{
console.log(this.child);
}
}
七、投影
常用标签
<ng-container> | 是一个没有投影内容的元素,它用于分组指令和投影内容,而不会创建额外的DOM节点。这使得<ng-container>成为在模板中组织结构和应用指令时非常有用的工具,尤其是在需要使用结构性指令(如*ngIf、*ngFor)时 |
<ng-template> | 是一个指令,用于声明一个模板,这个模板可以被用作执行内容投影(content projection)时的备用内容,或者用于创建可重复使用的模板片段 |
<ng-content> | 是一个指令,用于组件的模板中,它允许你投影外部传入的内容(即,父组件中放在组件标签内部的内容)。这个特性被称为“内容投影”(Content Projection),是组件化架构中的一个重要概念 |
条件投影
// 使用<ng-template>和ngIf、ngSwitch等结构性指令一起使用,实现条件内容投影
<ng-template [ngIf]="showContent">
<p>This is projected content</p>
</ng-template>
创建投影模板
// 子组件定义模板
//默认插槽,投影所有未被其他插槽匹配的内容
<ng-content></ng-content>
//select绑定html标签,投影父组件传入的所有<h1></h1>
<ng-content select = "h1" ></ng-content>
//select绑定自定义参数
<ng-content select="footer"></ng-content>
//<ng-template>可以与<ng-content>一起使用,使用*NgIf绑定变量实现动态投影
<ng-content *ngIf="show else temp" ></ng-content>
<ng-template #temp>
temp
</ng-template>
import { Input } from '@angular-cli';
@Component(...)
export class ChildComponent {
@Input() show:boolean = false;
}
使用投影模板
// 父组件使用子组件
<app-child show="true" >
<h1>slot</h1>
<h2>slot2</h2>
//父组件使用ngProjectAs指定投影在子组件的位置
<ng-container ngProjectAs="footer" >content</ng-container>
</app-child>
八、信号
基本用法
import { signal } from '@angular/core';
@Component(...)
export class AppComponent {
// 创建信号
sign = signal(1);
setSign(num:number){
// 更改信号的值
this.sign.set(num);
// 更改信号的值,从信号原来的值计算出一个新值
this.sign.update(value => value + 1);
// 访问信号
console.log(this.sign());
}
get num(){
// 计算型信号,类似vue computed
return computed(()=>{return sign()-10});
}
}
九、表单
基本用法
<form [formGroup]="myForm" (ngSubmit) = "onSubmit()">
<!-- formControlName必须与myForm中字段名一致 -->
<input type="text" formControlName="name" >
<input type="text" formControlName="age" >
<!-- 通过valid获取表单字段是否合法 -->
<button type="submit" [disabled]="!myForm.valid" >submit</button>
<!-- 获取表单状态 -->
{{myForm.status}}
</form>
import { ReactiveFormsModule, FormArray, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
@Component({
imports: [ReactiveFormsModule]
...
})
export class AppComponent{
myForm: FormGroup;
//依赖注入
constructor(private formBuilder: FormBuilder){
this.myForm = new FormGroup({
//Validators.required表示必填字段
name : new FormControl('angular',Validators.required),
//配置nonNullable为true,则reset()后字段重置为默认值
age: new FormControl('',{nonNullable: true}),
//通过FormBuilder定义表单数组
array: this.formBuilder.array([1])
})
}
setFormArray():void {
let myArray = this.myForm.get('array');
//在表单数组末尾插入
myArray?.push(new FormControl());
//在表单数组
myArray?.insert(0,new FormControl());
//移除表单数组
myArray?.removeAt(0);
//设置
myForm.setValue({name:'hello',age:'1',array:[1]});
//重置表单数组
myArray?.reset();
}
onSubmit(){
console.log(this.myForm);
}
getFormArgs(){
//获取表单参数
this.myForm.getRawValue();
}
}
十、路由
导入路由
import { ApplicationConfig} from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
],
};
创建路由
// app.routes.ts
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes)]
};
// 在app.routes.ts中配置路由
import { Routes } from '@angular/router';
import { HomeComponent } from './components/home/home.component';
import { HelloComponent } from './components/hello/hello.component';
export const routes: Routes = [
{
path: '',
//路由重定向
redirectTo: '/home',
pathMatch: 'full',
},
{
path:'home',
component: HomeComponent,
//路由嵌套
children:[
{
//路由传参
path:'hello/:name',
component: HelloComponent
}
]
},
{
path:'lazy',
//懒加载
loadChildren: () => import('./components/lazy/lazy.component').then(m => m.LazyModule)
}
];
使用路由
// 路由页面显示占位符
<router-outlet />
// 路由跳转
//方式一 使用routerLink标签
<a routerLink="home" >home</a>
//方式二 函数式跳转
import { RouterLink, RouterOutlet, Router } from '@angular/router';
@Component({
imports: [RouterOutlet,RouterLink]
...
})
export class AppComponent {
constructor(private router:Router){}
gotoHome(){
this.router.navigateByUrl('home');
}
gotoHello(){
this.router.navigateByUrl('hello/angular');
}
}
//组件依赖注入route获取路由传参
constructor(private route:ActivatedRoute) {
route.paramMap.subscribe(params => {
console.log(params.get('name')); // 打印 angular
});
}
路由守卫
常用路由守卫
CanActivate: | 用于在路由激活前进行检查。 |
CanActivateChild: | 用于在激活子路由之前进行检查。 |
CanLoad: | 用于延迟加载模块时进行检查。 |
CanDeactivate: | 用于在离开当前路由之前进行检查。 |
Resolve: | 用于在路由激活前获取数据。 |
创建路由守卫
//auth.guard.ts
import { CanActivateFn } from '@angular/router';
export const authGuard: CanActivateFn = (route, state) => {
console.log(route);
console.log(state);
return true;
};
使用路由守卫
import { Routes } from '@angular/router';
import { HomeComponent } from './components/home/home.component';
import { authGuard } from './auth.guard';
export const routes: Routes = [
{
path: 'home',
component: HomeComponent,
canActivate: [authGuard],
},
];
十一、HttpClient
导入HttpClient
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { ApplicationConfig } from '@angular/core';
import { provideHttpClient } from '@angular/common/http';
const appConfig: ApplicationConfig = {
providers: [
provideHttpClient()
]
};
bootstrapApplication(AppComponent, appConfig)
.catch((err) => console.error(err));
使用HttpClient
export class AppComponent {
constructor(private http: HttpClient) {}
getTest() {
//泛型类型参数,它指定服务器返回的数据类型。这个参数是可选的,如果省略它,则返回的数据将为any类型
this.http.get('/getApi').subscribe(result =>{
console.log(result);
});
//默认情况下, HttpClient 假定服务器会返回 JSON 数据。当与非 JSON API 交互时,可以告诉 HttpClient 在发出请求时期望和返回的响应类型。这个通过 responseType 选项完成
this.http.get('/getApi2',{responseType: 'arraybuffer'}).subscribe(result => {
console.log(result);
});
//get请求携带query参数
this.http.get('/getApi3',{params: {id: 1}});
}
postTest(body:any){
this.http.post<ResultType>('/postApi',body);
}
otherTest(){
this.http.put('');
this.http.delete('');
}
}
十二、代理
angular.json中proxyConfig指定代理文件,这里指定的是json文件,port指定的是项目启动端口
"serve": {
"options": {
"proxyConfig": "src/proxy.conf.json",
"port" : 55616
}
}
创建文件proxy.conf.json,编写以下内容配置代理,指定前缀为/api的请求转发到localhost:4300
{
"/api": {
"target": "http://localhost:4300",
"secure": false,
"changeOrigin": true
}
}
十三、ESLint
@angular-eslint/schematics 是一个 Angular CLI 工具,它用于将 eslint 和 @angular-eslint 插件自动配置到 Angular 项目中
安装angular-eslint
ng add @angular-eslint/schematics
配置eslint规则
//在eslint.config.js配置eslint规则
rules: {
"@angular-eslint/directive-selector": [
"error",
{
type: "attribute",
prefix: "app",
style: "camelCase",
},
],
"@angular-eslint/component-selector": [
"error",
{
type: "element",
prefix: "app",
style: "kebab-case",
},
],
"space-infix-ops": ["error", { "int32Hint": false }],
"no-multi-spaces": ["error"],
"key-spacing": ["error", { "beforeColon": false, "afterColon": true }],
"space-before-blocks": ["error", "always"],
"comma-dangle": ["error", "always-multiline"],
"semi": ["error", "always"],
"quote-props": ["error", "as-needed"],
"no-trailing-spaces": ["error"],
"object-curly-spacing": ["error", "always"],
"array-bracket-spacing": ["error", "never"],
"no-whitespace-before-property": ["error"]
}
VSCode快捷配置
//配置VSCode settings.json
{
"editor.codeActionsOnSave": {
//保存代码自动修复eslint错误
"source.fixAll": true
},
//保存代码不自动格式化
"editor.formatOnSave": false
}
十四、常见问题
项目打包出现browser目录问题

修改angular.json文件如下
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"main": "src/main.ts"
}
}
后续持续更新。。。如有错误请指出