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

认识Angular项目结构的组成

2023-12-135.5k 阅读

Angular项目结构基础组成

根目录文件

当我们创建一个新的Angular项目时,会生成一系列文件和目录。首先看项目根目录下的文件:

  • package.json:这个文件非常关键,它记录了项目的依赖信息。里面包含了项目运行和开发所需的所有npm包及其版本号。例如:
{
  "name": "my - angular - app",
  "version": "0.0.0",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "^14.0.0",
    "@angular/common": "^14.0.0",
    "@angular/compiler": "^14.0.0",
    "@angular/core": "^14.0.0",
    "@angular/forms": "^14.0.0",
    "@angular/platform - browser": "^14.0.0",
    "@angular/platform - browser - dynamic": "^14.0.0",
    "@angular/router": "^14.0.0",
    "rxjs": "~7.5.0",
    "ts - lib": "^2.0.0",
    "zone.js": "~0.11.4"
  },
  "devDependencies": {
    "@angular - cli": "^14.0.0",
    "@angular - compiler - cli": "^14.0.0",
    "@types/jasmine": "~4.3.0",
    "@types/node": "^18.11.18",
    "jasmine - core": "~4.5.0",
    "karma": "~6.4.0",
    "karma - chrome - launcher": "~3.1.0",
    "karma - jasmine": "~5.1.0",
    "karma - jasmine - html - reporter": "~2.1.0",
    "typescript": "~4.9.5"
  }
}

这里的dependencies字段是项目运行时依赖的包,比如Angular核心库以及RxJS等。devDependencies则是开发过程中用到的工具,像@angular - cli用于项目构建、测试等操作。

  • package - lock.json:这个文件会精确记录每个依赖包的具体版本以及依赖关系树。它保证了团队成员安装的依赖版本完全一致,避免因版本差异导致的兼容性问题。例如,如果@angular/core依赖于@angular/common的某个特定子版本,package - lock.json会详细记录下来。

  • tsconfig.json:TypeScript的配置文件,它定义了TypeScript编译器如何编译项目中的TypeScript代码。比如:

{
  "compileOnSave": false,
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist/out - tsc",
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "sourceMap": true,
    "inlineSources": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "strictNullChecks": true,
    "module": "es2020",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "lib": [
      "es2020",
      "dom"
    ]
  }
}

compilerOptions中的strict设置为true开启严格模式,有助于捕获更多潜在的错误。module指定了模块系统,这里使用es2020moduleResolution设置为node表示使用Node.js的模块解析策略。

  • angular.json:这是Angular项目的配置文件,管理项目的构建、开发服务器、测试、部署等各种任务的配置。例如,构建相关配置:
{
  "$schema": "./node_modules/@angular - cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "my - angular - app": {
      "projectType": "application",
      "schematics": {
        "@schematics/angular:component": {
          "style": "scss"
        }
      },
      "architect": {
        "build": {
          "builder": "@angular - cli:browser",
          "outputPath": "dist/my - angular - app",
          "index": "src/index.html",
          "main": "src/main.ts",
          "polyfills": "src/polyfills.ts",
          "tsConfig": "tsconfig.app.json",
          "assets": [
            "src/favicon.ico",
            "src/assets"
          ],
          "styles": [
            "src/styles.scss"
          ],
          "scripts": []
        },
        "serve": {
          "builder": "@angular - cli:dev - server",
          "browserTarget": "my - angular - app:build",
          "options": {
            "hmr": true
          }
        },
        "extract - i18n": {
          "builder": "@angular - cli:extract - i18n",
          "browserTarget": "my - angular - app:build"
        },
        "test": {
          "builder": "@angular - cli:karma",
          "main": "src/test.ts",
          "polyfills": "src/polyfills.ts",
          "tsConfig": "tsconfig.spec.json",
          "karmaConfig": "karma.conf.js",
          "assets": [
            "src/favicon.ico",
            "src/assets"
          ],
          "styles": [
            "src/styles.scss"
          ],
          "scripts": []
        },
        "lint": {
          "builder": "@angular - cli:tslint",
          "options": {
            "tsConfig": [
              "tsconfig.app.json",
              "tsconfig.spec.json"
            ],
            "exclude": [
              "**/node_modules/**"
            ]
          }
        }
      }
    }
  },
  "defaultProject": "my - angular - app"
}

