MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Angular项目结构深度解析

2024-12-163.7k 阅读

一、Angular 项目的初始结构

当我们通过 Angular CLI 创建一个新的 Angular 项目时,会得到一个具有特定结构的项目目录。以一个名为 my - app 的项目为例,其基本结构如下:

my - app/
├── e2e/
│   ├── protractor.conf.js
│   ├── src/
│   │   ├── app.e2e-spec.ts
│   │   ├── app.po.ts
│   │   └── index.ts
│   └── tsconfig.e2e.json
├── node_modules/
├── src/
│   ├── app/
│   │   ├── app.component.css
│   │   ├── app.component.html
│   │   ├── app.component.spec.ts
│   │   ├── app.component.ts
│   │   └── app.module.ts
│   ├── assets/
│   ├── environments/
│   │   ├── environment.prod.ts
│   │   └── environment.ts
│   ├── favicon.ico
│   ├── index.html
│   ├── main.ts
│   ├── polyfills.ts
│   ├── styles.css
│   ├── test.ts
│   └── tsconfig.app.json
│   └── tsconfig.spec.json
├──.angular - cli.json
├── README.md
├── karma.conf.js
├── package - lock.json
├── package.json
└── tsconfig.json

1.1 e2e 目录

e2e 目录是用于端到端(End - to - End)测试的。在这个目录中:

  • protractor.conf.js:这是 Protractor 的配置文件,Protractor 是一个用于 Angular 应用端到端测试的工具。它定义了测试运行的环境、测试文件的位置等配置。例如,以下是一个基本的 protractor.conf.js 配置片段:
exports.config = {
    // 测试框架
    framework: 'jasmine',
    // 测试文件的位置
    specs: ['src/app.e2e - spec.ts'],
    // 浏览器驱动配置
    capabilities: {
        'browserName': 'chrome'
    },
    // Jasmine 配置
    jasmineNodeOpts: {
        showColors: true,
        defaultTimeoutInterval: 30000
    }
};
  • src 目录下的文件
    • app.e2e - spec.ts:这里编写具体的端到端测试用例。例如,我们可以测试应用的首页是否正确加载:
describe('App', () => {
    it('should have a title', () => {
        browser.get('/');
        expect(browser.getTitle()).toEqual('my - app');
    });
});
- **app.po.ts**:Page Object 模式的文件,用于封装页面元素和操作。例如,如果我们有一个登录页面,我们可以在这个文件中定义登录表单元素和登录操作:
export class LoginPage {
    navigateTo() {
        return browser.get('/login');
    }

    getUsernameInput() {
        return element(by.css('input[name="username"]'));
    }

    getPasswordInput() {
        return element(by.css('input[name="password"]'));
    }

    clickLoginButton() {
        return element(by.css('button[type="submit"]')).click();
    }
}

1.2 node_modules 目录

node_modules 目录包含了项目所依赖的所有第三方 npm 包。Angular 项目依赖众多的包,如 @angular/corerxjs 等。这些包是项目运行和开发所必需的。例如,@angular/core 包提供了 Angular 核心的功能,像组件、指令、服务等的定义和使用。当我们在项目中使用 @Component 装饰器来定义一个组件时,就是从 @angular/core 包中引入的:

import { Component } from '@angular/core';

@Component({
    selector: 'app - my - component',
    templateUrl: './my - component.html',
    styleUrls: ['./my - component.css']
})
export class MyComponent { }

1.3 src 目录

src 目录是项目的核心源代码目录,包含了应用的主要代码、资源文件和配置。

  • app 目录:这是应用的组件和模块所在的目录。
    • app.component.cssapp.component 的样式文件,用于定义该组件的样式。例如:
/* app.component.css */
h1 {
    color: blue;
}
- **app.component.html**:`app.component` 的模板文件,定义了该组件的视图结构。例如:
<!-- app.component.html -->
<div>
    <h1>{{title}}</h1>
</div>
- **app.component.spec.ts**:用于编写 `app.component` 的单元测试。例如,测试组件的标题是否正确设置:
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AppComponent } from './app.component';

describe('AppComponent', () => {
    let component: AppComponent;
    let fixture: ComponentFixture<AppComponent>;

    beforeEach(async () => {
        await TestBed.configureTestingModule({
            declarations: [AppComponent]
        }).compileComponents();
    });

    beforeEach(() => {
        fixture = TestBed.createComponent(AppComponent);
        component = fixture.componentInstance;
        fixture.detectChanges();
    });

    it('should set the title', () => {
        expect(component.title).toBe('my - app');
    });
});
- **app.component.ts**:`app.component` 的逻辑代码,在这里定义组件的属性和方法。例如:
import { Component } from '@angular/core';

