Angular结合UI组件库ZORRO的实战

Angular结合UI组件库ZORRO的实战

1、创建angular项目

使用的代码编辑器为webStorm,直接使用图形化界面创建

image-20250329093858956

image-20250329094202603


我们进行一个项目的开发,好的代码目录结构设计可以让代码可维护性更高,在 Angular 项目中设计代码结构时,遵循清晰的分层架构和模块化思想非常重要。以下是一个经过实践验证的标准项目结构设计方案,适用于中大型项目:


1.1 代码路径设计核心原则

  1. 模块化架构(按功能/业务划分)
  2. 单一职责原则(每个文件只做一件事)
  3. 分层设计(展示层/业务逻辑层/数据层分离)
  4. 可扩展性(方便新增功能模块)
  5. 可维护性(清晰的文件定位)
src/
├── app/
│   ├── core/                  # 核心模块(单例服务/拦截器等)
│   │   ├── guards/           # 路由守卫
│   │   ├── interceptors/     # HTTP拦截器
│   │   ├── services/         # 全局服务(如AuthService)
│   │   └── core.module.ts    # 核心模块定义
│   │
│   ├── shared/               # 共享模块(组件/指令/管道等)
│   │   ├── components/       # 通用UI组件
│   │   ├── directives/       # 共享指令
│   │   ├── pipes/            # 共享管道
│   │   ├── models/           # 数据模型/接口
│   │   └── shared.module.ts # 共享模块定义
│   │
│   ├── features/             # 功能模块(按业务划分)
│   │   ├── auth/             # 认证模块
│   │   ├── dashboard/        # 仪表盘模块
│   │   ├── products/         # 商品管理模块
│   │   └── ...              # 其他业务模块
│   │
│   ├── app-routing.module.ts # 主路由配置
│   └── app.component.ts      # 根组件
│
├── assets/                   # 静态资源
├── environments/             # 环境配置
└── styles/                   # 全局样式
Feature Module (示例:商品模块)
features/
└── products/
    ├── components/         # 模块专用组件
    ├── services/           # 模块服务
    ├── store/              # 状态管理(如使用 NgRx)
    ├── models/             # 模块数据模型
    ├── products-routing.module.ts
    ├── products.component.ts
    └── products.module.ts