architect.build中指定了输出路径outputPath,入口文件index.htmlmain.ts等。architect.serve配置了开发服务器相关选项,hmr开启热模块替换,让我们在开发过程中代码更新后页面能实时刷新。

  • .gitignore:用于指定哪些文件或目录不需要纳入Git版本控制。例如:
# Logs
logs
*.log
npm - debug.log*
yarn - debug.log*
yarn - error.log*

# Dependency directories
node_modules
jspm_packages

# IDEs and editors
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw*

这里忽略了日志文件、node_modules目录以及一些IDE相关的配置文件,避免将不必要的文件提交到版本库。

src目录

这是项目的源代码目录,包含了应用程序的主要代码和资源。

  • main.ts:这是整个Angular应用的入口文件。它引导Angular应用的启动过程,通常会导入并引导根模块。例如:
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));

首先根据environment.production的值判断是否是生产环境,如果是则启用生产模式,优化应用性能。然后通过platformBrowserDynamic()获取浏览器平台的动态加载器,调用bootstrapModule方法引导AppModule,从而启动整个应用。

  • polyfills.ts:这个文件用于加载一些浏览器的垫片(polyfills),以确保应用在不同浏览器上具有一致的行为。例如,某些较新的JavaScript特性在旧浏览器上可能不支持,通过引入相应的polyfills可以解决这个问题。比如,如果要支持Promise在不支持它的浏览器上运行,可以在polyfills.ts中引入:
import 'core - js/stable';
import 'zone.js/dist/zone';

core - js库提供了一系列JavaScript特性的垫片,zone.js用于在Angular应用中管理异步操作和变更检测。

  • index.html:这是应用的主HTML文件,它是浏览器加载应用的入口。它通常包含一个根元素,Angular应用会挂载到这个根元素上。例如:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf - 8">
  <title>My Angular 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>
  <script src="runtime.js"></script>
  <script src="polyfills.js"></script>
  <script src="styles.js"></script>
  <script src="vendor.js"></script>
  <script src="main.js"></script>
</body>
</html>

<base href="/">指定了应用的基础URL,<app - root>是Angular应用的根组件选择器,Angular会将应用渲染到这个元素中。后面的<script>标签加载了运行时脚本、垫片脚本、样式脚本、第三方库脚本以及应用的主脚本。

  • styles.scss:这是全局样式文件,应用的全局样式可以在这里定义。例如:
body {
  font - family: Arial, sans - serif;
  margin: 0;
  padding: 0;
}

这里设置了整个应用的字体为Arial,并且去除了body元素的默认边距和内边距。

  • environments目录:包含不同环境下的配置文件,如environment.ts用于开发环境,environment.prod.ts用于生产环境。例如,environment.ts可能如下:
export const environment = {
  production: false,
  apiUrl: 'http://localhost:3000/api'
};

environment.prod.ts可能会有:

export const environment = {
  production: true,
  apiUrl: 'https://my - production - api.com/api'
};

这样在不同环境下,应用可以根据不同的配置来访问相应的API地址等,并且在生产环境中可以启用一些优化措施,如开启生产模式。

Angular模块结构

模块的概念

在Angular中,模块(Module)是一个组织和管理代码的重要概念。模块将相关的组件、服务、管道、指令等组织在一起,形成一个功能单元。一个Angular应用至少有一个根模块,通常命名为AppModule。例如,AppModule的定义可能如下:

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 { }

@NgModule装饰器用于定义一个模块。declarations数组用于声明该模块中包含的组件、指令和管道。imports数组用于导入其他模块,这里导入了BrowserModule,它提供了在浏览器中运行应用所需的基础功能。providers数组用于注册服务,这些服务可以在整个模块及其子模块中使用。bootstrap数组指定了应用的根组件,这里是AppComponent,Angular在启动时会渲染这个根组件。

特性模块