@Component({
    selector: 'app - root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent {
    title ='my - app';
}
- **app.module.ts**:应用的根模块,它将组件、服务等组合在一起,并定义了应用的启动点。例如:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform - browser';
import { AppComponent } from './app.component';

@NgModule({
    declarations: [AppComponent],
    imports: [BrowserModule],
    providers: [],
    bootstrap: [AppComponent]
})
export class AppModule { }
  • assets 目录:这个目录用于存放静态资源,如图像、字体、JSON 文件等。例如,如果我们有一个 logo 图片,可以放在这个目录下,并在组件模板中引用:
<img src="assets/logo.png" alt="My App Logo">
  • environments 目录:包含不同环境下的配置文件。environment.ts 用于开发环境的配置,environment.prod.ts 用于生产环境的配置。例如,我们可以在这些文件中配置 API 地址:
// environment.ts
export const environment = {
    production: false,
    apiUrl: 'http://localhost:3000/api'
};

// environment.prod.ts
export const environment = {
    production: true,
    apiUrl: 'https://my - production - server.com/api'
};
  • 其他文件
    • favicon.ico:应用的图标,显示在浏览器标签上。
    • index.html:应用的主 HTML 文件,是应用的入口。它加载了应用的 JavaScript、CSS 等资源,并包含一个用于挂载 Angular 应用的根元素,通常是 <app - root></app - root>
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf - 8">
    <title>My App</title>
    <base href="/">
    <meta name="viewport" content="width=device - width, initial - scale = 1">
    <link rel="icon" type="image/x - icon" href="favicon.ico">
</head>

<body>
    <app - root></app - root>
</body>

</html>
- **main.ts**:应用的启动文件,它引导 Angular 应用的启动,加载根模块 `AppModule`:
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform - browser - dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';

if (environment.production) {
    enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule)
   .catch(err => console.error(err));
- **polyfills.ts**:用于加载一些浏览器的垫片(polyfills),以支持那些不支持现代 JavaScript 特性的浏览器。例如,加载 `es6 - promise` 垫片来支持 Promise 在老版本浏览器中的使用:
import 'core - js/es6/symbol';
import 'core - js/es6/object';
import 'core - js/es6/function';
import 'core - js/es6/parse - int';
import 'core - js/es6/parse - float';
import 'core - js/es6/number';
import 'core - js/es6/math';
import 'core - js/es6/string';
import 'core - js/es6/date';
import 'core - js/es6/array';
import 'core - js/es6/regexp';
import 'core - js/es6/map';
import 'core - js/es6/weak - map';
import 'core - js/es6/set';
import 'zone.js/dist/zone';
- **styles.css**:全局样式文件,定义的样式会应用到整个应用。例如:
body {
    font - family: Arial, sans - serif;
    margin: 0;
    padding: 0;
}
- **test.ts**:用于配置和启动单元测试。它设置了测试环境,并加载所有测试文件:
import 'zone.js/dist/zone - test - ng';
import { getTestBed } from '@angular/core/testing';
import {
    BrowserDynamicTestingModule,
    platformBrowserDynamicTesting
} from '@angular/platform - browser - dynamic/testing';

// 初始化测试环境
getTestBed().initTestEnvironment(
    BrowserDynamicTestingModule,
    platformBrowserDynamicTesting()
);

// 加载所有测试文件
const context = require.context('./', true, /\.spec\.ts$/);
context.keys().forEach(context);
- **tsconfig.app.json**:用于配置应用的 TypeScript 编译选项,如目标 ECMAScript 版本、模块系统等。例如:
{
    "extends": "../tsconfig.json",
    "compilerOptions": {
        "outDir": "../out - app",
        "module": "es2020",
        "moduleResolution": "node",
        "emitDecoratorMetadata": true,
        "experimentalDecorators": true,
        "importHelpers": true,
        "target": "es2015",
        "typeRoots": ["node_modules/@types"],
        "lib": ["es2018", "dom"],
        "skipLibCheck": true,
        "skipDefaultLibCheck": true
    },
    "exclude": ["test.ts", "**/*.spec.ts"]
}
- **tsconfig.spec.json**:用于配置单元测试的 TypeScript 编译选项,与 `tsconfig.app.json` 有所不同,例如它会包含测试相关的文件:
{
    "extends": "../tsconfig.json",
    "compilerOptions": {
        "outDir": "../out - spec",
        "module": "commonjs",
        "emitDecoratorMetadata": true,
        "experimentalDecorators": true,
        "importHelpers": true,
        "target": "es2015",
        "typeRoots": ["node_modules/@types"],
        "lib": ["es2018", "dom", "dom.iterable", "scripthost"],
        "skipLibCheck": true,
        "skipDefaultLibCheck": true
    },
    "files": ["test.ts"],
    "include": ["**/*.spec.ts", "**/*.d.ts"]
}

二、深入 Angular 组件结构

2.1 组件的文件组织

一个典型的 Angular 组件通常由多个文件组成,除了前面提到的 .ts.html.css.spec.ts 文件外,在复杂的组件中可能还会有其他辅助文件。

  • .ts 文件:这是组件的逻辑代码文件,定义组件的类。在这个文件中,我们可以定义组件的属性、方法、生命周期钩子等。例如,一个简单的计数器组件:
import { Component, OnInit } from '@angular/core';

@Component({
    selector: 'app - counter',
    templateUrl: './counter.component.html',
    styleUrls: ['./counter.component.css']
})
export class CounterComponent implements OnInit {
    count = 0;

    constructor() { }

    ngOnInit(): void {
        // 组件初始化时的逻辑
    }

    increment() {
        this.count++;
    }

    decrement() {
        if (this.count > 0) {
            this.count--;
        }
    }
}
  • .html 文件:组件的模板文件,定义组件的视图。对于上述计数器组件,模板文件可能如下:
<div>
    <p>Count: {{count}}</p>
    <button (click)="increment()">Increment</button>
    <button (click)="decrement()">Decrement</button>
</div>
  • .css 文件:定义组件的样式。例如,为计数器组件添加一些样式:
button {
    margin: 5px;
    padding: 10px 20px;
    background - color: blue;
    color: white;
    border: none;
    border - radius: 5px;
}
  • .spec.ts 文件:用于编写组件的单元测试。以下是计数器组件的单元测试示例:
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CounterComponent } from './counter.component';

