Angular UI v2.9 迁移到 v3.0 指南

在v3.0改变了什么?

Angular 10

新的ABP Angular UI基于Angular 10和TypeScript 3.9,我们已经放弃了对Angular 8的支持. 不过ABP模块将继续与Angular 9兼容使用. 因此如果你的项目是Angular 9,则无需更新为 Angular10. 更新通常很容易.

如何迁移?

在你的根文件夹中打开一个终端,然后运行以下命令:

  1. yarn ng update @angular/cli @angular/core --force

这会做如下修改:

  • 更新你的package.json并安装新的软件包
  • 修改tsconfig.json文件创建一个”Solution Style”配置
  • 重命名 browserlist.browserlistrc

另一方面,如果你单独使用 yarn ng update 命令检查首先要更新哪些包会更好. Angular会给你一个要更新的包列表.

Table of packages to update

当Angular报告上面的包后,运行命令:

  1. yarn ng update @angular/cli @angular/core ng-zorro-antd --force

如果Angular提示你的仓库有中未提交的更改,可以提交/存储它,也可以在命令中添加 --allow-dirty 参数.

配置模块

在ABP v2.x中,每个延迟加载的模块都有一个可通过单独的程序包使用的配置模块,模块配置如下:

  1. import { AccountConfigModule } from '@abp/ng.account.config';
  2. @NgModule({
  3. imports: [
  4. // other imports
  5. AccountConfigModule.forRoot({ redirectUrl: '/' }),
  6. ],
  7. // providers, declarations, and bootstrap
  8. })
  9. export class AppModule {}

…在app-routing.module.ts…

  1. const routes: Routes = [
  2. // other route configuration
  3. {
  4. path: 'account',
  5. loadChildren: () => import(
  6. './lazy-libs/account-wrapper.module'
  7. ).then(m => m.AccountWrapperModule),
  8. },
  9. ];

虽然有效,但有一些缺点:

  • 每个模块都有两个独立的程序包,但实际上这些程序包是相互依赖的.
  • 配置延迟加载的模块需要包装模块.
  • ABP Commercial具有可扩展性系统,在根模块上配置可扩展模块会增加 bundle 的大小.

在ABP v3.0中,我们为每个配置模块引入了辅助入口点,并且提供了一种在没有包装的情况下配置延迟加载的模块的新方法. 现在模块配置如下所示:

  1. import { AccountConfigModule } from '@abp/ng.account/config';
  2. @NgModule({
  3. imports: [
  4. // other imports
  5. AccountConfigModule.forRoot(),
  6. ],
  7. // providers, declarations, and bootstrap
  8. })
  9. export class AppModule {}

… 在app-routing.module.ts…

  1. const routes: Routes = [
  2. // other route configuration
  3. {
  4. path: 'account',
  5. loadChildren: () => import('@abp/ng.account')
  6. .then(m => m.AccountModule.forLazy({ redirectUrl: '/' })),
  7. },
  8. ];

这项更改帮助我们减少了捆绑包的大小并大大缩短了构建时间. 我们相信你会注意到你的应用程序有所不同.

一个更好的例子

AppModule:

  1. import { AccountConfigModule } from '@abp/ng.account/config';
  2. import { CoreModule } from '@abp/ng.core';
  3. import { IdentityConfigModule } from '@abp/ng.identity/config';
  4. import { SettingManagementConfigModule } from '@abp/ng.setting-management/config';
  5. import { TenantManagementConfigModule } from '@abp/ng.tenant-management/config';
  6. import { ThemeBasicModule } from '@abp/ng.theme.basic';
  7. import { ThemeSharedModule } from '@abp/ng.theme.shared';
  8. import { NgModule } from '@angular/core';
  9. import { BrowserModule } from '@angular/platform-browser';
  10. import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
  11. import { NgxsModule } from '@ngxs/store';
  12. import { environment } from '../environments/environment';
  13. import { AppRoutingModule } from './app-routing.module';
  14. @NgModule({
  15. imports: [
  16. BrowserModule,
  17. BrowserAnimationsModule,
  18. AppRoutingModule,
  19. CoreModule.forRoot({
  20. environment,
  21. sendNullsAsQueryParam: false,
  22. skipGetAppConfiguration: false,
  23. }),
  24. ThemeSharedModule.forRoot(),
  25. AccountConfigModule.forRoot(),
  26. IdentityConfigModule.forRoot(),
  27. TenantManagementConfigModule.forRoot(),
  28. SettingManagementConfigModule.forRoot(),
  29. ThemeBasicModule.forRoot(),
  30. NgxsModule.forRoot(),
  31. ],
  32. // providers, declarations, and bootstrap
  33. })
  34. export class AppModule {}

