|
After Width: | Height: | Size: 252 KiB |
|
After Width: | Height: | Size: 974 KiB |
|
After Width: | Height: | Size: 165 KiB |
|
After Width: | Height: | Size: 12 MiB |
|
After Width: | Height: | Size: 403 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 104 KiB |
@ -0,0 +1,225 @@ |
|||||
|
# Introducing ABP Low-Code: Build Real ABP Apps in Minutes |
||||
|
|
||||
|
**Create runtime-managed pages, generated React screens, code-first C# entities, and Script API extensions without leaving the ABP application model.** |
||||
|
|
||||
|
 |
||||
|
|
||||
|
> **Want to try the same path?** Start from ABP Studio, enable the Low-Code runtime and designer, define pages in the Admin Console, and see them resolve inside the running ABP app. |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## ABP Low-Code at a glance |
||||
|
|
||||
|
| Runtime authoring | Generated screens | ABP-native extensibility | One application model | |
||||
|
| :---: | :---: | :---: | :---: | |
||||
|
| Define and update pages in the Admin Console | Grid, Form, Calendar, Kanban, Gallery, Dashboard | Code-first entities, Script API actions, and C# query paths | Runtime metadata, generated UI, and application code stay together | |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## Built into the ABP Platform |
||||
|
|
||||
|
Low-code is most useful when speed does not create a separate stack to maintain later. |
||||
|
|
||||
|
That is where many low-code products start to strain. They move quickly at the beginning, then force a second implementation track when the app needs permissions, auditability, custom logic, or tighter integration with existing application code. |
||||
|
|
||||
|
ABP Low-Code takes a different path. It runs **inside the ABP Platform**, so runtime-managed pages are part of an application foundation that already includes identity, permissions, audit logging, APIs, and code-level extensibility. |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## Edit at runtime. See it in the app. |
||||
|
|
||||
|
In the Low-Code Designer, you update a runtime-managed page. A few seconds later, the same application surface is visible in the live app. No rebuild loop. No parallel front-end implementation. No "we will wire it later" gap between authoring and runtime. |
||||
|
|
||||
|
ABP Low-Code shortens the cycle from model change to running screen while keeping the output grounded in the same ABP application. |
||||
|
|
||||
|
 |
||||
|
|
||||
|
> **What this shows:** authoring and runtime are connected. Pages are defined in the designer and resolved in the running application. |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## CRUD is table stakes |
||||
|
|
||||
|
If low-code only saves you from drawing a table and a form, it is not enough. Business applications need richer operational surfaces. |
||||
|
|
||||
|
In the generated app, the `Events` screen ships with search, actions, filters, and form-driven editing. The form structure already understands tabs, relations, validation, and business-shaped input instead of leaving you with a blank shell to finish by hand. |
||||
|
|
||||
|
 |
||||
|
|
||||
|
The point is not just generated CRUD. It is generated CRUD that already looks like the operational screens teams maintain in real applications. |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## One model, multiple operational screens |
||||
|
|
||||
|
Business users do not think in one view. Operators want a calendar for scheduling, a kanban board for workflow, a grid for bulk operations, a gallery when media matters, and a dashboard when they need the state of the business at a glance. |
||||
|
|
||||
|
ABP Low-Code keeps those surfaces attached to the same underlying model. The same `Session` model can appear as a **calendar** for planning and a **kanban pipeline** for operational flow. The same generated app can also include a **speaker gallery** and an **overview dashboard** for metrics. |
||||
|
|
||||
|
 |
||||
|
|
||||
|
 |
