v2.9 到 v3.0 Angular UI 迁移指南
v3.0 有哪些变化?
Angular 10
新的 ABP Angular UI 基于 Angular 10 和 TypeScript 3.9,我们已停止对 Angular 8 的支持。不过,ABP 模块将继续与 Angular 9 兼容。因此,如果您的项目使用 Angular 9,则无需升级到 Angular 10。但通常来说,升级过程非常简单。
迁移时需要做什么?
在根文件夹打开终端并运行以下命令:
yarn ng update @angular/cli @angular/core --force
这将进行以下修改:
- 更新您的 package.json 并安装新包
- 修改 tsconfig.json 文件以创建 "Solution Style" 配置
- 将
browserslist重命名为.browserslistrc
另一方面,如果先单独使用 yarn ng update 命令检查需要更新哪些包会更好。Angular 会给出一个需要更新的包列表。
当 Angular 报告以上包时,您的命令将如下所示:
yarn ng update @angular/cli @angular/core ng-zorro-antd --force
如果 Angular 提示您的仓库中有未提交的更改,您可以提交/存储这些更改,或者在命令中添加
--allow-dirty参数。
配置模块
在 ABP v2.x 中,每个延迟加载模块都有一个可通过独立包使用的配置模块,模块配置如下:
import { AccountConfigModule } from '@abp/ng.account.config';
@NgModule({
imports: [
// 其他导入
AccountConfigModule.forRoot({ redirectUrl: '/' }),
],
// providers, declarations, 和 bootstrap
})
export class AppModule {}
... 以及在 app-routing.module.ts 中 ...
const routes: Routes = [
// 其他路由配置
{
path: 'account',
loadChildren: () => import(
'./lazy-libs/account-wrapper.module'
).then(m => m.AccountWrapperModule),
},
];
这种方式虽然可行,但有一些缺点:
- 每个模块都包含在两个独立的包中,但实际上这些包是相互依赖的。
- 配置延迟加载模块需要一个包装器模块。
- ABP 拥有可扩展系统,在根模块配置可扩展模块会增加包体积。
在 ABP v3.0 中,我们为每个配置模块引入了次要入口点,以及一种无需包装器即可配置延迟加载模块的新方法。现在,模块配置如下所示:
import { AccountConfigModule } from '@abp/ng.account/config';
@NgModule({
imports: [
// 其他导入
AccountConfigModule.forRoot(),
],
// providers, declarations, 和 bootstrap
})
export class AppModule {}
... 以及在 app-routing.module.ts 中 ...
const routes: Routes = [
// 其他路由配置
{
path: 'account',
loadChildren: () => import('@abp/ng.account')
.then(m => m.AccountModule.forLazy({ redirectUrl: '/' })),
},
];
此项更改帮助我们显著减少了包体积和构建时间。我们相信您会在您的应用程序中感受到差异。
一个更好的例子
AppModule:
import { AccountConfigModule } from '@abp/ng.account/config';
import { CoreModule } from '@abp/ng.core';
import { IdentityConfigModule } from '@abp/ng.identity/config';
import { SettingManagementConfigModule } from '@abp/ng.setting-management/config';
import { TenantManagementConfigModule } from '@abp/ng.tenant-management/config';
import { ThemeBasicModule } from '@abp/ng.theme.basic';
import { ThemeSharedModule } from '@abp/ng.theme.shared';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgxsModule } from '@ngxs/store';
import { environment } from '../environments/environment';
import { AppRoutingModule } from './app-routing.module';
@NgModule({
imports: [
BrowserModule,
BrowserAnimationsModule,
AppRoutingModule,
CoreModule.forRoot({
environment,
sendNullsAsQueryParam: false,
skipGetAppConfiguration: false,
}),
ThemeSharedModule.forRoot(),
AccountConfigModule.forRoot(),
IdentityConfigModule.forRoot(),
TenantManagementConfigModule.forRoot(),
SettingManagementConfigModule.forRoot(),
ThemeBasicModule.forRoot(),
NgxsModule.forRoot(),
],
// providers, declarations, 和 bootstrap
})
export class AppModule {}
AppRoutingModule:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
const routes: Routes = [
{
path: '',
pathMatch: 'full',
loadChildren: () => import('./home/home.module').then(m => m.HomeModule),
},
{
path: 'account',
loadChildren: () =>
import('@abp/ng.account').then(m => m.AccountModule.forLazy({ redirectUrl: '/' })),
},
{
path: 'identity',
loadChildren: () => import('@abp/ng.identity').then(m => m.IdentityModule.forLazy()),
},
{
path: 'tenant-management',
loadChildren: () =>
import('@abp/ng.tenant-management').then(m => m.TenantManagementModule.forLazy()),
},
{
path: 'setting-management',
loadChildren: () =>
import('@abp/ng.setting-management').then(m => m.SettingManagementModule.forLazy()),
},
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}
AppComponent:
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<abp-loader-bar></abp-loader-bar>
<abp-dynamic-layout></abp-dynamic-layout>
`,
})
export class AppComponent {}
您可能已经注意到,我们在 AppComponent 模板中使用了
<abp-dynamic-layout>而不是<router-outlet>。我们进行此更改是为了避免不必要的渲染和闪烁。这不是强制性的,但我们建议在您的 AppComponent 中执行相同的操作。
迁移时需要做什么?
- 使用
yarn remove从项目中移除配置包。 - 从次要入口点导入配置模块(例如
@abp/ng.identity/config)。 - 调用所有新配置模块的静态
forRoot方法,即使不传递配置。 - 调用
ThemeBasicModule(如果是商业版则调用ThemeLeptonModule)的静态forRoot方法,并从 imports 中移除SharedModule(除非您在其中添加了根模块必需的内容)。 - 在应用路由模块中直接导入延迟加载的 ABP 模块(例如
() => import('@abp/ng.identity').then(...))。 - 在
then内部调用所有延迟加载模块的静态forLazy方法,即使不传递配置。 - [可选] 在 AppComponent 模板中添加
<abp-dynamic-layout></abp-dynamic-layout>并移除<router-outlet></router-outlet>以获得更好的性能和用户体验。
RoutesService
在 ABP v2.x 中,向菜单添加路由有两种方式:
从 v3.0 开始,我们改变了添加和修改路由的方式。我们不再将路由存储在 ConfigState 中(破坏性变更)。取而代之的是一个名为 RoutesService 的新服务,用于添加、修补或删除菜单项。请查看文档了解详情。
迁移时需要做什么?
- 检查您是否曾经使用
ConfigState或ConfigStateService来添加任何路由。如果是,请将其替换为RoutesService的add方法。 - 检查您是否曾经修补过路由。如果是,请将其转换为
RoutesService的patch方法。 - 仔细检查在
add或patch方法调用中,是否为子菜单项使用了绝对路径并提供了parentName而不是children属性。
NavItemsService
在 ABP v2.x 中,添加导航元素是通过 LayoutStateService 完成的。
从 v3.0 开始,我们改变了添加和修改导航项的方式,之前的方法不再可用(破坏性变更)。请查看文档了解详情。
迁移时需要做什么?
- 将所有
dispatchAddNavigationElement调用替换为NavItemsService的addItems方法。
ngx-datatable
直到 v3 版本之前,我们一直使用一个自定义组件 abp-table 作为默认表格。然而,数据网格是复杂的组件,实现一个功能齐全的组件需要大量精力,而我们计划将这些精力投入到其他功能和问题上。
从 ABP v3 开始,我们切换到了一个经过实战检验、实现良好的数据网格:ngx-datatable。所有 ABP 模块都将附带已经实现好的 ngx-datatable。ThemeSharedModule 已经导出了 NgxDatatableModule。因此,如果您通过在终端中运行 yarn add @swimlane/ngx-datatable 来安装该包,它将在您应用的所有模块中可用。
为了正确的样式,您需要在 angular.json 文件的 styles 部分添加以下内容(在所有其他样式之上):
"styles": [
{
"input": "node_modules/@swimlane/ngx-datatable/index.css",
"inject": true,
"bundleName": "ngx-datatable-index"
},
{
"input": "node_modules/@swimlane/ngx-datatable/assets/icons.css",
"inject": true,
"bundleName": "ngx-datatable-icons"
},
{
"input": "node_modules/@swimlane/ngx-datatable/themes/material.css",
"inject": true,
"bundleName": "ngx-datatable-material"
},
// 其他样式
]
由于 abp-table 尚未被弃用,之前由 ABP v2.x 构建的模块不会突然失去所有表格。然而,它们的外观和感觉将与内置的 ABP v3 模块不同。因此,您可能希望将这些模块中的表格转换为 ngx-datatable。为了减少将 abp-table 转换为 ngx-datatable 所需的工作量,我们修改了 ListService 以更好地与 ngx-datatable 配合工作,并引入了两个新指令:NgxDatatableListDirective 和 NgxDatatableDefaultDirective。
这些指令的使用相当简单:
@Component({
providers: [ListService],
})
export class SomeComponent {
data$ = this.list.hookToQuery(
query => this.dataService.get(query)
);
constructor(
public readonly list: ListService,
public readonly dataService: SomeDataService,
) {}
}
... 以及在组件模板中 ...
<ngx-datatable
[rows]="(data$ | async)?.items || []"
[count]="(data$ | async)?.totalCount || 0"
[list]="list"
default
>
<!-- 列模板放在这里 -->
</ngx-datatable>
一旦您通过 NgxDatatableListDirective 绑定了注入的 ListService 实例,您就不再需要担心分页或排序。同样,NgxDatatableDefaultDirective 消除了多个属性绑定,使 ngx-datatable 适应我们的样式。
一个更好的例子
<ngx-datatable
[rows]="items"
[count]="count"
[list]="list"
default
>
<!-- 网格操作列 -->
<ngx-datatable-column
name=""
[maxWidth]="150"
[width]="150"
[sortable]="false"
>
<ng-template
ngx-datatable-cell-template
let-row="row"
let-i="rowIndex"
>
<abp-grid-actions
[index]="i"
[record]="row"
text="AbpUi::Actions"
></abp-grid-actions>
</ng-template>
</ngx-datatable-column>
<!-- 基础列 -->
<ngx-datatable-column
prop="someProp"
[name]="'::SomeProp' | abpLocalization"
[width]="200"
></ngx-datatable-column>
<!-- 具有自定义模板的列 -->
<ngx-datatable-column
prop="someOtherProp"
[name]="'::SomeOtherProp' | abpLocalization"
[width]="250"
>
<ng-template
ngx-datatable-cell-template
let-row="row"
let-i="index"
>
<div abpEllipsis>{{ row.someOtherProp }}</div>
</ng-template>
</ngx-datatable-column>
</ngx-datatable>
迁移时需要做什么?
- 安装
@swimlane/ngx-datatable包。 - 在 angular.json 文件中添加 ngx-datatable 样式。
- 如果可能,请根据上面的示例更新您的模块。
- 如果您必须稍后再进行此操作,并计划暂时保留 abp-table,请确保根据此处描述的破坏性变更更新您的分页逻辑。
重要提示: abp-table 尚未被移除,但已被弃用,并将在未来移除。请考虑切换到 ngx-datatable。
扩展系统 [商业版]
扩展系统现已开源,公开可从 @abp/ng.theme.shared/extensions 包获取,而不是 @volo/abp.commercial.ng.ui。此外,根据配置包的新结构,配置是通过上述的 forLazy 静态方法提供的。
迁移时需要做什么?
如果您以前从未使用过扩展系统,则无需执行任何操作。如果您使用过,请再次查看文档以了解发生了哪些变化。扩展系统本身的工作方式与以前相同。唯一的变化是导入的包,以及传递贡献者的静态方法和模块。
Lepton 主题徽标 [商业版]
在 ABP v2.x 中,Lepton 每个颜色主题都有一个浅色和一个深色徽标。我们意识到我们可以让它只使用一个浅色和一个深色徽标。因此,我们改变了 Lepton 查找徽标图像的方式,现在您只需要在项目中有一个 logo-light.png 和一个 logo-dark.png。
迁移时需要做什么?
如果您之前切换过模板徽标 PNG,更改很简单:
- 转到
/assets/images/logo文件夹。 - 将
theme1.png重命名为logo-light.png,将theme1-reverse.png重命名为logo-dark.png。 - 删除所有其他的
theme*.png文件。
如果您替换了徽标组件,更改略有不同,但仍然很简单。LayoutStateService 有两个新成员:primaryLogoColor 和 secondaryLogoColor。它们具有值为 'light' 和 'dark' 字符串的可观察流。您可以在自定义徽标组件模板中使用 async 管道使用它们的值。这是一个涵盖主布局和次要(账户)布局徽标的完整示例。
import { AddReplaceableComponent } from '@abp/ng.core';
import { CommonModule } from '@angular/common';
import { APP_INITIALIZER, Component, Injector, NgModule } from '@angular/core';
import { Store } from '@ngxs/store';
import { eAccountComponents } from '@volo/abp.ng.account';
import {
AccountLayoutComponent,
eThemeLeptonComponents,
LayoutStateService,
} from '@volo/abp.ng.theme.lepton';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Component({
template: `
<div class="account-brand p-4 text-center mb-1" *ngIf="isAccount; else link">
<ng-template [ngTemplateOutlet]="link"></ng-template>
</div>
<ng-template #link>
<a [style.background-image]="logoUrl | async" class="navbar-brand" routerLink="/"></a>
</ng-template>
`,
})
export class LogoComponent {
isAccount: boolean;
logoColor: Observable<'dark' | 'light'>;
get logoUrl() {
return this.logoColor.pipe(map(color => `url(/assets/images/logo/logo-${color}.png)`));
}
constructor(injector: Injector) {
const layout = injector.get(LayoutStateService);
this.isAccount = Boolean(injector.get(AccountLayoutComponent, false));
this.logoColor = this.isAccount ? layout.secondaryLogoColor : layout.primaryLogoColor;
}
}
@NgModule({
imports: [CommonModule],
declarations: [LogoComponent],
exports: [LogoComponent],
})
export class LogoModule {}
export const APP_LOGO_PROVIDER = [
{ provide: APP_INITIALIZER, useFactory: switchLogos, multi: true, deps: [Store] },
];
export function switchLogos(store: Store) {
return () => {
store.dispatch(
new AddReplaceableComponent({
component: LogoComponent,
key: eThemeLeptonComponents.Logo,
}),
);
store.dispatch(
new AddReplaceableComponent({
component: LogoComponent,
key: eAccountComponents.Logo,
}),
);
};
}
只需将 APP_LOGO_PROVIDER 添加到根模块(通常是 AppModule)的 providers 中,您就会拥有一个能适应主题颜色的自定义徽标组件。
已弃用的接口
一些接口早已被标记为弃用,现在它们已被移除。
迁移时需要做什么?
- 请检查您是否仍在使用此 issue 中列出的任何内容
抠丁客