AppRoutingModule:

  1. import { DynamicLayoutComponent } from '@abp/ng.core';
  2. import { NgModule } from '@angular/core';
  3. import { RouterModule, Routes } from '@angular/router';
  4. const routes: Routes = [
  5. {
  6. path: '',
  7. component: DynamicLayoutComponent,
  8. children: [
  9. {
  10. path: '',
  11. pathMatch: 'full',
  12. loadChildren: () => import('./home/home.module')
  13. .then(m => m.HomeModule),
  14. },
  15. {
  16. path: 'account',
  17. loadChildren: () => import('@abp/ng.account')
  18. .then(m => m.AccountModule.forLazy({ redirectUrl: '/' })),
  19. },
  20. {
  21. path: 'identity',
  22. loadChildren: () => import('@abp/ng.identity')
  23. .then(m => m.IdentityModule.forLazy()),
  24. },
  25. {
  26. path: 'tenant-management',
  27. loadChildren: () => import('@abp/ng.tenant-management')
  28. .then(m => m.TenantManagementModule.forLazy()),
  29. },
  30. {
  31. path: 'setting-management',
  32. loadChildren: () => import('@abp/ng.setting-management')
  33. .then(m => m.SettingManagementModule.forLazy()),
  34. },
  35. ],
  36. },
  37. ];
  38. @NgModule({
  39. imports: [RouterModule.forRoot(routes)],
  40. exports: [RouterModule],
  41. })
  42. export class AppRoutingModule {}

你可能已经注意到我们在top级别路由组件上使用了 DynamicLayoutComponent. 我们这样做是为了避免不必要的渲染和闪烁. 这不是强制的,但是我们建议在你的应用程序路由中做同样的事情.

如何迁移?

  • 使用 yarn remove 删除你的项目的配置包.
  • 从辅助入口点(例如@abp/ng.identity/config)导入配置模块.
  • 调用所有新配置模块的静态 forRoot方法,即使配置没有被传递.
  • 调用 ThemeBasicModule 的静态 forRoot 方法(或商业上的 ThemeLeptonModule),并从导入中删除 SharedModule(除非已在其中添加了根模块所需的任何内容).
  • 在app路由模块中直接导入延迟ABP模块 (如 () => import('@abp/ng.identity').then(...)).
  • 在所有延迟模块 then 中调用的静态 forLazy 方法,即使配置没有被传递.
  • [可选]使用 DynamicLayoutComponent 添加空的父路由,获得更好的性能和UX.

RoutesService

在ABP v2.x中,通过以下两种方式之一将路由添加到菜单:

从v3.0开始,我们更改了添加和修改路由的方式. 我们不再将路由存储在 ConfigState中(破坏性更改). 而是有一个名为 RoutesService 的新服务,该服务用于添加,修补或删除菜单项. 详情请查看文档.

如何迁移?

  • 检查你是否曾经使用 ConfigStateConfigStateService 添加路由. 请用 RoutesServiceadd 方法替换它们.
  • 检查你是否曾经修补的路由. 将其替换为 RoutesServicepatch 方法.
  • 仔细检查你是否使用绝对路径,并在 addpatch 方法调用中为子菜单项提供 parentName 而不是 children 属性.

NavItemsService

在ABP v2.x中,通过LayoutStateService添加导航元素.

从v3.0开始,我们改变了添加和修改导航项的方式,以前的方法不再可用(破坏性更改). 详情请查看文档.

如何迁移?

  • NavItemsServiceaddItems 方法替换所有 dispatchAddNavigationElement 调用.

ngx-datatable

在v3之前,我们一直使用自定义组件 abp-table 作为默认表. 但是数据表是复杂的组件,要实现功能齐全的数据表需要大量的精力,我们计划将其引入其他功能.

