Angular模块系统与组织

Angular模块(NgModule)是组织应用的基本构建块。理解模块系统对于构建可维护、可扩展的大型应用至关重要。

NgModule:声明组件的编译上下文,将相关代码组织成功能集,可以导入其他模块的功能,也可以导出自己的功能供其他模块使用。

1. 基础模块结构

// app.module.ts - 根模块
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { CoreModule } from './core/core.module';
import { SharedModule } from './shared/shared.module';
import { AppRoutingModule } from './app-routing.module';

@NgModule({
  // 声明属于本模块的组件、指令、管道
  declarations: [
    AppComponent
  ],
  // 导入其他模块提供的功能
  imports: [
    BrowserModule,          // 必需:浏览器支持
    CoreModule,             // 核心模块(单例服务)
    SharedModule,           // 共享模块(通用组件)
    AppRoutingModule        // 根路由模块
  ],
  // 提供服务(全局单例)
  providers: [],
  // 指定应用的主视图(根组件)
  bootstrap: [AppComponent],
  // 导出模块的功能(供其他模块使用)
  exports: []
})
export class AppModule { }

2. 模块类型与职责

根模块
AppModule
  • 应用入口点
  • 引导启动应用
  • 导入根路由模块
  • 声明根组件
特性模块
FeatureModule
  • 封装特定功能
  • 支持惰性加载
  • 保持代码分离
  • 提高可维护性
共享模块
SharedModule
  • 存放通用组件
  • 导出常用指令
  • 提供公共管道
  • 避免代码重复
核心模块
CoreModule
  • 存放单例服务
  • 全局配置
  • HTTP拦截器
  • 只导入到根模块

2.1 核心模块(Core Module)

用于存放应用级别的单例服务和一次性初始化的组件。

// core/core.module.ts
import { NgModule, Optional, SkipSelf } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { AuthGuard } from './guards/auth.guard';
import { AuthInterceptor } from './interceptors/auth.interceptor';
import { ErrorInterceptor } from './interceptors/error.interceptor';
import { ApiService } from './services/api.service';
import { AuthService } from './services/auth.service';
import { LoadingService } from './services/loading.service';
import { HeaderComponent } from './components/header/header.component';
import { FooterComponent } from './components/footer/footer.component';

@NgModule({
  declarations: [
    HeaderComponent,
    FooterComponent
  ],
  imports: [
    CommonModule,
    HttpClientModule
  ],
  exports: [
    HeaderComponent,
    FooterComponent
  ],
  providers: [
    AuthGuard,
    ApiService,
    AuthService,
    LoadingService,
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthInterceptor,
      multi: true
    },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: ErrorInterceptor,
      multi: true
    }
  ]
})
export class CoreModule {
  // 防止CoreModule被重复导入
  constructor(@Optional() @SkipSelf() parentModule?: CoreModule) {
    if (parentModule) {
      throw new Error(
        'CoreModule 已经导入。只能在 AppModule 中导入核心模块。'
      );
    }
  }
}

2.2 共享模块(Shared Module)

用于存放应用中多个模块共享的组件、指令和管道。

// shared/shared.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';

// 通用组件
import { ButtonComponent } from './components/button/button.component';
import { CardComponent } from './components/card/card.component';
import { ModalComponent } from './components/modal/modal.component';
import { SpinnerComponent } from './components/spinner/spinner.component';
import { PaginationComponent } from './components/pagination/pagination.component';

// 指令
import { HighlightDirective } from './directives/highlight.directive';
import { ClickOutsideDirective } from './directives/click-outside.directive';

// 管道
import { TruncatePipe } from './pipes/truncate.pipe';
import { CurrencyFormatPipe } from './pipes/currency-format.pipe';
import { SafeHtmlPipe } from './pipes/safe-html.pipe';

@NgModule({
  declarations: [
    // 组件
    ButtonComponent,
    CardComponent,
    ModalComponent,
    SpinnerComponent,
    PaginationComponent,

    // 指令
    HighlightDirective,
    ClickOutsideDirective,

    // 管道
    TruncatePipe,
    CurrencyFormatPipe,
    SafeHtmlPipe
  ],
  imports: [
    CommonModule,
    FormsModule,
    ReactiveFormsModule,
    RouterModule
  ],
  exports: [
    // 重新导出Angular模块
    CommonModule,
    FormsModule,
    ReactiveFormsModule,
    RouterModule,

    // 导出共享组件
    ButtonComponent,
    CardComponent,
    ModalComponent,
    SpinnerComponent,
    PaginationComponent,

    // 导出共享指令
    HighlightDirective,
    ClickOutsideDirective,

    // 导出共享管道
    TruncatePipe,
    CurrencyFormatPipe,
    SafeHtmlPipe
  ]
})
export class SharedModule { }

