Angular结合UI组件库ZORRO的实战
1、创建angular项目
使用的代码编辑器为webStorm,直接使用图形化界面创建


我们进行一个项目的开发,好的代码目录结构设计可以让代码可维护性更高,在 Angular 项目中设计代码结构时,遵循清晰的分层架构和模块化思想非常重要。以下是一个经过实践验证的标准项目结构设计方案,适用于中大型项目:
1.1 代码路径设计核心原则
- 模块化架构(按功能/业务划分)
- 单一职责原则(每个文件只做一件事)
- 分层设计(展示层/业务逻辑层/数据层分离)
- 可扩展性(方便新增功能模块)
- 可维护性(清晰的文件定位)
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 最佳实践建议
-
模块划分原则
- 每个功能模块应独立可懒加载
- 共享组件/服务放入
shared目录 - 全局单例服务放入
core目录
-
组件设计规范
-
组件前缀统一(如
app-product-list) -
使用容器组件(Smart)和展示组件(Dumb)分离模式
-
组件文件结构:
product-list/ ├── product-list.component.ts ├── product-list.component.html ├── product-list.component.scss └── product-list.component.spec.ts
-
-
状态管理(推荐方案)
products/ └── store/ ├── actions/ ├── reducers/ ├── effects/ ├── selectors/ └── models/
1.2 设计自己的代码目录结构
1.2.1 创建功能特性的组件
在features下创建功能特性的组件这里先创建一个登录界面,带有路由配置。
Angular CLI 命令用于快速生成一个带有路由配置的模块 ng g m pages/login --routing
也可以借助webstorm进行界面话操作

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

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 中修改内容如下

<router-outlet> 是 Angular 路由系统中的核心组件,它充当 动态视图的占位符,用于根据当前 URL 路径动态加载并显示对应的组件。可以理解为 Angular 路由的「插槽」或「容器」。
1.2.2.1核心概念
- 作用
- 当用户访问某个路由时(如
/login),Angular 会根据路由配置找到匹配的组件,并将其渲染到<router-outlet>的位置。 - 它是路由内容的 出口(Outlet),类似电视的「信号输出口」。
- 当用户访问某个路由时(如
- 类比理解
- 类似于 Vue 的
<router-view>或 React 的<Outlet>。 - 想象成一个「画框」,路由切换时,画框里的内容(组件)会动态更换,但画框本身不变。
- 类似于 Vue 的
测试

2、引入UI组件库NG-ZORRO
2.1 初始化配置
初始化配置,包括引入国际化文件,导入模块,引入样式文件等工作
ng add ng-zorro-antd

引入样式
在 angular.json 中引入了
{
"styles": [
"node_modules/ng-zorro-antd/ng-zorro-antd.min.css"
]
}

2.2 引入NG-ZORRO组件
在 Angular 项目中引入 NG-ZORRO(Ant Design 的 Angular 实现)组件时,推荐采用模块化、按需加载的方式,以提高性能和可维护性。
NG-ZORRO 支持 模块化导入,避免打包未使用的组件,减少体积。
2.2.1 在独立模块中导入(推荐)
在 shared.module.ts 或 ng-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 {
}
我们先来引入一个按钮组件看一下

在ng-zooro.module.ts 中集中管理zooro组件
... 是 JavaScript/TypeScript 的展开运算符(Spread Operator),它用于将数组 SHARED_ZORRO_MODULES 中的元素“展开”到 imports 和 exports 数组中。
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即可
我们把IconsModule和NgZooroModule 在最外层的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 检查登录页面,按钮的样式是否生效

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

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

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

按钮样式生效

2.3 设计登录页面
2.3.1 挑选组件库中的表单组件
选取如下图的样例,进行引入使用

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';
这里导入的时候需要注意NonNullableFormBuilder 和Validators 属于服务,对于服务类的,我们该怎么像SharedModule一样对Angular 的模块(NgModule)进行统一管理呢?
NonNullableFormBuilder:一个服务(替代默认的FormBuilder,生成非空表单控件)。Validators:一个包含验证函数的静态工具类(如Validators.required)。
为什么不能放到 SharedModule?
NonNullableFormBuilder:- 是服务(
Injectable),需要在组件或模块的providers中提供。 - Angular 的依赖注入系统会自动处理,无需通过
SharedModule导出。
- 是服务(
Validators:- 是纯静态工具类(类似
Math),直接通过导入使用,与模块无关。
- 是纯静态工具类(类似
最佳实践总结
| 内容 | 管理方式 | 示例 |
|---|---|---|
ReactiveFormsModule | 通过 SharedModule 统一管理 | exports: [ReactiveFormsModule] |
NonNullableFormBuilder | 在组件中直接注入 | constructor(private fb: NonNullableFormBuilder) |
Validators | 在组件中直接导入使用 | Validators.required |
常见问题
Q:能否在 SharedModule 中提供 NonNullableFormBuilder?
- 不推荐。因为:
- 服务通常由 Angular 的根注入器提供(默认在
AppModule或组件层级)。 - 如果在
SharedModule的providers中声明,可能导致服务实例重复(除非SharedModule是单例导入)。
- 服务通常由 Angular 的根注入器提供(默认在
Q:是否需要为 Validators 创建共享文件?
- 不需要。
Validators是静态工具类,直接导入即可,无需封装。
** 最终建议**
- 模块化的内容(如
ReactiveFormsModule):通过SharedModule统一管理。 - 服务或工具类(如
NonNullableFormBuilder和Validators):在使用的组件中直接导入和注入。
这样可以保持代码的清晰性和 Angular 依赖注入的最佳实践。


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});
}
});
}
}
}
界面效果

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. 界面效果如下


8590

被折叠的 条评论
为什么被折叠?