1.1.1 最佳实践建议
  1. 模块划分原则

    • 每个功能模块应独立可懒加载
    • 共享组件/服务放入 shared 目录
    • 全局单例服务放入 core 目录
  2. 组件设计规范

    • 组件前缀统一(如 app-product-list

    • 使用容器组件(Smart)和展示组件(Dumb)分离模式

    • 组件文件结构:

      product-list/
      ├── product-list.component.ts
      ├── product-list.component.html
      ├── product-list.component.scss
      └── product-list.component.spec.ts
      
  3. 状态管理(推荐方案)

    products/
    └── store/
        ├── actions/
        ├── reducers/
        ├── effects/
        ├── selectors/
        └── models/
    

1.2 设计自己的代码目录结构

1.2.1 创建功能特性的组件

在features下创建功能特性的组件这里先创建一个登录界面,带有路由配置。

Angular CLI 命令用于快速生成一个带有路由配置的模块 ng g m pages/login --routing

也可以借助webstorm进行界面话操作

image-20250329100737114

接下来创建登录界面的组件 ng g c pages/login

image-20250329100922066


1.2.2 路由配置

让项目的根目录默认自动指向登录页面,也可以通过登录页面的url固定进行访问

login-routing.module.ts 中对Routes进行配置

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { LoginComponent } from './login.component';

const routes: Routes = [
  {path: '', component: LoginComponent},
];

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

app-routing.module.ts 中配置父组件app的路由,让其默认指向/login,同时也支持固定路由访问子组件页面

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

const routes: Routes = [
  {path: '', pathMatch: 'full', redirectTo: '/login'},
  {path: 'login', loadChildren: () => import('./features/pages/login/login.module').then(m => m.LoginModule)},
];

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


app.component.html 中修改内容如下

image-20250329102411028


<router-outlet> 是 Angular 路由系统中的核心组件,它充当 动态视图的占位符,用于根据当前 URL 路径动态加载并显示对应的组件。可以理解为 Angular 路由的「插槽」或「容器」。

1.2.2.1核心概念
  1. 作用
    • 当用户访问某个路由时(如 /login),Angular 会根据路由配置找到匹配的组件,并将其渲染到 <router-outlet> 的位置。
    • 它是路由内容的 出口(Outlet),类似电视的「信号输出口」。
  2. 类比理解
    • 类似于 Vue 的 <router-view> 或 React 的 <Outlet>
    • 想象成一个「画框」,路由切换时,画框里的内容(组件)会动态更换,但画框本身不变。

测试

image-20250329102943820


2、引入UI组件库NG-ZORRO

2.1 初始化配置

参考 快速上手 | NG-ZORRO

初始化配置,包括引入国际化文件,导入模块,引入样式文件等工作

ng add ng-zorro-antd

image-20250329103723898

引入样式

angular.json 中引入了

{
  "styles": [
    "node_modules/ng-zorro-antd/ng-zorro-antd.min.css"
  ]
}

image-20250329103853536

2.2 引入NG-ZORRO组件

在 Angular 项目中引入 NG-ZORRO(Ant Design 的 Angular 实现)组件时,推荐采用模块化、按需加载的方式,以提高性能和可维护性。

NG-ZORRO 支持 模块化导入,避免打包未使用的组件,减少体积。


2.2.1 在独立模块中导入(推荐)

shared.module.tsng-zorro.module.ts 中集中管理 NG-ZORRO 组件:

│   ├── shared/                 # 共享模块(组件、指令、管道等)
│   │   ├── components/         # 公共组件(如LoadingSpinner、ErrorModal)
│   │   ├── directives/         # 公共指令
│   │   ├── pipes/              # 公共管道
│   │   ├── ng-zorro/           # NG-ZORRO 模块管理
│   │   │   ├── ng-zorro.module.ts  # 按需导入NG-ZORRO模块
│   │   │   └── icons.module.ts     # 图标模块

icons.module.ts

import { NgModule } from '@angular/core';
import { DashboardOutline, FormOutline, MenuFoldOutline, MenuUnfoldOutline } from '@ant-design/icons-angular/icons';
import { NzIconModule, provideNzIcons } from 'ng-zorro-antd/icon';


const icons = [
  MenuFoldOutline,
  MenuUnfoldOutline,
  DashboardOutline,
  FormOutline
]

@NgModule({
  declarations: [],
  imports: [
    NzIconModule,
  ],
  exports: [
    NzIconModule
  ],
  providers: [
    provideNzIcons(icons)
  ]
})
export class IconsModule {
}

我们先来引入一个按钮组件看一下

image-20250329105710789


ng-zooro.module.ts 中集中管理zooro组件

...JavaScript/TypeScript 的展开运算符(Spread Operator),它用于将数组 SHARED_ZORRO_MODULES 中的元素“展开”到 importsexports 数组中。

zorro.module.ts

import { NgModule } from '@angular/core';
import { NzButtonModule } from 'ng-zorro-antd/button';

const SHARED_ZORRO_MODULES = [
  NzButtonModule
];

@NgModule({
  declarations: [],
  imports: [
    ...SHARED_ZORRO_MODULES
  ],
  exports: [
    ...SHARED_ZORRO_MODULES
  ]
})
export class ZorroModule { }

集中管理这些组件,并且导出,其他组件使用时候只需要导入Module即可

我们把IconsModuleNgZooroModule 在最外层的module中导入,这样其他组件使用zooro组件和图标的时候,只需要导入SharedModule (我们这里直接在 AppModule 中导入)

shared.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { NgZooroModule } from './ng-zooro/ng-zooro.module';
import { IconsModule } from './ng-zooro/icons.module';



@NgModule({
  declarations: [],
  imports: [
    CommonModule,
    NgZooroModule,
    IconsModule
  ],
  exports:[
    CommonModule,
    NgZooroModule,
    IconsModule
  ]
})
export class SharedModule { }

app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { provideNzI18n } from 'ng-zorro-antd/i18n';
import { zh_CN } from 'ng-zorro-antd/i18n';
import { registerLocaleData } from '@angular/common';
import zh from '@angular/common/locales/zh';
import { FormsModule } from '@angular/forms';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { provideHttpClient } from '@angular/common/http';
import { SharedModule } from './shared/shared.module';

registerLocaleData(zh);

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    FormsModule,
    SharedModule,
  ],
  providers: [
    provideNzI18n(zh_CN),
    provideAnimationsAsync(),
    provideHttpClient()
  ],
  bootstrap: [AppComponent]
})
export class AppModule {
}