从ABP v3开始,我们已切换到经过严格测试,执行良好的数据表格:ngx-datatable. 所有的ABP模块都已经实现了ngx-datatable. ThemeSharedModule 已经导出了 NgxDatatableModule. 因此如果你在终端运行 yarn add @swimlane/ngx-datatable 来安装这个包,它将在你的应用的所有模块中可用.

为了正确设置样式,你需要在angular.json文件的样式部分中添加以下内容:

  1. {
  2. "input": "node_modules/@swimlane/ngx-datatable/index.css",
  3. "inject": true,
  4. "bundleName": "ngx-datatable-index"
  5. },
  6. {
  7. "input": "node_modules/@swimlane/ngx-datatable/assets/icons.css",
  8. "inject": true,
  9. "bundleName": "ngx-datatable-icons"
  10. },
  11. {
  12. "input": "node_modules/@swimlane/ngx-datatable/themes/material.css",
  13. "inject": true,
  14. "bundleName": "ngx-datatable-material"
  15. }

由于尚未删除 abp-table, 因此以前由ABP v2.x构建的模块不会突然丢失所有. 但是它们的外观与内置ABP v3模块有所不同, 因此你可能希望将这些模块中的表转换为ngx-datatable. 为了减少将abp-table转换为ngx-datatable所需的工作量,我们修改了 ListService 以使其与 ngx-datatable 一起很好地工作,并引入了两个新指令: NgxDatatableListDirectiveNgxDatatableDefaultDirective.

这些指令的用法很简单:

  1. @Component({
  2. providers: [ListService],
  3. })
  4. export class SomeComponent {
  5. data$ = this.list.hookToQuery(
  6. query => this.dataService.get(query)
  7. );
  8. constructor(
  9. public readonly list: ListService,
  10. public readonly dataService: SomeDataService,
  11. ) {}
  12. }

…在组件模板…

  1. <ngx-datatable
  2. [rows]="(data$ | async)?.items || []"
  3. [count]="(data$ | async)?.totalCount || 0"
  4. [list]="list"
  5. default
  6. >
  7. <!-- column templates here -->
  8. </ngx-datatable>

通过 NgxDatatableListDirective 绑定注入的 ListService 实例后,你不再需要担心分页或排序. 同样 NgxDatatableDefaultDirective 去除了几个属性绑定,以使ngx-datatable适合我们的样式.

一个更好的例子

  1. <ngx-datatable
  2. [rows]="items"
  3. [count]="count"
  4. [list]="list"
  5. default
  6. >
  7. <!-- the grid actions column -->
  8. <ngx-datatable-column
  9. name=""
  10. [maxWidth]="150"
  11. [width]="150"
  12. [sortable]="false"
  13. >
  14. <ng-template
  15. ngx-datatable-cell-template
  16. let-row="row"
  17. let-i="rowIndex"
  18. >
  19. <abp-grid-actions
  20. [index]="i"
  21. [record]="row"
  22. text="AbpUi::Actions"
  23. ></abp-grid-actions>
  24. </ng-template>
  25. </ngx-datatable-column>
  26. <!-- a basic column -->
  27. <ngx-datatable-column
  28. prop="someProp"
  29. [name]="'::SomeProp' | abpLocalization"
  30. [width]="200"
  31. ></ngx-datatable-column>
  32. <!-- a column with a custom template -->
  33. <ngx-datatable-column
  34. prop="someOtherProp"
  35. [name]="'::SomeOtherProp' | abpLocalization"
  36. [width]="250"
  37. >
  38. <ng-template
  39. ngx-datatable-cell-template
  40. let-row="row"
  41. let-i="index"
  42. >
  43. <div abpEllipsis>{{ row.someOtherProp }}</div>
  44. </ng-template>
  45. </ngx-datatable-column>
  46. </ngx-datatable>

如何迁移?

  • 安装 @swimlane/ngx-datatable 包.
  • 添加ngx-datatable样式到angular.json文件.
  • 如果可以的话,根据上面的例子更新你的模.
  • 如果你稍后需要这样做,并且打算保留abp-table一段时间,请确保根据此处描述的破坏性更改更新分页.

**重要说明:**abp-table没有被删除,但已被弃用并在以后的版本中删除. 请考虑切换到ngx-datatable。

过时的接口

某些接口早已被标记为已弃用,现在已将其删除.

如何迁移?

请检查你是否仍在使用Issue中列出的任何内容.

下一步是什么?