除了根模块,我们还可以创建特性模块来进一步组织应用的功能。比如,我们有一个电商应用,可能有一个产品模块用于管理产品相关的功能。

  • 创建产品模块:首先使用Angular CLI命令创建产品模块:
ng generate module products

这会生成一个products目录,里面包含products.module.ts文件。

  • 产品模块定义
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import { ProductsComponent } from './products.component';
import { ProductListComponent } from './product - list/product - list.component';
import { ProductDetailComponent } from './product - detail/product - detail.component';

@NgModule({
  declarations: [
    ProductsComponent,
    ProductListComponent,
    ProductDetailComponent
  ],
  imports: [
    CommonModule
  ],
  providers: [],
  exports: [
    ProductsComponent
  ]
})
export class ProductsModule { }

这里CommonModule是Angular提供的一个模块,包含了一些常用的指令和管道,如NgIfNgFor等。declarations声明了产品模块相关的组件。exports数组指定了哪些组件、指令或管道可以被其他模块使用,这里导出了ProductsComponent,以便其他模块可以引入并使用这个组件。

共享模块

共享模块用于存放一些可以在多个模块中复用的组件、指令和管道。例如,我们创建一个SharedModule

  • 创建共享模块
ng generate module shared
  • 共享模块定义
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import { HighlightDirective } from './highlight.directive';
import { FormatDatePipe } from './format - date.pipe';
import { LoadingSpinnerComponent } from './loading - spinner/loading - spinner.component';

@NgModule({
  declarations: [
    HighlightDirective,
    FormatDatePipe,
    LoadingSpinnerComponent
  ],
  imports: [
    CommonModule
  ],
  exports: [
    HighlightDirective,
    FormatDatePipe,
    LoadingSpinnerComponent
  ]
})
export class SharedModule { }

这样,其他模块只需要导入SharedModule,就可以使用其中的指令、管道和组件,避免了在每个模块中重复定义。

Angular组件结构

组件的基本结构

一个Angular组件通常由三部分组成:TypeScript类、HTML模板和CSS样式(也可以是SCSS等其他样式语言)。以一个简单的HelloWorldComponent为例:

  • TypeScript类
import { Component } from '@angular/core';

@Component({
  selector: 'app - hello - world',
  templateUrl: './hello - world.component.html',
  styleUrls: ['./hello - world.component.scss']
})
export class HelloWorldComponent {
  message: string = 'Hello, World!';
}

@Component装饰器定义了组件的元数据。selector是组件的选择器,在HTML中通过这个选择器来使用组件,如<app - hello - world></app - hello - world>templateUrl指定了组件的HTML模板文件路径,styleUrls指定了组件的样式文件路径。组件类中定义了一个message属性,用于在模板中显示数据。

  • HTML模板hello - world.component.html
<div>
  <p>{{message}}</p>
</div>

这里使用了Angular的插值语法{{message}},将组件类中的message属性值显示在模板中。

  • CSS样式(SCSS)hello - world.component.scss
div {
  background - color: lightblue;
  padding: 10px;
}

这里为组件的div元素设置了浅蓝色背景和10像素的内边距。

组件的层次结构

在实际应用中,组件通常会形成层次结构。比如,一个电商应用可能有一个AppComponent作为根组件,它包含一个ProductsComponentProductsComponent又包含ProductListComponentProductDetailComponent

  • 父组件与子组件通信:父组件可以通过属性绑定将数据传递给子组件。例如,ProductsComponent作为父组件,ProductListComponent作为子组件:
<!-- products.component.html -->
<app - product - list [products]="productList"></app - product - list>
// products.component.ts
import { Component } from '@angular/core';
import { Product } from '../models/product';

@Component({
  selector: 'app - products',
  templateUrl: './products.component.html',
  styleUrls: ['./products.component.scss']
})
export class ProductsComponent {
  productList: Product[] = [
    { id: 1, name: 'Product 1' },
    { id: 2, name: 'Product 2' }
  ];
}
// product - list.component.ts
import { Component, Input } from '@angular/core';
import { Product } from '../models/product';

@Component({
  selector: 'app - product - list',
  templateUrl: './product - list.component.html',
  styleUrls: ['./product - list.component.scss']
})
export class ProductListComponent {
  @Input() products: Product[];
}