||||
|
|
||||
|
This is where ABP Low-Code starts to feel less like a form generator and more like a runtime application layer: one model, many working screens, no second implementation track for each view type. |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## When low-code needs code |
||||
|
|
||||
|
The real differentiator is not that ABP Low-Code can go fast. It is that **speed does not require isolation from the application foundation**. |
||||
|
|
||||
|
When generated CRUD is not enough, you extend the same app instead of throwing the low-code layer away. |
||||
|
|
||||
|
ABP Low-Code exposes a server-side **Script API** inside the same application model. That scripting surface can back: |
||||
|
|
||||
|
- **Custom endpoints** when the UI needs an API-shaped response. |
||||
|
- **Interceptors** when create or update commands need validation or mutation. |
||||
|
- **Event handlers** when logic should react to runtime events. |
||||
|
- **Background jobs** when work should continue asynchronously. |
||||
|
- **Background workers** when operational logic should run on a schedule. |
||||
|
|
||||
|
In this article, the visible proof happens to be `GET /api/custom/eventflow/highlights`. The GIF shows an endpoint because it is the easiest proof surface to read. But the broader point is that endpoints are only one consumer of the same low-code scripting layer. |
||||
|
|
||||
|
That hybrid model matters in both directions: |
||||
|
|
||||
|
- **Code-first ABP entities can be surfaced in low-code flows and runtime pages.** |
||||
|
- **Low-code-managed data and screens stay reachable from Script API actions, application services, repository queries, and custom endpoints.** |
||||
|
- **Teams do not lose architectural control just because they gained a faster authoring layer.** |
||||
|
|
||||
|
 |