2.3 特性模块(Feature Module)

按功能划分的独立模块,可以支持惰性加载。

// features/products/products.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SharedModule } from '../../shared/shared.module';
import { ProductsRoutingModule } from './products-routing.module';

import { ProductListComponent } from './product-list/product-list.component';
import { ProductDetailComponent } from './product-detail/product-detail.component';
import { ProductEditComponent } from './product-edit/product-edit.component';
import { ProductSearchComponent } from './product-search/product-search.component';
import { ProductService } from './services/product.service';
import { ProductResolver } from './resolvers/product.resolver';
import { ProductFilterPipe } from './pipes/product-filter.pipe';

@NgModule({
  declarations: [
    ProductListComponent,
    ProductDetailComponent,
    ProductEditComponent,
    ProductSearchComponent,
    ProductFilterPipe
  ],
  imports: [
    CommonModule,
    SharedModule,          // 导入共享模块(而不是CommonModule + FormsModule等)
    ProductsRoutingModule  // 特性路由模块
  ],
  providers: [
    ProductService,
    ProductResolver
  ]
})
export class ProductsModule { }
// features/products/products-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ProductListComponent } from './product-list/product-list.component';
import { ProductDetailComponent } from './product-detail/product-detail.component';
import { ProductEditComponent } from './product-edit/product-edit.component';
import { ProductResolver } from './resolvers/product.resolver';

const routes: Routes = [
  {
    path: '',
    component: ProductListComponent
  },
  {
    path: 'search',
    component: ProductSearchComponent
  },
  {
    path: ':id',
    component: ProductDetailComponent,
    resolve: {
      product: ProductResolver
    }
  },
  {
    path: ':id/edit',
    component: ProductEditComponent,
    resolve: {
      product: ProductResolver
    }
  }
];

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

3. 惰性加载模块

按需加载模块,提升应用初始加载性能。

// app-routing.module.ts - 配置惰性加载
import { NgModule } from '@angular/core';
import { RouterModule, Routes, PreloadAllModules } from '@angular/router';
import { AuthGuard } from './core/guards/auth.guard';

const routes: Routes = [
  {
    path: '',
    redirectTo: '/dashboard',
    pathMatch: 'full'
  },
  {
    path: 'dashboard',
    loadChildren: () => import('./features/dashboard/dashboard.module')
      .then(m => m.DashboardModule),
    canActivate: [AuthGuard]
  },
  {
    path: 'products',
    loadChildren: () => import('./features/products/products.module')
      .then(m => m.ProductsModule),
    canActivate: [AuthGuard],
    data: { preload: true } // 预加载标志
  },
  {
    path: 'orders',
    loadChildren: () => import('./features/orders/orders.module')
      .then(m => m.OrdersModule),
    canActivate: [AuthGuard]
  },
  {
    path: 'admin',
    loadChildren: () => import('./features/admin/admin.module')
      .then(m => m.AdminModule),
    canActivate: [AuthGuard],
    data: { roles: ['admin'] } // 角色权限
  },
  {
    path: 'auth',
    loadChildren: () => import('./features/auth/auth.module')
      .then(m => m.AuthModule)
  },
  {
    path: '**',
    loadChildren: () => import('./features/not-found/not-found.module')
      .then(m => m.NotFoundModule)
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes, {
    preloadingStrategy: PreloadAllModules, // 预加载所有模块
    enableTracing: false, // 开发时启用路由追踪
    scrollPositionRestoration: 'enabled' // 恢复滚动位置
  })],
  exports: [RouterModule]
})
export class AppRoutingModule { }

3.1 自定义预加载策略

// core/strategies/selective-preloading.strategy.ts
import { Injectable } from '@angular/core';
import { PreloadingStrategy, Route } from '@angular/router';
import { Observable, of } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class SelectivePreloadingStrategy implements PreloadingStrategy {
  preloadedModules: string[] = [];

  preload(route: Route, load: () => Observable): Observable {
    if (route.data && route.data['preload'] && route.path) {
      // 添加预加载模块路径
      this.preloadedModules.push(route.path);
      return load();
    } else {
      return of(null);
    }
  }
}

