Browse Source

code refactoring

pull/23416/head
erdemcaygor 6 months ago
parent
commit
38805d14f5
  1. 12
      Directory.Packages.props
  2. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
  3. BIN
      docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/1752664190317-min.jpeg
  4. BIN
      docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15941-min.jpg
  5. BIN
      docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15944-min.jpg
  6. BIN
      docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15947-min.jpg
  7. BIN
      docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15948-min.jpg
  8. BIN
      docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15949-min.jpg
  9. BIN
      docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15959-min.jpg
  10. BIN
      docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15963-min.jpg
  11. BIN
      docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15995-min.jpg
  12. BIN
      docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15996-min.jpg
  13. BIN
      docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15998-min.jpg
  14. BIN
      docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15999-min.jpg
  15. BIN
      docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16011-min.jpg
  16. BIN
      docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16012-min.jpg
  17. BIN
      docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/cover.png
  18. BIN
      docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/image-20250722203102576.png
  19. 79
      docs/en/Blog-Posts/2025-08-08 v9_3_Release_Stable/POST.md
  20. BIN
      docs/en/Blog-Posts/2025-08-08 v9_3_Release_Stable/cover-image.png
  21. BIN
      docs/en/Blog-Posts/2025-08-08 v9_3_Release_Stable/upgrade-abp-packages.png
  22. BIN
      docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-saas-tenants-page.png
  23. BIN
      docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-studio-add-migration-select-dbcontext.png
  24. BIN
      docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-studio-add-migration-set-name.png
  25. BIN
      docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-studio-add-migration.png
  26. BIN
      docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-studio-browse.png
  27. BIN
      docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-studio-context-selection.png
  28. BIN
      docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-studio-open-with-terminal.png
  29. BIN
      docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-studio-solution-runner.png
  30. BIN
      docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/acme-tenant-screen.png
  31. BIN
      docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/added-product-entity-migration-main-context.png
  32. BIN
      docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/added-product-entity-migration-tenant-context.png
  33. BIN
      docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/builder-check-tenant-side.png
  34. BIN
      docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/dbcontext-factories.png
  35. BIN
      docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/dbmigrator-logs.png
  36. BIN
      docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/hybrid-database-multi-tenancy.png
  37. BIN
      docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/multi-tenancy-dbcontext-structure.png
  38. BIN
      docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/new-tenant-dialog-1.png
  39. BIN
      docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/new-tenant-dialog-2.png
  40. BIN
      docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/new-tenant-dialog-conn-string-1.png
  41. BIN
      docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/new-tenant-dialog-conn-string-2.png
  42. BIN
      docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/product-database-table.png
  43. BIN
      docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/separate-database.png
  44. BIN
      docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/separate-tenant-database-multi-tenancy.png
  45. BIN
      docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/separate-tenant-schema-option.png
  46. BIN
      docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/single-shared-database.png
  47. BIN
      docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/switch-host-side.png
  48. BIN
      docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/switch-tenant-dialog.png
  49. BIN
      docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/tenant-acme-name.png
  50. BIN
      docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/tenant-database.png
  51. BIN
      docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/user-login.png
  52. BIN
      docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/user-logout.png
  53. BIN
      docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/users-table-new-tenant.png
  54. 2
      docs/en/Community-Articles/2025-07-31-How-to-build-persistent-background-jobs-with-abp-framework-and-quartz/Post.md
  55. BIN
      docs/en/Community-Articles/2025-08-12-Integration-Services-Explained/integration-services.jpeg
  56. 138
      docs/en/Community-Articles/2025-08-12-Integration-Services-Explained/post.md
  57. BIN
      docs/en/Community-Articles/2025-08-19-Best-Practices-Azure-Devops/0-cover.png
  58. BIN
      docs/en/Community-Articles/2025-08-19-Best-Practices-Azure-Devops/1-pipeline-yaml.png
  59. BIN
      docs/en/Community-Articles/2025-08-19-Best-Practices-Azure-Devops/3-release.png
  60. BIN
      docs/en/Community-Articles/2025-08-19-Best-Practices-Azure-Devops/4-safe-deploy.png
  61. BIN
      docs/en/Community-Articles/2025-08-19-Best-Practices-Azure-Devops/5-summarizing.png
  62. 83
      docs/en/Community-Articles/2025-08-19-Best-Practices-Azure-Devops/POST.md
  63. 398
      docs/en/Community-Articles/2025-08-19-abp-now-supports-angular-standalone-applications/POST.md
  64. 84
      docs/en/docs-nav.json
  65. 10
      docs/en/framework/architecture/multi-tenancy/index.md
  66. 176
      docs/en/framework/infrastructure/object-to-object-mapping.md
  67. 71
      docs/en/framework/ui/angular/account-module.md
  68. 30
      docs/en/framework/ui/angular/authorization.md
  69. 5
      docs/en/framework/ui/angular/basic-theme.md
  70. 51
      docs/en/framework/ui/angular/caps-lock-directive.md
  71. 98
      docs/en/framework/ui/angular/card-component.md
  72. 36
      docs/en/framework/ui/angular/chart-component.md
  73. 141
      docs/en/framework/ui/angular/component-replacement.md
  74. 50
      docs/en/framework/ui/angular/config-state-service.md
  75. 25
      docs/en/framework/ui/angular/confirmation-service.md
  76. 16
      docs/en/framework/ui/angular/data-table-column-extensions.md
  77. 18
      docs/en/framework/ui/angular/dynamic-form-extensions.md
  78. 49
      docs/en/framework/ui/angular/ellipsis-directive.md
  79. 93
      docs/en/framework/ui/angular/entity-action-extensions.md
  80. 33
      docs/en/framework/ui/angular/entity-filters.md
  81. 26
      docs/en/framework/ui/angular/environment.md
  82. 4
      docs/en/framework/ui/angular/extensions-overall.md
  83. 41
      docs/en/framework/ui/angular/feature-libraries.md
  84. 62
      docs/en/framework/ui/angular/form-validation.md
  85. 59
      docs/en/framework/ui/angular/http-error-handling.md
  86. BIN
      docs/en/framework/ui/angular/images/quick-start---root-folder-structure.png
  87. BIN
      docs/en/framework/ui/angular/images/quick-start---source-folder-structure.png
  88. 1
      docs/en/framework/ui/angular/list-service.md
  89. 17
      docs/en/framework/ui/angular/loading-directive.md
  90. 74
      docs/en/framework/ui/angular/localization.md
  91. 25
      docs/en/framework/ui/angular/lookup-components.md
  92. 57
      docs/en/framework/ui/angular/manage-profile-page-tabs.md
  93. 69
      docs/en/framework/ui/angular/modal.md
  94. 90
      docs/en/framework/ui/angular/modifying-the-menu.md
  95. 13
      docs/en/framework/ui/angular/multi-tenancy.md
  96. 3
      docs/en/framework/ui/angular/oauth-module.md
  97. 84
      docs/en/framework/ui/angular/page-component.md
  98. 32
      docs/en/framework/ui/angular/page-toolbar-extensions.md
  99. 2
      docs/en/framework/ui/angular/password-complexity-indicator-component.md
  100. 9
      docs/en/framework/ui/angular/permission-management.md

12
Directory.Packages.props

@ -18,10 +18,10 @@
<PackageVersion Include="BunnyCDN.Net.Storage" Version="1.0.4" />
<PackageVersion Include="Azure.Messaging.ServiceBus" Version="7.19.0" />
<PackageVersion Include="Azure.Storage.Blobs" Version="12.24.0" />
<PackageVersion Include="Blazorise" Version="1.8.0" />
<PackageVersion Include="Blazorise.Components" Version="1.8.0" />
<PackageVersion Include="Blazorise.DataGrid" Version="1.8.0" />
<PackageVersion Include="Blazorise.Snackbar" Version="1.8.0" />
<PackageVersion Include="Blazorise" Version="1.8.1" />
<PackageVersion Include="Blazorise.Components" Version="1.8.1" />
<PackageVersion Include="Blazorise.DataGrid" Version="1.8.1" />
<PackageVersion Include="Blazorise.Snackbar" Version="1.8.1" />
<PackageVersion Include="Castle.Core" Version="5.1.1" />
<PackageVersion Include="Castle.Core.AsyncInterceptor" Version="2.1.0" />
<PackageVersion Include="CommonMark.NET" Version="0.15.1" />
@ -46,7 +46,7 @@
<PackageVersion Include="Hangfire.AspNetCore" Version="1.8.18" />
<PackageVersion Include="Hangfire.SqlServer" Version="1.8.18" />
<PackageVersion Include="HtmlSanitizer" Version="9.0.884" />
<PackageVersion Include="IdentityModel" Version="7.0.0" />
<PackageVersion Include="Duende.IdentityModel" Version="7.1.0" />
<PackageVersion Include="IdentityServer4" Version="4.1.2" />
<PackageVersion Include="IdentityServer4.AspNetIdentity" Version="4.1.2" />
<PackageVersion Include="JetBrains.Annotations" Version="2024.3.0" />
@ -136,7 +136,7 @@
<PackageVersion Include="Oracle.EntityFrameworkCore" Version="9.23.80" />
<PackageVersion Include="Polly" Version="8.5.2" />
<PackageVersion Include="Polly.Extensions.Http" Version="3.0.0" />
<PackageVersion Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0-rc.1.efcore.9.0.0" />
<PackageVersion Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0" />
<PackageVersion Include="MySql.EntityFrameworkCore" Version="9.0.6" />
<PackageVersion Include="Quartz" Version="3.14.0" />
<PackageVersion Include="Quartz.Extensions.DependencyInjection" Version="3.14.0" />

3
abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json

@ -774,6 +774,7 @@
"Menu:TelemetryMenu": "Telemetry Reports",
"Menu:Studio": "Studio",
"Menu:Solutions": "Solutions",
"Menu:Users": "Users"
"Menu:Users": "Users",
"Menu:UserReports": "Users"
}
}

BIN
docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/1752664190317-min.jpeg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 KiB

After

Width:  |  Height:  |  Size: 180 KiB

BIN
docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15941-min.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 749 KiB

After

Width:  |  Height:  |  Size: 712 KiB

BIN
docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15944-min.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 779 KiB

After

Width:  |  Height:  |  Size: 709 KiB

BIN
docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15947-min.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 697 KiB

After

Width:  |  Height:  |  Size: 667 KiB

BIN
docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15948-min.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 852 KiB

After

Width:  |  Height:  |  Size: 825 KiB

BIN
docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15949-min.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 738 KiB

After

Width:  |  Height:  |  Size: 714 KiB

BIN
docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15959-min.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 802 KiB

After

Width:  |  Height:  |  Size: 742 KiB

BIN
docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15963-min.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 633 KiB

After

Width:  |  Height:  |  Size: 620 KiB

BIN
docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15995-min.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15996-min.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15998-min.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15999-min.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

BIN
docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16011-min.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16012-min.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

After

Width:  |  Height:  |  Size: 2.1 MiB

BIN
docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/cover.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 602 KiB

After

Width:  |  Height:  |  Size: 594 KiB

BIN
docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/image-20250722203102576.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 44 KiB

79
docs/en/Blog-Posts/2025-08-08 v9_3_Release_Stable/POST.md