describe('CounterComponent', () => {
    let component: CounterComponent;
    let fixture: ComponentFixture<CounterComponent>;

    beforeEach(async () => {
        await TestBed.configureTestingModule({
            declarations: [CounterComponent]
        }).compileComponents();
    });

    beforeEach(() => {
        fixture = TestBed.createComponent(CounterComponent);
        component = fixture.componentInstance;
        fixture.detectChanges();
    });

    it('should create the component', () => {
        expect(component).toBeTruthy();
    });

    it('should increment the count', () => {
        const initialCount = component.count;
        component.increment();
        expect(component.count).toBe(initialCount + 1);
    });

    it('should decrement the count', () => {
        component.count = 5;
        const initialCount = component.count;
        component.decrement();
        expect(component.count).toBe(initialCount - 1);
    });
});

2.2 组件的层级结构

Angular 应用通常由多个组件组成,这些组件形成一个树状的层级结构。例如,一个简单的博客应用可能有一个 AppComponent 作为根组件,然后 AppComponent 包含一个 BlogListComponent 和一个 BlogDetailComponentBlogListComponent 又可以包含多个 BlogItemComponent

// app.component.ts
import { Component } from '@angular/core';

@Component({
    selector: 'app - root',
    templateUrl: './app.component.html'
})
export class AppComponent { }

// app.component.html
<div>
    <app - blog - list></app - blog - list>
    <app - blog - detail></app - blog - detail>
</div>

// blog - list.component.ts
import { Component } from '@angular/core';

@Component({
    selector: 'app - blog - list',
    templateUrl: './blog - list.component.html'
})
export class BlogListComponent { }

// blog - list.component.html
<div>
    <app - blog - item *ngFor="let blog of blogs"></app - blog - item>
</div>

// blog - item.component.ts
import { Component } from '@angular/core';

@Component({
    selector: 'app - blog - item',
    templateUrl: './blog - item.component.html'
})
export class BlogItemComponent { }

// blog - item.component.html
<div>
    <h2>{{blog.title}}</h2>
    <p>{{blog.excerpt}}</p>
</div>

这种层级结构使得组件的管理和维护更加清晰,每个组件专注于自己的职责,并且可以方便地进行复用。