||||
|
|
||||
|
The actual capability is the shared ABP application model behind it: script when runtime logic is enough, C# when typed application services and repository queries are the better fit. |
||||
|
|
||||
|
This is the difference between "low-code as a shortcut" and "low-code as part of your application platform." |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## From code-first entity to generated page |
||||
|
|
||||
|
The first direction is code-first to low-code. A **code-first** `SponsorActivation` entity checked into the ASP.NET Core project can still become a working runtime page without forking into a separate low-code-only model. |
||||
|
|
||||
|
The code-first entity carries the same metadata that ABP Low-Code uses to generate the page: |
||||
|
|
||||
|
```csharp |
||||
|
[DynamicEntity(DefaultDisplayPropertyName = nameof(CompanyName))] |
||||
|
[DynamicEntityUI("Sponsor Activations")] |
||||
|
[DynamicEntityAttachments("application/pdf", "image/*", MaxFileCount = 4)] |
||||
|
public class SponsorActivation : DynamicEntityBase |
||||
|
{ |
||||
|
[Required] |
||||
|
[DynamicPropertyUI(DisplayName = "Sponsor")] |
||||
|
public string CompanyName { get; private set; } |
||||
|
|
||||
|
[Required] |
||||
|
[EmailAddress] |
||||
|
[DynamicPropertyUI(DisplayName = "Contact Email")] |
||||
|
public string ContactEmail { get; private set; } |
||||
|
|
||||
|
public SponsorActivationStatus Status { get; set; } |
||||
|
|
||||
|
[DynamicForeignKey("EventFlow.Events.Event", "Title")] |
||||
|
public Guid? EventId { get; set; } |
||||
|
|
||||
|
[DynamicForeignKey("Volo.Abp.Identity.IdentityUser", nameof(IdentityUser.UserName), ForeignAccess.View)] |
||||
|
public Guid? OwnerUserId { get; set; } |
||||
|
|
||||
|
[DynamicPropertyType(EntityPropertyType.Money)] |
||||
|
public decimal ActivationBudget { get; set; } |
||||
|
|
||||
|
[DynamicPropertyImageOptions("image/png", "image/jpeg")] |
||||
|
public string? BrandLogo { get; set; } |
||||
|
|
||||
|
[DynamicPropertyFileOptions("application/pdf", ".pptx", ".docx")] |
||||
|
public string? ActivationBrief { get; set; } |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
That class lives as normal C# source, gets migrated like the rest of the application, and is seeded with real records so the runtime page does not open as an empty shell. |
||||
|
|
||||
|
Inside the designer, selecting the `SponsorActivation` entity auto-generates the page identity, binds the grid to the entity, and lands on a real runtime route at `/dynamic/sponsor-activation`. The generated surface includes sponsor, email, event lookup, owner lookup, budget, image, and file fields directly from the C# model. |
||||
|
|
||||
|
 |
||||
|
|
||||
|
 |
||||
|
|
||||
|
That is the distinction that matters: code-first ABP entities can move through low-code without becoming throwaway artifacts, and low-code-generated surfaces remain part of the same application story. |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## Low-code data stays reachable from C# |
||||
|
|
||||
|
The bridge also works in the other direction. A normal ABP application service can query a low-code model through `IRepository<DynamicEntity, Guid>`, apply real filters, and combine that result with code-first aggregates. |
||||
|
|
||||
|
The service behind the endpoint in the previous section looks like this: |
||||
|
|
||||
|
```csharp |
||||
|
public async Task<EventFlowLowCodeProofDto> GetHybridSummaryAsync() |
||||
|
{ |
||||
|
var liveSessionQuery = (await _dynamicEntityRepository |
||||
|
.SetEntityName("EventFlow.Events.Session") |
||||
|
.GetQueryableAsync()) |
||||
|
.Where("int(it[\"Status\"]) == @0", 2); |
||||
|
|
||||
|
var publicSessionQuery = liveSessionQuery |
||||
|
.Where("bool(it[\"IsPublic\"]) == @0", true); |
||||
|
|
||||
|
var sponsorQuery = (await _sponsorActivationRepository.GetQueryableAsync()) |
||||
|
.Where(activation => |
||||
|
activation.Status == SponsorActivationStatus.Approved || |
||||
|
activation.Status == SponsorActivationStatus.Live); |
||||
|
|
||||
|
var liveSessionCount = await AsyncExecuter.CountAsync(liveSessionQuery); |
||||
|
var publicSessionCount = await AsyncExecuter.CountAsync(publicSessionQuery); |
||||
|
var activeSponsorActivationCount = await AsyncExecuter.CountAsync(sponsorQuery); |
||||
|
|
||||
|
return new EventFlowLowCodeProofDto |
||||
|
{ |
||||
|
LiveSessionCount = liveSessionCount, |
||||
|
PublicSessionCount = publicSessionCount, |
||||
|
ActiveSponsorActivationCount = activeSponsorActivationCount |
||||
|
}; |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
Here, low-code-managed `Session` rows are filtered from C# with real `Where(...)` clauses, then combined with the typed `SponsorActivation` repository. The endpoint and dashboard are just one presentation surface for that shared ABP query path. |
||||
|
|
||||
|
That is the ABP difference: low-code data stays reachable from code, and code-first entities stay reachable from low-code. |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## Why ABP Low-Code matters |
||||
|
|
||||
|
The value is not novelty. It is a faster way to build real business applications without separating speed from the application foundation. |
||||
|
|
||||
|
- **Speed without replatforming.** Runtime-managed screens reduce delivery time without moving the team onto a separate application stack. |
||||
|
- **Governance without friction.** Permissions, identity, auditability, and ABP platform foundations stay part of the story from day one. |
||||
|
- **Extensibility without rewrite pressure.** When custom behavior shows up, the same application can be extended instead of replacing the low-code output. |
||||
|
|
||||
|
That is the core ABP Low-Code promise: faster delivery, still inside the application model you can extend. |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## Try it yourself |
||||
|
|
||||
|
The public starting point for ABP Low-Code is **ABP Studio**. |
||||
|
|
||||
|
 |
||||
|
|
||||
|
1. Open **ABP Studio** and create a new solution. |
||||
|
2. In the solution wizard, enable **Include Low-Code runtime and designer**. |
||||
|
3. Complete the wizard, then run the generated backend and React UI from the solution. |
||||
|
4. Sign in with the administrator account created for that solution. |
||||
|
5. Open **Admin Console** to define runtime-managed entities, forms, pages, permissions, endpoints, and script actions. |
||||
|
6. Switch to the application side to see those changes resolve live in the running app. |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## Further reading |
||||
|
|
||||
|
- [ABP Low-Code Designer Documentation](https://abp.io/docs/latest/low-code/designer) |
||||
|
- [ABP Low-Code Configuration & Fluent API](https://abp.io/docs/latest/low-code/fluent-api) |
||||
|
- [ABP Low-Code Scripting API](https://abp.io/docs/latest/low-code/scripting-api) |
||||
|
- [ABP Low-Code Script Actions](https://abp.io/docs/latest/low-code/script-actions) |
||||
|
- [ABP Low-Code Interceptors](https://abp.io/docs/latest/low-code/interceptors) |
||||
|
- [ABP Studio Documentation](https://abp.io/docs/latest/studio) |
||||
|
- [Get Started with ABP: Creating a Layered Web Application](https://abp.io/docs/latest/get-started/layered-web-application) |
||||
@ -0,0 +1 @@ |
|||||
|
Discover how ABP Low-Code blends runtime page building with code-first entities, C# queries, and extensible application logic. |
||||
@ -0,0 +1,116 @@ |
|||||
|
```json |
||||
|
//[doc-seo] |
||||
|
{ |
||||
|
"Description": "Upgrade your ABP solutions to Angular version 22.0.x" |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
# Release Notes: Angular 22 and TypeScript 6 Upgrade |
||||
|
|
||||
|
## Overview |
||||
|
|
||||
|
This release updates ABP Angular UI applications to: |
||||
|
|
||||
|
* Angular `22.x` |
||||
|
* TypeScript `6.x` |
||||
|
|
||||
|
This upgrade aligns ABP projects with the latest Angular ecosystem and provides access to the newest framework improvements while ensuring long-term maintainability and support. |
||||
|
|
||||
|
## What's Changed |
||||
|
|
||||
|
### 1. Frontend Stack Upgrades |
||||
|
|
||||
|
The core frontend stack has been updated: |
||||
|
|
||||
|
* `@angular/*` packages have been upgraded to version 22 |
||||
|
* `typescript` has been upgraded to version 6 |
||||
|
* ABP and ABP Commercial npm packages must be upgraded to the corresponding ABP release line (version 10.6) |
||||
|
|
||||
|
### 2. Change Detection Behavior |
||||
|
|
||||
|
Angular 22 introduces updated change detection behavior. |
||||
|
|
||||
|
* Components without explicit change detection configuration now follow OnPush-style behavior by default |
||||
|
* Some existing pages may no longer update automatically after asynchronous operations |
||||
|
* UI state should be managed using Angular Signals or the `async` pipe where appropriate |
||||
|
|
||||
|
### 3. Stricter Type and Template Checks |
||||
|
|
||||
|
Angular 22 and TypeScript 6 introduce additional compile-time validations. |
||||
|
|
||||
|
* More template and type-related issues may be reported during builds |
||||
|
* Existing assumptions around nullable values and optional properties may require additional guards or type refinements |
||||
|
* Applications with strict template checking enabled may require code updates |
||||
|
|
||||
|
### 4. Upload Progress Handling |
||||
|
|
||||
|
Applications that rely on file upload progress events may require additional HTTP client configuration. |
||||
|
|
||||
|
* Browser-side HTTP configuration may need `withXhr()` enabled to ensure upload progress events are emitted correctly |
||||
|
|
||||
|
### 5. Chart Update Behavior |
||||
|
|
||||
|
Chart components may require additional updates when used with asynchronous data sources. |
||||
|
|
||||
|
* Under OnPush-style change detection, chart updates may not be detected automatically |
||||
|
* Consider using Signals for chart data bindings |
||||
|
* Calling `reinit()` after asynchronous data updates may be necessary in some scenarios |
||||
|
|
||||
|
## Required Actions |
||||
|
|
||||
|
### 1. Upgrade Related Packages Together |
||||
|
|
||||
|
Keep Angular, TypeScript, ABP, and ABP Commercial packages on compatible versions. |
||||
|
|
||||
|
To use Angular 22: |
||||
|
|
||||
|
* Angular: `22.x` |
||||
|
* TypeScript: `6.x` |
||||
|
* ABP Framework: `10.6.x` |
||||
|
|
||||
|
### 2. Review UI State Management |
||||
|
|
||||
|
Review pages that depend on asynchronous state updates, including: |
||||
|
|
||||
|
* List and table data |
||||
|
* Loading and busy indicators |
||||
|
* Modal dialog state |
||||
|
* Dashboard and chart data |
||||
|
|
||||
|
Consider migrating these scenarios to Angular Signals or the `async` pipe. |
||||
|
|
||||
|
### 3. Apply a Temporary TypeScript Compatibility Setting (If Needed) |
||||
|
|
||||
|
If your project uses `downlevelIteration`, you may temporarily add the following configuration: |
||||
|
|
||||
|
```json |
||||
|
{ |
||||
|
"ignoreDeprecations": "6.0" |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
This can help ease the migration process while addressing TypeScript 6 deprecation warnings. |
||||
|
|
||||
|
### 4. Perform Regression Testing |
||||
|
|
||||
|
We recommend validating all critical application flows after upgrading, including: |
||||
|
|
||||
|
* Authentication and account management pages |
||||
|
* CRUD list and detail pages |
||||
|
* Permission and feature management dialogs |
||||
|
* File upload workflows |
||||
|
* Dashboard and chart components |
||||
|
|
||||
|
## Areas to Validate Carefully |
||||
|
|
||||
|
Pay particular attention to the following scenarios: |
||||
|
|
||||
|
* Busy or loading indicators not updating correctly |
||||
|
* Modal open/close state inconsistencies |
||||
|
* List pages not refreshing after asynchronous operations |
||||
|
* Upload progress events not being emitted |
||||
|
* Charts rendering without data after API responses |
||||
|
|
||||
|
## References |
||||
|
|
||||
|
* Detailed migration guide: [Upgrade ABP to 10.6](../../../../release-info/migration-guides/abp-10-6-angular-22.md) |
||||
@ -0,0 +1,193 @@ |
|||||
|
```json |
||||
|
//[doc-seo] |
||||
|
{ |
||||
|
"Description": "Upgrade your ABP solutions to Angular version 22.0.x" |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
# Angular 22 and ABP 10.6 Upgrade Guide |
||||
|
|
||||
|
This guide explains how to upgrade ABP Angular applications to **Angular 22** and **TypeScript 6**. |
||||
|
|
||||
|
## 1. Target Versions |
||||
|
|
||||
|
Update all frontend dependencies together to maintain compatibility: |
||||
|
|
||||
|
- `@angular/*` → `~22.0.0` |
||||
|
- `typescript` → `~6.0.0` |
||||
|
- `@abp/*` → corresponding ABP version (10.6) |
||||
|
- `@volo/*`, `@volosoft/*` (if applicable) → corresponding ABP version (10.6) |
||||
|
- `angular-oauth2-oidc` (if applicable) → `~22.0.0` |
||||
|
|
||||
|
Avoid mixing Angular 21 and Angular 22 packages within the same workspace. |
||||
|
|
||||
|
## 2. Prerequisites |
||||
|
|
||||
|
Before starting the upgrade: |
||||
|
|
||||
|
1. Use a Node.js version supported by Angular 22. |
||||
|
2. Ensure your backend ABP version is compatible with the frontend package versions you plan to install. |
||||
|
3. Commit or back up your current project state. |
||||
|
|
||||
|
## 3. Upgrade Process |
||||
|
|
||||
|
1. Update package versions in `package.json`. |
||||
|
2. Run the Angular or Nx migration commands applicable to your project. |
||||
|
3. Remove existing installation artifacts: |
||||
|
- Delete `node_modules` |
||||
|
- Delete the lock file (`package-lock.json`, `yarn.lock`, or `pnpm-lock.yaml`) |
||||
|
|
||||
|
4. Reinstall all dependencies. |
||||
|
5. Build the application and resolve any compilation or template errors. |
||||
|
|
||||
|
## 4. Required Changes |
||||
|
|
||||
|
### 4.1 TypeScript 6 Deprecation Handling |
||||
|
|
||||
|
Projects that still use `downlevelIteration: true` may encounter TypeScript 6 deprecation diagnostics. |
||||
|
|
||||
|
Add the following temporary setting to your root `tsconfig` file (and library production configurations if required): |
||||
|
|
||||
|
```json |
||||
|
{ |
||||
|
"compilerOptions": { |
||||
|
"downlevelIteration": true, |
||||
|
"ignoreDeprecations": "6.0" |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
As a long-term solution, remove `downlevelIteration` when your target runtime environment no longer requires it. |
||||
|
|
||||
|
### 4.2 Updated Change Detection Behavior |
||||
|
|
||||
|
Angular 22 introduces updated change detection behavior for components that do not explicitly configure a change detection strategy. |
||||
|
|
||||
|
Common symptoms include: |
||||
|
|
||||
|
- Loading indicators not updating |
||||
|
- Modal busy states not clearing |
||||
|
- Lists or charts not refreshing after asynchronous operations |
||||
|
|
||||
|
Recommended approaches: |
||||
|
|
||||
|
1. Use `signal()` for component state. |
||||
|
2. Use `toSignal()` when consuming observable streams. |
||||
|
3. Use the `async` pipe for observable-based UI state. |
||||
|
4. Use `ChangeDetectionStrategy.Eager` only as a temporary compatibility measure for legacy components. |
||||
|
|
||||
|
### 4.3 ABP List Pages (`ListService`) |
||||
|
|
||||
|
When working with `ListService`, prefer converting observable results to signals instead of manually subscribing. |
||||
|
|
||||
|
```typescript |
||||
|
readonly data = toSignal( |
||||
|
this.list.hookToQuery(query => this.service.getList(query)), |
||||
|
{ initialValue: { items: [], totalCount: 0 } }, |
||||
|
); |
||||
|
``` |
||||
|
|
||||
|
Update template bindings accordingly: |
||||
|
|
||||
|
- `data.items` → `data().items` |
||||
|
- `data.totalCount` → `data().totalCount` |
||||
|
|
||||
|
### 4.4 Modals and Loading States |
||||
|
|
||||
|
For components such as `abp-modal`, `abp-button`, and permission or feature management dialogs, maintain state using signals. |
||||
|
|
||||
|
```typescript |
||||
|
readonly isModalVisible = signal(false); |
||||
|
readonly modalBusy = signal(false); |
||||
|
``` |
||||
|
|
||||
|
```html |
||||
|
<abp-button [loading]="modalBusy()" /> |
||||
|
<abp-modal |
||||
|
[visible]="isModalVisible()" |
||||
|
(visibleChange)="isModalVisible.set($event)" |
||||
|
[busy]="modalBusy()" |
||||
|
/> |
||||
|
``` |
||||
|
|
||||
|
If you use `*abpReplaceableTemplate`, pass signal values through `inputs.value` and update state through the corresponding event callbacks. |
||||
|
|
||||
|
### 4.5 Template Type Checking |
||||
|
|
||||
|
Angular 22 enables `strictTemplates` by default. |
||||
|
|
||||
|
Resolve template typing issues where possible, or temporarily disable strict template checking: |
||||
|
|
||||
|
```json |
||||
|
{ |
||||
|
"angularCompilerOptions": { |
||||
|
"strictTemplates": false |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
Common adjustments include: |
||||
|
|
||||
|
- Updating optional chaining (`?.`) and null coalescing (`??`) usage |
||||
|
- Guarding optional form references before binding |
||||
|
- Resolving duplicate input or output bindings |
||||
|
|
||||
|
### 4.6 Upload Progress Events |
||||
|
|
||||
|
Applications that rely on upload progress events should include the XHR backend in browser-side HTTP configuration: |
||||
|
|
||||
|
```typescript |
||||
|
provideHttpClient(withFetch(), withXhr()); |
||||
|
``` |
||||
|
|
||||
|
Do not enable the XHR backend in server-side rendering (SSR) bootstrap code. |
||||
|
|
||||
|
### 4.7 Chart Components (`abp-chart`) |
||||
|
|
||||
|
If charts do not update after asynchronous data loading: |
||||
|
|
||||
|
- Store chart data in a signal |
||||
|
- Bind chart inputs using signal values (for example, `[data]="chartData()"`) |
||||
|
- Call `reinit()` after assigning new data rather than relying solely on `refresh()` |
||||
|
|
||||
|
## 5. Custom or Forked UI Modules |
||||
|
|
||||
|
If your project contains customized copies of ABP modules such as Identity, Tenant Management, Account, or CMS Kit: |
||||
|
|
||||
|
1. Compare your implementation with the updated package versions. |
||||
|
2. Apply the recommended signal-based state management patterns. |
||||
|
3. Re-test CRUD pages, permission dialogs, feature dialogs, and account-related workflows. |
||||
|
|
||||
|
## 6. Validation Checklist |
||||
|
|
||||
|
After completing the upgrade, verify that: |
||||
|
|
||||
|
- Dependencies are installed correctly without duplicate Angular versions |
||||
|
- The application builds successfully |
||||
|
- Unit tests pass (if applicable) |
||||
|
- Login, registration, and password recovery workflows function correctly |
||||
|
- CRUD list pages refresh as expected |
||||
|
- Modal loading and busy states behave correctly |
||||
|
- Permission and feature dialogs open and close correctly |
||||
|
- Upload progress events work as expected (if applicable) |
||||
|
- Dashboard charts render correctly after data is loaded |
||||
|
|
||||
|
## 7. Troubleshooting |
||||
|
|
||||
|
| Symptom | Likely Cause | Resolution | |
||||
|
| ----------------------------------------------------------------- | ----------------------------------------- | ----------------------------------------------------------- | |
||||
|
| Form type conflicts (`AbstractControl`, etc.) | Multiple Angular versions installed | Align package versions and perform a clean reinstall | |
||||
|
| TypeScript deprecation errors related to `downlevelIteration` | TypeScript 6 diagnostics | Add `ignoreDeprecations: "6.0"` temporarily | |
||||
|
| Errors involving optional configuration or environment properties | Stricter type checking | Add null checks and optional chaining where appropriate | |
||||
|
| DTO or library compilation issues | Type incompatibilities in DTO definitions | Prefer interfaces and optional properties where appropriate | |
||||
|
| Upload progress events are not emitted | Missing XHR backend configuration | Add `withXhr()` to browser-side HTTP configuration | |
||||
|
| Charts remain empty after data loads | State changes are not being detected | Use signals and call `reinit()` after updating chart data | |
||||
|
|
||||
|
## 8. Summary |
||||
|
|
||||
|
When upgrading to Angular 22, focus on the following areas: |
||||
|
|
||||
|
1. Upgrade Angular, TypeScript, ABP, and commercial packages together. |
||||
|
2. Update UI state management to use signals, `toSignal()`, or the `async` pipe where appropriate. |
||||
|
3. Resolve TypeScript 6 and template type-checking issues. |
||||
|
4. Validate critical application workflows, including modals, list pages, uploads, and chart components. |
||||
@ -0,0 +1 @@ |
|||||
|
{} |
||||
@ -0,0 +1 @@ |
|||||
|
{} |
||||
@ -0,0 +1 @@ |
|||||
|
{} |
||||
@ -1,55 +1,81 @@ |
|||||
@if (actionList.length > 1) { |
@if (actionList.length > 1) { |
||||
<div ngbDropdown container="body" class="d-inline-block"> |
<div ngbDropdown container="body" class="d-inline-block"> |
||||
<button class="btn btn-primary btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" ngbDropdownToggle> |
<button |
||||
<i [class]="icon()" [class.me-1]="icon()"></i>{{ text() | abpLocalization }} |
class="btn btn-primary btn-sm dropdown-toggle" |
||||
</button> |
data-toggle="dropdown" |
||||
<div ngbDropdownMenu> |
aria-haspopup="true" |
||||
@for (action of actionList; track action.text) { |
ngbDropdownToggle |
||||
<ng-container [ngTemplateOutlet]="dropDownBtnItemTmp" [ngTemplateOutletContext]="{ $implicit: action }"> |
> |
||||
</ng-container> |
<i [class]="icon()" [class.me-1]="icon()"></i>{{ text() | abpLocalization }} |
||||
} |
</button> |
||||
|
<div ngbDropdownMenu> |
||||
|
@for (action of actionList; track $index) { |
||||
|
<ng-container |
||||
|
[ngTemplateOutlet]="dropDownBtnItemTmp" |
||||
|
[ngTemplateOutletContext]="{ $implicit: action }" |
||||
|
/> |
||||
|
} |
||||
|
</div> |
||||
</div> |
</div> |
||||
</div> |
|
||||
} |
} |
||||
|
|
||||
@if (actionList.length === 1) { |
@if (actionList.length === 1) { |
||||
<ng-container [ngTemplateOutlet]="btnTmp" |
<ng-container |
||||
[ngTemplateOutletContext]="{ $implicit: actionList.get(0).value }"></ng-container> |
[ngTemplateOutlet]="btnTmp" |
||||
|
[ngTemplateOutletContext]="{ $implicit: actionList.get(0).value }" |
||||
|
/> |
||||
} |
} |
||||
|
|
||||
<ng-template #dropDownBtnItemTmp let-action> |
<ng-template #dropDownBtnItemTmp let-action> |
||||
@if (action.visible(data)) { |
@if (action.visible(data)) { |
||||
<button ngbDropdownItem *abpPermission="action.permission; runChangeDetection: false" (click)="action.action(data)" |
<button |
||||
type="button"> |
ngbDropdownItem |
||||
<ng-container *ngTemplateOutlet="buttonContentTmp; context: { $implicit: action }"></ng-container> |
*abpPermission="action.permission; runChangeDetection: false" |
||||
</button> |
(click)="action.action(data)" |
||||
|
type="button" |
||||
|
> |
||||
|
<ng-container *ngTemplateOutlet="buttonContentTmp; context: { $implicit: action }" /> |
||||
|
</button> |
||||
} |
} |
||||
</ng-template> |
</ng-template> |
||||
|
|
||||
<ng-template #buttonContentTmp let-action> |
<ng-template #buttonContentTmp let-action> |
||||
<i [class]="action.icon" [class.me-1]="action.icon && !action.showOnlyIcon"></i> |
<i [class]="action.icon" [class.me-1]="action.icon && !action.showOnlyIcon"></i> |
||||
@if (!action.showOnlyIcon) { |
@if (!action.showOnlyIcon) { |
||||
@if (action.icon) { |
@if (action.icon) { |
||||
<span>{{ action.text | abpLocalization }}</span> |
<span>{{ action.text | abpLocalization }}</span> |
||||
} @else { |
} @else { |
||||
<div abpEllipsis>{{ action.text | abpLocalization }}</div> |
<div abpEllipsis>{{ action.text | abpLocalization }}</div> |
||||
} |
} |
||||
} |
} |
||||
</ng-template> |
</ng-template> |
||||
|
|
||||
<ng-template #btnTmp let-action> |
<ng-template #btnTmp let-action> |
||||
@if (action.visible(data)) { |
@if (action.visible(data)) { |
||||
@if (action.tooltip) { |
@if (action.tooltip) { |
||||
<button *abpPermission="action.permission; runChangeDetection: false" (click)="action.action(data)" type="button" |
<button |
||||
[class]="action.btnClass" [style]="action.btnStyle" [ngbTooltip]="action.tooltip.text | abpLocalization" |
*abpPermission="action.permission; runChangeDetection: false" |
||||
[placement]="action.tooltip.placement || 'auto'" triggers="hover" container="body"> |
(click)="action.action(data)" |
||||
<ng-container *ngTemplateOutlet="buttonContentTmp; context: { $implicit: action }"></ng-container> |
type="button" |
||||
</button> |
[class]="action.btnClass" |
||||
} @else { |
[style]="action.btnStyle" |
||||
<button *abpPermission="action.permission; runChangeDetection: false" (click)="action.action(data)" type="button" |
[ngbTooltip]="action.tooltip.text | abpLocalization" |
||||
[class]="action.btnClass" [style]="action.btnStyle"> |
[placement]="action.tooltip.placement || 'auto'" |
||||
<ng-container *ngTemplateOutlet="buttonContentTmp; context: { $implicit: action }"></ng-container> |
triggers="hover" |
||||
</button> |
container="body" |
||||
} |
> |
||||
|
<ng-container *ngTemplateOutlet="buttonContentTmp; context: { $implicit: action }" /> |
||||
|
</button> |
||||
|
} @else { |
||||
|
<button |
||||
|
*abpPermission="action.permission; runChangeDetection: false" |
||||
|
(click)="action.action(data)" |
||||
|
type="button" |
||||
|
[class]="action.btnClass" |
||||
|
[style]="action.btnStyle" |
||||
|
> |
||||
|
<ng-container *ngTemplateOutlet="buttonContentTmp; context: { $implicit: action }" /> |
||||
|
</button> |
||||
|
} |
||||
} |
} |
||||
</ng-template> |
</ng-template> |
||||
|
|||||