mirror of https://github.com/abpframework/abp.git
3 changed files with 342 additions and 0 deletions
@ -0,0 +1,338 @@ |
|||
# v2.9 to v3.0 Angular UI Migration Guide |
|||
|
|||
## What Changed in v3.0? |
|||
|
|||
### Angular 10 |
|||
|
|||
The new ABP Angular UI is based on Angular 10 and TypeScript 3.9, and we have dropped support for Angular 8. Nevertheless, ABP modules will keep working with Angular 9. Therefore, if your project is Angular 9, you do not need to update to Angular 10. The update is usually very easy though. |
|||
|
|||
#### What to Do When Migrating? |
|||
|
|||
Open a terminal at your root folder and run the following command: |
|||
|
|||
```sh |
|||
yarn ng update @angular/cli @angular/core --force |
|||
``` |
|||
|
|||
This will make the following modifications: |
|||
|
|||
- Update your package.json and install new packages |
|||
- Revise tsconfig.json files to create a "Solution Style" configuration |
|||
- Rename `browserlist` as `.browserlistrc` |
|||
|
|||
On the other hand, it would be better if you check which packages to update first with `yarn ng update` command alone. Angular will give you a list of packages to update. |
|||
|
|||
 |
|||
|
|||
When Angular reports the packages above, your command would look like this: |
|||
|
|||
```sh |
|||
yarn ng update @angular/cli @angular/core ng-zorro-antd --force |
|||
``` |
|||
|
|||
> If Angular complains about uncommited changes in your repo, you can either commit/stash them or add `--allow-dirty` parameter to the command. |
|||
|
|||
|
|||
### Config Modules |
|||
|
|||
In ABP v2.x, every lazy loaded module had a config module available via a separate package and module configuration was as follows: |
|||
|
|||
```ts |
|||
import { AccountConfigModule } from '@abp/ng.account.config'; |
|||
|
|||
@NgModule({ |
|||
imports: [ |
|||
// other imports |
|||
AccountConfigModule.forRoot({ redirectUrl: '/' }), |
|||
], |
|||
// providers, declarations, and bootstrap |
|||
}) |
|||
export class AppModule {} |
|||
``` |
|||
|
|||
...and in app-routing.module.ts... |
|||
|
|||
```ts |
|||
const routes: Routes = [ |
|||
// other route configuration |
|||
{ |
|||
path: 'account', |
|||
loadChildren: () => import( |
|||
'./lazy-libs/account-wrapper.module' |
|||
).then(m => m.AccountWrapperModule), |
|||
}, |
|||
]; |
|||
``` |
|||
|
|||
Although working, this had a few disadvantages: |
|||
|
|||
- Every module came in two independent packages, but in reality, those packages were interdependent. |
|||
- Configuring lazy loaded modules required a wrapper module. |
|||
- ABP Commercial had extensibility system and configuring extensible modules at the root module was increasing the bundle size. |
|||
|
|||
In ABP v3.0, we have introduced a secondary entry points for each config module as well as a new way to configure lazy loaded modules without the wrappers. Now, the module configuration looks like this: |
|||
|
|||
```ts |
|||
import { AccountConfigModule } from '@abp/ng.account/config'; |
|||
|
|||
@NgModule({ |
|||
imports: [ |
|||
// other imports |
|||
AccountConfigModule.forRoot(), |
|||
], |
|||
// providers, declarations, and bootstrap |
|||
}) |
|||
export class AppModule {} |
|||
``` |
|||
|
|||
...and in app-routing.module.ts... |
|||
|
|||
```ts |
|||
const routes: Routes = [ |
|||
// other route configuration |
|||
{ |
|||
path: 'account', |
|||
loadChildren: () => import('@abp/ng.account') |
|||
.then(m => m.AccountModule.forLazy({ redirectUrl: '/' })), |
|||
}, |
|||
]; |
|||
``` |
|||
|
|||
This change helped us reduce bundle size and build times substantially. We believe you will notice the difference in your apps. |
|||
|
|||
#### A Better Example |
|||
|
|||
AppModule: |
|||
|
|||
```ts |
|||
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, and bootstrap |
|||
}) |
|||
export class AppModule {} |
|||
``` |
|||
|
|||
AppRoutingModule: |
|||
|
|||
```ts |
|||
import { DynamicLayoutComponent } from '@abp/ng.core'; |
|||
import { NgModule } from '@angular/core'; |
|||
import { RouterModule, Routes } from '@angular/router'; |
|||
|
|||
const routes: Routes = [ |
|||
{ |
|||
path: '', |
|||
component: DynamicLayoutComponent, |
|||
children: [ |
|||
{ |
|||
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 {} |
|||
``` |
|||
|
|||
> You may have noticed that we used `DynamicLayoutComponent` at top level route component. We made this change in order to avoid unnecessary renders and flickering. It is not mandatory, but we recommend doing the same in your app routing. |
|||
|
|||
#### What to Do When Migrating? |
|||
|
|||
- Remove config packages from your project using `yarn remove`. |
|||
- Import config modules from secondary entry points (e.g. `@abp/ng.identity/config`). |
|||
- Call static `forRoot` method of all new config modules, even if a configuration is not passed. |
|||
- Import lazy ABP modules directly in app routing module (e.g. `() => import('@abp/ng.identity').then(...)`). |
|||
- Call static `forLazy` method of all lazy modules inside `then`, even if a configuration is not passed. |
|||
- [OPTIONAL] Add an empty parent route with `DynamicLayoutComponent` for better performance and UX. |
|||
|
|||
|
|||
### RoutesService |
|||
|
|||
In ABP v2.x, adding a route to the menu was done by one of two ways: |
|||
|
|||
- [Via `routes` Property in `AppRoutingModule`](https://docs.abp.io/en/abp/2.9.0/UI/Angular/Modifying-the-Menu#via-routes-property-in-approutingmodule) |
|||
- [Via ConfigState](https://docs.abp.io/en/abp/2.9.0/UI/Angular/Modifying-the-Menu#via-configstate) |
|||
|
|||
As of v3.0, we have changed how routes are added and modified. We are no longer storing routes in `ConfigState` (breaking change). Instead, there is a new service called `RoutesService` which is used for adding, patching, or removing menu items. Please check [the documentation](./Modifying-the-Menu.md) for details. |
|||
|
|||
#### What to Do When Migrating? |
|||
|
|||
- Check if you have ever used `ConfigState` or `ConfigStateService` to add any routes. If so, replace them with `add` method of `RoutesService`. |
|||
- Check if you have ever patched a route. If so, convert them to `patch` method of `RoutesService`. |
|||
- Double-check that you are using absolute paths and providing a `parentName` instead of `children` property for sub-menu items in `add` or `patch` method calls. |
|||
|
|||
|
|||
### NavItemsService |
|||
|
|||
In ABP v2.x, adding a navigation element was done [via LayoutStateService](https://docs.abp.io/en/abp/2.9.0/UI/Angular/Modifying-the-Menu#how-to-add-an-element-to-right-part-of-the-menu) |
|||
|
|||
As of v3.0, we have changed how navigation items are added and modified and previous method of doing so is no longer available (breaking change). Please check [the documentation](./Modifying-the-Menu.md) for details. |
|||
|
|||
#### What to Do When Migrating? |
|||
|
|||
- Replace all `dispatchAddNavigationElement` calls with `addItems` method of `NavItemsService`. |
|||
|
|||
|
|||
### ngx-datatable |
|||
|
|||
Until v3, we had been using a custom component, `abp-table`, as the default table. However, data grids are complicated components and implementing a fully-featured one requires considerable effort, which we are planning to put in to other features and issues. |
|||
|
|||
As of ABP v3, we have switched to a battle-tested, well-executed data grid: [ngx-datatable](https://github.com/swimlane/ngx-datatable). All ABP modules will come with ngx-datatable already implemented in them. `ThemeSharedModule` already exports `NgxDatatableModule`. So, if you install the package by running `yarn add @swimlane/ngx-datatable` in your terminal, it will be available for use in all modules of your app. |
|||
|
|||
Since `abp-table` is not dropped yet, modules previously built by ABP v2.x will not suddenly lose all their tables. Yet, they will look and feel different from built-in ABP v3 modules. Therefore, you will probably want to convert the tables in those modules to ngx-datatable. In order to decrease the amount of work required to convert an abp-table into ngx-datatable, we have modified the [ListService](./List-Service.md) to work well with ngx-datatable and [introduced](https://volosoft.com/blog/attribute-directives-to-avoid-repetition-in-angular-templates) two new directives: `NgxDatatableListDirective` and `NgxDatatableDefaultDirective`. |
|||
|
|||
The usage of those directives is rather simple: |
|||
|
|||
```ts |
|||
@Component({ |
|||
providers: [ListService], |
|||
}) |
|||
export class SomeComponent { |
|||
data$ = this.list.hookToQuery( |
|||
query => this.dataService.get(query) |
|||
); |
|||
|
|||
constructor( |
|||
public readonly list: ListService, |
|||
public readonly dataService: SomeDataService, |
|||
) {} |
|||
} |
|||
``` |
|||
|
|||
...and in component template... |
|||
|
|||
```html |
|||
<ngx-datatable |
|||
[rows]="(data$ | async)?.items || []" |
|||
[count]="(data$ | async)?.totalCount || 0" |
|||
[list]="list" |
|||
default |
|||
> |
|||
<!-- column templates here --> |
|||
</ngx-datatable> |
|||
``` |
|||
|
|||
Once you bind the injected `ListService` instance through `NgxDatatableListDirective`, you no longer need to worry about pagination or sorting. Similarly, `NgxDatatableDefaultDirective` gets rid of several property bindings to make ngx-datatable fit our styles. |
|||
|
|||
#### A Better Example |
|||
|
|||
```html |
|||
<ngx-datatable |
|||
[rows]="items" |
|||
[count]="count" |
|||
[list]="list" |
|||
default |
|||
> |
|||
<!-- the grid actions column --> |
|||
<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> |
|||
|
|||
<!-- a basic column --> |
|||
<ngx-datatable-column |
|||
prop="someProp" |
|||
[name]="'::SomeProp' | abpLocalization" |
|||
[width]="200" |
|||
></ngx-datatable-column> |
|||
|
|||
<!-- a column with a custom template --> |
|||
<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> |
|||
``` |
|||
|
|||
#### What to Do When Migrating? |
|||
|
|||
- If you can, update your modules according to the example above. |
|||
- If you have to do that later and are planning to keep abp-table for a while, make sure you update your pagination according to the [breaking change described here](./List-Service.md). |
|||
|
|||
**Important Note:** The `abp-table` is not removed, but is deprecated and will be removed in the future. Please consider switching to ngx-datatable. |
|||
|
|||
|
|||
## What's Next? |
|||
|
|||
* [Service Proxies](./Service-Proxies.md) |
|||
|
|||
|
After Width: | Height: | Size: 152 KiB |
Loading…
Reference in new issue