三、Angular 模块结构

3.1 模块的作用

Angular 模块(NgModule)是一种组织和管理 Angular 应用的方式。它将相关的组件、指令、管道和服务组合在一起,方便进行代码的复用和管理。例如,我们可以创建一个 SharedModule 来存放应用中多个组件都可以共享的组件、指令和管道。

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SharedComponent } from './shared.component';
import { SharedDirective } from './shared.directive';
import { SharedPipe } from './shared.pipe';

@NgModule({
    declarations: [SharedComponent, SharedDirective, SharedPipe],
    imports: [CommonModule],
    exports: [SharedComponent, SharedDirective, SharedPipe]
})
export class SharedModule { }

然后在其他模块中,如 AppModule,我们可以导入 SharedModule 来使用其中的共享内容:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform - browser';
import { AppComponent } from './app.component';
import { SharedModule } from './shared.module';

@NgModule({
    declarations: [AppComponent],
    imports: [BrowserModule, SharedModule],
    providers: [],
    bootstrap: [AppComponent]
})
export class AppModule { }

3.2 模块的分类

  • 根模块:每个 Angular 应用都有一个根模块,通常命名为 AppModule。它是应用的启动点,负责引导应用并配置全局的设置,如导入其他模块、定义根组件等。例如:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform - browser';
import { AppComponent } from './app.component';
import { RouterModule } from '@angular/router';
import { routes } from './app.routes';

@NgModule({
    declarations: [AppComponent],
    imports: [BrowserModule, RouterModule.forRoot(routes)],
    providers: [],
    bootstrap: [AppComponent]
})
export class AppModule { }
  • 特性模块:特性模块用于将应用的特定功能模块进行封装。例如,一个电商应用可能有一个 ProductModule 来管理与产品相关的组件、服务和路由。
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ProductListComponent } from './product - list.component';
import { ProductDetailComponent } from './product - detail.component';
import { ProductService } from './product.service';
import { RouterModule } from '@angular/router';
import { productRoutes } from './product.routes';

@NgModule({
    declarations: [ProductListComponent, ProductDetailComponent],
    imports: [CommonModule, RouterModule.forChild(productRoutes)],
    providers: [ProductService],
    exports: [ProductListComponent, ProductDetailComponent]
})
export class ProductModule { }
  • 共享模块:共享模块用于存放可以在多个模块中复用的组件、指令和管道。如前面提到的 SharedModule

四、服务在 Angular 项目中的结构与使用

4.1 服务的定义与文件结构

服务是 Angular 中一个重要的概念,用于封装可复用的业务逻辑、数据访问等功能。一个服务通常是一个类,通过依赖注入(Dependency Injection)在组件或其他服务中使用。例如,我们定义一个简单的 UserService 来管理用户数据:

import { Injectable } from '@angular/core';

@Injectable({
    providedIn: 'root'
})
export class UserService {
    private users = [
        { id: 1, name: 'John' },
        { id: 2, name: 'Jane' }
    ];

    getUsers() {
        return this.users;
    }

    getUserById(id: number) {
        return this.users.find(user => user.id === id);
    }
}

这里,@Injectable 装饰器将这个类标记为可注入的服务,providedIn: 'root' 表示这个服务在应用的根模块中提供,这样整个应用都可以使用它。

4.2 服务在组件中的使用

在组件中使用服务,我们通过构造函数进行依赖注入。例如,在 UserListComponent 中使用 UserService

import { Component, OnInit } from '@angular/core';
import { UserService } from './user.service';

@Component({
    selector: 'app - user - list',
    templateUrl: './user - list.component.html'
})
export class UserListComponent implements OnInit {
    users = [];

    constructor(private userService: UserService) { }

    ngOnInit(): void {
        this.users = this.userService.getUsers();
    }
}

在模板文件 user - list.component.html 中,我们可以展示用户列表:

<ul>
    <li *ngFor="let user of users">{{user.name}}</li>
</ul>

五、路由在 Angular 项目中的结构

5.1 路由模块的定义

路由是 Angular 应用实现页面导航和多视图切换的关键。在 Angular 中,我们通过 RouterModule 来配置路由。首先,我们定义路由配置数组。例如,对于一个简单的应用,有首页和关于页面:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './home.component';
import { AboutComponent } from './about.component';

const routes: Routes = [
    { path: '', component: HomeComponent },
    { path: 'about', component: AboutComponent }
];

