# ng_project **Repository Path**: mexforces/ng_project ## Basic Information - **Project Name**: ng_project - **Description**: 基于Angular 10.2和ng-zorro-antd 10.2而实现的前端项目架构 - **Primary Language**: TypeScript - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 1 - **Created**: 2020-12-24 - **Last Updated**: 2021-08-11 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # NgProject ## 项目简介 本项目用于一般项目基础架构,基于 [Angular 10.2](https://angular.cn/) 和 [ng-zorro-antd 10.2](https://ng.ant.design/) 开发,并使用 [mockjs](http://mockjs.com/) 模拟服务端数据 ## 主要功能模块 - 公共模块(`SharedModule`)和 common 文件夹 该模块主要用于将常用业务组件、管道、第三方模块引入并提供给业务模块使用,而 common 文件夹下也包含了框架中路由守卫、HTTP 拦截器和服务 > `SharedModule` 中使用的 NG_ZORRO_ANTD 模块为常用模块,其他模块引入尽量不要在此模块中引用,例如: ```typescript /** * 登陆授权模块代码,模块中引入了 SharedModule 模块 * 但因为 SharedModule 没有引入 NzResultModule,所以单独引入 */ import { NzResultModule } from 'ng-zorro-antd/result'; import { SharedModule } from 'src/app/common/shared.module'; @NgModule({ declarations: [LoginComponent, NotFoundComponent], imports: [CommonModule, SharedModule, NzResultModule, AuthRoutingModule], }) export class AuthModule {} ``` - 布局文件夹 该文件夹下主要用于存放布局组件和布局相关的组件,布局组件在路由设置中使用,例如: ```typescript const routes: Routes = [ { path: 'home', canActivate: [LoginGuard], // 在此处使用布局组件 component: PageComponent, data: { title: '首页', }, // 子路由在布局组件中通过 router-outlet 输出 children: [ { path: '', redirectTo: 'index', pathMatch: 'full', }, { path: 'index', loadChildren: () => import('./pages/home/home.module').then((m) => m.HomeModule), data: { title: '首页', }, }, ], }, ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule], }) export class AppRoutingModule {} ``` - 业务模块文件夹 该文件夹下主要用于存放登陆模块、业务模块和菜单配置文件(menu.config.ts),菜单配置文件需要严格实现 `AppMenuItem` 接口以便于自动生成菜单。其中,`title` 和 `level` 属性是必须设置属性,`title` 用于定义菜单名称,`level` 用于定义菜单当前层级: ```typescript interface AppMenuItem { title: string; icon?: string; level: number; routeUrl?: string; open?: boolean; selected?: boolean; disabled?: boolean; children?: AppMenuItem[]; } export const menuConfig: AppMenuItem[] = [ { title: '首页', icon: 'home', level: 1, routeUrl: '/home/index/index', }, { title: '代码示例', icon: 'code', level: 1, children: [ { title: '列表', level: 2, routeUrl: '/home/template/list', }, { title: '表单', level: 2, routeUrl: '/home/template/form', }, ], }, ]; ``` ## 路由设置 根路由和子路由建议都设置默认路由,并且默认路由重定向至指定路由,并且指定路由设置 `data.title` 属性,以便面包屑组件可以根据路由自动生成 > 空路由设置 `data.title` 属性无法在面包屑组件中正常生成!! ```typescript const routes: Routes = [ { // 默认路由,重定向至 home path: '', redirectTo: 'home', pathMatch: 'full', }, { path: 'home', // 路由守卫,检测是否登陆 canActivate: [LoginGuard], component: PageComponent, data: { title: '首页', }, children: [ { // 默认路由,重定向至 index path: '', redirectTo: 'index', pathMatch: 'full', }, { path: 'index', loadChildren: () => import('./pages/home/home.module').then((m) => m.HomeModule), data: { title: '首页', }, }, ], }, { // 登陆授权模块 path: 'auth', loadChildren: () => import('./pages/auth/auth.module').then((m) => m.AuthModule), }, { // 通配符路由,定义 404 页面 path: '**', redirectTo: '/auth/not-found', pathMatch: 'full', }, ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule], }) export class AppRoutingModule {} ``` 路由守卫,用以实现登录检测和权限检测: ```typescript // 定义登录路由守卫,如果未登录,则直接跳转回登录页 @Injectable({ providedIn: 'root', }) export class LoginGuard implements CanActivateChild { constructor(private router: Router, private storage: StorageService) {} canActivateChild(next: ActivatedRouteSnapshot, state: RouterStateSnapshot) { // 获取缓存的 token 数据,并根据其值决定是否可以继续访问 const token = this.storage.get('token'); if (token) { // 继续访问 return true; } else { // 拒绝访问并返回路由 UrlTree,使路由跳转回登录页 return this.router.parseUrl('/auth/login'); } } } ``` ## HTTP 服务 网络请求直接使用 Angular 中自带的 `HttpClientModule` 模块,并且使用 rxjs 库做请求处理,例如在业务中,从列表页跳转至详情页时,常用的操作: ```typescript import { switchMap } from 'rxjs/operators'; // 从路由 /detail/:id 中获取 id 参数,并直接进行 http 请求获取数据 this.route.paramMap .pipe( // switchMap 方法用以消除路由参数频繁变更造成的性能影响,并且返回转换后的 http 请求 switchMap((param) => { return this.templateServ.get(param.get('id')); }) ) // 只有当 subscribe (订阅)后,rxjs 才会进行以上一系列的操作,否则获取路由参数和 http 请求不会执行 .subscribe((res) => { console.log(res); }); ``` ## 服务(Service) 服务定义如无特殊情况,不建议在任何模块中注入!推荐用法是在服务定义时,指定服务的作用域,如: ```typescript import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root', }) export class UserService {} ``` `providedIn: 'root'` 规定了该服务将会注册在根注入器中,即在所有业务模块中可用。如果明确某个模块只在特定的模块中使用,在 `providedIn` 中指定该模块即可: ```typescript import { Injectable } from '@angular/core'; import { UserModule } from './user.module'; @Injectable({ // 只在 UserModule 中使用 providedIn: UserModule, }) export class UserService {} ``` 如果多个模块需要使用该服务,而又不想将它注册在根注入器中,此时则只能在模块中注入该服务: ```typescript import { TemplateService } from 'src/app/common/providers'; @NgModule({ declarations: [IndexComponent], imports: [CommonModule, SharedModule, TemplateRoutingModule], // 在模块中注册服务 providers: [TemplateService], }) export class TemplateModule {} ``` 更有特殊情况,在组件中注册服务,此时该服务只在这组件中可见: ```typescript import { TemplateService } from 'src/app/common/providers'; @Component({ selector: 'app-index', templateUrl: './index.component.html', styleUrls: ['./index.component.less'], // 在组件中注册服务 providers: [TemplateService], }) export class IndexComponent implements OnInit {} ``` ## 服务端代理 为避免在开发阶段的跨域问题,推荐使用服务端代理方式,在项目根文件夹下新建代理配置 json 文件(proxy.config.json): ```json { "/api": { "target": "http://localhost:8000", "secure": false, "logLevel": "debug", "changeOrigin": true } } ``` 然后在 package.json 的 `scripts` 配置启动命令: ```json { "scripts": { "start": "ng serve --proxy-config proxy.config.json" } } ```