@ -0,0 +1,79 @@
# ABP.IO Platform 9.3 Final Has Been Released!
We are glad to announce that [ABP](https://abp.io/) 9.3 stable version has been released today.
## What's New With Version 9.3?
All the new features were explained in detail in the [9.3 RC Announcement Post](https://abp.io/community/announcements/announcing-abp-9-3-release-candidate-4dqgiryf), so there is no need to review them again. You can check it out for more details.
## Getting Started with 9.3
### Creating New Solutions
You can check the [Get Started page](https://abp.io/get-started) to see how to get started with ABP. You can either download [ABP Studio](https://abp.io/get-started#abp-studio-tab) (**recommended**, if you prefer a user-friendly GUI application - desktop application) or use the [ABP CLI](https://abp.io/docs/latest/cli) to create new solutions.
> **Note**: ABP Studio **v1.2.1** has been released with support for **ABP 9.3**. If you already have ABP Studio installed, update it to v1.2.1 (or later, if available) to create new applications targeting 9.3. ABP Studio checks for updates automatically and will prompt you in-app modal to update to the latest version, or you can download the latest installer from the [Studio](https://abp.io/studio) page. See the [upgrading guide](https://abp.io/docs/latest/studio/installation#upgrading) for details. After updating, the New Solution wizard will create applications with ABP 9.3 by default. You can check the [ABP Studio and ABP Startup Template Version Mappings](https://abp.io/docs/latest/studio/version-mapping) documentation to see the corresponding ABP versions for other versions of Studio.
### How to Upgrade an Existing Solution
You can upgrade your existing solutions with either ABP Studio or ABP CLI. In the following sections, both approaches are explained:
### Upgrading via ABP Studio
If you are already using the ABP Studio, you can upgrade it to the latest version. ABP Studio periodically checks for updates in the background, and when a new version of ABP Studio is available, you will be notified through a modal. Then, you can update it by confirming the opened modal. See [the documentation](https://abp.io/docs/latest/studio/installation#upgrading) for more info.
After upgrading the ABP Studio, then you can open your solution in the application, and simply click the **Upgrade ABP Packages** action button to instantly upgrade your solution:
![](upgrade-abp-packages.png)
### Upgrading via ABP CLI
Alternatively, you can upgrade your existing solution via ABP CLI. First, you need to install the ABP CLI or upgrade it to the latest version.
If you haven't installed it yet, you can run the following command:
```bash
dotnet tool install -g Volo.Abp.Studio.Cli
```
Or to update the existing CLI, you can run the following command:
```bash
dotnet tool update -g Volo.Abp.Studio.Cli
```
After installing/updating the ABP CLI, you can use the [`update` command](https://abp.io/docs/latest/CLI#update) to update all the ABP related NuGet and NPM packages in your solution as follows:
```bash
abp update
```
You can run this command in the root folder of your solution to update all ABP related packages.
## Migration Guides
There are a few breaking changes in this version that may affect your application. Please read the migration guide carefully, if you are upgrading from v9.2: [ABP Version 9.3 Migration Guide](https://abp.io/docs/9.3/release-info/migration-guides/abp-9-3)
## Community News
### New ABP Community Articles
As always, exciting articles have been contributed by the ABP community. I will highlight some of them here:
* [Fahri Gedik](https://abp.io/community/members/fahrigedik) has published 2 new articles:
* [A Modern Approach to Angular Dependency Injection using inject function](https://abp.io/community/articles/a-modern-approach-to-angular-dependency-injection-using-8np4o1ap)
* [Angular Application Builder: Transitioning from Webpack to Esbuild](https://abp.io/community/articles/angular-application-builder-transitioning-from-webpack-to-3yzhzfl0)
* [Benjamin Fadina](https://abp.io/community/members/benjaminsqlserver@gmail.com) has published several videos on various topics such as **Blazor Web Assembly Using ABP.IO**, **CQRS Implementation with MediatR in ABP** and more. You can see all his videos [here](https://abp.io/community/members/benjaminsqlserver@gmail.com).
* [Mansur Besleney](https://abp.io/community/members/mansur.besleney) has published [How to Build Persistent Background Jobs with ABP Framework and Quartz](https://abp.io/community/articles/how-to-build-persistent-background-jobs-with-abp-framework-n9aloh93)
* [Halil Ibrahim Kalkan](https://x.com/hibrahimkalkan) has published [Multitenancy with Separate Databases in .NET and ABP](https://abp.io/community/articles/multitenancy-with-separate-databases-in-dotnet-and-abp-51nvl4u9)
* [Alex Maiereanu](https://abp.io/community/members/alex.maiereanu@3sstudio.com) has published [ABP-Hangfire-AzurePostgreSQL](https://abp.io/community/articles/abphangfireazurepostgresql-s1jnf3yg)
* [Jack Fistelmann](https://abp.io/community/members/jfistelmann) has published [ABP and maildev](https://abp.io/community/articles/abp-and-maildev-gy13cr1p)
* [Harsh Gupta](https://abp.io/community/members/harshgupta) has published [How to Add a Module in the ABP.io Application?](https://abp.io/community/articles/how-to-add-a-module-in-the-abp.io-application-sdeajkn6)
* [Tarık Özdemir](https://abp.io/community/members/mtozdemir) has published [AI-First Architecture for .NET Projects: A Modern Blueprint Inspired by McKinsey](https://abp.io/community/articles/AI-First%20Architecture%20for%20.NET%20Projects%3A%20A%20Modern%20Blueprint-h2wgcoq3)
* [Liming Ma](https://github.com/maliming) has published [Using Hangfire Dashboard in ABP API Website](https://abp.io/community/articles/using-hangfire-dashboard-in-abp-api-website--r32ox497)
Thanks to the ABP Community for all the content they have published. You can also [post your ABP related (text or video) content](https://abp.io/community/posts/create) to the ABP Community.
## About the Next Version
The next feature version will be 10.0. You can follow the [release planning here](https://github.com/abpframework/abp/milestones). Please [submit an issue](https://github.com/abpframework/abp/issues/new) if you have any problems with this version.

BIN
docs/en/Blog-Posts/2025-08-08 v9_3_Release_Stable/cover-image.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 676 KiB

BIN
docs/en/Blog-Posts/2025-08-08 v9_3_Release_Stable/upgrade-abp-packages.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-saas-tenants-page.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 26 KiB

BIN
docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-studio-add-migration-select-dbcontext.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 16 KiB

BIN
docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-studio-add-migration-set-name.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

BIN
docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-studio-add-migration.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 41 KiB

BIN
docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-studio-browse.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

BIN
docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-studio-context-selection.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 17 KiB

BIN
docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-studio-open-with-terminal.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 40 KiB

BIN
docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-studio-solution-runner.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 11 KiB

BIN
docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/acme-tenant-screen.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 48 KiB

BIN
docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/added-product-entity-migration-main-context.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

BIN
docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/added-product-entity-migration-tenant-context.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

BIN
docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/builder-check-tenant-side.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 17 KiB

BIN
docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/dbcontext-factories.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

BIN
docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/dbmigrator-logs.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 62 KiB

BIN
docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/hybrid-database-multi-tenancy.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 11 KiB

BIN
docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/multi-tenancy-dbcontext-structure.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 16 KiB

BIN
docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/new-tenant-dialog-1.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 24 KiB

BIN
docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/new-tenant-dialog-2.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 24 KiB

BIN
docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/new-tenant-dialog-conn-string-1.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

BIN
docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/new-tenant-dialog-conn-string-2.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 18 KiB

BIN
docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/product-database-table.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 25 KiB

BIN
docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/separate-database.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

BIN
docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/separate-tenant-database-multi-tenancy.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

BIN
docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/separate-tenant-schema-option.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 48 KiB

BIN
docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/single-shared-database.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

BIN
docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/switch-host-side.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

BIN
docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/switch-tenant-dialog.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

BIN
docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/tenant-acme-name.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/tenant-database.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 11 KiB

BIN
docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/user-login.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 62 KiB

BIN
docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/user-logout.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 18 KiB

BIN
docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/users-table-new-tenant.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 27 KiB

2
docs/en/Community-Articles/2025-07-31-How-to-build-persistent-background-jobs-with-abp-framework-and-quartz/Post.md

@ -543,4 +543,6 @@ Complex background processing doesn't have to be complicated to implement. ABP's
Whether you're building subscription management, financial reporting, or data synchronization, these patterns provide a solid foundation for reliable, maintainable solutions.
You can reach sample project's source code from [here](https://github.com/MansurBesleney/MySaaSApplication)
**Happy coding, and may your background jobs never miss a beat!** 🚀

BIN
docs/en/Community-Articles/2025-08-12-Integration-Services-Explained/integration-services.jpeg

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

138
docs/en/Community-Articles/2025-08-12-Integration-Services-Explained/post.md

@ -0,0 +1,138 @@
# Integration Services in ABP — What they are, when to use them, and how they behave 🚦
If you’ve been building with ABP for a while, you’ve probably used Application Services for your UI and APIs in your .NET and ASP.NET Core apps. Integration Services are similar—but with a different mission: they exist for service-to-service or module-to-module communication, not for end users.
If you want the formal spec, see the official doc: [Integration Services](../../framework/api-development/integration-services.md). This post is the practical, no-fluff guide.
## What is an Integration Service?
An Integration Service is an application service or ASP.NET Core MVC controller marked with the `[IntegrationService]` attribute. That marker tells ABP “this endpoint is for internal communication.”
- They are not exposed by default (safer for reusable modules and monoliths).
- When exposed, their route prefix is `/integration-api` (so you can easily protect them at your gateway or firewall).
- Auditing is disabled by default for them (less noise for machine-to-machine calls).
Quick look:
```csharp
[IntegrationService]
public interface IProductIntegrationService : IApplicationService
{
Task<List<ProductDto>> GetProductsByIdsAsync(List<Guid> ids);
}
public class ProductIntegrationService : ApplicationService, IProductIntegrationService
{
public Task<List<ProductDto>> GetProductsByIdsAsync(List<Guid> ids)
{
// fetch and return minimal product info for other services/modules
}
}
```
## Are they HTTP endpoints?
- By default: no (they won’t be reachable over HTTP in the ASP.NET Core routing pipeline).
- If you need them over HTTP (typically for microservices), explicitly enable:
```csharp
Configure<AbpAspNetCoreMvcOptions>(options =>
{
options.ExposeIntegrationServices = true;
});
```
Once exposed, ABP puts them under `/integration-api/...` instead of `/api/...` in the ASP.NET Core routing pipeline. That’s your hint to restrict them from public internet access.
## Enable auditing (optional)
If you want audit logs for integration calls, enable it explicitly:
```csharp
Configure<AbpAuditingOptions>(options =>
{
options.IsEnabledForIntegrationServices = true;
});
```
## When should you use Integration Services?
- Internal, synchronous operations between services or modules.
- You need a “thin” API designed for other services (not for UI): minimal DTOs, no view concerns, predictable contracts.
- You want to hide these endpoints from public clients, or only allow them inside your private network or k8s cluster.
- You’re packaging a reusable module that might be used in both monolith and microservice deployments.
## When NOT to use them
- Public APIs or anything intended for browsers/mobile apps → use regular application services/controllers.
- Asynchronous cross-service workflows → consider domain events + outbox/inbox; use Integration Services for sync calls.
- Complex, chatty UI endpoints → those belong to your external API surface, not internal integration.
## Common use-cases and examples
- Identity lookups across services: an Ordering service needs basic user info from the Identity service.
- Permission checks from another module: a CMS module asks a Permission service for access decisions.
- Product data hydrations: a Cart service needs minimal product details (price, name) from Catalog.
- Internal admin/maintenance operations that aren’t meant for end users but are needed by other services.
## Example: microservice-to-microservice call
1) Mark and expose the integration service in the target service:
```csharp
[IntegrationService]
public interface IUserIntegrationService : IApplicationService
{
Task<UserBriefDto?> FindByIdAsync(Guid id);
}
Configure<AbpAspNetCoreMvcOptions>(o => o.ExposeIntegrationServices = true);
```
2) In the caller service, add an HTTP client proxy only for Integration Services if you like to keep things clean:
```csharp
services.AddHttpClientProxies(
typeof(TargetServiceApplicationModule).Assembly,
remoteServiceConfigurationName: "TargetService",
asDefaultServices: true,
applicationServiceTypes: ApplicationServiceTypes.IntegrationServices);
```
3) Call it just like a local service (ABP’s HTTP proxy handles the wire):
```csharp
public class OrderAppService : ApplicationService
{
private readonly IUserIntegrationService _userIntegrationService;
public OrderAppService(IUserIntegrationService userIntegrationService)
{
_userIntegrationService = userIntegrationService;
}
public async Task PlaceOrderAsync(CreateOrderDto input)
{
var user = await _userIntegrationService.FindByIdAsync(CurrentUser.GetId());
// validate user status, continue placing order...
}
}
```
## Monolith vs. Microservices
- Monolith: keep them unexposed and call via DI in-process. You get the same clear contract with zero network overhead.
- Microservices: expose them and route behind your gateway. The `/integration-api` prefix makes it easy to firewall/gateway-restrict.
## Practical tips
- Keep integration DTOs lean and stable. These are machine contracts—don’t mix UI concerns.
- Name them clearly (e.g., `UserIntegrationService`) so intent is obvious.
- Guard your ASP.NET Core gateway application: block `/integration-api/*` from public traffic.
- Enable auditing only if you truly need the logs for these calls.
## Further reading
- Official docs: [Integration Services](../../framework/api-development/integration-services.md)
That’s it! Integration Services give you a clean, intentional way to design internal APIs—great in monoliths, essential in microservices.

BIN
docs/en/Community-Articles/2025-08-19-Best-Practices-Azure-Devops/0-cover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 MiB

BIN
docs/en/Community-Articles/2025-08-19-Best-Practices-Azure-Devops/1-pipeline-yaml.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 KiB

BIN
docs/en/Community-Articles/2025-08-19-Best-Practices-Azure-Devops/3-release.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

BIN
docs/en/Community-Articles/2025-08-19-Best-Practices-Azure-Devops/4-safe-deploy.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

BIN
docs/en/Community-Articles/2025-08-19-Best-Practices-Azure-Devops/5-summarizing.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

83
docs/en/Community-Articles/2025-08-19-Best-Practices-Azure-Devops/POST.md

@ -0,0 +1,83 @@
# 🚀 Best Practices for Azure DevOps CI/CD Pipelines
**CI/CD (Continuous Integration / Continuous Delivery)** is not just fancy tech talk - it's now a must-have for modern software teams.
Microsoft's **Azure DevOps** helps make these processes easier to manage.
But how do you create pipelines that work well for your team? Let's look at some practical tips that will make your life easier.
---
## 1. 📜 Define Your Pipeline as Code
Don't use the manual setup method that's hard to track. Azure DevOps lets you use **YAML files** for your pipelines, which gives you:
- A record of all changes - who made them and when
- The same setup across all environments
- The ability to undo changes when something goes wrong
This stops the common problem where something works on one computer but not another.
![1-pipeline-yaml](1-pipeline-yaml.png)
---
## 2. 🔑 Store Sensitive Information Safely
Never put passwords directly in your code, even temporarily.
Each environment should have its own settings, and keep sensitive information in **Azure Key Vault** or **Library Variable Groups**.
You'll avoid security problems later.
<!-- ![2-azure-key](2-azure-key.png) -->
---
## 3. 🏗️ Keep Building and Releasing Separate
Think of **Building** like cooking a meal - you prepare everything and package it up.
**Releasing** is like delivering that meal to different people.
Keeping these as separate steps means:
- You create your package once, then send it to multiple places
- You save time and resources by not rebuilding the same thing over and over
![3-release](3-release.png)
---
## 4. 🧪 Add Automatic Testing
Don't waste time testing the same things manually over and over.
Set up **different types of tests** to run automatically. When tests run every time you make changes:
- You catch problems before your customers do
- Your software quality stays high without extra manual work
Azure DevOps has tools to help you see test results easily without searching through technical logs.
---
## 5. 🛡️ Add Safety Checks
Automatic doesn't mean pushing everything to your live system right away.
For important environments, add **human approval steps** or **automatic checks** like security scans.
This helps you avoid emergency problems in the middle of the night.
![4-safe-deploy](4-safe-deploy.png)
---
## ✅ Conclusion
Good Azure DevOps pipelines aren't just about automation - they help you feel confident in your process.
Remember these main points:
✔ Use YAML files to keep everything visible and trackable
✔ Keep passwords and sensitive data in secure storage (not in your code)
✔ Build once, deploy to many places
✔ Let automatic tests find problems before users do
✔ Add safety checks for important systems
![5-summarizing](5-summarizing.png)
---

398
docs/en/Community-Articles/2025-08-19-abp-now-supports-angular-standalone-applications/POST.md

@ -0,0 +1,398 @@
# ABP Now Supports Angular Standalone Applications
We are excited to announce that **ABP now supports Angular’s standalone component structure** in the latest Studio update. This article walks you through how to generate a standalone application, outlines the migration steps, and highlights the benefits of this shift over traditional module-based architecture.
---
## Why Standalone?
Angular's standalone component architecture, which is introduced in version 14 and made default in version 19, is a major leap forward for Angular development. Here is why it matters:
### 🔧 Simplified Project Structure
Standalone components eliminate the need for `NgModule` wrappers. This leads to:
- Fewer files to manage
- Cleaner folder organization
- Reduced boilerplate
Navigating and understanding your codebase becomes easier for everyone on your team.
### 🚀 Faster Bootstrapping
Standalone apps simplify app initialization:
```ts
bootstrapApplication(AppComponent, appConfig);
```
This avoids the need for `AppModule` and speeds up startup times.
### 📦 Smaller Bundle Sizes
Since components declare their own dependencies, Angular can more effectively tree-shake unused code. Result? Smaller bundle sizes and faster load times.
### 🧪 Easier Testing & Reusability
Standalone components are self-contained. They declare their dependencies within the `imports` array, making them:
- Easier to test in isolation
- Easier to reuse in different contexts
### 🧠 Clearer Dependency Management
Standalone components explicitly define what they need. No more hidden dependencies buried in shared modules.
### 🔄 Gradual Adoption
You can mix and match standalone and module-based components. This allows for **incremental migration**, reducing risk in larger codebases. Here is the related document for the [standalone migration](https://angular.dev/reference/migrations/standalone).
---
## Getting Started: Creating a Standalone Angular App
Angular CLI makes it easy to start:
```bash
ng new my-app
```
With Angular 19, new apps follow this bootstrapping model:
```ts
// main.ts
import { bootstrapApplication } from "@angular/platform-browser";
import { appConfig } from "./app/app.config";
import { AppComponent } from "./app/app.component";
bootstrapApplication(AppComponent, appConfig).catch((err) =>
console.error(err)
);
```
The `app.config.ts` file replaces `AppModule`:
```ts
// app.config.ts
import { ApplicationConfig, provideZoneChangeDetection } from "@angular/core";
import { provideRouter } from "@angular/router";
import { routes } from "./app.routes";
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes),
],
};
```
Routing is defined in a simple `Routes` array:
```ts
// app.routes.ts
import { Routes } from "@angular/router";
export const routes: Routes = [];
```
---
## ABP Studio Support for Standalone Structure
Starting with the latest release (insert version number here), ABP Studio fully supports Angular's standalone structure. While the new format is encouraged, module-based structure will continue to be supported for backwards compatibility.
To try it out, simply update your ABP Studio to create apps with the latest version.
---
## What’s New in ABP Studio Templates?
When you generate an app using the latest ABP Studio, the project structure aligns with Angular's standalone architecture.
This migration is split into four parts:
1. **Package updates**
2. **Schematics updates**
3. **Suite code generation updates**
4. **Template refactors**
---
## Package Migration Details
Migration has been applied to packages in the [ABP GitHub repository](https://github.com/abpframework/abp/tree/dev/npm/ng-packs/packages). Here is an example from the Identity package.
### 🧩 Migrating Components
Components are made standalone, using:
```bash
ng g @angular/core:standalone
```
Example:
```ts
@Component({
selector: 'abp-roles',
templateUrl: './roles.component.html',
providers: [...],
imports: [
ReactiveFormsModule,
LocalizationPipe,
...
],
})
export class RolesComponent implements OnInit { ... }
```
### 🛣 Updating Routing
Old lazy-loaded routes using `forLazy()`:
```ts
{
path: 'identity',
loadChildren: () => import('@abp/ng.identity').then(m => m.IdentityModule.forLazy({...}))
}
```
Now replaced with:
```ts
{
path: 'identity',
loadChildren: () => import('@abp/ng.identity').then(c => c.createRoutes({...}))
}
```
### 🧱 Replacing Module Declarations
The old setup:
```ts
// identity.module.ts
@NgModule({
imports: [IdentityRoutingModule, RolesComponent, UsersComponent],
})
export class IdentityModule {...}
```
```ts
//identity-routing.module
const routes: Routes = [...];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class IdentityRoutingModule {}
```
New setup:
```ts
// identity-routes.ts
export function provideIdentity(options: IdentityConfigOptions = {}): Provider[] {
return [...];
}
export const createRoutes = (options: IdentityConfigOptions = {}): Routes => [
{
path: '',
component: RouterOutletComponent,
providers: provideIdentity(options),
children: [
{
path: 'roles',
component: ReplaceableRouteContainerComponent,
data: {
requiredPolicy: 'AbpIdentity.Roles',
replaceableComponent: {
key: eIdentityComponents.Roles,
defaultComponent: RolesComponent,
},
},
title: 'AbpIdentity::Roles',
},
...
],
},
];
```
---
## ABP Schematics Migration Details
You can reach details by checking [ABP Schematics codebase](https://github.com/abpframework/abp/tree/dev/npm/ng-packs/packages/schematics).
### 📚 Library creation
When you run the `abp create-lib` command, the prompter will ask you the `templateType`. It supports both module and standalone templates.
```ts
"templateType": {
"type": "string",
"description": "Type of the template",
"enum": ["module", "standalone"],
"x-prompt": {
"message": "Select the type of template to generate:",
"type": "list",
"items": [
{ "value": "module", "label": "Module Template" },
{ "value": "standalone", "label": "Standalone Template" }
]
}
},
```
---
## ABP Suite Code Generation Migration Details
ABP Suite will also be supporting both structures. If you have a project that is generated with the previous versions, the Suite will detect the structure in that way and generate the related code accordingly. Conversely, here is what is changed for the standalone migration:
**❌ Discarded module files**
```ts
// entity-one.module.ts
@NgModule({
declarations: [],
imports: [EntityOneComponent, EntityOneRoutingModule],
})
export class EntityOneModule {}
```
```ts
// entity-one-routing.module.ts
export const routes: Routes = [
{
path: "",
component: EntityOneComponent,
canActivate: [authGuard, permissionGuard],
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class EntityOneRoutingModule {}
```
```ts
// app-routing.module.ts
{
path: 'entity-ones',
loadChildren: () =>
import('./entity-ones/entity-one/entity-one.module').then(m => m.EntityOneModule),
},
```
**✅ Added routes configuration**
```ts
// entity-one.routes.ts
export const ENTITY_ONE_ROUTES: Routes = [
{
path: "",
loadComponent: () => {
return import("./components/entity-one.component").then(
(c) => c.EntityOneComponent
);
},
canActivate: [authGuard, permissionGuard],
},
];
```
```ts
// app.routes.ts
{ path: 'entity-ones', children: ENTITY_ONE_ROUTES },
```
---
## Template Migration Details
### 🧭 Routing: `app.routes.ts`
```ts
// app.routes.ts
import { Routes } from '@angular/router';
export const APP_ROUTES: Routes = [
{
path: '',
pathMatch: 'full',
loadComponent: () => import('./home/home.component').then(m => m.HomeComponent),
},
{
path: 'account',
loadChildren: () => import('@abp/ng.account').then(m => m.createRoutes()),
},
...
];
```
### ⚙ Configuration: `app.config.ts`
```ts
// app.config.ts
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(APP_ROUTES),
APP_ROUTE_PROVIDER,
provideAbpCore(
withOptions({
environment,
registerLocaleFn: registerLocale(),
...
})
),
provideAbpOAuth(),
provideAbpThemeShared(),
...
],
};
```
### 🧼 Removed: `shared.module.ts`
This file has been removed to reduce unnecessary shared imports. Components now explicitly import what they need—leading to better encapsulation and less coupling.
---
## Common Problems
You may encounter these common problems that you would need to manage.
### 1. Missing Imports
In standalone structure, components must declare all their dependencies in `imports`. Forgetting this often causes template errors.
### 2. Mixed Structures
Combining modules and standalone in the same feature leads to confusion. Migrate features fully or keep them module-based.
### 3. Routing Errors
Incorrect migration from `forLazy()` to `createRoutes()` or `loadComponent` can break navigation. Double-check route configs.
### 4. Service Injection
Services provided in old modules may be missing. Add them in the component’s `providers` or `app.config.ts`.
### 5. Shared Module Habit
Reintroducing a shared module reduces the benefits of standalone. Import dependencies directly where needed.
---
## Conclusion
Angular’s standalone component architecture is a significant improvement for scalability, simplicity, and performance. With latest version of ABP Studio, you can adopt this modern approach with ease—without losing support for existing module-based projects.
**Ready to modernize your Angular development?**
Update your ABP Studio today and start building with standalone power!

84
docs/en/docs-nav.json

@ -2345,11 +2345,91 @@
},
{
"text": "CMS Kit",
"path": "modules/cms-kit"
"isLazyExpandable": true,
"path": "modules/cms-kit",
"items": [
{
"text": "Overview",
"path": "modules/cms-kit.md",
"isIndex": true
},
{
"text": "Pages",
"path": "modules/cms-kit/pages.md"
},
{
"text": "Blogging",
"path": "modules/cms-kit/blogging.md"
},
{
"text": "Tag Management",
"path": "modules/cms-kit/tags.md"
},
{
"text": "Comments",
"path": "modules/cms-kit/comments.md"
},
{
"text": "Reaction System",
"path": "modules/cms-kit/reactions.md"
},
{
"text": "Rating System",
"path": "modules/cms-kit/ratings.md"
},
{
"text": "Menus",
"path": "modules/cms-kit/menus.md"
},
{
"text": "Global Resources",
"path": "modules/cms-kit/global-resources.md"
},
{
"text": "Dynamic Widget",
"path": "modules/cms-kit/dynamic-widget.md"
},
{
"text": "Marked Item System",
"path": "modules/cms-kit/marked-items.md"
}
]
},
{
"text": "CMS Kit (Pro)",
"path": "modules/cms-kit-pro"
"isLazyExpandable": true,
"path": "modules/cms-kit-pro",
"items": [
{
"text": "Overview",
"path": "modules/cms-kit-pro.md",
"isIndex": true
},
{
"text": "Newsletter System",
"path": "modules/cms-kit-pro/newsletter.md"
},
{
"text": "Contact Management",
"path": "modules/cms-kit-pro/contact-form.md"
},
{
"text": "URL Forwarding System",
"path": "modules/cms-kit-pro/URL-forwarding.md"
},
{
"text": "Poll System",
"path": "modules/cms-kit-pro/poll.md"
},
{
"text": "Page Feedback System",
"path": "modules/cms-kit-pro/page-feedback.md"
},
{
"text": "FAQ System",
"path": "modules/cms-kit-pro/faq.md"
}
]
},
{
"text": "Docs",

10
docs/en/framework/architecture/multi-tenancy/index.md

@ -231,18 +231,20 @@ services.Configure<AbpAspNetCoreMultiTenancyOptions>(options =>
If you change the `TenantKey`, make sure to pass it to `provideAbpCore` via `withOptions` method in the Angular client as follows:
```js
@NgModule({
// app.config.ts
// ...
export const appConfig: ApplicationConfig = {
providers: [
// ...
provideAbpCore(
withOptions({
// ...
tenantKey: "MyTenantKey",
})
),
// ...
],
// ...
})
export class AppModule {}
};
```
If you need to access it, you can inject it as follows:

176
docs/en/framework/infrastructure/object-to-object-mapping.md

@ -302,6 +302,23 @@ public partial class UserToUserDtoMapper : MapperBase<User, UserDto>
It is suggested to use the `MapExtraPropertiesAttribute` attribute if both classes are extensible objects (implement the `IHasExtraProperties` interface). See the [object extension document](../fundamentals/object-extensions.md) for more.
### Property Setter Method
Mapperly requires that properties of both source and destination objects have `setter` methods. Otherwise, the property will be ignored. You can use `protected set` or `private set` to control the visibility of the `setter` method, but each property must have a `setter` method.
### Deep Cloning
By default, Mapperly does not create deep copies of objects to improve performance. If an object can be directly assigned to the target, it will do so (e.g., if the source and target type are both `List<T>`, the list and its entries will not be cloned). To create deep copies, set the `UseDeepCloning` property on the `MapperAttribute` to `true`.
````csharp
[Mapper(UseDeepCloning = true)]
public partial class UserToUserDtoMapper : MapperBase<User, UserDto>
{
public override partial UserDto Map(User source);
public override partial void Map(User source, UserDto destination);
}
````
### Lists and Arrays Support
ABP Mapperly integration also supports mapping lists and arrays as explained in the [IObjectMapper<TSource, TDestination> Interface](#iobjectmappertsource-tdestination-interface) section.
@ -320,6 +337,165 @@ var users = await _userRepository.GetListAsync(); // returns List<User>
var dtos = ObjectMapper.Map<List<User>, List<UserDto>>(users); // creates List<UserDto>
````
### Nested Mapping
When working with nested object mapping, there's an important limitation to be aware of. If you have separate mappers for nested types like in the example below, the parent mapper (`SourceTypeToDestinationTypeMapper`) will not automatically use the nested mapper (`SourceNestedTypeToDestinationNestedTypeMapper`) to handle the mapping of nested properties. This means that configurations like the `MapperIgnoreTarget` attribute on the nested mapper will be ignored during the parent mapping operation.
````csharp
public class SourceNestedType
{
public string Name { get; set; }
public string Ignored { get; set; }
}
public class SourceType
{
public string Name { get; set; }
public SourceNestedType Nested { get; set; }
}
public class DestinationNestedType
{
public string Name { get; set; }
public string Ignored { get; set; }
}
public class DestinationType
{
public string Name { get; set; }
public DestinationNestedType Nested { get; set; }
}
[Mapper]
public partial class SourceTypeToDestinationTypeMapper : MapperBase<SourceType, DestinationType>
{
public override partial DestinationType Map(SourceType source);
public override partial void Map(SourceType source, DestinationType destination);
}
[Mapper]
public partial class SourceNestedTypeToDestinationNestedTypeMapper : MapperBase<SourceNestedType, DestinationNestedType>
{
[MapperIgnoreTarget(nameof(SourceNestedType.Ignored))]
public override partial DestinationNestedType Map(SourceNestedType source);
[MapperIgnoreTarget(nameof(SourceNestedType.Ignored))]
public override partial void Map(SourceNestedType source, DestinationNestedType destination);
}
````
There are several ways to solve this nested mapping issue. Choose the approach that best fits your specific requirements:
#### Solution 1: Multi-Interface Implementation
Implement both mapping interfaces (`IAbpMapperlyMapper<SourceType, DestinationType>` and `IAbpMapperlyMapper<SourceNestedType, DestinationNestedType>`) in a single mapper class. This approach consolidates all related mapping logic into one class.
**Important:** Remember to implement `ITransientDependency` to register the mapper class with the dependency injection container.
````csharp
[Mapper]
public partial class SourceTypeToDestinationTypeMapper : IAbpMapperlyMapper<SourceType, DestinationType>, IAbpMapperlyMapper<SourceNestedType, DestinationNestedType>, ITransientDependency
{
public partial DestinationType Map(SourceType source);
public partial void Map(SourceType source, DestinationType destination);
public void BeforeMap(SourceType source)
{
}
public void AfterMap(SourceType source, DestinationType destination)
{
}
[MapperIgnoreTarget(nameof(SourceNestedType.Ignored))]
public partial DestinationNestedType Map(SourceNestedType source);
[MapperIgnoreTarget(nameof(SourceNestedType.Ignored))]
public partial void Map(SourceNestedType source, DestinationNestedType destination);
public void BeforeMap(SourceNestedType source)
{
}
public void AfterMap(SourceNestedType source, DestinationNestedType destination)
{
}
}
````
#### Solution 2: Consolidate Mapping Methods
Copy the nested mapping methods from `SourceNestedTypeToDestinationNestedTypeMapper` to the parent `SourceTypeToDestinationTypeMapper` class. This ensures all mapping logic is contained within a single mapper.
Example:
````csharp
[Mapper]
public partial class SourceTypeToDestinationTypeMapper : MapperBase<SourceType, DestinationType>
{
public override partial DestinationType Map(SourceType source);
public override partial void Map(SourceType source, DestinationType destination);
[MapperIgnoreTarget(nameof(SourceNestedType.Ignored))]
public override partial DestinationNestedType Map(SourceNestedType source);
[MapperIgnoreTarget(nameof(SourceNestedType.Ignored))]
public override partial void Map(SourceNestedType source, DestinationNestedType destination);
}
[Mapper]
public partial class SourceNestedTypeToDestinationNestedTypeMapper : MapperBase<SourceNestedType, DestinationNestedType>
{
[MapperIgnoreTarget(nameof(SourceNestedType.Ignored))]
public override partial DestinationNestedType Map(SourceNestedType source);
[MapperIgnoreTarget(nameof(SourceNestedType.Ignored))]
public override partial void Map(SourceNestedType source, DestinationNestedType destination);
}
````
#### Solution 3: Dependency Injection Approach
Inject the nested mapper as a dependency into the parent mapper and use it in the `AfterMap` method to handle nested object mapping manually.
Example:
````csharp
[Mapper]
public partial class SourceTypeToDestinationTypeMapper : MapperBase<SourceType, DestinationType>
{
private readonly SourceNestedTypeToDestinationNestedTypeMapper _sourceNestedTypeToDestinationNestedTypeMapper;
public SourceTypeToDestinationTypeMapper(SourceNestedTypeToDestinationNestedTypeMapper sourceNestedTypeToDestinationNestedTypeMapper)
{
_sourceNestedTypeToDestinationNestedTypeMapper = sourceNestedTypeToDestinationNestedTypeMapper;
}
public override partial DestinationType Map(SourceType source);
public override partial void Map(SourceType source, DestinationType destination);
public override void AfterMap(SourceType source, DestinationType destination)
{
if (source.Nested != null)
{
destination.Nested = _sourceNestedTypeToDestinationNestedTypeMapper.Map(source.Nested);
}
}
}
````
#### Choosing the Right Solution
Each solution has its own advantages:
- **Solution 1** consolidates all mapping logic in one place and works well when mappings are tightly related.
- **Solution 2** is simple but can lead to code duplication if you need the nested mapper elsewhere.
- **Solution 3** maintains separation of concerns and reusability but requires manual mapping in the `AfterMap` method.
Choose the approach that best aligns with your application's architecture and maintainability requirements.
### More Mapperly Features
Most of Mapperly's features such as `Ignore` can be configured through its attributes. See the [Mapperly documentation](https://mapperly.riok.app/docs/intro/) for more details.

71
docs/en/framework/ui/angular/account-module.md

@ -17,36 +17,35 @@ npm install @abp/ng.account
> Make sure v4.3 or higher version is installed.
Open the `app.module.ts` and add `provideAccountConfig()` to the providers array as shown below:
Open the `app.config.ts` and add `provideAccountConfig()` to the providers array as shown below:
```js
// app.module.ts
// app.config.ts
import { provideAccountConfig } from "@abp/ng.account/config";
//...
// ...
@NgModule({
export const appConfig: ApplicationConfig = {
providers: [
//...
// ...
provideAccountConfig(),
// ...
],
//...
})
export class AppModule {}
};
```
Open the `app-routing.module.ts` and add the `account` route to `routes` array as follows:
Open the `app.routes.ts` and add the `account` route to `APP_ROUTES` array as follows:
```js
// app-routing.module.ts
const routes: Routes = [
// app.routes.ts
export const APP_ROUTES: Routes = [
//...
{
path: 'account',
loadChildren: () => import('@abp/ng.account').then(m => m.AccountModule.forLazy()),
loadChildren: () => import('@abp/ng.account').then(c => c.createRoutes()),
},
//...
export class AppRoutingModule {}
];
```
## Account Public Module Implementation for Commercial Templates
@ -59,42 +58,42 @@ npm install @volo/abp.ng.account
> Make sure v4.3 or higher version is installed.
Open the `app.module.ts` and add `AccountPublicConfigModule.forRoot()` to the imports array as shown below:
Open the `app.config.ts` and add `provideAccountPublicConfig()` to the providers array as shown below:
> Ensure that the `Account Layout Module` has been added if you are using the Lepton X theme. If you miss the step, you will get an error message that says `Account layout not found. Please check your configuration. If you are using LeptonX, please make sure you have added "AccountLayoutModule.forRoot()" to your app.module configuration.` when you try to access the account pages. Otherwise, you can skip adding the `AccountLayoutModule` step.
> Ensure that the `Account Layout Provider` has been added if you are using the Lepton X theme. If you miss the step, you will get an error message that says `Account layout not found. Please check your configuration. If you are using LeptonX, please make sure you have added "provideAccountLayout()" to your app configuration.` Otherwise, you can skip adding the `provideAccountLayout()` step.
```js
// app.module.ts
// app.config.ts
import { AccountPublicConfigModule } from "@volo/abp.ng.account/public/config";
// if you are using or want to use Lepton X, you should add AccountLayoutModule
// import { AccountLayoutModule } from '@volosoft/abp.ng.theme.lepton-x/account'
import { provideAccountPublicConfig } from "@volo/abp.ng.account/public/config";
// if you are using or want to use Lepton X, you should add provideAccountLayout
// import { provideAccountLayout } from '@volosoft/abp.ng.theme.lepton-x/account'
//...
@NgModule({
imports: [
//...
AccountPublicConfigModule.forRoot(),
// AccountLayoutModule.forRoot() // Only for Lepton X
export const appConfig: ApplicationConfig = {
providers: [
// ...
provideAccountPublicConfig(),
provideAccountLayout() // Only for Lepton X
// ...
],
//...
})
export class AppModule {}
};
```
Open the `app-routing.module.ts` and add the `account` route to `routes` array as follows:
Open the `app.routes.ts` and add the `account` route to `APP_ROUTES` array as follows:
```js
// app-routing.module.ts
const routes: Routes = [
// app.routes.ts
export const APP_ROUTES: Routes = [
//...
{
path: 'account',
loadChildren: () => import('@volo/abp.ng.account/public').then(m => m.AccountPublicModule.forLazy()),
loadChildren: () => import('@volo/abp.ng.account/public').then(c => c.createRoutes()),
},
//...
export class AppRoutingModule {}
];
```
## My Account Page
@ -108,15 +107,15 @@ When the user changes their own data on the personal settings tab in My Account,
If you want to disable these warning, You should set `isPersonalSettingsChangedConfirmationActive` false
```js
// app-routing.module.ts
const routes: Routes = [
// app.routes.ts
export const APP_ROUTES: Routes = [
//...
{
path: 'account',
loadChildren: () => import('@volo/abp.ng.account/public').then(m => m.AccountPublicModule.forLazy({ isPersonalSettingsChangedConfirmationActive:false })),
loadChildren: () => import('@volo/abp.ng.account/public').then(c => c.create({ isPersonalSettingsChangedConfirmationActive:false })),
},
//...
export class AppRoutingModule {}
];
```
## Security Logs Page [COMMERCIAL]

30
docs/en/framework/ui/angular/authorization.md

@ -61,15 +61,18 @@ The `AuthErrorFilterService` is an abstract service that needs to be replaced wi
### Usage
#### 1.Create an auth-filter.provider
#### 1.Create an auth filter provider
```js
import { APP_INITIALIZER, inject } from '@angular/core';
//auth-filter.provider.ts
import { inject, provideAppInitializer } from '@angular/core';
import { AuthErrorFilter, AuthErrorEvent, AuthErrorFilterService } from '@abp/ng.core';
import { eCustomersAuthFilterNames } from '../enums';
export const CUSTOMERS_AUTH_FILTER_PROVIDER = [
{ provide: APP_INITIALIZER, useFactory: configureAuthFilter, multi: true },
provideAppInitializer(() => {
configureAuthFilter()
}),
];
type Reason = object & { error: { grant_type: string | undefined } };
@ -100,20 +103,17 @@ function configureAuthFilter() {
- `executable:` a status for the filter object. If it's false then it won't work, yet it'll stay in the list
- `execute:` a function that stores the skip logic
#### 2.Add to the FeatureConfigModule
#### 2.Add to the customer configuration provider
```js
import { ModuleWithProviders, NgModule } from "@angular/core";
import { CUSTOMERS_AUTH_FILTER_PROVIDER } from "./providers/auth-filter.provider";
@NgModule()
export class CustomersConfigModule {
static forRoot(): ModuleWithProviders<CustomersConfigModule> {
return {
ngModule: CustomersConfigModule,
providers: [CUSTOMERS_AUTH_FILTER_PROVIDER],
};
}
// customer-config.provider.ts
import { EnvironmentProviders, makeEnvironmentProviders } from "@angular/core";
import { CUSTOMERS_AUTH_FILTER_PROVIDER } from "./auth-filter.provider";
export function provideCustomerConfig(): EnvironmentProviders {
return makeEnvironmentProviders([
CUSTOMERS_AUTH_FILTER_PROVIDER
])
}
```

5
docs/en/framework/ui/angular/basic-theme.md

@ -11,10 +11,9 @@ The Basic Theme is a theme implementation for the Angular UI. It is a minimalist
If you need to manually this theme, follow the steps below:
* Install the [@abp/ng.theme.basic](https://www.npmjs.com/package/@abp/ng.theme.basic) NPM package to your Angular project.
* Open the `src/app/app.module.ts` file, import `ThemeBasicModule`,`provideThemeBasicConfig` (it can be imported from `@abp/ng.theme.basic` package), and add `ThemeBasicModule` to the `imports` array and provide `provideThemeBasicConfig()` to the providers array.
* Open the `src/app/shared/shared.module` file, import `ThemeBasicModule` (it can be imported from `@abp/ng.theme.basic` package), and add `ThemeBasicModule` to the `imports` and `exports` array.
* Open the `src/app/app.config.ts` file, import `provideThemeBasicConfig` (it can be imported from `@abp/ng.theme.basic` package), and provide `provideThemeBasicConfig()` to the providers array.
The `ThemeBasicModule` is registered own layouts (`ApplicationLayoutComponent`, `AccountLayoutComponent`, `EmptyLayoutComponent`) to a service which is exposed by `@abp/ng.core` package on application initialization.
The `BASIC_THEME_STYLES_PROVIDERS` has registered three layouts being `ApplicationLayoutComponent`, `AccountLayoutComponent`, and `EmptyLayoutComponent`. These are provided inside `provideThemeBasicConfig()` function that is exposed by `@abp/ng.theme.basic` package on application initialization.
## Application Layout

51
docs/en/framework/ui/angular/caps-lock-directive.md

@ -5,66 +5,27 @@ In password inputs, You may want to show if Caps Lock is on. To make this even e
## Getting Started
`TrackCapsLockDirective` is standalone. In order to use the `TrackCapsLockDirective` in an HTML template, import it to related module or your standalone component:
**Importing to NgModule**
```ts
import { TrackCapsLockDirective } from '@abp/ng.core';
@NgModule({
//...
declarations: [
...,
TestComponent
],
imports: [
...,
TrackCapsLockDirective
],
})
export class MyFeatureModule {}
```
## Usage
The `TrackCapsLockDirective` is very easy to use. The directive's selector is **`abpCapsLock`**. By adding the `abpCapsLock` event to an element, you can track the status of Caps Lock. You can use this to warn user.
`TrackCapsLockDirective` is standalone. In order to use the `TrackCapsLockDirective` in an HTML template, import it to related component. The selector of the directive is **`abpCapsLock`**. By adding the `abpCapsLock` event to an element, you can track the status of Caps Lock. You can use this to warn user.
See an example usage:
**NgModule Component usage**
```ts
@Component({
selector: 'test-component',
template: `
<div class="d-flex flex-column">
<label>Password</label>
<input (abpCapsLock)="capsLock = $event"/>
<i *ngIf="capsLock">icon</i>
</div>
`
})
export class TestComponent{
capsLock = false;
}
```
**Standalone Component usage**
```ts
import { TrackCapsLockDirective } from '@abp/ng.core'
@Component({
selector: 'standalone-component',
standalone: true,
selector: 'sample-component',
template: `
<div class="d-flex flex-column">
<label>Password</label>
<input (abpCapsLock)="capsLock = $event"/>
<i *ngIf="capsLock">icon</i>
@if (capslock) {
<i>icon</i>
}
</div>
`,
imports: [TrackCapsLockDirective]
})
export class StandaloneComponent{
export class SampleComponent{
capsLock = false;
}
```

98
docs/en/framework/ui/angular/card-component.md

@ -23,27 +23,7 @@ In addition to these components, the Card component provides directives like `Ca
# Usage
ABP Card Component is a part of the `ThemeSharedModule` module. If you've imported that module into your module, you don't need to import it again. If not, first import it as shown below:
```ts
// my-feature.module.ts
import { ThemeSharedModule } from '@abp/ng.theme.shared';
import { CardDemoComponent } from './card-demo.component';
@NgModule({
imports: [
ThemeSharedModule ,
// ...
],
declarations: [CardDemoComponent],
// ...
})
export class MyFeatureModule {}
```
Then, the `abp-card` component can be used. See the examples below:
ABP Card Component is a part of the `theme-shared` package. Once you import the necessary components, you can use them. See the examples below:
## CardBody
@ -51,16 +31,18 @@ Then, the `abp-card` component can be used. See the examples below:
// card-demo.component.ts
import { Component } from '@angular/core';
import { CardComponent, CardBodyComponent } from '@abp/ng.theme.shared';
@Component({
selector: 'app-card-demo',
imports: [CardComponent, CardBodyComponent],
template: `
<abp-card [cardStyle]="{width: '18rem'}">
<abp-card-body>This is some text within a card body</abp-card-body>
</abp-card>
`,
})
export class CardDemoComponent { }
export class CardDemoComponent {}
```
See the card body result below:
@ -72,22 +54,36 @@ See the card body result below:
//card-demo.component.ts
import { Component } from '@angular/core';
import {
CardComponent,
CardBodyComponent,
CardTitleDirective,
CardSubtitleDirective
} from '@abp/ng.theme.shared';
@Component({
selector: 'app-card-demo',
imports: [
CardComponent,
CardBodyComponent,
CardTitleDirective,
CardSubtitleDirective
],
template: `
<abp-card [cardStyle]="{width: '18rem'}">
<abp-card-body>
<h5 abpCardTitle>Card Title</h5>
<h6 abpCardSubtitle class="mb-2 text-muted">Card subtitle</h6>
<p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
<a href="#" class="card-link" >Card link</a>
<a href="#" class="card-link" >Another link</a>
<p class="card-text">
Some quick example text to build on the card title and make up the bulk of the card's content.
</p>
<a href="#" class="card-link">Card link</a>
<a href="#" class="card-link">Another link</a>
</abp-card-body>
</abp-card>
`,
})
export class CardDemoComponent { }
export class CardDemoComponent {}
```
See the card title, text and link result below:
@ -99,19 +95,23 @@ See the card title, text and link result below:
//card-demo.component.ts
import { Component } from '@angular/core';
import { CardComponent, CardBodyComponent, CardImgTopDirective } from '@abp/ng.theme.shared';
@Component({
selector: 'app-card-demo',
imports: [CardComponent, CardBodyComponent, CardImgTopDirective],
template: `
<abp-card [cardStyle]="{width:'18rem'}">
<img abpCardImgTop src="..." alt="...">
<abp-card-body>
<p class="card-text" >Some quick example text to build on the card title and make up the bulk of the card's content.</p>
<p class="card-text">
Some quick example text to build on the card title and make up the bulk of the card's content.
</p>
</abp-card-body>
</abp-card>
`,
})
export class CardDemoComponent { }
export class CardDemoComponent {}
```
See the card image result below:
@ -123,9 +123,11 @@ See the card image result below:
//card-demo.component.ts
import { Component } from '@angular/core';
import { CardComponent } from '@abp/ng.theme.shared';
@Component({
selector: 'app-card-demo',
imports: [CardComponent],
template: `
<abp-card [cardStyle]="{width:'18rem'}">
<ul class="list-group list-group-flush">
@ -136,7 +138,7 @@ import { Component } from '@angular/core';
</abp-card>
`,
})
export class CardDemoComponent { }
export class CardDemoComponent {}
```
See the group list result below:
@ -148,15 +150,29 @@ See the group list result below:
//card-demo.component.ts
import { Component } from '@angular/core';
import {
CardComponent,
CardBodyComponent,
CardImgTopDirective,
CardTitleDirective
} from '@abp/ng.theme.shared';
@Component({
selector: 'app-card-demo',
imports: [
CardComponent,
CardBodyComponent,
CardImgTopDirective,
CardTitleDirective
],
template: `
<abp-card [cardStyle]="{width:'18rem'}">
<img abpCardImgTop src="../../assets/thinh-nguyen-aRrS37GKlVA-unsplash.jpg" alt="...">
<abp-card-body>
<h5 abpCardTitle>Card title</h5>
<p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
<p class="card-text">
Some quick example text to build on the card title and make up the bulk of the card's content.
</p>
</abp-card-body>
<ul class="list-group list-group-flush">
<li class="list-group-item">An item</li>
@ -170,7 +186,7 @@ import { Component } from '@angular/core';
</abp-card>
`,
})
export class CardDemoComponent { }
export class CardDemoComponent {}
```
See kitchen sink result below:
@ -182,15 +198,31 @@ See kitchen sink result below:
//card-demo.component.ts
import { Component } from '@angular/core';
import {
CardComponent,
CardHeaderComponent,
CardBodyComponent,
CardTitleDirective,
CardFooterComponent
} from '@abp/ng.theme.shared';
@Component({
selector: 'app-card-demo',
imports: [
CardComponent,
CardHeaderComponent,
CardBodyComponent,
CardTitleDirective,
CardFooterComponent
],
template: `
<abp-card class="text-center">
<abp-card-header>Featured</abp-card-header>
<abp-card-body>
<h5 abpCardTitle>Special title treatment</h5>
<p class="card-text">With supporting text below as a natural lead-in to additional content.</p>
<p class="card-text">
With supporting text below as a natural lead-in to additional content.
</p>
<a class="card-link" href="#" class="btn btn-primary">Go somewhere</a>
</abp-card-body>
<abp-card-footer class="text-muted">
@ -199,7 +231,7 @@ import { Component } from '@angular/core';
</abp-card>
`,
})
export class CardDemoComponent { }
export class CardDemoComponent {}
```
See the header and footer result below:

36
docs/en/framework/ui/angular/chart-component.md

@ -6,34 +6,16 @@ ABP Chart component exposed by `@abp/ng.components/chart.js` is based on [`chart
## How to Use
First of all, need to import the `ChartModule` to your feature module as follows:
```ts
// your-feature.module.ts
import { ChartModule } from "@abp/ng.components/chart.js";
import { ChartDemoComponent } from "./chart-demo.component";
@NgModule({
imports: [
ChartModule,
// ...
],
declarations: [ChartDemoComponent],
// ...
})
export class YourFeatureModule {}
```
Then, `abp-chart` component can be used. See an example:
First of all, need to import the `ChartComponent` to your component. Then, `abp-chart` component can be used. See an example:
```ts
// chart-demo.component.ts
import { Component } from "@angular/core";
import { ChartComponent } from "@abp/ng.components/chart.js";
@Component({
selector: "app-chart-demo",
imports: [ChartComponent],
template: ` <abp-chart type="pie" [data]="data"></abp-chart> `,
})
export class ChartDemoComponent {
@ -62,9 +44,11 @@ See the result:
```ts
import { Component } from "@angular/core";
import { ChartComponent } from "@abp/ng.components/chart.js";
@Component({
selector: "app-chart-demo",
imports: [ChartComponent],
template: `
<abp-chart
type="doughnut"
@ -73,7 +57,7 @@ import { Component } from "@angular/core";
width="400px"
height="400px"
[plugins]="myPlugin"
></abp-chart>
/>
`,
})
export class ChartDemoComponent {
@ -119,16 +103,18 @@ Result:
```ts
import { Component } from "@angular/core";
import { ChartComponent } from "@abp/ng.components/chart.js";
@Component({
selector: "app-chart-demo",
imports: [ChartComponent]
template: `
<abp-chart
type="bar"
[data]="data"
width="400px"
height="400px"
></abp-chart>
/>
`,
})
export class ChartDemoComponent {
@ -158,16 +144,18 @@ Result:
```ts
import { Component } from "@angular/core";
import { ChartComponent } from "@abp/ng.components/chart.js";
@Component({
selector: "app-chart-demo",
imports: [ChartComponent]
template: `
<abp-chart
type="radar"
[data]="data"
width="400px"
height="400px"
></abp-chart>
/>
<button class="btn btn-primary-outline mt-4" (click)="addDataset()">
Add dataset

141
docs/en/framework/ui/angular/component-replacement.md

@ -6,7 +6,7 @@ The reason that you **can replace** but **cannot customize** default ABP compone
## How to Replace a Component
Create a new component that you want to use instead of an ABP component. Add that component to `declarations` and `entryComponents` in the `AppModule`.
Create a new component that you want to use instead of an ABP component.
Then, open the `app.component.ts` and execute the `add` method of `ReplaceableComponentsService` to replace your component with an ABP component as shown below:
@ -32,7 +32,7 @@ export class AppComponent {
## How to Replace a Layout
Each ABP theme module has 3 layouts named `ApplicationLayoutComponent`, `AccountLayoutComponent`, `EmptyLayoutComponent`. These layouts can be replaced the same way.
Each ABP theme package has 3 layouts named `ApplicationLayoutComponent`, `AccountLayoutComponent`, `EmptyLayoutComponent`. These layouts can be replaced the same way.
> A layout component template should contain `<router-outlet></router-outlet>` element.
@ -70,7 +70,7 @@ export class AppComponent {
}
```
> If you like to replace a layout component at runtime (e.g: changing the layout by pressing a button), pass the second parameter of the `add` method of `ReplaceableComponentsService` as true. DynamicLayoutComponent loads content using a router-outlet. When the second parameter of the `add` method is true, the route will be refreshed, so use it with caution. Your component state will be gone and any initiation logic (including HTTP requests) will be repeated.
> If you would like to replace a layout component at runtime (e.g: changing the layout by pressing a button), pass the second parameter of the `add` method of `ReplaceableComponentsService` as true. DynamicLayoutComponent loads content using a router-outlet. When the second parameter of the `add` method is true, the route will be refreshed, so use it with caution. Your component state will be gone and any initiation logic (including HTTP requests) will be repeated.
### Layout Components
@ -96,8 +96,6 @@ This command will create a new component named `new-layout`. Now, open the new-l
This 'router-outlet' will act as a placeholder that Angular dynamically fills based on the current router state.
note: (don't forget: you should add the app in the app.module.ts file)
#### Step 2: Define a Variable for the Layout Component
Although this step is optional, it can be useful if you're going to use the layout component's value multiple times. You can define a variable for the layout component like this:
@ -116,33 +114,29 @@ You can use this variable when you need to refer to the layout component.
Next, you need to add the new layout component to the `ReplaceableComponentsService`. This service allows you to replace a component with another one dynamically.
You can do this by defining a provider for `APP_INITIALIZER` that uses a factory function. In this function, you inject the `ReplaceableComponentsService` and use its `add` method to add the new layout component.
You can do this by defining a provider for `provideAppInitializer` that uses a factory function. In this function, you inject the `ReplaceableComponentsService` and use its `add` method to add the new layout component.
Here's how you can do it:
```javascript
export const CUSTOM_LAYOUT_PROVIDERS = [
{
provide: APP_INITIALIZER,
useFactory: configureLayoutFn,
deps: [ReplaceableComponentsService],
multi: true,
},
provideAppInitializer(()=>{
configureLayoutFn();
}),
];
function configureLayoutFn() {
const service = inject(ReplaceableComponentsService);
return () => {
service.add({
key: eCustomLayout.component,
component: CustomLayoutComponent,
});
};
service.add({
key: eCustomLayout.component,
component: CustomLayoutComponent,
});
}
```
In this code, `configureLayoutFn` is a factory function that adds the new layout component to the `ReplaceableComponentsService`. The `APP_INITIALIZER` provider runs this function when the application starts.
In this code, `configureLayoutFn` is a factory function that adds the new layout component to the `ReplaceableComponentsService`. The `provideAppInitializer` provider runs this function when the application starts.
note: (don't forget: you should add the CUSTOM_LAYOUT_PROVIDERS in the app.module.ts file)
note: (don't forget: you should add the CUSTOM_LAYOUT_PROVIDERS in the app.config.ts file)
#### Step 4: Define the Application's Dynamic Layouts
@ -154,14 +148,14 @@ You can add the new layout to the existing layouts like this:
export const myDynamicLayouts = new Map<string, string>([...DEFAULT_DYNAMIC_LAYOUTS, [eCustomLayout.key, eCustomLayout.component]]);
```
#### Step 5: Pass the Dynamic Layouts to the CoreModule
#### Step 5: Pass the Dynamic Layouts to the Core Provider
The final step is to pass the dynamic layouts to the `provideAbpCore` using the `withOptions` method. This method allows you to configure the module with a static method.
The final step is to pass the dynamic layouts to the `provideAbpCore` using the `withOptions` method. This method allows you to configure the provider with a static method.
Here's how you can do it:
```ts
@NgModule({
export const appConfig: ApplicationConfig = {
providers: [
// ...
provideAbpCore(
@ -172,8 +166,7 @@ Here's how you can do it:
}),
),
],
})
export class AppModule {}
};
```
In this code, `myDynamicLayouts` is the map of dynamic layouts you defined earlier. We pass this map to the `provideAbpCore` using the `withOptions` method.
@ -186,32 +179,33 @@ Here's how you can do it:
// route.provider.ts
import { eCustomLayout } from './custom-layout/custom-layout.provider';
import { RoutesService, eLayoutType } from '@abp/ng.core';
import { APP_INITIALIZER } from '@angular/core';
import { provideAppInitializer } from '@angular/core';
export const APP_ROUTE_PROVIDER = [
{ provide: APP_INITIALIZER, useFactory: configureRoutes, deps: [RoutesService], multi: true },
provideAppInitializer(() => {
configureRoutes();
}),
];
function configureRoutes(routes: RoutesService) {
return () => {
routes.add([
{
path: '/',
name: '::Menu:Home',
iconClass: 'fas fa-home',
order: 1,
layout: eLayoutType.application,
},
{
path: '/dashboard',
name: '::Menu:Dashboard',
iconClass: 'fas fa-chart-line',
order: 2,
layout: eCustomLayout.key as eLayoutType,
requiredPolicy: 'MyProjectName.Dashboard.Host || MyProjectName.Dashboard.Tenant',
},
]);
};
function configureRoutes() {
const routes = inject(RoutesService);
routes.add([
{
path: '/',
name: '::Menu:Home',
iconClass: 'fas fa-home',
order: 1,
layout: eLayoutType.application,
},
{
path: '/dashboard',
name: '::Menu:Dashboard',
iconClass: 'fas fa-chart-line',
order: 2,
layout: eCustomLayout.key as eLayoutType,
requiredPolicy: 'MyProjectName.Dashboard.Host || MyProjectName.Dashboard.Tenant',
},
]);
}
```
@ -288,10 +282,23 @@ Open the generated `routes.component.ts` in `src/app/routes` folder and replace
```js
import { Component, HostBinding } from "@angular/core";
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
import { LocalizationPipe, PermissionDirective } from "@abp/ng.core";
import { EllipsisDirective } from '@abp/ng.theme.shared';
@Component({
selector: "app-routes",
templateUrl: "routes.component.html",
imports: [
CommonModule,
RouterModule,
NgbDropdownModule,
PermissionDirective,
EllipsisDirective,
LocalizationPipe,
]
})
export class RoutesComponent {
@HostBinding("class.mx-auto")
@ -303,21 +310,6 @@ export class RoutesComponent {
}
```
Import the `SharedModule` to the `imports` array of `AppModule`:
```js
// app.module.ts
import { SharedModule } from './shared/shared.module';
@NgModule({
imports: [
//...
SharedModule
]
)}
```
Open the generated `routes.component.html` in `src/app/routes` folder and replace its content with the following:
```html
@ -482,8 +474,12 @@ import {
LanguageInfo,
NAVIGATE_TO_MANAGE_PROFILE,
SessionStateService,
LocalizationPipe
} from '@abp/ng.core';
import { Component, Inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import snq from 'snq';
@ -491,6 +487,12 @@ import snq from 'snq';
@Component({
selector: 'app-nav-items',
templateUrl: 'nav-items.component.html',
imports: [
CommonModule,
FormsModule,
NgbDropdownModule,
LocalizationPipe
]
})
export class NavItemsComponent {
currentUser$: Observable<CurrentUserDto> = this.configState.getOne$('currentUser');
@ -549,21 +551,6 @@ export class NavItemsComponent {
}
```
Import the `SharedModule` to the `imports` array of `AppModule`:
```js
// app.module.ts
import { SharedModule } from './shared/shared.module';
@NgModule({
imports: [
//...
SharedModule
]
)}
```
Open the generated `nav-items.component.html` in `src/app/nav-items` folder and replace the content with the following:
```html

50
docs/en/framework/ui/angular/config-state-service.md

@ -13,11 +13,11 @@ import { ConfigStateService } from '@abp/ng.core';
/* class metadata here */
})
class DemoComponent {
constructor(private config: ConfigStateService) {}
private config = inject(ConfigStateService);
}
```
You do not have to provide the `ConfigStateService` at module or component/directive level, because it is already **provided in root**.
You do not have to provide the `ConfigStateService` at component or directive level, because it is already **provided in root**.
## Get Methods
@ -35,9 +35,9 @@ You can use the `getAll` or `getAll$` method of `ConfigStateService` to get all
const config = this.config.getAll();
// or
this.config.getAll$().subscribe(config => {
// use config here
})
this.config.getAll$().subscribe((config) => {
// use config here
});
```
### How to Get a Specific Configuration
@ -50,9 +50,9 @@ You can use the `getOne` or `getOne$` method of `ConfigStateService` to get a sp
const currentUser = this.config.getOne("currentUser");
// or
this.config.getOne$("currentUser").subscribe(currentUser => {
// use currentUser here
})
this.config.getOne$("currentUser").subscribe((currentUser) => {
// use currentUser here
});
```
On occasion, you will probably want to be more specific than getting just the current user. For example, here is how you can get the `tenantId`:
@ -61,9 +61,9 @@ On occasion, you will probably want to be more specific than getting just the cu
const tenantId = this.config.getDeep("currentUser.tenantId");
// or
this.config.getDeep$("currentUser.tenantId").subscribe(tenantId => {
// use tenantId here
})
this.config.getDeep$("currentUser.tenantId").subscribe((tenantId) => {
// use tenantId here
});
```
or by giving an array of keys as parameter:
@ -84,9 +84,11 @@ You can use the `getFeature` or `getFeature$` method of `ConfigStateService` to
const enableLdapLogin = this.config.getFeature("Account.EnableLdapLogin");
// or
this.config.getFeature$("Account.EnableLdapLogin").subscribe(enableLdapLogin => {
// use enableLdapLogin here
})
this.config
.getFeature$("Account.EnableLdapLogin")
.subscribe((enableLdapLogin) => {
// use enableLdapLogin here
});
```
> For more information, see the [features document](./features.md).
@ -98,12 +100,16 @@ You can use the `getSetting` or `getSetting$` method of `ConfigStateService` to
```js
// this.config is instance of ConfigStateService
const twoFactorBehaviour = this.config.getSetting("Abp.Identity.TwoFactor.Behaviour");
const twoFactorBehaviour = this.config.getSetting(
"Abp.Identity.TwoFactor.Behaviour"
);
// or
this.config.getSetting$("Abp.Identity.TwoFactor.Behaviour").subscribe(twoFactorBehaviour => {
// use twoFactorBehaviour here
})
this.config
.getSetting$("Abp.Identity.TwoFactor.Behaviour")
.subscribe((twoFactorBehaviour) => {
// use twoFactorBehaviour here
});
```
> For more information, see the [settings document](./settings.md).
@ -112,7 +118,6 @@ this.config.getSetting$("Abp.Identity.TwoFactor.Behaviour").subscribe(twoFactorB
Please refer to `ApplicationConfigurationDto` type for all the properties you can get with `getOne` and `getDeep`. It can be found in the [models.ts file](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/proxy/volo/abp/asp-net-core/mvc/application-configurations/models.ts#L11).
## Set State
`ConfigStateService` has a method named `setState` which allow you to set the state value.
@ -120,9 +125,12 @@ Please refer to `ApplicationConfigurationDto` type for all the properties you ca
You can get the application configuration response and set the `ConfigStateService` state value as shown below:
```js
import {AbpApplicationConfigurationService, ConfigStateService} from '@abp/ng.core';
import { AbpApplicationConfigurationService, ConfigStateService } from '@abp/ng.core';
private abpApplicationConfigurationService = inject(AbpApplicationConfigurationService);
private config = inject(ConfigStateService);
constructor(private abpApplicationConfigurationService: AbpApplicationConfigurationService, private config: ConfigStateService) {
constructor() {
this.abpApplicationConfigurationService.get({ includeLocalizationResources: false }).subscribe(config => {
this.config.setState(config);
})

25
docs/en/framework/ui/angular/confirmation-service.md

@ -4,7 +4,7 @@ You can use the `ConfirmationService` in @abp/ng.theme.shared package to display
## Getting Started
You do not have to provide the `ConfirmationService` at module or component level, because it is already **provided in root**. You can inject and start using it immediately in your components, directives, or services.
You do not have to provide the `ConfirmationService` at component level, because it is already **provided in root**. You can inject and start using it immediately in your components, directives, or services.
```js
import { ConfirmationService } from '@abp/ng.theme.shared';
@ -129,24 +129,25 @@ this.confirmation.clear();
### How to Change Icons of The Confirmation Popup
You can change icons with the `withConfirmationIcon()` method of `provideAbpThemeShared` function in the app.module.ts. The changes will affect all confirmation popup in the project.
You can change icons with the `withConfirmationIcon()` method inside `provideAbpThemeShared` function in the app.config.ts. The changes will affect all confirmation popup in the project.
```ts
import { provideAbpThemeShared, withConfirmationIcon } from '@abp/ng.theme.shared';
@NgModule({
export const appConfig: ApplicationConfig = {
providers: [
// ...
provideAbpThemeShared(withConfirmationIcon({
info: 'fa fa-info-circle',
success: 'fa fa-check-circle',
warning: 'fa fa-exclamation-triangle',
error: 'fa fa-times-circle',
default: 'fa fa-question-circle',
})),
provideAbpThemeShared(
withConfirmationIcon({
info: 'fa fa-info-circle',
success: 'fa fa-check-circle',
warning: 'fa fa-exclamation-triangle',
error: 'fa fa-times-circle',
default: 'fa fa-question-circle',
})
),
],
})
export class AppModule {}
};
```
## API

16
docs/en/framework/ui/angular/data-table-column-extensions.md

@ -14,7 +14,7 @@ In this example, we will add a "Name" column and display the value of the `name`
### Step 1. Create Entity Prop Contributors
The following code prepares a constant named `identityEntityPropContributors`, ready to be imported and used in your root module:
The following code prepares a constant named `identityEntityPropContributors`, ready to be imported and used in your root application configuration:
```js
// src/app/entity-prop-contributors.ts
@ -52,22 +52,22 @@ The list of props, conveniently named as `propList`, is a **doubly linked list**
### Step 2. Import and Use Entity Prop Contributors
Import `identityEntityPropContributors` in your routing module and pass it to the static `forLazy` method of `IdentityModule` as seen below:
Import `identityEntityPropContributors` in your routing configuration and pass it to the static `createRoutes` method for `identity` route as seen below:
```js
// src/app/app-routing.module.ts
// src/app/app.routes.ts
// other imports
import { identityEntityPropContributors } from './entity-prop-contributors';
const routes: Routes = [
export const APP_ROUTES: Routes = [
// other routes
{
path: 'identity',
loadChildren: () =>
import('@abp/ng.identity').then(m =>
m.IdentityModule.forLazy({
import('@abp/ng.identity').then(c =>
c.createRoutes({
entityPropContributors: identityEntityPropContributors,
})
),
@ -77,7 +77,7 @@ const routes: Routes = [
];
```
That is it, `nameProp` entity prop will be added, and you will see the "Name" column next to the usernames on the grid in the users page (`UsersComponent`) of the `IdentityModule`.
That is it, `nameProp` entity prop will be added, and you will see the "Name" column next to the usernames on the grid in the users page (`UsersComponent`) of the `identity` package.
## How to Render Custom HTML in Cells
@ -318,7 +318,7 @@ export function reorderUserContributors(
### EntityPropContributorCallback\<R = any\>
`EntityPropContributorCallback` is the type that you can pass as entity prop contributor callbacks to static `forLazy` methods of the modules.
`EntityPropContributorCallback` is the type that you can pass as entity prop contributor callbacks to static `createRoutes` methods of the packages.
```js
export function isLockedOutPropContributor(

18
docs/en/framework/ui/angular/dynamic-form-extensions.md

@ -15,7 +15,7 @@ In this example, we will add a "Date of Birth" field in the user management page
### Step 1. Create Form Prop Contributors
The following code prepares two constants named `identityCreateFormPropContributors` and `identityEditFormPropContributors`, ready to be imported and used in your root module:
The following code prepares two constants named `identityCreateFormPropContributors` and `identityEditFormPropContributors`, ready to be imported and used in your root application configuration:
```js
// src/app/form-prop-contributors.ts
@ -57,10 +57,10 @@ The list of props, conveniently named as `propList`, is a **doubly linked list**
### Step 2. Import and Use Form Prop Contributors
Import `identityCreateFormPropContributors` and `identityEditFormPropContributors` in your routing module and pass it to the static `forLazy` method of `IdentityModule` as seen below:
Import `identityCreateFormPropContributors` and `identityEditFormPropContributors` in your routing configuration and pass it to the static `createRoutes` method for `identity` route as seen below:
```js
// src/app/app-routing.module.ts
// src/app/app.routes.ts
// other imports
import {
@ -68,14 +68,14 @@ import {
identityEditFormPropContributors,
} from './form-prop-contributors';
const routes: Routes = [
export const APP_ROUTES: Routes = [
// other routes
{
path: 'identity',
loadChildren: () =>
import('@abp/ng.identity').then(m =>
m.IdentityModule.forLazy({
import('@abp/ng.identity').then(c =>
c.createRoutes({
createFormPropContributors: identityCreateFormPropContributors,
editFormPropContributors: identityEditFormPropContributors,
})
@ -86,7 +86,7 @@ const routes: Routes = [
];
```
That is it, `birthdayProp` form prop will be added, and you will see the datepicker for the "Date of Birth" field right before the "Email address" in the forms of the users page in the `IdentityModule`.
That is it, `birthdayProp` form prop will be added, and you will see the datepicker for the "Date of Birth" field right before the "Email address" in the forms of the users page in the `identity` package.
## Object Extensions
@ -309,7 +309,7 @@ export function reorderUserContributors(
### CreateFormPropContributorCallback\<R = any\>
`CreateFormPropContributorCallback` is the type that you can pass as **create form** prop contributor callbacks to static `forLazy` methods of the modules.
`CreateFormPropContributorCallback` is the type that you can pass as **create form** prop contributor callbacks to static `createRoutes` methods of the packages.
```js
export function myPropCreateContributor(
@ -326,7 +326,7 @@ export const identityCreateFormPropContributors = {
### EditFormPropContributorCallback\<R = any\>
`EditFormPropContributorCallback` is the type that you can pass as **edit form** prop contributor callbacks to static `forLazy` methods of the modules.
`EditFormPropContributorCallback` is the type that you can pass as **edit form** prop contributor callbacks to static `createRoutes` methods of the packages.
```js
export function myPropEditContributor(

49
docs/en/framework/ui/angular/ellipsis-directive.md

@ -5,47 +5,26 @@ Text inside an HTML element can be truncated easily with an ellipsis by using CS
## Getting Started
In order to use the `EllipsisDirective` in an HTML template, the **`ThemeSharedModule`** should be imported into your module like this:
In order to use the `EllipsisDirective` in an HTML template, it should be imported in your component. The selector of directive is **`abpEllipsis`**. By adding the `abpEllipsis` attribute to an HTML element, you can activate the `EllipsisDirective` for the HTML element.
```js
// ...
import { ThemeSharedModule } from '@abp/ng.theme.shared';
import { EllipsisDirective } from '@abp/ng.theme.shared';
@NgModule({
@Component({
//...
imports: [..., ThemeSharedModule],
imports: [EllipsisDirective],
template: `
<p abpEllipsis>
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Laboriosam commodi quae aspernatur,
corporis velit et suscipit id consequuntur amet minima expedita cum reiciendis dolorum
cupiditate? Voluptas eaque voluptatum odio deleniti quo vel illum nemo accusamus nulla ratione
impedit dolorum expedita necessitatibus fugiat ullam beatae, optio eum cupiditate ducimus
architecto.
</p>
`
})
export class MyFeatureModule {}
```
or **if you would not like to import** the `ThemeSharedModule`, you can import the **`EllipsisModule`** as shown below:
```js
// ...
import { EllipsisModule } from '@abp/ng.theme.shared';
@NgModule({
//...
imports: [..., EllipsisModule],
})
export class MyFeatureModule {}
```
## Usage
The `EllipsisDirective` is very easy to use. The directive's selector is **`abpEllipsis`**. By adding the `abpEllipsis` attribute to an HTML element, you can activate the `EllipsisDirective` for the HTML element.
See an example usage:
```html
<p abpEllipsis>
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Laboriosam commodi quae aspernatur,
corporis velit et suscipit id consequuntur amet minima expedita cum reiciendis dolorum
cupiditate? Voluptas eaque voluptatum odio deleniti quo vel illum nemo accusamus nulla ratione
impedit dolorum expedita necessitatibus fugiat ullam beatae, optio eum cupiditate ducimus
architecto.
</p>
export class SampleComponent {}
```
The `abpEllipsis` attribute has been added to the `<p>` element that containing very long text inside to activate the `EllipsisDirective`.

93
docs/en/framework/ui/angular/entity-action-extensions.md

@ -14,7 +14,7 @@ In this example, we will add a "Click Me!" action and alert the current row's `u
### Step 1. Create Entity Action Contributors
The following code prepares a constant named `identityEntityActionContributors`, ready to be imported and used in your root module:
The following code prepares a constant named `identityEntityActionContributors`, ready to be imported and used in your root application configuration:
```ts
// src/app/entity-action-contributors.ts
@ -49,22 +49,22 @@ The list of actions, conveniently named as `actionList`, is a **doubly linked li
### Step 2. Import and Use Entity Action Contributors
Import `identityEntityActionContributors` in your routing module and pass it to the static `forLazy` method of `IdentityModule` as seen below:
Import `identityEntityActionContributors` in your routing configuration and pass it to the static `configureRoutes` method for `identity` routes as seen below:
```js
// src/app/app-routing.module.ts
// src/app/app.routes.ts
// other imports
import { identityEntityActionContributors } from './entity-action-contributors';
const routes: Routes = [
export const APP_ROUTES: Routes = [
// other routes
{
path: 'identity',
loadChildren: () =>
import('@abp/ng.identity').then(m =>
m.IdentityModule.forLazy({
import('@abp/ng.identity').then(c =>
c.createRoutes({
entityActionContributors: identityEntityActionContributors,
})
),
@ -74,11 +74,11 @@ const routes: Routes = [
];
```
That is it, `alertUserName` entity action will be added as the last action on the grid dropdown in the "Users" page (`UsersComponent`) of the `IdentityModule`.
That is it, `alertUserName` entity action will be added as the last action on the grid dropdown in the "Users" page (`UsersComponent`) of the `identity` package.
## How to Place a Custom Modal and Trigger It by Entity Actions
Let's employ dependency injection to extend the functionality of `IdentityModule` and add a quick view action for the User entity. We will take a lazy-loaded approach.
Let's employ dependency injection to extend the functionality of `identity` package and add a quick view action for the User entity. We will take a lazy-loaded approach.
<img alt="Entity Action Extension Example: Custom Modal" src="./images/entity-action-extensions---custom-modal.gif" width="800px" style="max-width:100%">
@ -117,16 +117,27 @@ Let's employ dependency injection to extend the functionality of `IdentityModule
};
```
3. Create a parent component to the identity module.
3. Create a parent component to the identity package.
```js
// src/app/identity-extended/identity-extended.component.ts
import { IdentityUserDto } from '@abp/ng.identity';
import { LocalizationPipe } from '@abp/ng.core';
import { IdentityUserDto } from '@abp/ng.identity/proxy';
import { ModalCloseDirective, ModalComponent } from '@abp/ng.theme.shared';
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
@Component({
selector: 'app-identity-extended',
templateUrl: './identity-extended.component.html',
imports: [
CommonModule,
ModalComponent,
RouterOutlet,
LocalizationPipe,
ModalCloseDirective
]
})
export class IdentityExtendedComponent {
isUserQuickViewVisible: boolean;
@ -184,55 +195,47 @@ Let's employ dependency injection to extend the functionality of `IdentityModule
</abp-modal>
```
5. Add a module for the component and load `IdentityModule` as seen below:
5. Add a routing configuration for the component as seen below:
```js
// src/app/identity-extended/identity-extended.module.ts
// src/app/identity-extended/identity-extended.routes.ts
import { CoreModule } from '@abp/ng.core';
import { IdentityModule } from '@abp/ng.identity';
import { ThemeSharedModule } from '@abp/ng.theme.shared';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { identityEntityActionContributors } from './entity-action-contributors';
import { Routes } from '@angular/router';
import { IdentityExtendedComponent } from './identity-extended.component';
import { identityEntityActionContributors } from './entity-action-contributors';
@NgModule({
imports: [
CoreModule,
ThemeSharedModule,
RouterModule.forChild([
export const createExtendedIdentityRoutes = (): Routes => [
{
path: '',
component: IdentityExtendedComponent,
children: [
{
path: '',
component: IdentityExtendedComponent,
children: [
{
path: '',
loadChildren: () =>
IdentityModule.forLazy({
entityActionContributors: identityEntityActionContributors,
}),
},
],
loadChildren: () =>
import('@abp/ng.identity').then(c =>
c.createRoutes({
entityActionContributors: identityEntityActionContributors,
}),
),
},
]),
],
declarations: [IdentityExtendedComponent],
})
export class IdentityExtendedModule {}
],
},
];
```
6. Load `IdentityExtendedModule` instead of `IdentityModule` in your root routing module.
6. Use `createExtendedIdentityRoutes` instead of the `createRoutes` function in your root routing configuration.
Since the routes are already lazily loaded in the `createExtendedIdentityRoutes` function, you can directly use its children array to avoid an unnecessary additional lazy-loading call.
```js
// src/app/app-routing.module.ts
// src/app/app.routes.ts
const routes: Routes = [
export const APP_ROUTES: Routes = [
// other routes
{
path: 'identity',
loadChildren: () =>
import('./identity-extended/identity-extended.module')
.then(m => m.IdentityExtendedModule),
children: [
...createExtendedIdentityRoutes()
],
},
// other routes
@ -387,7 +390,7 @@ export function reorderUserContributors(
### EntityActionContributorCallback\<R = any\>
`EntityActionContributorCallback` is the type that you can pass as entity action contributor callbacks to static `forLazy` methods of the modules.
`EntityActionContributorCallback` is the type that you can pass as entity action contributor callbacks to static `createRoutes` methods of the packages.
```js
// lockUserContributor should have EntityActionContributorCallback<IdentityUserDto> type

33
docs/en/framework/ui/angular/entity-filters.md

@ -5,27 +5,9 @@ Every CRUD page includes some sort of inputs to filter the listed data. Some of
## Setup
The components are in the _@volo/abp.commercial.ng.ui_ package, which is included in the ABP templates. So, as long as your project is a product of these templates and unless you delete the package, you have access to the entity filter components.
You can either import the `CommercialUiModule` which contains other components as well as `AdvancedEntityFilters` or you can directly import the `AdvancedEntityFiltersModule` if you do not need other components. Here is how you import them in your Angular module:
```javascript
import {
CommercialUiModule,
AdvancedEntityFiltersModule,
} from "@volo/abp.commercial.ng.ui";
@NgModule({
imports: [
// other imports
CommercialUiModule,
// OR
AdvancedEntityFiltersModule,
],
// rest of the module metadata
})
export class YourModule {}
```
Advanced entity filters are composed of several components: `AdvancedEntityFiltersComponent`, `AdvancedEntityFiltersToggleComponent`, `AdvancedEntityFiltersFormComponent`, and `AdvancedEntityFiltersAboveSearchComponent`. You can use these components directly by importing them into your standalone components.
## Usage
@ -33,7 +15,7 @@ Let's take a look at the `Users` page from the `Identity` module.
![ABP Angular UI Users Page with Advanced Entity Filters](./images/angular-advanced-entity-filters.png)
As shown in the screenshot, `abp-advanced-entity-filters` usually contain two parts, an entity filter (common among entities), i.e. `abp-entity-filter`, and entity-specific filters which are encapsulated within the `abp-advanced-entity-filters-form` component.
As shown in the screenshot, `abp-advanced-entity-filters` usually contain two parts, an entity filter (common among entities), i.e. `abp-entity-filter`, and entity-specific filters which are encapsulated within the `abp-advanced-entity-filters-form` component. You will need to add `AdvancedEntityFiltersComponent` and `AdvancedEntityFiltersFormComponent` to your components' imports array to be able to use them.
`users.component.html`
@ -72,7 +54,7 @@ As shown in the screenshot, `abp-advanced-entity-filters` usually contain two pa
</abp-advanced-entity-filters>
```
The `abp-advanced-entity-filters` already contains the `abp-entity-filter` component so you do not need to pass it. However, the `abp-entity-filter` component needs an instance of `ListService` which is usually stored in the `list` field of the page. You can also change the placeholder of the component via `entityFilterPlaceholder` input which is passed into the `abpLocalization` pipe so that it uses the translated text. Default is `'AbpUi::PagerSearch'`
The `abp-advanced-entity-filters` already contains the `abp-entity-filter` component so you do not need to pass it. However, the `abp-entity-filter` component needs an instance of `ListService` which is usually stored in the `list` field of the page. You can also change the placeholder of the component via `entityFilterPlaceholder` input which is passed into the `abpLocalization` pipe so that it uses the translated text. The default is `'AbpUi::PagerSearch'`
E.g
@ -100,8 +82,7 @@ E.g.
Let's remove `form` from the `Users` page
```html
<abp-advanced-entity-filters [list]="list" localizationSourceName="AbpIdentity">
</abp-advanced-entity-filters>
<abp-advanced-entity-filters [list]="list" localizationSourceName="AbpIdentity" />
```
![ABP Angular UI Users Page with Advanced Entity Filters without form](./images/angular-advanced-entity-filters-without-form.png)
@ -122,7 +103,7 @@ E.g.
![ABP Angular UI Users Page with Advanced Entity Filters with form](./images/angular-advanced-entity-filters-with-form.png)
Last but not least, if you need to render some content above the `abp-entity-filter` component, you can use the `abp-advanced-entity-filters-above-search`.
Last but not least, if you need to render some content above the `abp-entity-filter` component, you can use the `abp-advanced-entity-filters-above-search`. This time, you will need to add `AdvancedEntityFiltersComponent`, `AdvancedEntityFiltersFormComponent`, and `AdvancedEntityFiltersAboveSearchComponent` to the imports' array of your component.
E.g.

26
docs/en/framework/ui/angular/environment.md

@ -101,22 +101,28 @@ export interface RemoteEnv {
- `method`: HTTP method to be used when retrieving environment config. Default: `GET`
- `headers`: If extra headers are needed for the request, it can be set through this field.
## Provide Environment Variable to Core Module
## Configure Core Provider with Environment
`environment` variable comes from angular host application.
```js
import { environment } from '../environments/environment';
@NgModule({
imports: [
//...other imports
CoreModule.forRoot({
environment
}),
]
})
export const appConfig: ApplicationConfig = {
providers: [
...
provideAbpCore(
withOptions({
environment,
...
})
),
...
],
};
```
## EnvironmentService
` EnvironmentService` is a singleton service, i.e. provided in root level of your application, and keeps the environment in the internal store.
@ -132,7 +138,7 @@ import { EnvironmentService } from '@abp/ng.core';
/* class metadata here */
})
class DemoComponent {
constructor(private environment: EnvironmentService) {}
private environment = inject(EnvironmentService);
}
```

4
docs/en/framework/ui/angular/extensions-overall.md

@ -21,8 +21,8 @@ Using [ngx-datatable](https://github.com/swimlane/ngx-datatable) in extensible t
[actionsColumnWidth]="38"
[actionsTemplate]="customAction"
[list]="list"
(tableActivate)="onTableSelect($event)" >
</abp-extensible-table>
(tableActivate)="onTableSelect($event)"
/>
````
* ` actionsText : ` ** Column name of action column. **Type** : string

41
docs/en/framework/ui/angular/feature-libraries.md

@ -4,10 +4,10 @@ ABP has an ever-growing number of feature modules and [introducing a new one](..
## Feature Library Content
Each library has at least two modules:
Each library has at least two key elements:
1. The main module contains all components, services, types, enums, etc. to deliver the required UI when the feature is loaded. From here on, we will refer to these modules as **"feature module"**.
2. There is also a **"config module"** per library which helps us configure applications to run these modules or make them accessible.
1. A **feature definition** that encapsulates all components, services, types, enums, and routing logic needed to deliver the UI for a given feature. With standalone structure, this is often expressed through a `routes.ts` file and associated components, and we will refer to this as the **"feature structure"**.
2. A **configuration provider** that exposes setup logic, such as `provideMyProjectNameConfig()` functions or environment, specific tokens—allowing the feature to be initialized or integrated differently across applications. We will refer to this as the **configuration structure**.
## How to Add a Feature Library to Your Project
@ -37,55 +37,46 @@ yarn add @abp/ng.identity
> Identity is used just as an example. If you have initiated your project with ABP CLI or ABP Suite, the identity library will already be installed and configured in your project.
### 2. Import the Config Module
### 2. Import the Configuration Provider
As of ABP v3.0, every lazy-loaded module has a config module available via a secondary entry point on the same package. Importing them in your root module looks like this:
As of ABP v9.3, every lazy-loaded route has a config provider available via a secondary entry point on the same package. Importing them in your root configuration looks like this:
```ts
import { provideIdentityConfig } from "@abp/ng.identity/config";
@NgModule({
export const appConfig: ApplicationConfig = {
providers: [
// other imports
// other providers
provideIdentityConfig(),
],
// providers, declarations, and bootstrap
})
export class AppModule {}
};
```
We need the config modules for actions required before feature modules are loaded (lazily). For example, the above import configures the menu to display links to identity pages.
We need the config providers for actions required before feature structure is loaded (lazily). For example, the above import configures the menu to display links to identity pages.
Furthermore, depending on the library, the `.forRoot` static method may receive some options that configure how the feature works.
Furthermore, depending on the library, the `.createRoutes` static method may receive some options that configure how the feature works.
### 3. Import the Feature Module
### 3. Import the Feature Definition
Finally, the feature module should be [loaded lazily via Angular router](https://angular.io/guide/lazy-loading-ngmodules). If you open the `/src/app/app-routing.module.ts` file, you should see `IdentityModule` is loaded exactly as follows:
Finally, the feature structure should be [loaded lazily via Angular router](https://angular.dev/reference/migrations/route-lazy-loading). In a standalone setup, routing is typically defined in a `app.routes.ts` file, and feature modules are replaced with route-level feature definitions. You should see the identity routes configured like this:
```js
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { Routes } from "@angular/router";
const routes: Routes = [
const APP_ROUTES: Routes = [
// other routes
{
path: "identity",
loadChildren: () =>
import("@abp/ng.identity").then((m) => m.IdentityModule.forLazy()),
import("@abp/ng.identity").then((m) => m.createRoutes()),
},
// other routes
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}
```
When you load the identity feature like this, the "Users" page, for example, will have a route path of `/identity/users`. <sup id="a-modify-route">[1](#f-modify-route)</sup>
Depending on the library, the `.forLazy` static method may also receive some options that configure how the feature works.
Depending on the library, the `.createRoutes` static method may also receive some options that configure how the feature works.
---

62
docs/en/framework/ui/angular/form-validation.md

@ -6,31 +6,31 @@ Reactive forms in ABP Angular UI are validated by [ngx-validate](https://www.npm
## How to Add New Error Messages
You can add a new error message by passing validation options to the `withValidationBluePrint` method of `provideAbpThemeShared` function in your root module.
You can add a new error message by passing validation options to the `withValidationBluePrint` method inside `provideAbpThemeShared` function in your root application configuration.
```ts
import { provideAbpThemeShared, withValidationBluePrint } from '@abp/ng.theme.shared';
@NgModule({
export const appConfig: ApplicationConfig = {
providers: [
// ...
provideAbpThemeShared(
withValidationBluePrint({
uniqueUsername: "::AlreadyExists[{%{{{ username }}}%}]"
})
),
...
// ...
],
})
export class AppModule {}
};
```
Alternatively, you may provide the `VALIDATION_BLUEPRINTS` token directly in your root module. Please do not forget to spread `DEFAULT_VALIDATION_BLUEPRINTS`. Otherwise, built-in ABP validation messages will not work.
Alternatively, you may provide the `VALIDATION_BLUEPRINTS` token directly in your root configuration. Please do not forget to spread `DEFAULT_VALIDATION_BLUEPRINTS`. Otherwise, built-in ABP validation messages will not work.
```js
import { VALIDATION_BLUEPRINTS } from "@ngx-validate/core";
import { DEFAULT_VALIDATION_BLUEPRINTS } from "@abp/ng.theme.shared";
@NgModule({
export const appConfig: ApplicationConfig = {
providers: [
{
provide: VALIDATION_BLUEPRINTS,
@ -42,10 +42,7 @@ import { DEFAULT_VALIDATION_BLUEPRINTS } from "@abp/ng.theme.shared";
// other providers
],
// rest of the module metadata
})
export class AppModule {}
};
```
When a [validator](https://angular.io/guide/form-validation#defining-custom-validators) or an [async validator](https://angular.io/guide/form-validation#creating-asynchronous-validators) returns an error with the key given to the error blueprints (`uniqueUsername` here), the validation library will be able to display an error message after localizing according to the given key and interpolation params. The result will look like this:
@ -61,7 +58,7 @@ In this example;
## How to Change Existing Error Messages
You can overwrite an existing error message by passing validation options to the `ThemeSharedModule` in your root module. Let's imagine you have a custom localization resource for required inputs.
You can overwrite an existing error message by passing validation options to the `provideAbpThemeShared` in your root application configuration. Let's imagine you have a custom localization resource for required inputs.
```json
"RequiredInput": "Oops! We need this input."
@ -72,24 +69,26 @@ To use this instead of the built-in required input message, all you need to do i
```ts
import { provideAbpThemeShared, withValidationBluePrint } from '@abp/ng.theme.shared';
@NgModule({
export const appConfig: ApplicationConfig = {
providers: [
provideAbpThemeShared(withValidationBluePrint({
required: "::RequiredInput",
})),
...
// ...
provideAbpThemeShared(
withValidationBluePrint({
required: "::RequiredInput",
})
),
// ...
],
})
export class AppModule {}
};
```
Alternatively, you may provide the `VALIDATION_BLUEPRINTS` token directly in your root module. Please do not forget to spread `DEFAULT_VALIDATION_BLUEPRINTS`. Otherwise, built-in ABP validation messages will not work.
Alternatively, you may provide the `VALIDATION_BLUEPRINTS` token directly in your root app configuration. Please do not forget to spread `DEFAULT_VALIDATION_BLUEPRINTS`. Otherwise, built-in ABP validation messages will not work.
```js
import { VALIDATION_BLUEPRINTS } from "@ngx-validate/core";
import { DEFAULT_VALIDATION_BLUEPRINTS } from "@abp/ng.theme.shared";
@NgModule({
export const appConfig: ApplicationConfig = {
providers: [
{
provide: VALIDATION_BLUEPRINTS,
@ -101,10 +100,7 @@ import { DEFAULT_VALIDATION_BLUEPRINTS } from "@abp/ng.theme.shared";
// other providers
],
// rest of the module metadata
})
export class AppModule {}
};
```
The error message will look like this:
@ -134,11 +130,14 @@ Validation works on any element or component with a `formControl` or `formContro
First, build a custom error component. Extending the existing `ValidationErrorComponent` would make it easier.
```js
import { LocalizationPipe } from "@abp/ng.core";
import { ValidationErrorComponent } from "@abp/ng.theme.basic";
import { CommonModule } from "@angular/common";
import { ChangeDetectionStrategy, Component } from "@angular/core";
@Component({
selector: "app-validation-error",
imports:[CommonModule, LocalizationPipe],
template: `
<div
class="font-weight-bold font-italic px-1 invalid-feedback"
@ -152,18 +151,12 @@ import { ChangeDetectionStrategy, Component } from "@angular/core";
export class ErrorComponent extends ValidationErrorComponent {}
```
Then, declare and provide it in your root module.
Then, provide it in your root configuration.
```js
import { VALIDATION_ERROR_TEMPLATE } from "@ngx-validate/core";
@NgModule({
// rest of the module metadata
declarations: [
// other declarables
ErrorComponent,
],
export const appConfig: ApplicationConfig = {
providers: [
// other providers
{
@ -171,8 +164,7 @@ import { VALIDATION_ERROR_TEMPLATE } from "@ngx-validate/core";
useValue: ErrorComponent,
},
],
})
export class AppModule {}
};
```
The error message will be bold and italic now:

59
docs/en/framework/ui/angular/http-error-handling.md

@ -5,25 +5,25 @@
ABP offers a configurations for errors handling like below
```ts
import { ThemeSharedModule } from '@abp/ng.theme.shared';
import { MyCustomRouteErrorComponent } from './my-custom-route.component';
//app.config.ts
import { provideAbpThemeShared } from '@abp/ng.theme.shared';
import { CustomErrorComponent } from './custom-error.component';
@NgModule({
imports: [
ThemeSharedModule.forRoot({
httpErrorConfig: {
export const appConfig: ApplicationConfig = {
providers: [
provideAbpThemeShared(
withHttpErrorConfig({
skipHandledErrorCodes: [403],
errorScreen: {
forWhichErrors: [404],
component: CustomErrorComponent,
hideCloseIcon: false
}
}
}),
...
forWhichErrors: [404],
hideCloseIcon: false,
},
}),
),
],
})
export class AppModule {}
};
```
- `ErrorScreenErrorCodes` the error codes that you can pass to `skipHandledErrorCodes` and `forWhichErrors`.
@ -67,27 +67,24 @@ export function handleHttpErrors(injector: Injector, httpError: HttpErrorRespons
return of(httpError);
}
// app.module.ts
// app.config.ts
import { Error404Component } from './error404/error404.component';
import { handleHttpErrors } from './http-error-handling';
import { HTTP_ERROR_HANDLER, ... } from '@abp/ng.theme.shared';
@NgModule({
// ...
export const appConfig: ApplicationConfig = {
providers: [
// ...
{ provide: HTTP_ERROR_HANDLER, useValue: handleHttpErrors }
...
{ provide: HTTP_ERROR_HANDLER, useValue: handleHttpErrors },
...
],
declarations: [
//...
Error404Component],
})
export class AppModule {}
};
```
In the example above:
- Created a function named `handleHttpErrors` and defined as value of the `HTTP_ERROR_HANDLER` provider in app.module. After this, the function executes when an HTTP error occurs.
- Created a function named `handleHttpErrors` and defined as value of the `HTTP_ERROR_HANDLER` provider in `app.config.ts`. After this, the function executes when an HTTP error occurs.
- 400 bad request errors is handled. When a 400 error occurs.
- Since `of(httpError)` is returned at bottom of the `handleHttpErrors`, the `ErrorHandler` will handle the HTTP errors except 400 and 404 errors.
@ -195,22 +192,20 @@ export class MyCustomErrorHandlerService
```ts
// app.module.ts
// app.config.ts
import { CUSTOM_ERROR_HANDLERS, ... } from '@abp/ng.theme.shared';
import { MyCustomErrorHandlerService } from './custom-error-handler.service';
@NgModule({
// ...
export const appConfig: ApplicationConfig = {
providers: [
// ...
//...
{
provide: CUSTOM_ERROR_HANDLERS,
useExisting: MyCustomErrorHandlerService,
multi: true,
}
]
})
export class AppModule {}
],
};
```
In the example above:

BIN
docs/en/framework/ui/angular/images/quick-start---root-folder-structure.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 70 KiB

BIN
docs/en/framework/ui/angular/images/quick-start---source-folder-structure.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 73 KiB

1
docs/en/framework/ui/angular/list-service.md

@ -151,7 +151,6 @@ import { Component, inject } from '@angular/core';
import { BookDto, BooksService } from './books.service';
@Component({
standalone: true,
selector: 'app-books',
templateUrl: './books.component.html',
providers: [ListService, BooksService],

17
docs/en/framework/ui/angular/loading-directive.md

@ -6,23 +6,24 @@ You may want to block a part of the UI and show a spinner for a while; the `Load
## Getting Started
In order to use the `LoadingDirective` in an HTML template, the **`ThemeSharedModule`** should be imported into your module like this:
In order to use the `LoadingDirective` in an HTML template, it should be imported into your component like this:
```js
// ...
import { ThemeSharedModule } from '@abp/ng.theme.shared';
import { LoadingDirective } from '@abp/ng.theme.shared';
@NgModule({
//...
imports: [..., ThemeSharedModule],
@Component({
// ...
imports: [ LoadingDirective ]
// ...
})
export class MyFeatureModule {}
export class SampleComponent {}
```
## Usage
The `LoadingDirective` is easy to use. The directive's selector is **`abpLoading`**. By adding the `abpLoading` attribute to an HTML element, you can activate the `LoadingDirectiveective` for the HTML element when the value is true.
The `LoadingDirective` is easy to use. The directive's selector is **`abpLoading`**. By adding the `abpLoading` attribute to an HTML element, you can activate the `LoadingDirective` for the HTML element when the value is true.
See an example usage:
@ -33,7 +34,7 @@ See an example usage:
cupiditate? Voluptas eaque voluptatum odio deleniti quo vel illum nemo accusamus nulla ratione
impedit dolorum expedita necessitatibus fugiat ullam beatae, optio eum cupiditate ducimus
architecto.
</div>
</div>
```

74
docs/en/framework/ui/angular/localization.md

@ -9,7 +9,7 @@ The Localization key format consists of 2 sections which are **Resource Name** a
```js
const environment = {
//...
// ...
localization: {
defaultResourceName: "MyProjectName",
},
@ -49,7 +49,7 @@ Localization data is stored in key-value pairs:
```js
{
//...
// ...
AbpAccount: { // AbpAccount is the resource name
Key: "Value",
PagerInfo: "Showing {0} to {1} of {2} entries"
@ -121,12 +121,12 @@ See an example:
```ts
import { provideAbpCore, withOptions } from '@abp/ng.core';
@NgModule({
export const appConfig: ApplicationConfig = {
providers: [
// ...
provideAbpCore(
withOptions({
...,
// ...,
localizations: [
{
culture: 'en',
@ -155,22 +155,18 @@ import { provideAbpCore, withOptions } from '@abp/ng.core';
],
}),
),
...
],
})
export class AppModule {}
};
```
...or, you can determine the localizations in a feature module:
...or, you can determine the localizations in a feature provider configuration:
```ts
// your feature module
// your feature configuration
@NgModule({
imports: [
//...other imports
CoreModule.forChild({
localizations: [
export function provideFeatureConfiguration(): EnvironmentProviders{
return provideAbpCoreChild({
localizations: [
{
culture: 'en',
resources: [
@ -196,9 +192,8 @@ export class AppModule {}
],
},
],
}),
]
})
})
}
```
The localizations above can be used like this:
@ -267,8 +262,8 @@ import { Component } from "@angular/core";
@Component({
selector: "app-root",
template: `
<abp-loader-bar></abp-loader-bar>
<router-outlet></router-outlet>
<abp-loader-bar />
<router-outlet />
`,
})
export class AppComponent {}
@ -276,7 +271,7 @@ export class AppComponent {}
## Registering a New Locale
Since ABP has more than one language, Angular locale files loads lazily using [Webpack's import function](https://webpack.js.org/api/module-methods/#import-1) to avoid increasing the bundle size and register to Angular core using the [`registerLocaleData`](https://angular.io/api/common/registerLocaleData) function. The chunks to be included in the bundle are specified by the [Webpack's magic comments](https://webpack.js.org/api/module-methods/#magic-comments) as hard-coded. Therefore a `registerLocale` function that returns Webpack `import` function must be passed to `CoreModule`.
Since ABP has more than one language, Angular locale files loads lazily using [Webpack's import function](https://webpack.js.org/api/module-methods/#import-1) to avoid increasing the bundle size and register to Angular core using the [`registerLocaleData`](https://angular.io/api/common/registerLocaleData) function. The chunks to be included in the bundle are specified by the [Webpack's magic comments](https://webpack.js.org/api/module-methods/#magic-comments) as hard-coded. Therefore a `registerLocale` function that returns Webpack `import` function must be passed to `provideAbpCore(withOptions({...}))`.
### registerLocaleFn
@ -286,11 +281,12 @@ Since ABP has more than one language, Angular locale files loads lazily using [W
import { provideAbpCore, withOptions } from '@abp/ng.core';
import { registerLocale } from '@abp/ng.core/locale';
@NgModule({
export const appConfig: ApplicationConfig = {
providers: [
// ...
provideAbpCore(
withOptions({
...,
// ...,
registerLocaleFn: registerLocale(
// you can pass the cultureNameLocaleFileMap and errorHandlerFn as optionally
{
@ -302,10 +298,9 @@ import { registerLocale } from '@abp/ng.core/locale';
),
}),
),
...
// ...
],
})
export class AppModule {}
};
```
### Mapping of Culture Name to Angular Locale File Name
@ -317,19 +312,18 @@ Some of the culture names defined in .NET do not match Angular locales. In such
If you see an error like this, you should pass the `cultureNameLocaleFileMap` property like below to the `registerLocale` function.
```js
// app.module.ts
// app.config.ts
import { registerLocale } from '@abp/ng.core/locale';
// if you have commercial license and the language management module, add the below import
// import { registerLocale } from '@volo/abp.ng.language-management/locale';
@NgModule({
export const appConfig: ApplicationConfig = {
providers: [
// ...
provideAbpCore(
withOptions({
...,
// ...,
registerLocaleFn: registerLocale(
{
cultureNameLocaleFileMap: {
@ -340,18 +334,18 @@ import { registerLocale } from '@abp/ng.core/locale';
)
}),
),
]
})
],
};
```
See [all locale files in Angular](https://github.com/angular/angular/tree/master/packages/common/locales).
### Adding a New Culture
Add the below code to the `app.module.ts` by replacing `your-locale` placeholder with a correct locale name.
Add the below code to the `app.config.ts` by replacing `your-locale` placeholder with a correct locale name.
```js
//app.module.ts
//app.config.ts
import { storeLocaleData } from "@abp/ng.core/locale";
import(
@ -361,7 +355,7 @@ import(
).then((m) => storeLocaleData(m.default, "your-locale"));
```
...or a custom `registerLocale` function can be passed to the `CoreModule`:
...or a custom `registerLocale` function can be passed to the abp core provider configuration options:
```js
// register-locale.ts
@ -376,22 +370,22 @@ export function registerLocale(locale: string) {
)
}
// app.module.ts
// app.config.ts
import { registerLocale } from './register-locale';
@NgModule({
export const appConfig: ApplicationConfig = {
providers: [
// ...
// ...
provideAbpCore(
withOptions({
...,
// ...,
registerLocaleFn: registerLocale,
}),
),
//...
]
})
],
};
```
After this custom `registerLocale` function, since the en and fr added to the `webpackInclude`, only en and fr locale files will be created as chunks:

25
docs/en/framework/ui/angular/lookup-components.md

@ -4,22 +4,7 @@ The Angular UI of ABP Commercial introduces some components with `abp-lookup-...
## Setup
The components are in the _@volo/abp.commercial.ng.ui_ package, which is included in the ABP templates. So, as long as your project is a product of these templates and unless you delete the package, you have access to the lookup components. Here is how you import them in your Angular module:
```javascript
import { CommercialUiModule } from '@volo/abp.commercial.ng.ui';
@NgModule({
imports: [
// other imports
CommercialUiModule,
],
// rest of the module metadata
})
export class YourModule {}
```
Now you can use the lookup components in your components declared by this module.
The components are in the _@volo/abp.commercial.ng.ui_ package, which is included in the ABP templates. So, as long as your project is a product of these templates and unless you delete the package, you have access to the lookup components. You can import these in your standalone components in order to be able to use them.
## Lookup HTTP Requests
@ -48,7 +33,7 @@ Typeahead is a good choice when you have an unknown number of records for the re
![ABP Angular UI Typeahead Lookup](./images/angular-lookup-typeahead.gif)
Here is how it is used in the template.
Do not forget to import `LookupTypeaheadComponent` in your component, and here is how it is used in the template.
```html
<abp-lookup-typeahead
@ -57,7 +42,7 @@ Here is how it is used in the template.
displayNameProp="name"
[editingData]="selected?.country"
[getFn]="service.getCountryLookup"
></abp-lookup-typeahead>
/>
```
The available properties are as follows:
@ -77,7 +62,7 @@ Select is a good choice when you have a low (and usually fixed) number of record
![ABP Angular UI Select Lookup](./images/angular-lookup-select.gif)
Here is how it is used in the template.
Do not forget to import `LookupSelectComponent` in your component, and here is how it is used in the template.
```html
<abp-lookup-select
@ -85,7 +70,7 @@ Here is how it is used in the template.
formControlName="countryId"
displayNameProp="name"
[getFn]="service.getCountryLookup"
></abp-lookup-select>
/>
```
The available properties are as follows:

57
docs/en/framework/ui/angular/manage-profile-page-tabs.md

@ -9,7 +9,7 @@ See the example below, covers all features:
```ts
// manage-profile-tabs.provider.ts
import { APP_INITIALIZER, Component } from "@angular/core";
import { provideAppInitializer, Component } from "@angular/core";
import { TwoFactorTabComponent } from "@volo/abp.ng.account/public";
import {
eAccountManageProfileTabNames,
@ -18,48 +18,47 @@ import {
import { MyAwesomeTabComponent } from "./my-awesome-tab/my-awesome-tab.component";
@Component({
standalone: true,
selector: "abp-my-awesome-tab",
template: `My Awesome Tab`,
})
class MyAwesomeTabComponent {}
export const MANAGE_PROFILE_TAB_PROVIDER = {
provide: APP_INITIALIZER,
useFactory: configureManageProfileTabs,
deps: [ManageProfileTabsService],
multi: true,
provideAppInitializer(()=>{
configureManageProfileTabs();
}),
};
export function configureManageProfileTabs(tabs: ManageProfileTabsService) {
return () => {
tabs.add([
{
name: "::MyAwesomeTab", // supports localization keys
order: 5,
component: MyAwesomeTabComponent,
},
]);
tabs.patch(eAccountManageProfileTabNames.TwoFactor, {
name: "Two factor authentication",
component: TwoFactorTabComponent,
});
tabs.remove([eAccountManageProfileTabNames.ProfilePicture]);
};
export function configureManageProfileTabs() {
tabs = inject(ManageProfileTabsService);
tabs.add([
{
name: "::MyAwesomeTab", // supports localization keys
order: 5,
component: MyAwesomeTabComponent,
},
]);
tabs.patch(eAccountManageProfileTabNames.TwoFactor, {
name: "Two factor authentication",
component: TwoFactorTabComponent,
});
tabs.remove([eAccountManageProfileTabNames.ProfilePicture]);
}
```
```ts
//app.module.ts
//app.config.ts
import { MANAGE_PROFILE_TAB_PROVIDER } from "./manage-profile-tabs.provider";
@NgModule({
providers: [MANAGE_PROFILE_TAB_PROVIDER],
})
export class AppModule {}
export const appConfig: ApplicationConfig = {
providers: [
// ...
MANAGE_PROFILE_TAB_PROVIDER
],
};
```
What we have done above;
@ -70,7 +69,7 @@ What we have done above;
- Renamed the "Two factor" tab label.
- Removed the "Profile picture" tab.
- Determined the `MANAGE_PROFILE_TAB_PROVIDER` to be able to run the `configureManageProfileTabs` function on initialization.
- Registered the `MANAGE_PROFILE_TAB_PROVIDER` to the `AppModule` providers.
- Registered the `MANAGE_PROFILE_TAB_PROVIDER` to the `appConfig` providers.
See the result:

69
docs/en/framework/ui/angular/modal.md

@ -15,27 +15,33 @@ The `abp-modal` provides some additional benefits:
## Getting Started
In order to use the `abp-modal` in an HTML template, the **`ThemeSharedModule`** should be imported into your module like this:
In order to use the `abp-modal` in an HTML template, the **`ModalComponent`** should be imported into your component like this:
```js
// sample.component.ts
// ...
import { ThemeSharedModule } from '@abp/ng.theme.shared';
import { ModalComponent, ModalCloseDirective } from '@abp/ng.theme.shared';
@NgModule({
@Component({
//...
imports: [..., ThemeSharedModule],
,
imports: [
// ...,
ModalComponent,
ModalCloseDirective // if you use `abpClose` directive in the html template
],
})
export class MyFeatureModule {}
export class SampleComponent {
isModalOpen = false;
}
```
## Usage
You can add the `abp-modal` to your component very quickly. See an example:
```html
<!-- sample.component.html -->
<button class="btn btn-primary" (click)="isModalOpen = true">Open modal</button>
<button class="btn btn-primary" (click)="isModalOpen = true">
Open modal
</button>
<abp-modal [(visible)]="isModalOpen">
<ng-template #abpHeader>
@ -43,24 +49,17 @@ You can add the `abp-modal` to your component very quickly. See an example:
</ng-template>
<ng-template #abpBody>
<p>Modal content</p>
<p>Modal content</p>
</ng-template>
<ng-template #abpFooter>
<button type="button" class="btn btn-secondary" abpClose>Close</button>
<button type="button" class="btn btn-secondary" abpClose>
Close
</button>
</ng-template>
</abp-modal>
```
```js
// sample.component.ts
@Component(/* component metadata */)
export class SampleComponent {
isModalOpen = false
}
```
![Example modal result](./images/modal-result-1.jpg)
@ -136,7 +135,10 @@ import { FormBuilder, Validators } from '@angular/forms';
@Component(/* component metadata */)
export class BookComponent {
form = this.fb.group({
private fb = inject(FormBuilder);
private service = inject(BookService);
form = this.fb.group({
author: [null, [Validators.required]],
name: [null, [Validators.required]],
price: [null, [Validators.required, Validators.min(0)]],
@ -148,10 +150,10 @@ export class BookComponent {
isModalOpen: boolean;
constructor(private fb: FormBuilder, private service: BookService) {}
save() {
if (this.form.invalid) return;
if (this.form.invalid) {
return;
}
this.inProgress = true;
@ -257,19 +259,16 @@ export class NgbdModalOptions {
**`suppressUnsavedChangesWarning`** is a boolean input that determines whether the confirmation popup triggering active or not. It can also be set globally as shown below:
```ts
//app.module.ts
// app.module.ts
// app.config.ts
import { SUPPRESS_UNSAVED_CHANGES_WARNING } from '@abp/ng.theme.shared';
// ...
@NgModule({
// ...
providers: [{provide: SUPPRESS_UNSAVED_CHANGES_WARNING, useValue: true}]
})
export class AppModule {}
export const appConfig: ApplicationConfig = {
providers: [
// ...
{ provide: SUPPRESS_UNSAVED_CHANGES_WARNING, useValue: true }
],
};
```
Note: The `suppressUnsavedChangesWarning` input of `abp-modal` value overrides the `SUPPRESS_UNSAVED_CHANGES_WARNING` injection token value.

90
docs/en/framework/ui/angular/modifying-the-menu.md

@ -59,32 +59,33 @@ An alternative and probably cleaner way is to use a route provider. First create
```js
// route.provider.ts
import { RoutesService, eLayoutType } from '@abp/ng.core';
import { APP_INITIALIZER } from '@angular/core';
import { provideAppInitializer } from '@angular/core';
export const APP_ROUTE_PROVIDER = [
{ provide: APP_INITIALIZER, useFactory: configureRoutes, deps: [RoutesService], multi: true },
provideAppInitializer(() => {
configureRoutes();
}),
];
function configureRoutes(routes: RoutesService) {
return () => {
routes.add([
{
path: '/your-path',
name: 'Your navigation',
requiredPolicy: 'permission key here',
order: 101,
iconClass: 'fas fa-question-circle',
layout: eLayoutType.application,
},
{
path: '/your-path/child',
name: 'Your child navigation',
parentName: 'Your navigation',
requiredPolicy: 'permission key here',
order: 1,
},
]);
};
function configureRoutes() {
const routesService = inject(RoutesService);
routes.add([
{
path: '/your-path',
name: 'Your navigation',
requiredPolicy: 'permission key here',
order: 101,
iconClass: 'fas fa-question-circle',
layout: eLayoutType.application,
},
{
path: '/your-path/child',
name: 'Your child navigation',
parentName: 'Your navigation',
requiredPolicy: 'permission key here',
order: 1,
},
]);
}
```
@ -95,22 +96,21 @@ We can also define a group for navigation elements. It's an optional property
// route.provider.ts
import { RoutesService } from '@abp/ng.core';
function configureRoutes(routes: RoutesService) {
return () => {
routes.add([
{
//etc..
group: 'ModuleName::GroupName'
},
{
path: '/your-path/child',
name: 'Your child navigation',
parentName: 'Your navigation',
requiredPolicy: 'permission key here',
order: 1,
},
]);
};
function configureRoutes() {
const routesService = inject(RoutesService);
routes.add([
{
//etc..
group: 'ModuleName::GroupName'
},
{
path: '/your-path/child',
name: 'Your child navigation',
parentName: 'Your navigation',
requiredPolicy: 'permission key here',
order: 1,
},
]);
}
```
@ -131,25 +131,23 @@ export class AppComponent {
}
```
...and then in app.module.ts...
...and then in app.config.ts...
- The `groupedVisible` method will return the `Others` group for ungrouped items, the default key is `AbpUi::OthersGroup`, we can change this `key` via the `OTHERS_GROUP` injection token
```js
import { NgModule } from '@angular/core';
import { OTHERS_GROUP } from '@abp/ng.core';
import { APP_ROUTE_PROVIDER } from './route.provider';
@NgModule({
export const appConfig: ApplicationConfig = {
providers: [
// ...
APP_ROUTE_PROVIDER,
{
provide: OTHERS_GROUP,
useValue: 'ModuleName::MyOthersGroupKey',
},
],
// imports, declarations, and bootstrap
})
export class AppModule {}
};
```
### Singularize Route Item
@ -182,9 +180,9 @@ Here is what every property works as:
- `invisible` makes the item invisible in the menu. (default: `false`)
- `group` is an optional property that is used to group together related routes in an application. (type: `string`, default: `AbpUi::OthersGroup`)
### Via `routes` Property in `AppRoutingModule`
### Via `routes` Property in `APP_ROUTES`
You can define your routes by adding `routes` as a child property to `data` property of a route configuration in the `app-routing.module`. The `@abp/ng.core` package organizes your routes and stores them in the `RoutesService`.
You can define your routes by adding `routes` as a child property to `data` property of a route configuration in the `app.routes.ts`. The `@abp/ng.core` package organizes your routes and stores them in the `RoutesService`.
You can add the `routes` property like below:

13
docs/en/framework/ui/angular/multi-tenancy.md

@ -133,12 +133,9 @@ Example:
```ts
import { TENANT_NOT_FOUND_BY_NAME } from '@abp/ng.core';
@NgModule({
imports: [
// removed for clarity
],
export const appConfig: ApplicationConfig = {
providers: [
// removed for clarity
// removed for clarity
{
provide: TENANT_NOT_FOUND_BY_NAME,
useFactory: function () {
@ -148,11 +145,7 @@ import { TENANT_NOT_FOUND_BY_NAME } from '@abp/ng.core';
},
},
],
declarations: [AppComponent],
bootstrap: [AppComponent],
})
export class AppModule {}
};
```
## See Also

3
docs/en/framework/ui/angular/oauth-module.md

@ -2,8 +2,7 @@
The authentication functionality has been moved from @abp/ng.core to @abp/ng.ouath since v7.0.
If your app is version 8.3 or higher, you should include "provideAbpOAuth()" in your app.module.ts as an providers after "provideAbpCore()
".
If your app is version 8.3 or higher, you should include "provideAbpOAuth()" after "provideAbpCore()" in the `appConfig` array of your `app.config.ts`.
Those abstractions can be found in the @abp/ng-core packages.

84
docs/en/framework/ui/angular/page-component.md

@ -14,10 +14,10 @@ Let's look at the following example without `abp-page` component.
<h1 class="content-header-title">{%{{{ '::Dashboard' | abpLocalization }}}%}</h1>
</div>
<div id="breadcrumb" class="col-lg-auto pl-lg-0">
<abp-breadcrumb></abp-breadcrumb>
<abp-breadcrumb />
</div>
<div class="col">
<abp-page-toolbar [record]="data"></abp-page-toolbar>
<abp-page-toolbar [record]="data" />
</div>
</div>
@ -42,22 +42,19 @@ export enum PageParts {
## Usage
Firstly, you need to import `PageModule` from `@abp/ng.components/page` as follows:
Firstly, you need to import Page components from `@abp/ng.components/page` based on your usage. Here is an example:
`dashboard.module.ts`
`dashboard.component.ts`
```javascript
import { PageModule } from '@abp/ng.components/page';
import { DashboardComponent } from './dashboard.component';
@NgModule({
declarations: [DashboardComponent],
imports: [PageModule]
@Component({
imports: [ PageComponent, ... ]
})
export class DashboardModule {}
export class  DashboardComponent {}
```
And change the template of `dashboard.component.ts` to the following:
And change the template of `dashboard.component.html` to the following:
```html
<abp-page [title]="'::Dashboard' | abpLocalization" [toolbar]="data">
@ -75,35 +72,55 @@ And change the template of `dashboard.component.ts` to the following:
## Overriding template
If you need to replace the template of any part, you can use the following sub-components.
If you need to replace the template of any part, you can use the following sub-components. You will need to import these components and modify the html template accordingly.
```html
<abp-page>
<abp-page-title-container class="col">
<h2>Custom Title</h2>
</abp-page-title-container>
<abp-page-breacrumb-container class="col">
<my-breadcrumb></my-breadcrumb>
</abp-page-breacrumb-container>
<abp-page-toolbar-container class="col">
<button (click)="doSth()">Some Action</button>
</abp-page-toolbar-container>
</abp-page>
```javascript
import {
PageComponent,
PageTitleContainerComponent,
PageBreadcrumbContainerComponent,
PageToolbarContainerComponent
} from '@abp/ng.components/page';
@Component({
selector: 'app-sample-component',
template: `
<abp-page>
<abp-page-title-container class="col">
<h2>Custom Title</h2>
</abp-page-title-container>
<abp-page-breacrumb-container class="col">
<my-breadcrumb />
</abp-page-breacrumb-container>
<abp-page-toolbar-container class="col">
<button (click)="doSth()">Some Action</button>
</abp-page-toolbar-container>
</abp-page>
`
imports: [
PageComponent,
PageTitleContainerComponent,
PageBreadcrumbContainerComponent,
MyBreadcrumbComponent,
PageToolbarContainerComponent
]
})
export class SampleCompnent {}
```
You do not have to provide them all. You can just use which one you need to replace. These components have priority over the inputs declared above. If you use these components, you can omit the inputs.
## PagePartDirective
`PageModule` provides a structural directive that is used internally within `PageComponent` and can also be used externally.
`Components` package provides a structural directive that is used internally within `PageComponent` and can also be used externally.
`PageComponent` employs this directive internally as follows:
```html
<div class="col-lg-auto pl-lg-0" *abpPagePart="pageParts.breadcrumb">
<abp-breadcrumb></abp-breadcrumb>
<abp-breadcrumb />
</div>
```
@ -111,7 +128,7 @@ It also can take a context input as follows:
```html
<div class="col" *abpPagePart="pageParts.toolbar; context: toolbarData">
<abp-page-toolbar [record]="toolbarData"></abp-page-toolbar>
<abp-page-toolbar [record]="toolbarData" />
</div>
```
@ -194,17 +211,14 @@ export class MyPageRenderStrategy implements PageRenderStrategy {
})
export class DashboardComponent {}
@NgModule({
imports: [PageModule],
declarations: [DashboardComponent],
export const appConfig: ApplicationConfig = {
providers: [
{
provide: PAGE_RENDER_STRATEGY,
useClass: MyPageRenderStrategy,
}
]
})
export class DashboardModule {}
],
};
```
## See Also

32
docs/en/framework/ui/angular/page-toolbar-extensions.md

@ -14,7 +14,7 @@ In this example, we will add a "Click Me!" action and log `userName` of all user
### Step 1. Create Toolbar Action Contributors
The following code prepares a constant named `identityToolbarActionContributors`, ready to be imported and used in your root module:
The following code prepares a constant named `identityToolbarActionContributors`, ready to be imported and used in your root application configuration:
```js
// src/app/toolbar-action-contributors.ts
@ -53,22 +53,22 @@ The list of actions, conveniently named as `actionList`, is a **doubly linked li
### Step 2. Import and Use Toolbar Action Contributors
Import `identityToolbarActionContributors` in your routing module and pass it to the static `forLazy` method of `IdentityModule` as seen below:
Import `identityToolbarActionContributors` in your routing configuration and pass it to the static `createRoutes` method for `identity` route as seen below:
```js
// src/app/app-routing.module.ts
// src/app/app.routes.ts
// other imports
import { identityToolbarActionContributors } from './toolbar-action-contributors';
const routes: Routes = [
export const APP_ROUTES: Routes = [
// other routes
{
path: 'identity',
loadChildren: () =>
import('@abp/ng.identity').then(m =>
m.IdentityModule.forLazy({
import('@abp/ng.identity').then(c =>
c.createRoutes({
toolbarActionContributors: identityToolbarActionContributors,
})
),
@ -78,7 +78,7 @@ const routes: Routes = [
];
```
That is it, `logUserNames` toolbar action will be added as the first action on the page toolbar in the users page (`UsersComponent`) of the `IdentityModule`.
That is it, `logUserNames` toolbar action will be added as the first action on the page toolbar in the users page (`UsersComponent`) of the `identity` package.
## How to Add a Custom Component to Page Toolbar
@ -93,9 +93,9 @@ We need to have a component before we can pass it to the toolbar action contribu
```js
// src/app/click-me-button.component.ts
import { Component, Inject } from '@angular/core';
import { IdentityUserDto } from '@abp/ng.identity/proxy';
import { ActionData, EXTENSIONS_ACTION_DATA } from '@abp/ng.components/extensible';
import { Component, Inject } from '@angular/core';
@Component({
selector: 'app-click-me-button',
@ -120,7 +120,7 @@ Here, `EXTENSIONS_ACTION_DATA` token provides us the context from the page toolb
### Step 2. Create Toolbar Action Contributors
The following code prepares a constant named `identityToolbarActionContributors`, ready to be imported and used in your root module. When `ToolbarComponent` is used instead of `ToolbarAction`, we can pass a component in:
The following code prepares a constant named `identityToolbarActionContributors`, ready to be imported and used in your root application configuration. When `ToolbarComponent` is used instead of `ToolbarAction`, we can pass a component in:
```js
// src/app/toolbar-action-contributors.ts
@ -156,22 +156,22 @@ The list of actions, conveniently named as `actionList`, is a **doubly linked li
### Step 3. Import and Use Toolbar Action Contributors
Import `identityToolbarActionContributors` in your routing module and pass it to the static `forLazy` method of `IdentityModule` as seen below.
Import `identityToolbarActionContributors` in your routing configuration and pass it to the static `createRoutes` method for `identity` route as seen below.
```js
// src/app/app-routing.module.ts
// src/app/app.routes.ts
// other imports
import { identityToolbarActionContributors } from './toolbar-action-contributors';
const routes: Routes = [
export const APP_ROUTES: Routes = [
// other routes
{
path: 'identity',
loadChildren: () =>
import('@abp/ng.identity').then(m =>
m.IdentityModule.forLazy({
import('@abp/ng.identity').then(c =>
c.createRoutes({
toolbarActionContributors: identityToolbarActionContributors,
})
),
@ -181,7 +181,7 @@ const routes: Routes = [
];
```
That is it, `logUserNames` toolbar action will be added as the first action on the page toolbar in the users page (`UsersComponent`) of the `IdentityModule` and it will be triggered by a custom button, i.e. `ClickMeButtonComponent`. Please note that **component projection is not limited to buttons** and you may use other UI components.
That is it, `logUserNames` toolbar action will be added as the first action on the page toolbar in the users page (`UsersComponent`) of the `identity` package and it will be triggered by a custom button, i.e. `ClickMeButtonComponent`. Please note that **component projection is not limited to buttons** and you may use other UI components.
## How to Place a Custom Modal and Trigger It by Toolbar Actions
@ -380,7 +380,7 @@ export const identityEntityActionContributors = {
### ToolbarActionContributorCallback\<R = any\>
`ToolbarActionContributorCallback` is the type that you can pass as toolbar action contributor callbacks to static `forLazy` methods of the modules.
`ToolbarActionContributorCallback` is the type that you can pass as toolbar action contributor callbacks to static `createRoutes` methods of the packages.
```js
// exportUsersContributor should have ToolbarActionContributorCallback<IdentityUserDto[]> type

2
docs/en/framework/ui/angular/password-complexity-indicator-component.md

@ -42,7 +42,7 @@ The `PasswordComplexityIndicatorService` is for calculating the password complex
It's easy, imagine you have a password input that you want to add the complexity indicator under. Put this component under the input
```ts
<abp-password-complexity-indicator [progressBar]="ProgressBarStatsObject"></abp-password-complexity-indicator>
<abp-password-complexity-indicator [progressBar]="ProgressBarStatsObject" />
```
- Pass the password to the `validatePassword` method of the `PasswordComplexityIndicatorService`, and bind return the value to the `progressBar` property of the `abp-password-complexity-indicator`

9
docs/en/framework/ui/angular/permission-management.md

@ -100,11 +100,10 @@ export class CustomPermissionService extends PermissionService {
}
```
- Then, in `app.module.ts`, provide this service as follows:
- Then, in `app.config.ts`, provide this service as follows:
```js
@NgModule({
// ...
export const appConfig: ApplicationConfig = {
providers: [
// ...
{
@ -112,9 +111,7 @@ export class CustomPermissionService extends PermissionService {
useExisting: CustomPermissionService,
},
],
// ...
})
export class AppModule {}
};
```
That's it. Now, when a directive/guard asks for `PermissionService` from angular, it will inject your service.

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save