这里ProductsComponent通过[products]="productList"productList数组传递给ProductListComponentProductListComponent使用@Input()装饰器来接收这个数据。

  • 子组件与父组件通信:子组件可以通过事件绑定将数据传递给父组件。例如,ProductDetailComponent中有一个按钮,点击按钮时通知ProductsComponent
<!-- product - detail.component.html -->
<button (click)="onButtonClick()">Notify Parent</button>
// product - detail.component.ts
import { Component, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app - product - detail',
  templateUrl: './product - detail.component.html',
  styleUrls: ['./product - detail.component.scss']
})
export class ProductDetailComponent {
  @Output() notifyParent = new EventEmitter();

  onButtonClick() {
    this.notifyParent.emit('Button clicked in ProductDetailComponent');
  }
}
<!-- products.component.html -->
<app - product - detail (notifyParent)="handleNotification($event)"></app - product - detail>
// products.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app - products',
  templateUrl: './products.component.html',
  styleUrls: ['./products.component.scss']
})
export class ProductsComponent {
  handleNotification(message: string) {
    console.log(message);
  }
}

这里ProductDetailComponent通过@Output()EventEmitter创建了一个事件发射器notifyParent,点击按钮时发射数据。ProductsComponent通过(notifyParent)="handleNotification($event)"监听这个事件,并在handleNotification方法中处理接收到的数据。

服务与依赖注入

服务的概念

服务是一个在Angular应用中提供特定功能的类,它通常用于处理业务逻辑、数据访问等。例如,我们创建一个UserService用于处理用户相关的操作。

  • 创建用户服务
ng generate service user

这会生成一个user.service.ts文件。

  • 用户服务定义
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class UserService {
  private users: string[] = ['User 1', 'User 2'];

  getUsers() {
    return this.users;
  }
}

@Injectable装饰器用于标记这个类是一个可注入的服务。providedIn: 'root'表示这个服务在根模块中提供,整个应用都可以使用。getUsers方法返回用户列表。

依赖注入

依赖注入是Angular中一个非常重要的机制,它允许我们将一个服务注入到其他组件或服务中。例如,我们在AppComponent中注入UserService

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

@Component({
  selector: 'app - root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  users: string[];

  constructor(private userService: UserService) {
    this.users = this.userService.getUsers();
  }
}

AppComponent的构造函数中,通过private userService: UserServiceUserService注入进来,然后就可以使用userService的方法获取用户列表。Angular会自动创建UserService的实例并注入到AppComponent中,这样组件就不需要自己创建服务实例,提高了代码的可测试性和可维护性。

服务的作用域

服务的作用域取决于它在哪里被提供。如果像上面那样在根模块中提供服务(providedIn: 'root'),那么整个应用共享这个服务实例。如果在某个特性模块中提供服务,那么只有这个特性模块及其子模块可以使用这个服务实例。例如,我们在ProductsModule中提供一个ProductService

import { NgModule } from '@angular/core';
import { ProductService } from './product.service';

@NgModule({
  providers: [ProductService]
})
export class ProductsModule { }

这样,ProductService的实例只在ProductsModule及其子模块中有效,不同模块中的ProductService实例是相互独立的。

总结Angular项目结构的优势

Angular通过这种模块化、组件化的项目结构,使得代码组织清晰,易于维护和扩展。模块将相关功能组织在一起,组件实现了视图和逻辑的分离,服务集中处理业务逻辑和数据访问,依赖注入提高了代码的可测试性和可复用性。无论是开发小型应用还是大型企业级应用,Angular的项目结构都能提供良好的支持,帮助开发者高效地构建高质量的前端应用。通过深入理解这些结构组成,开发者可以更好地驾驭Angular项目的开发过程,编写出结构优美、易于维护的代码。在实际开发中,根据项目的需求合理地划分模块、设计组件和服务,能够大大提升开发效率和应用的质量。同时,随着项目的发展和需求的变化,这种清晰的结构也便于进行代码的重构和功能的扩展,为项目的长期维护奠定了坚实的基础。