// 在路由模块中使用
import { SelectivePreloadingStrategy } from './core/strategies/selective-preloading.strategy';

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

4. 模块组织架构

src/
├── app/
│   ├── core/                    # 核心模块(单例服务,一次性组件)
│   │   ├── components/         # 核心组件(Header, Footer等)
│   │   ├── guards/             # 路由守卫
│   │   ├── interceptors/       # HTTP拦截器
│   │   ├── models/             # 核心模型/接口
│   │   ├── services/           # 单例服务
│   │   ├── strategies/         # 策略(预加载等)
│   │   ├── utils/              # 工具函数
│   │   └── core.module.ts
│   │
│   ├── shared/                 # 共享模块(通用组件/指令/管道)
│   │   ├── components/         # 可复用组件
│   │   ├── directives/         # 共享指令
│   │   ├── pipes/              # 共享管道
│   │   └── shared.module.ts
│   │
│   ├── features/               # 特性模块(按功能划分)
│   │   ├── dashboard/          # 仪表板模块
│   │   │   ├── components/
│   │   │   ├── services/
│   │   │   └── dashboard.module.ts
│   │   │
│   │   ├── products/           # 产品管理模块
│   │   │   ├── components/
│   │   │   ├── services/
│   │   │   └── products.module.ts
│   │   │
│   │   └── auth/               # 认证模块
│   │       ├── components/
│   │       ├── services/
│   │       └── auth.module.ts
│   │
│   ├── app.component.ts        # 根组件
│   ├── app.module.ts           # 根模块
│   └── app-routing.module.ts   # 根路由配置
│
├── assets/                     # 静态资源
├── environments/               # 环境配置
└── styles/                     # 全局样式
                            

5. 模块通信与依赖

5.1 模块间的服务共享

// 方案1:在根模块提供服务(单例)
// app.module.ts
@NgModule({
  providers: [
    UserService  // 全局单例,所有模块共享
  ]
})

// 方案2:使用providedIn: 'root'
// user.service.ts
@Injectable({
  providedIn: 'root'  // 自动在根注入器中提供
})
export class UserService { }

// 方案3:模块级别的服务
// products.module.ts
@NgModule({
  providers: [
    ProductService  // 只在ProductsModule作用域内
  ]
})

// 方案4:在CoreModule中提供核心服务
// core.module.ts
@NgModule({
  providers: [
    ApiService,
    AuthService,
    LoggingService
  ]
})

5.2 模块导入/导出规则

// ❌ 错误:循环导入
// module-a.ts 导入 module-b.ts,同时 module-b.ts 导入 module-a.ts

// ✅ 正确:使用共享模块
// shared.module.ts
@NgModule({
  exports: [CommonModule, FormsModule, SomeSharedComponent]
})

// ✅ 正确:模块重构
// 将共享内容提取到第三个模块中

// 重新导出模块的模式
@NgModule({
  imports: [CommonModule, FormsModule, SomeThirdPartyModule],
  exports: [CommonModule, FormsModule, SomeThirdPartyModule]
})
export class ReexportModule { }

6. 动态模块加载

// 动态加载模块(非路由)
import { Component, ViewChild, ViewContainerRef, ComponentFactoryResolver } from '@angular/core';

@Component({
  selector: 'app-dynamic-loader',
  template: `<ng-container #container></ng-container>`
})
export class DynamicLoaderComponent {
  @ViewChild('container', { read: ViewContainerRef }) container: ViewContainerRef;

  constructor(private cfr: ComponentFactoryResolver) {}

  async loadModule() {
    // 动态导入模块
    const { FeatureModule } = await import('./features/feature/feature.module');

    // 创建NgModule工厂
    const moduleFactory = this.createNgModuleFactory(FeatureModule);

    // 创建模块引用
    const moduleRef = moduleFactory.create(this.injector);

    // 获取模块中的组件工厂
    const componentFactory = moduleRef.componentFactoryResolver
      .resolveComponentFactory(FeatureComponent);

    // 创建组件
    this.container.clear();
    this.container.createComponent(componentFactory);
  }

  private createNgModuleFactory(moduleType: any) {
    // 使用编译器创建模块工厂
    // 注:实际实现需要注入Compiler
  }
}

7. 性能优化技巧