@NgModule({
    imports: [RouterModule.forRoot(routes)],
    exports: [RouterModule]
})
export class AppRoutingModule { }

这里,forRoot 方法用于在根模块中配置路由,path 表示路由路径,component 表示该路径对应的组件。

5.2 路由在组件中的使用

在组件模板中,我们可以使用 <router - outlet> 来显示匹配路由的组件内容。例如,在 app.component.html 中:

<nav>
    <a routerLink="/">Home</a>
    <a routerLink="/about">About</a>
</nav>
<router - outlet></router - outlet>

当用户点击导航链接时,RouterModule 会根据路由配置加载相应的组件并显示在 <router - outlet> 位置。

六、深入理解 Angular 项目中的配置文件

6.1 tsconfig.json

tsconfig.json 是整个项目的 TypeScript 配置文件,它定义了项目的编译选项。例如:

{
    "compileOnSave": false,
    "compilerOptions": {
        "baseUrl": "./",
        "outDir": "./dist/out - tsc",
        "sourceMap": true,
        "declaration": false,
        "module": "esnext",
        "moduleResolution": "node",
        "emitDecoratorMetadata": true,
        "experimentalDecorators": true,
        "importHelpers": true,
        "target": "es2015",
        "typeRoots": ["node_modules/@types"],
        "lib": ["es2018", "dom"],
        "skipLibCheck": true,
        "skipDefaultLibCheck": true
    }
}
  • baseUrl:指定模块解析的基础路径,这对于设置自定义的模块导入路径很有用。
  • outDir:指定编译后的输出目录。
  • sourceMap:设置为 true 时,会生成源映射文件,方便调试。
  • module:指定生成的模块系统,esnext 表示使用 ES2020 模块系统。
  • moduleResolution:指定模块解析策略,node 表示使用 Node.js 的模块解析策略。

6.2 angular - cli.json

在较新的 Angular 版本中,angular - cli.json 已被 angular.json 取代。angular.json 是 Angular CLI 的配置文件,它包含了项目的构建、测试、部署等各种配置。例如:

{
    "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
    "version": 1,
    "newProjectRoot": "projects",
    "projects": {
        "my - app": {
            "root": "",
            "sourceRoot": "src",
            "projectType": "application",
            "architect": {
                "build": {
                    "builder": "@angular - cli:browser",
                    "outputPath": "dist/my - app",
                    "index": "src/index.html",
                    "main": "src/main.ts",
                    "polyfills": "src/polyfills.ts",
                    "tsConfig": "src/tsconfig.app.json",
                    "assets": [
                        "src/favicon.ico",
                        "src/assets"
                    ],
                    "styles": [
                        "src/styles.css"
                    ],
                    "scripts": []
                },
                "serve": {
                    "builder": "@angular - cli:dev - server",
                    "browserTarget": "my - app:build"
                },
                "test": {
                    "builder": "@angular - cli:karma",
                    "main": "src/test.ts",
                    "polyfills": "src/polyfills.ts",
                    "tsConfig": "src/tsconfig.spec.json",
                    "karmaConfig": "./karma.conf.js",
                    "styles": [
                        "src/styles.css"
                    ],
                    "scripts": [],
                    "assets": [
                        "src/favicon.ico",
                        "src/assets"
                    ]
                },
                "lint": {
                    "builder": "@angular - cli:tslint",
                    "tsConfig": [
                        "src/tsconfig.app.json",
                        "src/tsconfig.spec.json"
                    ],
                    "exclude": ["**/node_modules/**"]
                }
            }
        }
    },
    "defaultProject": "my - app"
}
  • architect.build:配置构建相关的选项,如输出路径、入口文件等。
  • architect.serve:配置开发服务器相关的选项,它依赖于 build 的配置。
  • architect.test:配置单元测试相关的选项,如测试入口文件、测试配置文件等。
  • architect.lint:配置代码检查相关的选项,如要检查的 TypeScript 配置文件和排除的目录。

通过深入理解这些配置文件,我们可以根据项目的需求灵活调整项目的编译、构建、测试等行为,从而更好地管理和优化 Angular 项目。同时,清晰的项目结构和合理的配置是构建大型、可维护的 Angular 应用的基础。无论是组件、模块、服务还是路由的结构,都相互关联,共同构成了一个完整且高效的 Angular 应用架构。在实际开发中,我们应根据项目的规模和需求,合理地组织和扩展这些结构,以实现最佳的开发体验和应用性能。