2.2.2 检查登录页面,按钮的样式是否生效

image-20250329175832201

发现样式没有生效,为什么呢?

image-20250329175902719


主要原因是对模块关系理解不到位,如下是一些分析

2025-03-29_183217


目前我们修改为即在app.module.ts中导入SharedModule,然后又在特性模块中按需导入SharedModule

image-20250329183631553

按钮样式生效

image-20250329183715913


2.3 设计登录页面

2.3.1 挑选组件库中的表单组件

选取如下图的样例,进行引入使用
image-20250329184138701

2.3.2 引入组件
import { Component, inject } from '@angular/core';
import { NonNullableFormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';

import { NzButtonModule } from 'ng-zorro-antd/button';
import { NzCheckboxModule } from 'ng-zorro-antd/checkbox';
import { NzFormModule } from 'ng-zorro-antd/form';
import { NzInputModule } from 'ng-zorro-antd/input';

这里导入的时候需要注意NonNullableFormBuilderValidators 属于服务,对于服务类的,我们该怎么像SharedModule一样对Angular 的模块(NgModule)进行统一管理呢?

  • NonNullableFormBuilder:一个服务(替代默认的 FormBuilder,生成非空表单控件)。
  • Validators:一个包含验证函数的静态工具类(如 Validators.required)。
为什么不能放到 SharedModule
  1. NonNullableFormBuilder
    • 是服务(Injectable),需要在组件或模块的 providers 中提供。
    • Angular 的依赖注入系统会自动处理,无需通过 SharedModule 导出。
  2. Validators
    • 是纯静态工具类(类似 Math),直接通过导入使用,与模块无关。

最佳实践总结
内容管理方式示例
ReactiveFormsModule通过 SharedModule 统一管理exports: [ReactiveFormsModule]
NonNullableFormBuilder在组件中直接注入constructor(private fb: NonNullableFormBuilder)
Validators在组件中直接导入使用Validators.required

常见问题
Q:能否在 SharedModule 中提供 NonNullableFormBuilder
  • 不推荐。因为:
    • 服务通常由 Angular 的根注入器提供(默认在 AppModule 或组件层级)。
    • 如果在 SharedModuleproviders 中声明,可能导致服务实例重复(除非 SharedModule 是单例导入)。
Q:是否需要为 Validators 创建共享文件?
  • 不需要Validators 是静态工具类,直接导入即可,无需封装。

** 最终建议**
  • 模块化的内容(如 ReactiveFormsModule:通过 SharedModule 统一管理。
  • 服务或工具类(如 NonNullableFormBuilderValidators:在使用的组件中直接导入和注入。

这样可以保持代码的清晰性和 Angular 依赖注入的最佳实践。


image-20250329190649601

image-20250329190810980


2.3.2 修改特性组件LoginComponent的html、css、ts相关代码

login.component.html

<form nz-form [formGroup]="validateForm" class="login-form" (ngSubmit)="submitForm()">
  <nz-form-item>
    <nz-form-control nzErrorTip="Please input your username!">
      <nz-input-group nzPrefixIcon="user">
        <input type="text" nz-input formControlName="username" placeholder="Username"/>
      </nz-input-group>
    </nz-form-control>
  </nz-form-item>
  <nz-form-item>
    <nz-form-control nzErrorTip="Please input your Password!">
      <nz-input-group nzPrefixIcon="lock">
        <input type="password" nz-input formControlName="password" placeholder="Password"/>
      </nz-input-group>
    </nz-form-control>
  </nz-form-item>
  <div nz-row class="login-form-margin">
    <div nz-col [nzSpan]="12">
      <label nz-checkbox formControlName="remember">
        <span>Remember me</span>
      </label>
    </div>
    <div nz-col [nzSpan]="12">
      <a class="login-form-forgot">Forgot password</a>
    </div>
  </div>
  <button nz-button class="login-form-button login-form-margin" [nzType]="'primary'">Log in</button>
  Or
  <a>register now!</a>
</form>

login.component.ts

  • 通过 inject() 注入依赖(替代传统的 constructor 注入)。
import { Component, inject } from '@angular/core';
import { NonNullableFormBuilder, Validators } from '@angular/forms';

@Component({
  selector: 'app-login',
  standalone: false,
  templateUrl: './login.component.html',
  styleUrl: './login.component.css'
})
export class LoginComponent {
  private fb = inject(NonNullableFormBuilder);
  public validateForm = this.fb.group({
    username: this.fb.control('', [Validators.required]),
    password: this.fb.control('', [Validators.required]),
    remember: this.fb.control(true)
  });

  submitForm(): void {
    if (this.validateForm.valid) {
      console.log('submit', this.validateForm.value);
    } else {
      Object.values(this.validateForm.controls).forEach(control => {
        if (control.invalid) {
          control.markAsDirty();
          control.updateValueAndValidity({onlySelf: true});
        }
      });
    }
  }
}


界面效果

image-20250329191214191

2.3.3 调整界面样式

实现 全局固定 Header 和 Footer,同时让 登录组件居中

2.3.3.1 修改 app.component.html(固定 Header 和 Footer)
<!-- 固定头部 -->
<header class="header">
  <h1 class="title">智能学习辅助系统</h1>
</header>

<!-- 动态内容区(由路由控制) -->
<main class="content">
  <router-outlet></router-outlet>
</main>

<!-- 固定底部 -->
<footer class="footer">
  <p class="copyright">所属权 © Google</p>
</footer>
2.3.3.2 添加全局样式 styles.css
/* 重置默认边距 */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

/* 全局布局 */
app-root {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

body {
  margin: 0;
  min-height: 100vh;
}

/* 头部样式 */
.header {
  background-color: #1890ff;
  color: white;
  padding: 24px;
  text-align: center;
}

.title {
  font-size: 2.5rem;
  font-weight: bold;
  margin: 0;
}

/* 内容区样式 */
.content {
  flex: 1;
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 40px; /* 增大内边距 */
}

/* 底部样式 */
.footer {
  background-color: #f0f0f0;
  padding: 20px;
  text-align: center;
  flex-shrink: 0;
}

.copyright {
  margin: 0;
  font-size: 0.875rem;
  color: #666;
}
2.3.3.3. 登录组件特殊处理(居中)

login.component.html 无任何变化

login.component.css

.login-form {
  /*width: 100%;*/
  /*max-width: 600px;*/
  width: 350px;
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
  background: white;
}

/* 其他样式保持不变 */
.login-form-margin {
  margin-bottom: 16px;
}

.login-form-forgot {
  float: right;
}

.login-form-button {
  width: 100%;
}

2.3.3.4. 界面效果如下

增大内边距 */
}

/* 底部样式 */
.footer {
background-color: #f0f0f0;
padding: 20px;
text-align: center;
flex-shrink: 0;
}

.copyright {
margin: 0;
font-size: 0.875rem;
color: #666;
}


##### **2.3.3.3. 登录组件特殊处理(居中)**

`login.component.html`  无任何变化

`login.component.css`

```css
.login-form {
  /*width: 100%;*/
  /*max-width: 600px;*/
  width: 350px;
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
  background: white;
}

/* 其他样式保持不变 */
.login-form-margin {
  margin-bottom: 16px;
}

.login-form-forgot {
  float: right;
}

.login-form-button {
  width: 100%;
}

2.3.3.4. 界面效果如下

image-20250329203045797

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值