模块性能优化:
  • 使用forChild()而不是forRoot()创建特性模块路由
  • 合理使用providedIn: 'root'避免重复服务实例
  • 通过core.module.ts的构造函数防止重复导入
  • 将不常用的功能拆分为惰性加载模块
  • 使用共享模块避免重复导入CommonModule
  • Tree-shaking友好:避免在模块中导入未使用的功能
// 服务摇树优化
// 仅在需要时提供特定服务
@NgModule({
  providers: [
    {
      provide: 'API_CONFIG',
      useFactory: () => {
        // 动态配置,支持摇树
        return environment.apiConfig;
      }
    }
  ]
})

// 条件性导入模块
const imports = [
  CommonModule,
  SharedModule
];

// 仅在开发环境导入调试模块
if (!environment.production) {
  imports.push(DevToolsModule);
}

@NgModule({
  imports: imports
})

8. 测试模块组织

// 为测试创建专用模块
// test.module.ts
import { TestBed } from '@angular/core/testing';

export function createTestModule(metadata: NgModule): void {
  @NgModule({
    imports: metadata.imports || [],
    declarations: metadata.declarations || [],
    providers: metadata.providers || [],
    exports: metadata.exports || []
  })
  class TestModule {}

  TestBed.configureTestingModule({
    imports: [TestModule]
  });
}

// 测试中使用
beforeEach(() => {
  createTestModule({
    imports: [SharedModule],
    declarations: [TestComponent],
    providers: [TestService]
  });
});

// 特性模块的测试配置
// products.module.spec.ts
describe('ProductsModule', () => {
  let module: ProductsModule;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [ProductsModule]
    });
    module = TestBed.inject(ProductsModule);
  });

  it('应该创建模块', () => {
    expect(module).toBeTruthy();
  });
});

9. 模块设计最佳实践

模块设计原则:
  • 单一职责:每个模块只负责一个特定功能
  • 高内聚:相关组件、服务组织在一起
  • 低耦合:模块间通过明确定义的接口通信
  • 可复用:共享模块设计为可复用的
  • 可测试:模块独立,便于单元测试
  • 渐进式:支持按需加载和增量开发
常见陷阱:
  • 模块过大:包含太多不相关功能
  • 循环依赖:模块间相互导入
  • 重复导入:多次导入相同模块
  • 作用域混乱:服务作用域不明确
  • 过度抽象:过早创建不必要的模块
  • 导入未使用:导入不需要的模块,增加包体积

10. 现代化模块模式

// 使用独立组件API(Angular 14+)
// product-list.component.ts
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ProductCardComponent } from '../product-card/product-card.component';

@Component({
  selector: 'app-product-list',
  standalone: true,  // 独立组件
  imports: [CommonModule, ProductCardComponent],  // 直接导入依赖
  template: `
    <div *ngFor="let product of products">
      <app-product-card [product]="product"></app-product-card>
    </div>
  `
})
export class ProductListComponent {
  products = [...];
}

// 惰性加载独立组件
const routes: Routes = [{
  path: 'products',
  loadComponent: () => import('./product-list.component')
    .then(m => m.ProductListComponent)
}];

10.1 模块库开发

// 库模块设计
// ui-library.module.ts
import { ModuleWithProviders, NgModule } from '@angular/core';

@NgModule({
  declarations: [...],
  exports: [...]
})
export class UiLibraryModule {
  // 提供配置的静态方法
  static forRoot(config: UiConfig): ModuleWithProviders<UiLibraryModule> {
    return {
      ngModule: UiLibraryModule,
      providers: [
        {
          provide: UI_CONFIG,
          useValue: config
        }
      ]
    };
  }

  // 不带配置的导入
  static forChild(): ModuleWithProviders<UiLibraryModule> {
    return {
      ngModule: UiLibraryModule,
      providers: []
    };
  }
}

// 使用
@NgModule({
  imports: [
    UiLibraryModule.forRoot({
      theme: 'dark',
      animations: true
    })
  ]
})
export class AppModule { }

@NgModule({
  imports: [
    UiLibraryModule.forChild()
  ]
})
export class FeatureModule { }
架构演进建议:
  • 小型应用:使用根模块+几个特性模块
  • 中型应用:引入核心模块和共享模块
  • 大型应用:采用微前端架构,拆分为多个独立应用
  • 企业级:考虑模块联邦、动态加载、独立部署
  • 迁移路径:从传统模块逐步迁移到独立组件