Browse Source

Merge pull request #10010 from abpframework/refactor/9952

Made the Angular packages NGXS independent
pull/10049/head
Bunyamin Coskuner 5 years ago
committed by GitHub
parent
commit
1088d04bc6
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 67
      docs/en/UI/Angular/How-Replaceable-Components-Work-with-Extensions.md
  2. 276
      docs/en/UI/Angular/Permission-Management-Component-Replacement.md
  3. 4
      modules/cms-kit/angular/projects/dev-app/src/app/app.module.ts
  4. 1
      npm/ng-packs/angular.json
  5. 2
      npm/ng-packs/apps/dev-app/src/app/app.module.ts
  6. 4
      npm/ng-packs/package.json
  7. 2
      npm/ng-packs/packages/account/src/lib/components/manage-profile/manage-profile.component.ts
  8. 10
      npm/ng-packs/packages/account/src/lib/services/manage-profile.state.service.ts
  9. 1
      npm/ng-packs/packages/core/ng-package.json
  10. 1
      npm/ng-packs/packages/core/package.json
  11. 24
      npm/ng-packs/packages/core/src/lib/actions/config.actions.ts
  12. 4
      npm/ng-packs/packages/core/src/lib/actions/index.ts
  13. 11
      npm/ng-packs/packages/core/src/lib/actions/loader.actions.ts
  14. 15
      npm/ng-packs/packages/core/src/lib/actions/profile.actions.ts
  15. 6
      npm/ng-packs/packages/core/src/lib/actions/rest.actions.ts
  16. 5
      npm/ng-packs/packages/core/src/lib/core.module.ts
  17. 10
      npm/ng-packs/packages/core/src/lib/guards/permission.guard.ts
  18. 65
      npm/ng-packs/packages/core/src/lib/models/config.ts
  19. 1
      npm/ng-packs/packages/core/src/lib/models/index.ts
  20. 44
      npm/ng-packs/packages/core/src/lib/models/profile.ts
  21. 7
      npm/ng-packs/packages/core/src/lib/pipes/localization.pipe.ts
  22. 26
      npm/ng-packs/packages/core/src/lib/services/http-error-reporter.service.ts
  23. 2
      npm/ng-packs/packages/core/src/lib/services/index.ts
  24. 11
      npm/ng-packs/packages/core/src/lib/services/localization.service.ts
  25. 28
      npm/ng-packs/packages/core/src/lib/services/profile-state.service.ts
  26. 71
      npm/ng-packs/packages/core/src/lib/services/profile.service.ts
  27. 8
      npm/ng-packs/packages/core/src/lib/services/rest.service.ts
  28. 264
      npm/ng-packs/packages/core/src/lib/states/config.state.ts
  29. 2
      npm/ng-packs/packages/core/src/lib/states/index.ts
  30. 47
      npm/ng-packs/packages/core/src/lib/states/profile.state.ts
  31. 11
      npm/ng-packs/packages/core/src/lib/strategies/auth-flow.strategy.ts
  32. 8
      npm/ng-packs/packages/core/src/lib/tests/config-state.service.spec.ts
  33. 8
      npm/ng-packs/packages/core/src/lib/tests/date-utils.spec.ts
  34. 18
      npm/ng-packs/packages/core/src/lib/tests/environment.service.spec.ts
  35. 27
      npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts
  36. 57
      npm/ng-packs/packages/core/src/lib/tests/profile-state.service.spec.ts
  37. 19
      npm/ng-packs/packages/core/src/lib/tests/profile.service.spec.ts
  38. 74
      npm/ng-packs/packages/core/src/lib/tests/profile.state.spec.ts
  39. 19
      npm/ng-packs/packages/core/src/lib/tests/rest.service.spec.ts
  40. 11
      npm/ng-packs/packages/core/src/lib/tests/utils/common.utils.ts
  41. 7
      npm/ng-packs/packages/core/src/lib/utils/environment-utils.ts
  42. 6
      npm/ng-packs/packages/core/src/public-api.ts
  43. 2
      npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.ts
  44. 3
      npm/ng-packs/packages/feature-management/src/lib/feature-management.module.ts
  45. 64
      npm/ng-packs/packages/identity/src/lib/actions/identity.actions.ts
  46. 1
      npm/ng-packs/packages/identity/src/lib/actions/index.ts
  47. 6
      npm/ng-packs/packages/identity/src/lib/components/roles/roles.component.html
  48. 51
      npm/ng-packs/packages/identity/src/lib/components/roles/roles.component.ts
  49. 6
      npm/ng-packs/packages/identity/src/lib/components/users/users.component.html
  50. 69
      npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts
  51. 3
      npm/ng-packs/packages/identity/src/lib/identity.module.ts
  52. 12
      npm/ng-packs/packages/identity/src/lib/models/identity.ts
  53. 1
      npm/ng-packs/packages/identity/src/lib/models/index.ts
  54. 82
      npm/ng-packs/packages/identity/src/lib/services/identity-state.service.ts
  55. 1
      npm/ng-packs/packages/identity/src/lib/services/index.ts
  56. 138
      npm/ng-packs/packages/identity/src/lib/states/identity.state.ts
  57. 1
      npm/ng-packs/packages/identity/src/lib/states/index.ts
  58. 58
      npm/ng-packs/packages/identity/src/lib/tests/identity-state.service.spec.ts
  59. 3
      npm/ng-packs/packages/identity/src/public-api.ts
  60. 1
      npm/ng-packs/packages/permission-management/src/lib/actions/index.ts
  61. 11
      npm/ng-packs/packages/permission-management/src/lib/actions/permission-management.actions.ts
  62. 21
      npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.html
  63. 133
      npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts
  64. 4
      npm/ng-packs/packages/permission-management/src/lib/permission-management.module.ts
  65. 1
      npm/ng-packs/packages/permission-management/src/lib/services/index.ts
  66. 27
      npm/ng-packs/packages/permission-management/src/lib/services/permission-management-state.service.ts
  67. 1
      npm/ng-packs/packages/permission-management/src/lib/states/index.ts
  68. 47
      npm/ng-packs/packages/permission-management/src/lib/states/permission-management.state.ts
  69. 60
      npm/ng-packs/packages/permission-management/src/lib/tests/permission-management-state.service.spec.ts
  70. 9
      npm/ng-packs/packages/permission-management/src/public-api.ts
  71. 1
      npm/ng-packs/packages/setting-management/src/lib/actions/index.ts
  72. 6
      npm/ng-packs/packages/setting-management/src/lib/actions/setting-management.actions.ts
  73. 14
      npm/ng-packs/packages/setting-management/src/lib/components/setting-management.component.ts
  74. 1
      npm/ng-packs/packages/setting-management/src/lib/models/index.ts
  75. 7
      npm/ng-packs/packages/setting-management/src/lib/models/setting-management.ts
  76. 10
      npm/ng-packs/packages/setting-management/src/lib/setting-management.module.ts
  77. 1
      npm/ng-packs/packages/setting-management/src/lib/states/index.ts
  78. 26
      npm/ng-packs/packages/setting-management/src/lib/states/setting-management.state.ts
  79. 1
      npm/ng-packs/packages/tenant-management/src/lib/actions/index.ts
  80. 26
      npm/ng-packs/packages/tenant-management/src/lib/actions/tenant-management.actions.ts
  81. 26
      npm/ng-packs/packages/tenant-management/src/lib/components/tenants/tenants.component.html
  82. 160
      npm/ng-packs/packages/tenant-management/src/lib/components/tenants/tenants.component.ts
  83. 3533
      npm/ng-packs/packages/tenant-management/src/lib/proxy/generate-proxy.json
  84. 16
      npm/ng-packs/packages/tenant-management/src/lib/proxy/models.ts
  85. 4
      npm/ng-packs/packages/tenant-management/src/lib/proxy/tenant.service.ts
  86. 1
      npm/ng-packs/packages/tenant-management/src/lib/services/index.ts
  87. 41
      npm/ng-packs/packages/tenant-management/src/lib/services/tenant-management-state.service.ts
  88. 1
      npm/ng-packs/packages/tenant-management/src/lib/states/index.ts
  89. 70
      npm/ng-packs/packages/tenant-management/src/lib/states/tenant-management.state.ts
  90. 3
      npm/ng-packs/packages/tenant-management/src/lib/tenant-management.module.ts
  91. 61
      npm/ng-packs/packages/tenant-management/src/lib/tests/tenant-management-state.service.spec.ts
  92. 3
      npm/ng-packs/packages/tenant-management/src/public-api.ts
  93. 3
      npm/ng-packs/packages/theme-basic/src/lib/providers/styles.provider.ts
  94. 2
      npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb/breadcrumb.component.ts
  95. 6
      npm/ng-packs/packages/theme-shared/src/lib/components/http-error-wrapper/http-error-wrapper.component.ts
  96. 44
      npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts
  97. 6
      npm/ng-packs/packages/theme-shared/src/lib/models/confirmation.ts
  98. 18
      npm/ng-packs/packages/theme-shared/src/lib/models/toaster.ts
  99. 10
      npm/ng-packs/packages/theme-shared/src/lib/tests/breadcrumb.component.spec.ts
  100. 3
      npm/ng-packs/packages/theme-shared/src/lib/tests/confirmation.service.spec.ts

67
docs/en/UI/Angular/How-Replaceable-Components-Work-with-Extensions.md

@ -17,30 +17,18 @@ yarn ng generate component my-roles/my-roles --flat --export
Open the generated `src/app/my-roles/my-roles.component.ts` file and replace its content with the following:
```js
import { ListService, PagedAndSortedResultRequestDto } from '@abp/ng.core';
import {
CreateRole,
DeleteRole,
eIdentityComponents,
GetRoleById,
GetRoles,
IdentityRoleDto,
IdentityState,
RolesComponent,
UpdateRole,
} from '@abp/ng.identity';
import { ListService, PagedAndSortedResultRequestDto, PagedResultDto } from '@abp/ng.core';
import { eIdentityComponents, IdentityRoleDto, IdentityRoleService, RolesComponent } from '@abp/ng.identity';
import { ePermissionManagementComponents } from '@abp/ng.permission-management';
import { Confirmation, ConfirmationService } from '@abp/ng.theme.shared';
import {
EXTENSIONS_IDENTIFIER,
FormPropData,
generateFormFromProps,
generateFormFromProps
} from '@abp/ng.theme.shared/extensions';
import { Component, Injector, OnInit } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { Select, Store } from '@ngxs/store';
import { Observable } from 'rxjs';
import { finalize, pluck } from 'rxjs/operators';
import { finalize } from 'rxjs/operators';
@Component({
selector: 'app-my-roles',
@ -51,15 +39,14 @@ import { finalize, pluck } from 'rxjs/operators';
provide: EXTENSIONS_IDENTIFIER,
useValue: eIdentityComponents.Roles,
},
{ provide: RolesComponent, useExisting: MyRolesComponent },
{
provide: RolesComponent,
useExisting: MyRolesComponent
}
],
})
export class MyRolesComponent implements OnInit {
@Select(IdentityState.getRoles)
data$: Observable<IdentityRoleDto[]>;
@Select(IdentityState.getRolesTotalCount)
totalCount$: Observable<number>;
data: PagedResultDto<IdentityRoleDto> = { items: [], totalCount: 0 };
form: FormGroup;
@ -82,8 +69,8 @@ export class MyRolesComponent implements OnInit {
constructor(
public readonly list: ListService<PagedAndSortedResultRequestDto>,
protected confirmationService: ConfirmationService,
protected store: Store,
protected injector: Injector
protected injector: Injector,
protected service: IdentityRoleService,
) {}
ngOnInit() {
@ -106,25 +93,21 @@ export class MyRolesComponent implements OnInit {
}
edit(id: string) {
this.store
.dispatch(new GetRoleById(id))
.pipe(pluck('IdentityState', 'selectedRole'))
.subscribe(selectedRole => {
this.selected = selectedRole;
this.openModal();
});
this.service.get(id).subscribe(res => {
this.selected = res;
this.openModal();
});
}
save() {
if (!this.form.valid) return;
this.modalBusy = true;
this.store
.dispatch(
this.selected.id
? new UpdateRole({ ...this.selected, ...this.form.value, id: this.selected.id })
: new CreateRole(this.form.value)
)
const { id } = this.selected;
(id
? this.service.update(id, { ...this.selected, ...this.form.value })
: this.service.create(this.form.value)
)
.pipe(finalize(() => (this.modalBusy = false)))
.subscribe(() => {
this.isModalVisible = false;
@ -139,13 +122,13 @@ export class MyRolesComponent implements OnInit {
})
.subscribe((status: Confirmation.Status) => {
if (status === Confirmation.Status.confirm) {
this.store.dispatch(new DeleteRole(id)).subscribe(() => this.list.get());
this.service.delete(id).subscribe(() => this.list.get());
}
});
}
private hookToQuery() {
this.list.hookToQuery(query => this.store.dispatch(new GetRoles(query))).subscribe();
this.list.hookToQuery(query => this.service.getList(query)).subscribe(res => (this.data = res));
}
openPermissionsModal(providerKey: string) {
@ -189,15 +172,15 @@ Open the generated `src/app/my-role/my-role.component.html` file and replace its
<h5 class="card-title">My Roles</h5>
</div>
<div class="text-right col col-md-6">
<abp-page-toolbar [record]="data$ | async"></abp-page-toolbar>
<abp-page-toolbar [record]="data.items"></abp-page-toolbar>
</div>
</div>
</div>
<div class="card-body">
<abp-extensible-table
[data]="data$ | async"
[recordsTotal]="totalCount$ | async"
[data]="data.items"
[recordsTotal]="data.totalCount"
[list]="list"
></abp-extensible-table>
</div>

276
docs/en/UI/Angular/Permission-Management-Component-Replacement.md

@ -5,37 +5,27 @@
Run the following command in `angular` folder to create a new component called `PermissionManagementComponent`.
```bash
yarn ng generate component permission-management --entryComponent --inlineStyle
# You don't need the --entryComponent option in Angular 9
yarn ng generate component permission-management --inlineStyle
```
Open the generated `permission-management.component.ts` in `src/app/permission-management` folder and replace the content with the following:
```js
import { ConfigStateService, CurrentUserDto, ReplaceableComponents } from '@abp/ng.core';
import {
GetPermissionListResultDto,
PermissionGrantInfoDto, PermissionGroupDto, PermissionManagement, PermissionsService, ProviderInfoDto, UpdatePermissionDto
} from '@abp/ng.permission-management';
import { LocaleDirection } from '@abp/ng.theme.shared';
import {
Component,
EventEmitter,
Input,
Output,
Renderer2,
TrackByFunction,
Inject,
Optional,
EventEmitter, Inject, Input, Optional, Output, TrackByFunction
} from '@angular/core';
import { ReplaceableComponents } from '@abp/ng.core';
import { Select, Store } from '@ngxs/store';
import { Observable } from 'rxjs';
import { finalize, map, pluck, take, tap } from 'rxjs/operators';
import {
GetPermissions,
UpdatePermissions,
PermissionManagement,
PermissionManagementState,
} from '@abp/ng.permission-management';
import { of } from 'rxjs';
import { finalize, switchMap, tap } from 'rxjs/operators';
type PermissionWithMargin = PermissionManagement.Permission & {
margin: number;
type PermissionWithStyle = PermissionGrantInfoDto & {
style: string;
};
@Component({
@ -52,8 +42,8 @@ type PermissionWithMargin = PermissionManagement.Permission & {
})
export class PermissionManagementComponent
implements
PermissionManagement.PermissionManagementComponentInputs,
PermissionManagement.PermissionManagementComponentOutputs {
PermissionManagement.PermissionManagementComponentInputs,
PermissionManagement.PermissionManagementComponentOutputs {
protected _providerName: string;
@Input()
get providerName(): string {
@ -115,15 +105,11 @@ export class PermissionManagementComponent
@Output() readonly visibleChange = new EventEmitter<boolean>();
@Select(PermissionManagementState.getPermissionGroups)
groups$: Observable<PermissionManagement.Group[]>;
data: GetPermissionListResultDto = { groups: [], entityDisplayName: null };
@Select(PermissionManagementState.getEntityDisplayName)
entityName$: Observable<string>;
selectedGroup: PermissionGroupDto;
selectedGroup: PermissionManagement.Group;
permissions: PermissionManagement.Permission[] = [];
permissions: PermissionGrantInfoDto[] = [];
selectThisTab = false;
@ -131,25 +117,26 @@ export class PermissionManagementComponent
modalBusy = false;
trackByFn: TrackByFunction<PermissionManagement.Group> = (_, item) => item.name;
get selectedGroupPermissions$(): Observable<PermissionWithMargin[]> {
return this.groups$.pipe(
map((groups) =>
this.selectedGroup
? groups.find((group) => group.name === this.selectedGroup.name).permissions
: []
),
map<PermissionManagement.Permission[], PermissionWithMargin[]>((permissions) =>
permissions.map(
(permission) =>
(({
...permission,
margin: findMargin(permissions, permission),
isGranted: this.permissions.find((per) => per.name === permission.name).isGranted,
} as any) as PermissionWithMargin)
)
)
trackByFn: TrackByFunction<PermissionGroupDto> = (_, item) => item.name;
get selectedGroupPermissions(): PermissionWithStyle[] {
if (!this.selectedGroup) return [];
const margin = `margin-${
(document.body.dir as LocaleDirection) === 'rtl' ? 'right' : 'left'
}.px`;
const permissions = this.data.groups.find(
group => group.name === this.selectedGroup.name,
).permissions;
return permissions.map(
permission =>
({
...permission,
style: { [margin]: findMargin(permissions, permission) },
isGranted: this.permissions.find(per => per.name === permission.name).isGranted,
} as unknown as PermissionWithStyle),
);
}
@ -166,21 +153,22 @@ export class PermissionManagementComponent
PermissionManagement.PermissionManagementComponentInputs,
PermissionManagement.PermissionManagementComponentOutputs
>,
private store: Store
private service: PermissionsService,
private configState: ConfigStateService
) {}
getChecked(name: string) {
return (this.permissions.find((per) => per.name === name) || { isGranted: false }).isGranted;
return (this.permissions.find(per => per.name === name) || { isGranted: false }).isGranted;
}
isGrantedByOtherProviderName(grantedProviders: PermissionManagement.GrantedProvider[]): boolean {
isGrantedByOtherProviderName(grantedProviders: ProviderInfoDto[]): boolean {
if (grantedProviders.length) {
return grantedProviders.findIndex((p) => p.providerName !== this.providerName) > -1;
return grantedProviders.findIndex(p => p.providerName !== this.providerName) > -1;
}
return false;
}
onClickCheckbox(clickedPermission: PermissionManagement.Permission, value) {
onClickCheckbox(clickedPermission: PermissionGrantInfoDto, value) {
if (
clickedPermission.isGranted &&
this.isGrantedByOtherProviderName(clickedPermission.grantedProviders)
@ -188,7 +176,7 @@ export class PermissionManagementComponent
return;
setTimeout(() => {
this.permissions = this.permissions.map((per) => {
this.permissions = this.permissions.map(per => {
if (clickedPermission.name === per.name) {
return { ...per, isGranted: !per.isGranted };
} else if (clickedPermission.name === per.parentName && clickedPermission.isGranted) {
@ -206,24 +194,22 @@ export class PermissionManagementComponent
}
setTabCheckboxState() {
this.selectedGroupPermissions$.pipe(take(1)).subscribe((permissions) => {
const selectedPermissions = permissions.filter((per) => per.isGranted);
const element = document.querySelector('#select-all-in-this-tabs') as any;
if (selectedPermissions.length === permissions.length) {
element.indeterminate = false;
this.selectThisTab = true;
} else if (selectedPermissions.length === 0) {
element.indeterminate = false;
this.selectThisTab = false;
} else {
element.indeterminate = true;
}
});
const selectedPermissions = this.selectedGroupPermissions.filter(per => per.isGranted);
const element = document.querySelector('#select-all-in-this-tabs') as any;
if (selectedPermissions.length === this.selectedGroupPermissions.length) {
element.indeterminate = false;
this.selectThisTab = true;
} else if (selectedPermissions.length === 0) {
element.indeterminate = false;
this.selectThisTab = false;
} else {
element.indeterminate = true;
}
}
setGrantCheckboxState() {
const selectedAllPermissions = this.permissions.filter((per) => per.isGranted);
const selectedAllPermissions = this.permissions.filter(per => per.isGranted);
const checkboxElement = document.querySelector('#select-all-in-all-tabs') as any;
if (selectedAllPermissions.length === this.permissions.length) {
@ -238,26 +224,24 @@ export class PermissionManagementComponent
}
onClickSelectThisTab() {
this.selectedGroupPermissions$.pipe(take(1)).subscribe((permissions) => {
permissions.forEach((permission) => {
if (permission.isGranted && this.isGrantedByOtherProviderName(permission.grantedProviders))
return;
const index = this.permissions.findIndex((per) => per.name === permission.name);
this.permissions = [
...this.permissions.slice(0, index),
{ ...this.permissions[index], isGranted: !this.selectThisTab },
...this.permissions.slice(index + 1),
];
});
this.selectedGroupPermissions.forEach(permission => {
if (permission.isGranted && this.isGrantedByOtherProviderName(permission.grantedProviders))
return;
const index = this.permissions.findIndex(per => per.name === permission.name);
this.permissions = [
...this.permissions.slice(0, index),
{ ...this.permissions[index], isGranted: !this.selectThisTab },
...this.permissions.slice(index + 1),
];
});
this.setGrantCheckboxState();
}
onClickSelectAll() {
this.permissions = this.permissions.map((permission) => ({
this.permissions = this.permissions.map(permission => ({
...permission,
isGranted:
this.isGrantedByOtherProviderName(permission.grantedProviders) || !this.selectAllTab,
@ -266,43 +250,41 @@ export class PermissionManagementComponent
this.selectThisTab = !this.selectAllTab;
}
onChangeGroup(group: PermissionManagement.Group) {
onChangeGroup(group: PermissionGroupDto) {
this.selectedGroup = group;
this.setTabCheckboxState();
}
submit() {
this.modalBusy = true;
const unchangedPermissions = getPermissions(
this.store.selectSnapshot(PermissionManagementState.getPermissionGroups)
);
const unchangedPermissions = getPermissions(this.data.groups);
const changedPermissions: PermissionManagement.MinimumPermission[] = this.permissions
.filter((per) =>
unchangedPermissions.find((unchanged) => unchanged.name === per.name).isGranted ===
const changedPermissions: UpdatePermissionDto[] = this.permissions
.filter(per =>
unchangedPermissions.find(unchanged => unchanged.name === per.name).isGranted ===
per.isGranted
? false
: true
: true,
)
.map(({ name, isGranted }) => ({ name, isGranted }));
if (changedPermissions.length) {
this.store
.dispatch(
new UpdatePermissions({
providerKey: this.providerKey,
providerName: this.providerName,
permissions: changedPermissions,
})
)
.pipe(finalize(() => (this.modalBusy = false)))
.subscribe(() => {
this.visible = false;
});
} else {
this.modalBusy = false;
if (!changedPermissions.length) {
this.visible = false;
return;
}
this.modalBusy = true;
this.service
.update(this.providerName, this.providerKey, { permissions: changedPermissions })
.pipe(
switchMap(() =>
this.shouldFetchAppConfig() ? this.configState.refreshAppState() : of(null),
),
finalize(() => (this.modalBusy = false)),
)
.subscribe(() => {
this.visible = false;
});
}
openModal() {
@ -310,20 +292,13 @@ export class PermissionManagementComponent
throw new Error('Provider Key and Provider Name are required.');
}
return this.store
.dispatch(
new GetPermissions({
providerKey: this.providerKey,
providerName: this.providerName,
})
)
.pipe(
pluck('PermissionManagementState', 'permissionRes'),
tap((permissionRes: PermissionManagement.Response) => {
this.selectedGroup = permissionRes.groups[0];
this.permissions = getPermissions(permissionRes.groups);
})
);
return this.service.get(this.providerName, this.providerKey).pipe(
tap((permissionRes: GetPermissionListResultDto) => {
this.data = permissionRes;
this.selectedGroup = permissionRes.groups[0];
this.permissions = getPermissions(permissionRes.groups);
}),
);
}
initModal() {
@ -331,6 +306,13 @@ export class PermissionManagementComponent
this.setGrantCheckboxState();
}
getAssignedCount(groupName: string) {
return this.permissions.reduce(
(acc, val) => (val.name.split('.')[0] === groupName && val.isGranted ? acc + 1 : acc),
0,
);
}
onVisibleChange(visible: boolean) {
this.visible = visible;
@ -339,13 +321,20 @@ export class PermissionManagementComponent
this.replaceableData.outputs.visibleChange(visible);
}
}
shouldFetchAppConfig() {
const currentUser = this.configState.getOne('currentUser') as CurrentUserDto;
if (this.providerName === 'R') return currentUser.roles.some(role => role === this.providerKey);
if (this.providerName === 'U') return currentUser.id === this.providerKey;
return false;
}
}
function findMargin(
permissions: PermissionManagement.Permission[],
permission: PermissionManagement.Permission
) {
const parentPermission = permissions.find((per) => per.name === permission.parentName);
function findMargin(permissions: PermissionGrantInfoDto[], permission: PermissionGrantInfoDto) {
const parentPermission = permissions.find(per => per.name === permission.parentName);
if (parentPermission && parentPermission.parentName) {
let margin = 20;
@ -355,7 +344,7 @@ function findMargin(
return parentPermission ? 20 : 0;
}
function getPermissions(groups: PermissionManagement.Group[]): PermissionManagement.Permission[] {
function getPermissions(groups: PermissionGroupDto[]): PermissionGrantInfoDto[] {
return groups.reduce((acc, val) => [...acc, ...val.permissions], []);
}
```
@ -363,16 +352,12 @@ function getPermissions(groups: PermissionManagement.Group[]): PermissionManagem
Open the generated `permission-management.component.html` in `src/app/permission-management` folder and replace the content with the below:
```html
<abp-modal
[visible]="isVisible"
(visibleChange)="onVisibleChange($event)"
(init)="initModal()"
[busy]="modalBusy"
>
<ng-container *ngIf="{ entityName: entityName$ | async } as data">
<abp-modal [visible]="isVisible" (visibleChange)="onVisibleChange($event)" [busy]="modalBusy">
<ng-container *ngIf="data.entityDisplayName">
<ng-template #abpHeader>
<h4>
{%{{{ 'AbpPermissionManagement::Permissions' | abpLocalization }}}%} - {%{{{ data.entityName }}}%}
{%{{{ 'AbpPermissionManagement::Permissions' | abpLocalization }}}%} -
{%{{{ data.entityDisplayName }}}%}
</h4>
</ng-template>
<ng-template #abpBody>
@ -394,13 +379,18 @@ Open the generated `permission-management.component.html` in `src/app/permission
<div class="row">
<div class="overflow-scroll col-md-4">
<ul class="nav nav-pills flex-column">
<li *ngFor="let group of groups$ | async; trackBy: trackByFn" class="nav-item">
<li *ngFor="let group of data.groups; trackBy: trackByFn" class="nav-item">
<a
*ngIf="{ assignedCount: getAssignedCount(group.name) } as count"
class="nav-link pointer"
[class.active]="selectedGroup?.name === group?.name"
(click)="onChangeGroup(group)"
>{%{{{ group?.displayName }}}%}</a
>
<div [class.font-weight-bold]="count.assignedCount">
{%{{{ group?.displayName }}}%}
<span>({%{{{ count.assignedCount }}}%})</span>
</div>
</a>
</li>
</ul>
</div>
@ -423,12 +413,8 @@ Open the generated `permission-management.component.html` in `src/app/permission
</div>
<hr class="mb-3" />
<div
*ngFor="
let permission of selectedGroupPermissions$ | async;
let i = index;
trackBy: trackByFn
"
[style.margin-left]="permission.margin + 'px'"
*ngFor="let permission of selectedGroupPermissions; let i = index; trackBy: trackByFn"
[ngStyle]="permission.style"
class="custom-checkbox custom-control mb-2"
>
<input

4
modules/cms-kit/angular/projects/dev-app/src/app/app.module.ts

@ -3,17 +3,16 @@ import { CoreModule } from '@abp/ng.core';
import { IdentityConfigModule } from '@abp/ng.identity/config';
import { SettingManagementConfigModule } from '@abp/ng.setting-management/config';
import { TenantManagementConfigModule } from '@abp/ng.tenant-management/config';
import { ThemeBasicModule } from '@abp/ng.theme.basic';
import { ThemeSharedModule } from '@abp/ng.theme.shared';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { CmsKitConfigModule } from '@my-company-name/cms-kit/config';
import { NgxsModule } from '@ngxs/store';
import { environment } from '../environments/environment';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { APP_ROUTE_PROVIDER } from './route.provider';
import { ThemeBasicModule } from '@abp/ng.theme.basic';
@NgModule({
imports: [
@ -31,7 +30,6 @@ import { ThemeBasicModule } from '@abp/ng.theme.basic';
CmsKitConfigModule.forRoot(),
TenantManagementConfigModule.forRoot(),
SettingManagementConfigModule.forRoot(),
NgxsModule.forRoot(),
ThemeBasicModule.forRoot(),
],
providers: [APP_ROUTE_PROVIDER],

1
npm/ng-packs/angular.json

@ -622,6 +622,7 @@
}
},
"cli": {
"analytics": false,
"defaultCollection": "@nrwl/angular"
},
"schematics": {

2
npm/ng-packs/apps/dev-app/src/app/app.module.ts

@ -9,7 +9,6 @@ import { ThemeSharedModule } from '@abp/ng.theme.shared';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgxsModule } from '@ngxs/store';
import { environment } from '../environments/environment';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
@ -31,7 +30,6 @@ import { APP_ROUTE_PROVIDER } from './route.provider';
IdentityConfigModule.forRoot(),
TenantManagementConfigModule.forRoot(),
SettingManagementConfigModule.forRoot(),
NgxsModule.forRoot(),
ThemeBasicModule.forRoot(),
],
providers: [APP_ROUTE_PROVIDER],

4
npm/ng-packs/package.json

@ -70,7 +70,6 @@
"@ng-bootstrap/ng-bootstrap": "^7.0.0",
"@ngneat/spectator": "^8.0.3",
"@ngx-validate/core": "^0.0.13",
"@ngxs/store": "^3.7.0",
"@nrwl/angular": "12.6.5",
"@nrwl/cli": "12.6.5",
"@nrwl/cypress": "12.6.5",
@ -103,7 +102,6 @@
"lerna": "^4.0.0",
"ng-packagr": "^12.2.0",
"ng-zorro-antd": "^12.0.1",
"ngxs-schematic": "^1.1.9",
"prettier": "^2.3.1",
"protractor": "~7.0.0",
"rxjs": "~6.6.0",
@ -117,4 +115,4 @@
"typescript": "~4.3.5",
"zone.js": "~0.11.4"
}
}
}

2
npm/ng-packs/packages/account/src/lib/components/manage-profile/manage-profile.component.ts

@ -1,4 +1,4 @@
import { Profile, ProfileService } from '@abp/ng.core';
import { ProfileService } from '@abp/ng.core';
import { fadeIn } from '@abp/ng.theme.shared';
import { transition, trigger, useAnimation } from '@angular/animations';
import { Component, OnInit } from '@angular/core';

10
npm/ng-packs/packages/account/src/lib/services/manage-profile.state.service.ts

@ -1,9 +1,9 @@
import { InternalStore, ProfileDto } from '@abp/ng.core';
import { Injectable } from '@angular/core';
import { InternalStore, Profile } from '@abp/ng.core';
import { Observable } from 'rxjs';
export interface ManageProfileState {
profile: Profile.Response;
profile: ProfileDto;
}
@Injectable({ providedIn: 'root' })
@ -14,15 +14,15 @@ export class ManageProfileStateService {
return this.store.sliceUpdate;
}
getProfile$(): Observable<Profile.Response> {
getProfile$(): Observable<ProfileDto> {
return this.store.sliceState(state => state.profile);
}
getProfile(): Profile.Response {
getProfile(): ProfileDto {
return this.store.state.profile;
}
setProfile(profile: Profile.Response) {
setProfile(profile: ProfileDto) {
this.store.patch({ profile });
}
}

1
npm/ng-packs/packages/core/ng-package.json

@ -6,7 +6,6 @@
},
"allowedNonPeerDependencies": [
"@abp/utils",
"@ngxs/store",
"angular-oauth2-oidc",
"just-compare",
"just-clone",

1
npm/ng-packs/packages/core/package.json

@ -8,7 +8,6 @@
},
"dependencies": {
"@abp/utils": "^4.4.2",
"@ngxs/store": "^3.7.0",
"angular-oauth2-oidc": "^12.1.0",
"just-clone": "^3.2.1",
"just-compare": "^1.4.0",

24
npm/ng-packs/packages/core/src/lib/actions/config.actions.ts

@ -1,24 +0,0 @@
import { Config } from '../models/config';
/**
* @deprecated Use ConfigStateService. To be deleted in v5.0.
*/
export class GetAppConfiguration {
static readonly type = '[Config] Get App Configuration';
}
/**
* @deprecated Use EnvironmentService instead. To be deleted in v5.0.
*/
export class SetEnvironment {
static readonly type = '[Config] Set Environment';
constructor(public environment: Config.Environment) {}
}
/**
* @deprecated Use EnvironmentService instead. To be deleted in v5.0.
*/
export class PatchConfigState {
static readonly type = '[Config] Set State';
constructor(public state: Config.State) {}
}

4
npm/ng-packs/packages/core/src/lib/actions/index.ts

@ -1,4 +0,0 @@
export { GetAppConfiguration, SetEnvironment } from './config.actions';
export * from './loader.actions';
export * from './profile.actions';
export * from './rest.actions';

11
npm/ng-packs/packages/core/src/lib/actions/loader.actions.ts

@ -1,11 +0,0 @@
import { HttpRequest } from '@angular/common/http';
export class StartLoader {
static readonly type = '[Loader] Start';
constructor(public payload: HttpRequest<any>) {}
}
export class StopLoader {
static readonly type = '[Loader] Stop';
constructor(public payload: HttpRequest<any>) {}
}

15
npm/ng-packs/packages/core/src/lib/actions/profile.actions.ts

@ -1,15 +0,0 @@
import { Profile } from '../models';
export class GetProfile {
static readonly type = '[Profile] Get';
}
export class UpdateProfile {
static readonly type = '[Profile] Update';
constructor(public payload: Profile.Response) {}
}
export class ChangePassword {
static readonly type = '[Profile] Change Password';
constructor(public payload: Profile.ChangePasswordRequest) {}
}

6
npm/ng-packs/packages/core/src/lib/actions/rest.actions.ts

@ -1,6 +0,0 @@
import { HttpErrorResponse } from '@angular/common/http';
export class RestOccurError {
static readonly type = '[Rest] Error';
constructor(public payload: HttpErrorResponse | any) {}
}

5
npm/ng-packs/packages/core/src/lib/core.module.ts

@ -3,7 +3,6 @@ import { HttpClientModule, HttpClientXsrfModule, HTTP_INTERCEPTORS } from '@angu
import { APP_INITIALIZER, Injector, ModuleWithProviders, NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { NgxsModule } from '@ngxs/store';
import { OAuthModule, OAuthStorage } from 'angular-oauth2-oidc';
import { AbstractNgModelComponent } from './abstracts/ng-model.component';
import { DynamicLayoutComponent } from './components/dynamic-layout.component';
@ -27,13 +26,12 @@ import { LocalizationPipe } from './pipes/localization.pipe';
import { SortPipe } from './pipes/sort.pipe';
import { LocaleProvider } from './providers/locale.provider';
import { LocalizationService } from './services/localization.service';
import { ProfileState } from './states/profile.state';
import { oAuthStorage } from './strategies/auth-flow.strategy';
import { coreOptionsFactory, CORE_OPTIONS } from './tokens/options.token';
import { TENANT_KEY } from './tokens/tenant-key.token';
import { noop } from './utils/common-utils';
import './utils/date-extensions';
import { getInitialData, localeInitializer } from './utils/initial-utils';
import { TENANT_KEY } from './tokens/tenant-key.token';
export function storageFactory(): OAuthStorage {
return oAuthStorage;
@ -111,7 +109,6 @@ export class BaseCoreModule {}
imports: [
BaseCoreModule,
LocalizationModule,
NgxsModule.forFeature([ProfileState]),
OAuthModule.forRoot(),
HttpClientXsrfModule.withOptions({
cookieName: 'XSRF-TOKEN',

10
npm/ng-packs/packages/core/src/lib/guards/permission.guard.ts

@ -1,12 +1,12 @@
import { HttpErrorResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import { Store } from '@ngxs/store';
import { Observable, of } from 'rxjs';
import { tap } from 'rxjs/operators';
import { RestOccurError } from '../actions/rest.actions';
import { HttpErrorReporterService } from '../services/http-error-reporter.service';
import { PermissionService } from '../services/permission.service';
import { RoutesService } from '../services/routes.service';
import { findRoute, getRoutePath } from '../utils/route-utils';
import { PermissionService } from '../services/permission.service';
@Injectable({
providedIn: 'root',
@ -15,8 +15,8 @@ export class PermissionGuard implements CanActivate {
constructor(
private router: Router,
private routesService: RoutesService,
private store: Store,
private permissionService: PermissionService,
private httpErrorReporter: HttpErrorReporterService,
) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
@ -32,7 +32,7 @@ export class PermissionGuard implements CanActivate {
return this.permissionService.getGrantedPolicy$(requiredPolicy).pipe(
tap(access => {
if (!access) {
this.store.dispatch(new RestOccurError({ status: 403 }));
this.httpErrorReporter.reportError({ status: 403 } as HttpErrorResponse);
}
}),
);

65
npm/ng-packs/packages/core/src/lib/models/config.ts

@ -1,65 +0,0 @@
import { ApplicationConfiguration } from './application-configuration';
import { ABP } from './common';
import { Environment as IEnvironment } from './environment';
import {
LocalizationParam as ILocalizationParam,
LocalizationWithDefault as ILocalizationWithDefault,
} from './localization';
export namespace Config {
/**
* @deprecated Use ApplicationConfiguration.Response instead. To be deleted in v5.0.
*/
export type State = ApplicationConfiguration.Response & ABP.Root & { environment: IEnvironment };
export type Environment = IEnvironment;
/**
* @deprecated Use ApplicationInfo interface instead. To be deleted in v5.0.
*/
export interface Application {
name: string;
baseUrl?: string;
logoUrl?: string;
}
/**
* @deprecated Use ApiConfig interface instead. To be deleted in v5.0.
*/
export type ApiConfig = {
[key: string]: string;
url: string;
} & Partial<{
rootNamespace: string;
}>;
/**
* @deprecated Use Apis interface instead. To be deleted in v5.0.
*/
export interface Apis {
[key: string]: ApiConfig;
default: ApiConfig;
}
export type LocalizationWithDefault = ILocalizationWithDefault;
export type LocalizationParam = ILocalizationParam;
/**
* @deprecated Use customMergeFn type instead. To be deleted in v5.0.
*/
export type customMergeFn = (
localEnv: Partial<Config.Environment>,
remoteEnv: any,
) => Config.Environment;
/**
* @deprecated Use RemoteEnv interface instead. To be deleted in v5.0.
*/
export interface RemoteEnv {
url: string;
mergeStrategy: 'deepmerge' | 'overwrite' | customMergeFn;
method?: string;
headers?: ABP.Dictionary<string>;
}
}

1
npm/ng-packs/packages/core/src/lib/models/index.ts

@ -1,7 +1,6 @@
export * from './application-configuration';
export * from './auth';
export * from './common';
export * from './config';
export * from './dtos';
export * from './environment';
export * from './localization';

44
npm/ng-packs/packages/core/src/lib/models/profile.ts

@ -1,28 +1,26 @@
import { ExtensibleObject } from './dtos';
export namespace Profile {
export interface State {
profile: Response;
}
export interface ChangePasswordInput {
currentPassword: string;
newPassword: string;
}
export interface Response extends Partial<ExtensibleObject> {
userName: string;
email: string;
name: string;
surname: string;
phoneNumber: string;
/**
* Following 4 fields are added as optional (for backward compatibility) on 17.08.2020
* Also, this interface extends `ExtensibleObject` as partial for extraProperties field.
*/
isExternal?: boolean;
hasPassword?: boolean;
emailConfirmed?: boolean;
phoneNumberConfirmed?: boolean;
}
export interface ProfileDto extends ExtensibleObject {
userName: string;
email: string;
name: string;
surname: string;
phoneNumber: string;
isExternal?: boolean;
hasPassword?: boolean;
emailConfirmed?: boolean;
phoneNumberConfirmed?: boolean;
}
export interface ChangePasswordRequest {
currentPassword: string;
newPassword: string;
}
export interface UpdateProfileDto extends ExtensibleObject {
userName: string;
email: string;
name: string;
surname: string;
phoneNumber: string;
}

7
npm/ng-packs/packages/core/src/lib/pipes/localization.pipe.ts

@ -1,5 +1,5 @@
import { Injectable, Pipe, PipeTransform } from '@angular/core';
import { Config } from '../models';
import { LocalizationWithDefault } from '../models/localization';
import { LocalizationService } from '../services/localization.service';
@Injectable()
@ -9,10 +9,7 @@ import { LocalizationService } from '../services/localization.service';
export class LocalizationPipe implements PipeTransform {
constructor(private localization: LocalizationService) {}
transform(
value: string | Config.LocalizationWithDefault = '',
...interpolateParams: string[]
): string {
transform(value: string | LocalizationWithDefault = '', ...interpolateParams: string[]): string {
return this.localization.instant(
value,
...interpolateParams.reduce(

26
npm/ng-packs/packages/core/src/lib/services/http-error-reporter.service.ts

@ -0,0 +1,26 @@
import { HttpErrorResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { BehaviorSubject, Subject } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class HttpErrorReporterService {
private _reporter$ = new Subject();
private _errors$ = new BehaviorSubject<HttpErrorResponse[]>([]);
get reporter$() {
return this._reporter$.asObservable();
}
get errors$() {
return this._errors$.asObservable();
}
get errors() {
return this._errors$.value;
}
reportError = (error: HttpErrorResponse) => {
this._reporter$.next(error);
this._errors$.next([...this.errors, error]);
};
}

2
npm/ng-packs/packages/core/src/lib/services/index.ts

@ -4,13 +4,13 @@ export * from './config-state.service';
export * from './content-projection.service';
export * from './dom-insertion.service';
export * from './environment.service';
export * from './http-error-reporter.service';
export * from './http-wait.service';
export * from './lazy-load.service';
export * from './list.service';
export * from './localization.service';
export * from './multi-tenancy.service';
export * from './permission.service';
export * from './profile-state.service';
export * from './profile.service';
export * from './replaceable-components.service';
export * from './resource-wait.service';

11
npm/ng-packs/packages/core/src/lib/services/localization.service.ts

@ -3,7 +3,7 @@ import { Injectable, Injector, isDevMode, Optional, SkipSelf } from '@angular/co
import { from, Observable, Subject } from 'rxjs';
import { filter, map, mapTo, switchMap } from 'rxjs/operators';
import { ABP } from '../models/common';
import { Config } from '../models/config';
import { LocalizationWithDefault } from '../models/localization';
import { ApplicationConfigurationDto } from '../proxy/volo/abp/asp-net-core/mvc/application-configurations/models';
import { CORE_OPTIONS } from '../tokens/options.token';
import { createLocalizer, createLocalizerWithFallback } from '../utils/localization-utils';
@ -67,10 +67,7 @@ export class LocalizationService {
* @param key Localizaton key to replace with localized text
* @param interpolateParams Values to interpolate
*/
get(
key: string | Config.LocalizationWithDefault,
...interpolateParams: string[]
): Observable<string> {
get(key: string | LocalizationWithDefault, ...interpolateParams: string[]): Observable<string> {
return this.configState
.getAll$()
.pipe(map(state => getLocalization(state, key, ...interpolateParams)));
@ -89,7 +86,7 @@ export class LocalizationService {
* @param key Localization key to replace with localized text
* @param interpolateParams Values to intepolate.
*/
instant(key: string | Config.LocalizationWithDefault, ...interpolateParams: string[]): string {
instant(key: string | LocalizationWithDefault, ...interpolateParams: string[]): string {
return getLocalization(this.configState.getAll(), key, ...interpolateParams);
}
@ -124,7 +121,7 @@ export class LocalizationService {
function getLocalization(
state: ApplicationConfigurationDto,
key: string | Config.LocalizationWithDefault,
key: string | LocalizationWithDefault,
...interpolateParams: string[]
) {
if (!key) key = '';

28
npm/ng-packs/packages/core/src/lib/services/profile-state.service.ts

@ -1,28 +0,0 @@
import { Injectable } from '@angular/core';
import { Store } from '@ngxs/store';
import { ProfileState } from '../states';
import { Profile } from '../models';
import { GetProfile, UpdateProfile, ChangePassword } from '../actions';
@Injectable({
providedIn: 'root',
})
export class ProfileStateService {
constructor(private store: Store) {}
getProfile() {
return this.store.selectSnapshot(ProfileState.getProfile);
}
dispatchGetProfile() {
return this.store.dispatch(new GetProfile());
}
dispatchUpdateProfile(...args: ConstructorParameters<typeof UpdateProfile>) {
return this.store.dispatch(new UpdateProfile(...args));
}
dispatchChangePassword(...args: ConstructorParameters<typeof ChangePassword>) {
return this.store.dispatch(new ChangePassword(...args));
}
}

71
npm/ng-packs/packages/core/src/lib/services/profile.service.ts

@ -1,7 +1,6 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { ChangePasswordInput, ProfileDto, UpdateProfileDto } from '../models/profile';
import { RestService } from './rest.service';
import { Profile, Rest } from '../models';
@Injectable({
providedIn: 'root',
@ -9,42 +8,34 @@ import { Profile, Rest } from '../models';
export class ProfileService {
apiName = 'AbpIdentity';
constructor(private rest: RestService) {}
get(): Observable<Profile.Response> {
const request: Rest.Request<null> = {
method: 'GET',
url: '/api/identity/my-profile',
};
return this.rest.request<null, Profile.Response>(request, { apiName: this.apiName });
}
update(body: Profile.Response): Observable<Profile.Response> {
const request: Rest.Request<Profile.Response> = {
method: 'PUT',
url: '/api/identity/my-profile',
body,
};
return this.rest.request<Profile.Response, Profile.Response>(request, {
apiName: this.apiName,
});
}
changePassword(
body: Profile.ChangePasswordRequest,
skipHandleError: boolean = false,
): Observable<null> {
const request: Rest.Request<Profile.ChangePasswordRequest> = {
method: 'POST',
url: '/api/identity/my-profile/change-password',
body,
};
return this.rest.request<Profile.ChangePasswordRequest, null>(request, {
skipHandleError,
apiName: this.apiName,
});
}
changePassword = (input: ChangePasswordInput, skipHandleError = false) =>
this.restService.request<ChangePasswordInput, void>(
{
method: 'POST',
url: '/api/identity/my-profile/change-password',
body: input,
},
{ apiName: this.apiName, skipHandleError },
);
get = () =>
this.restService.request<null, ProfileDto>(
{
method: 'GET',
url: '/api/identity/my-profile',
},
{ apiName: this.apiName },
);
update = (input: UpdateProfileDto) =>
this.restService.request<UpdateProfileDto, ProfileDto>(
{
method: 'PUT',
url: '/api/identity/my-profile',
body: input,
},
{ apiName: this.apiName },
);
constructor(protected restService: RestService) {}
}

8
npm/ng-packs/packages/core/src/lib/services/rest.service.ts

@ -1,14 +1,13 @@
import { HttpClient, HttpRequest } from '@angular/common/http';
import { Inject, Injectable } from '@angular/core';
import { Store } from '@ngxs/store';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { RestOccurError } from '../actions/rest.actions';
import { ABP } from '../models/common';
import { Rest } from '../models/rest';
import { CORE_OPTIONS } from '../tokens/options.token';
import { isUndefinedOrEmptyString } from '../utils/common-utils';
import { EnvironmentService } from './environment.service';
import { HttpErrorReporterService } from './http-error-reporter.service';
@Injectable({
providedIn: 'root',
@ -18,7 +17,7 @@ export class RestService {
@Inject(CORE_OPTIONS) protected options: ABP.Root,
protected http: HttpClient,
protected environment: EnvironmentService,
protected store: Store,
protected httpErrorReporter: HttpErrorReporterService,
) {}
protected getApiFromStore(apiName: string): string {
@ -26,11 +25,10 @@ export class RestService {
}
handleError(err: any): Observable<any> {
this.store.dispatch(new RestOccurError(err));
this.httpErrorReporter.reportError(err);
return throwError(err);
}
// TODO: Deprecate service or improve interface in v5.0
request<T, R>(
request: HttpRequest<T> | Rest.Request<T>,
config?: Rest.Config,

264
npm/ng-packs/packages/core/src/lib/states/config.state.ts

@ -1,264 +0,0 @@
import { HttpErrorResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Action, createSelector, Selector, State, StateContext, Store } from '@ngxs/store';
import compare from 'just-compare';
import { throwError } from 'rxjs';
import { catchError, distinctUntilChanged } from 'rxjs/operators';
import { GetAppConfiguration, PatchConfigState, SetEnvironment } from '../actions/config.actions';
import { RestOccurError } from '../actions/rest.actions';
import { Config } from '../models/config';
import { ConfigStateService } from '../services/config-state.service';
import { EnvironmentService } from '../services/environment.service';
import { interpolate } from '../utils/string-utils';
/**
* @deprecated Use ConfigStateService instead. To be deleted in v5.0.
*/
@State<Config.State>({
name: 'ConfigState',
defaults: {} as Config.State,
})
@Injectable()
export class ConfigState {
@Selector()
static getAll(state: Config.State) {
return state;
}
@Selector()
static getApplicationInfo(state: Config.State): Config.Application {
return state.environment.application || ({} as Config.Application);
}
@Selector()
static getEnvironment(state: Config.State): Config.Environment {
return state.environment;
}
static getOne(key: string) {
const selector = createSelector([ConfigState], (state: Config.State) => {
return state[key];
});
return selector;
}
static getDeep(keys: string[] | string) {
if (typeof keys === 'string') {
keys = keys.split('.');
}
if (!Array.isArray(keys)) {
throw new Error('The argument must be a dot string or an string array.');
}
const selector = createSelector([ConfigState], (state: Config.State) => {
return (keys as string[]).reduce((acc, val) => {
if (acc) {
return acc[val];
}
return undefined;
}, state);
});
return selector;
}
static getApiUrl(key?: string) {
const selector = createSelector([ConfigState], (state: Config.State): string => {
return (state.environment.apis[key || 'default'] || state.environment.apis.default).url;
});
return selector;
}
static getFeature(key: string) {
const selector = createSelector([ConfigState], (state: Config.State) => {
return state.features.values?.[key];
});
return selector;
}
static getSetting(key: string) {
const selector = createSelector([ConfigState], (state: Config.State) => {
return state.setting.values?.[key];
});
return selector;
}
static getSettings(keyword?: string) {
const selector = createSelector([ConfigState], (state: Config.State) => {
const settings = state.setting.values || {};
if (!keyword) return settings;
const keysFound = Object.keys(settings).filter(key => key.indexOf(keyword) > -1);
return keysFound.reduce((acc, key) => {
acc[key] = settings[key];
return acc;
}, {});
});
return selector;
}
/**
* @deprecated use PermissionService's getGrantedPolicyStream or getGrantedPolicy methods.
*/
static getGrantedPolicy(key: string) {
const selector = createSelector([ConfigState], (state: Config.State): boolean => {
if (!key) return true;
const getPolicy = (k: string) => state.auth.grantedPolicies[k] || false;
const orRegexp = /\|\|/g;
const andRegexp = /&&/g;
// TODO: Allow combination of ANDs & ORs
if (orRegexp.test(key)) {
const keys = key.split('||').filter(Boolean);
if (keys.length < 2) return false;
return keys.some(k => getPolicy(k.trim()));
} else if (andRegexp.test(key)) {
const keys = key.split('&&').filter(Boolean);
if (keys.length < 2) return false;
return keys.every(k => getPolicy(k.trim()));
}
return getPolicy(key);
});
return selector;
}
static getLocalizationResource(resourceName: string) {
const selector = createSelector(
[ConfigState],
(
state: Config.State,
): {
[key: string]: string;
} => {
return state.localization.values[resourceName];
},
);
return selector;
}
static getLocalization(
key: string | Config.LocalizationWithDefault,
...interpolateParams: string[]
) {
if (!key) key = '';
let defaultValue: string;
if (typeof key !== 'string') {
defaultValue = key.defaultValue;
key = key.key;
}
const keys = key.split('::') as string[];
const selector = createSelector([ConfigState], (state: Config.State): string => {
const warn = (message: string) => {
if (!state.environment.production) console.warn(message);
};
if (keys.length < 2) {
warn('The localization source separator (::) not found.');
return defaultValue || (key as string);
}
if (!state.localization) return defaultValue || keys[1];
const sourceName =
keys[0] ||
state.environment.localization?.defaultResourceName ||
state.localization.defaultResourceName;
const sourceKey = keys[1];
if (sourceName === '_') {
return defaultValue || sourceKey;
}
if (!sourceName) {
warn(
'Localization source name is not specified and the defaultResourceName was not defined!',
);
return defaultValue || sourceKey;
}
const source = state.localization.values[sourceName];
if (!source) {
warn('Could not find localization source: ' + sourceName);
return defaultValue || sourceKey;
}
let localization = source[sourceKey];
if (typeof localization === 'undefined') {
return defaultValue || sourceKey;
}
interpolateParams = interpolateParams.filter(params => params != null);
if (localization) localization = interpolate(localization, interpolateParams);
if (typeof localization !== 'string') localization = '';
return localization || defaultValue || (key as string);
});
return selector;
}
constructor(
private store: Store,
private environmentService: EnvironmentService,
private configState: ConfigStateService,
) {
this.syncConfigState();
this.syncEnvironment();
}
private syncConfigState() {
this.configState
.createOnUpdateStream(state => state)
.pipe(distinctUntilChanged(compare))
.subscribe(config => this.store.dispatch(new PatchConfigState(config as any)));
}
private syncEnvironment() {
this.environmentService
.createOnUpdateStream(state => state)
.pipe(distinctUntilChanged(compare))
.subscribe(env => this.store.dispatch(new PatchConfigState({ environment: env } as any)));
}
@Action(GetAppConfiguration)
addData({ patchState, dispatch }: StateContext<Config.State>) {
const apiName = 'default';
const api = this.store.selectSnapshot(ConfigState.getApiUrl(apiName));
return this.configState.refreshAppState().pipe(
catchError((err: HttpErrorResponse) => {
dispatch(new RestOccurError(err));
return throwError(err);
}),
);
}
@Action(SetEnvironment)
setEnvironment(_, { environment }: SetEnvironment) {
return this.environmentService.setState(environment);
}
@Action(PatchConfigState)
setConfig({ patchState, getState }: StateContext<Config.State>, { state }: PatchConfigState) {
patchState({ ...getState(), ...state });
}
}

2
npm/ng-packs/packages/core/src/lib/states/index.ts

@ -1,2 +0,0 @@
export * from './config.state';
export * from './profile.state';

47
npm/ng-packs/packages/core/src/lib/states/profile.state.ts

@ -1,47 +0,0 @@
import { Injectable } from '@angular/core';
import { Action, Selector, State, StateContext } from '@ngxs/store';
import { tap } from 'rxjs/operators';
import { ChangePassword, GetProfile, UpdateProfile } from '../actions/profile.actions';
import { Profile } from '../models/profile';
import { ProfileService } from '../services/profile.service';
@State<Profile.State>({
name: 'ProfileState',
defaults: {} as Profile.State,
})
@Injectable()
export class ProfileState {
@Selector()
static getProfile({ profile }: Profile.State): Profile.Response {
return profile;
}
constructor(private profileService: ProfileService) {}
@Action(GetProfile)
getProfile({ patchState }: StateContext<Profile.State>) {
return this.profileService.get().pipe(
tap(profile =>
patchState({
profile,
}),
),
);
}
@Action(UpdateProfile)
updateProfile({ patchState }: StateContext<Profile.State>, { payload }: UpdateProfile) {
return this.profileService.update(payload).pipe(
tap(profile =>
patchState({
profile,
}),
),
);
}
@Action(ChangePassword)
changePassword(_, { payload }: ChangePassword) {
return this.profileService.changePassword(payload, true);
}
}

11
npm/ng-packs/packages/core/src/lib/strategies/auth-flow.strategy.ts

@ -1,7 +1,6 @@
import { HttpHeaders } from '@angular/common/http';
import { Injector } from '@angular/core';
import { Params, Router } from '@angular/router';
import { Store } from '@ngxs/store';
import {
AuthConfig,
OAuthErrorEvent,
@ -11,21 +10,21 @@ import {
} from 'angular-oauth2-oidc';
import { from, Observable, of, pipe } from 'rxjs';
import { filter, switchMap, tap } from 'rxjs/operators';
import { RestOccurError } from '../actions/rest.actions';
import { LoginParams } from '../models/auth';
import { ConfigStateService } from '../services/config-state.service';
import { EnvironmentService } from '../services/environment.service';
import { HttpErrorReporterService } from '../services/http-error-reporter.service';
import { SessionStateService } from '../services/session-state.service';
import { TENANT_KEY } from '../tokens/tenant-key.token';
import { removeRememberMe, setRememberMe } from '../utils/auth-utils';
import { noop } from '../utils/common-utils';
import { TENANT_KEY } from '../tokens/tenant-key.token';
export const oAuthStorage = localStorage;
export abstract class AuthFlowStrategy {
abstract readonly isInternalAuth: boolean;
protected store: Store;
protected httpErrorReporter: HttpErrorReporterService;
protected environment: EnvironmentService;
protected configState: ConfigStateService;
protected oAuthService: OAuthService;
@ -38,10 +37,10 @@ export abstract class AuthFlowStrategy {
abstract logout(queryParams?: Params): Observable<any>;
abstract login(params?: LoginParams | Params): Observable<any>;
private catchError = err => this.store.dispatch(new RestOccurError(err));
private catchError = err => this.httpErrorReporter.reportError(err);
constructor(protected injector: Injector) {
this.store = injector.get(Store);
this.httpErrorReporter = injector.get(HttpErrorReporterService);
this.environment = injector.get(EnvironmentService);
this.configState = injector.get(ConfigStateService);
this.oAuthService = injector.get(OAuthService);

8
npm/ng-packs/packages/core/src/lib/tests/config-state.service.spec.ts

@ -1,6 +1,5 @@
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest';
import { Store } from '@ngxs/store';
import {
ApplicationConfigurationDto,
CurrentUserDto,
@ -101,17 +100,14 @@ export const CONFIG_STATE_DATA = {
registerLocaleFn: () => Promise.resolve(),
} as any as ApplicationConfigurationDto;
describe('ConfigState', () => {
describe('ConfigStateService', () => {
let spectator: SpectatorService<ConfigStateService>;
let configState: ConfigStateService;
const createService = createServiceFactory({
service: ConfigStateService,
imports: [HttpClientTestingModule],
providers: [
{ provide: CORE_OPTIONS, useValue: { skipGetAppConfiguration: true } },
{ provide: Store, useValue: {} },
],
providers: [{ provide: CORE_OPTIONS, useValue: { skipGetAppConfiguration: true } }],
});
beforeEach(() => {

8
npm/ng-packs/packages/core/src/lib/tests/date-utils.spec.ts

@ -1,5 +1,5 @@
import { ConfigStateService } from '../services';
import { getShortDateFormat, getShortTimeFormat, getShortDateShortTimeFormat } from '../utils';
import { getShortDateFormat, getShortDateShortTimeFormat, getShortTimeFormat } from '../utils';
const dateTimeFormat = {
calendarAlgorithmType: 'SolarCalendar',
@ -19,7 +19,7 @@ describe('Date Utils', () => {
});
describe('#getShortDateFormat', () => {
test('should get the short date format from ConfigState and return it', () => {
test('should get the short date format from ConfigStateService and return it', () => {
const getDeepSpy = jest.spyOn(config, 'getDeep');
getDeepSpy.mockReturnValueOnce(dateTimeFormat);
@ -29,7 +29,7 @@ describe('Date Utils', () => {
});
describe('#getShortTimeFormat', () => {
test('should get the short time format from ConfigState and return it', () => {
test('should get the short time format from ConfigStateService and return it', () => {
const getDeepSpy = jest.spyOn(config, 'getDeep');
getDeepSpy.mockReturnValueOnce(dateTimeFormat);
@ -39,7 +39,7 @@ describe('Date Utils', () => {
});
describe('#getShortDateShortTimeFormat', () => {
test('should get the short date time format from ConfigState and return it', () => {
test('should get the short date time format from ConfigStateService and return it', () => {
const getDeepSpy = jest.spyOn(config, 'getDeep');
getDeepSpy.mockReturnValueOnce(dateTimeFormat);

18
npm/ng-packs/packages/core/src/lib/tests/environment.service.spec.ts

@ -25,7 +25,7 @@ export const ENVIRONMENT_DATA = {
},
} as any as Environment;
describe('ConfigState', () => {
describe('Environment', () => {
let spectator: SpectatorService<EnvironmentService>;
let environment: EnvironmentService;
@ -64,20 +64,4 @@ describe('ConfigState', () => {
}),
);
});
// TODO: create permission.service.spec.ts
// describe('#getGrantedPolicy', () => {
// it('should return a granted policy', () => {
// expect(ConfigState.getGrantedPolicy('Abp.Identity')(CONFIG_STATE_DATA)).toBe(false);
// expect(ConfigState.getGrantedPolicy('Abp.Identity || Abp.Account')(CONFIG_STATE_DATA)).toBe(
// true,
// );
// expect(ConfigState.getGrantedPolicy('Abp.Account && Abp.Identity')(CONFIG_STATE_DATA)).toBe(
// false,
// );
// expect(ConfigState.getGrantedPolicy('Abp.Account &&')(CONFIG_STATE_DATA)).toBe(false);
// expect(ConfigState.getGrantedPolicy('|| Abp.Account')(CONFIG_STATE_DATA)).toBe(false);
// expect(ConfigState.getGrantedPolicy('')(CONFIG_STATE_DATA)).toBe(true);
// });
// });
});

27
npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts

@ -3,11 +3,9 @@ import { HttpClientTestingModule } from '@angular/common/http/testing';
import { Component } from '@angular/core';
import { RouterModule } from '@angular/router';
import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/jest';
import { Actions, Store } from '@ngxs/store';
import { of } from 'rxjs';
import { RestOccurError } from '../actions';
import { PermissionGuard } from '../guards/permission.guard';
import { PermissionService } from '../services';
import { HttpErrorReporterService, PermissionService } from '../services';
import { RoutesService } from '../services/routes.service';
import { CORE_OPTIONS } from '../tokens';
@ -15,7 +13,7 @@ describe('PermissionGuard', () => {
let spectator: SpectatorService<PermissionGuard>;
let guard: PermissionGuard;
let routes: SpyObject<RoutesService>;
let store: SpyObject<Store>;
let httpErrorReporter: SpyObject<HttpErrorReporterService>;
let permissionService: SpyObject<PermissionService>;
@Component({ template: '' })
@ -23,7 +21,7 @@ describe('PermissionGuard', () => {
const createService = createServiceFactory({
service: PermissionGuard,
mocks: [PermissionService, Store],
mocks: [PermissionService],
declarations: [DummyComponent],
imports: [
HttpClientTestingModule,
@ -45,14 +43,6 @@ describe('PermissionGuard', () => {
provide: APP_BASE_HREF,
useValue: '/',
},
{
provide: Actions,
useValue: {
pipe() {
return of(null);
},
},
},
{ provide: CORE_OPTIONS, useValue: { skipGetAppConfiguration: true } },
],
});
@ -61,13 +51,13 @@ describe('PermissionGuard', () => {
spectator = createService();
guard = spectator.service;
routes = spectator.inject(RoutesService);
store = spectator.inject(Store);
httpErrorReporter = spectator.inject(HttpErrorReporterService);
permissionService = spectator.inject(PermissionService);
});
it('should return true when the grantedPolicy is true', done => {
permissionService.getGrantedPolicy$.andReturn(of(true));
const spy = jest.spyOn(store, 'dispatch');
const spy = jest.spyOn(httpErrorReporter, 'reportError');
guard.canActivate({ data: { requiredPolicy: 'test' } } as any, null).subscribe(res => {
expect(res).toBe(true);
expect(spy.mock.calls).toHaveLength(0);
@ -75,13 +65,12 @@ describe('PermissionGuard', () => {
});
});
it('should return false and dispatch RestOccurError when the grantedPolicy is false', done => {
it('should return false and report an error when the grantedPolicy is false', done => {
permissionService.getGrantedPolicy$.andReturn(of(false));
const spy = jest.spyOn(store, 'dispatch');
const spy = jest.spyOn(httpErrorReporter, 'reportError');
guard.canActivate({ data: { requiredPolicy: 'test' } } as any, null).subscribe(res => {
expect(res).toBe(false);
expect(spy.mock.calls[0][0] instanceof RestOccurError).toBeTruthy();
expect((spy.mock.calls[0][0] as RestOccurError).payload).toEqual({
expect(spy.mock.calls[0][0]).toEqual({
status: 403,
});
done();

57
npm/ng-packs/packages/core/src/lib/tests/profile-state.service.spec.ts

@ -1,57 +0,0 @@
import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/jest';
import { ProfileStateService } from '../services/profile-state.service';
import { ProfileState } from '../states/profile.state';
import { Store } from '@ngxs/store';
import * as ProfileActions from '../actions';
describe('ProfileStateService', () => {
let service: ProfileStateService;
let spectator: SpectatorService<ProfileStateService>;
let store: SpyObject<Store>;
const createService = createServiceFactory({ service: ProfileStateService, mocks: [Store] });
beforeEach(() => {
spectator = createService();
service = spectator.service;
store = spectator.inject(Store);
});
test('should have the all ProfileState static methods', () => {
const reg = /(?<=static )(.*)(?=\()/gm;
ProfileState.toString()
.match(reg)
.forEach(fnName => {
expect(service[fnName]).toBeTruthy();
const spy = jest.spyOn(store, 'selectSnapshot');
spy.mockClear();
const isDynamicSelector = ProfileState[fnName].name !== 'memoized';
if (isDynamicSelector) {
ProfileState[fnName] = jest.fn((...args) => args);
service[fnName]('test', 0, {});
expect(ProfileState[fnName]).toHaveBeenCalledWith('test', 0, {});
} else {
service[fnName]();
expect(spy).toHaveBeenCalledWith(ProfileState[fnName]);
}
});
});
test('should have a dispatch method for every ProfileState action', () => {
const reg = /(?<=dispatch)(\w+)(?=\()/gm;
ProfileStateService.toString()
.match(reg)
.forEach(fnName => {
expect(ProfileActions[fnName]).toBeTruthy();
const spy = jest.spyOn(store, 'dispatch');
spy.mockClear();
const params = Array.from(new Array(ProfileActions[fnName].length));
service[`dispatch${fnName}`](...params);
expect(spy).toHaveBeenCalledWith(new ProfileActions[fnName](...params));
});
});
});

19
npm/ng-packs/packages/core/src/lib/tests/profile.service.spec.ts

@ -1,6 +1,11 @@
import { createHttpFactory, HttpMethod, SpectatorHttp, SpyObject } from '@ngneat/spectator/jest';
import { Store } from '@ngxs/store';
import { EnvironmentService, ProfileService, RestService } from '../services';
import { UpdateProfileDto } from '../models';
import {
EnvironmentService,
HttpErrorReporterService,
ProfileService,
RestService,
} from '../services';
import { CORE_OPTIONS } from '../tokens';
describe('ProfileService', () => {
@ -9,11 +14,7 @@ describe('ProfileService', () => {
const createHttp = createHttpFactory({
service: ProfileService,
providers: [
RestService,
{ provide: CORE_OPTIONS, useValue: {} },
{ provide: Store, useValue: {} },
],
providers: [RestService, HttpErrorReporterService, { provide: CORE_OPTIONS, useValue: {} }],
mocks: [EnvironmentService],
});
@ -48,7 +49,9 @@ describe('ProfileService', () => {
phoneNumber: '+123456',
isExternal: false,
hasPassword: false,
};
extraProperties: {},
} as UpdateProfileDto;
spectator.service.update(mock).subscribe();
const req = spectator.expectOne('https://abp.io/api/identity/my-profile', HttpMethod.PUT);
expect(req.request.body).toEqual(mock);

74
npm/ng-packs/packages/core/src/lib/tests/profile.state.spec.ts

@ -1,74 +0,0 @@
import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/jest';
import { Session } from '../models/session';
import { ProfileService } from '../services';
import { ProfileState } from '../states';
import { GetAppConfiguration } from '../actions/config.actions';
import { of } from 'rxjs';
import { Profile } from '../models/profile';
export class DummyClass {}
export const PROFILE_STATE_DATA = {
profile: { userName: 'admin', email: 'info@abp.io', name: 'Admin' },
} as Profile.State;
describe('ProfileState', () => {
let spectator: SpectatorService<DummyClass>;
let state: ProfileState;
let profileService: SpyObject<ProfileService>;
let patchedData;
const patchState = jest.fn(data => (patchedData = data));
const createService = createServiceFactory({
service: DummyClass,
mocks: [ProfileService],
});
beforeEach(() => {
spectator = createService();
profileService = spectator.inject(ProfileService);
state = new ProfileState(profileService);
});
describe('#getProfile', () => {
it('should return the current language', () => {
expect(ProfileState.getProfile(PROFILE_STATE_DATA)).toEqual(PROFILE_STATE_DATA.profile);
});
});
describe('#GetProfile', () => {
it('should call the profile service get method and update the state', () => {
const mockData = { userName: 'test', email: 'test@abp.io' };
const spy = jest.spyOn(profileService, 'get');
spy.mockReturnValue(of(mockData as any));
state.getProfile({ patchState } as any).subscribe();
expect(patchedData).toEqual({ profile: mockData });
});
});
describe('#UpdateProfile', () => {
it('should call the profile service update method and update the state', () => {
const mockData = { userName: 'test2', email: 'test@abp.io' };
const spy = jest.spyOn(profileService, 'update');
spy.mockReturnValue(of(mockData as any));
state.updateProfile({ patchState } as any, { payload: mockData as any }).subscribe();
expect(patchedData).toEqual({ profile: mockData });
});
});
describe('#ChangePassword', () => {
it('should call the profile service changePassword method', () => {
const mockData = { currentPassword: 'test123', newPassword: 'test123' };
const spy = jest.spyOn(profileService, 'changePassword');
spy.mockReturnValue(of(null));
state.changePassword(null, { payload: mockData }).subscribe();
expect(spy).toHaveBeenCalledWith(mockData, true);
});
});
});

19
npm/ng-packs/packages/core/src/lib/tests/rest.service.spec.ts

@ -1,29 +1,32 @@
import { createHttpFactory, HttpMethod, SpectatorHttp, SpyObject } from '@ngneat/spectator/jest';
import { Store } from '@ngxs/store';
import { OAuthService } from 'angular-oauth2-oidc';
import { of, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { Rest } from '../models';
import { EnvironmentService } from '../services';
import { EnvironmentService, HttpErrorReporterService } from '../services';
import { RestService } from '../services/rest.service';
import { CORE_OPTIONS } from '../tokens';
describe('HttpClient testing', () => {
let spectator: SpectatorHttp<RestService>;
let environmentService: SpyObject<EnvironmentService>;
let store: SpyObject<Store>;
let httpErrorReporter: SpyObject<HttpErrorReporterService>;
const api = 'https://abp.io';
const createHttp = createHttpFactory({
service: RestService,
providers: [EnvironmentService, { provide: CORE_OPTIONS, useValue: { environment: {} } }],
mocks: [OAuthService, Store],
providers: [
EnvironmentService,
HttpErrorReporterService,
{ provide: CORE_OPTIONS, useValue: { environment: {} } },
],
mocks: [OAuthService],
});
beforeEach(() => {
spectator = createHttp();
environmentService = spectator.inject(EnvironmentService);
store = spectator.inject(Store);
httpErrorReporter = spectator.inject(HttpErrorReporterService);
environmentService.setState({
apis: {
default: {
@ -80,7 +83,7 @@ describe('HttpClient testing', () => {
});
test('should handle the error', () => {
const spy = jest.spyOn(store, 'dispatch');
const spy = jest.spyOn(httpErrorReporter, 'reportError');
spectator.service
.request({ method: HttpMethod.GET, url: '/test' }, { observe: Rest.Observe.Events })
@ -98,7 +101,7 @@ describe('HttpClient testing', () => {
});
test('should not handle the error when skipHandleError is true', () => {
const spy = jest.spyOn(store, 'dispatch');
const spy = jest.spyOn(httpErrorReporter, 'reportError');
spectator.service
.request(

11
npm/ng-packs/packages/core/src/lib/tests/utils/common.utils.ts

@ -1,16 +1,7 @@
import { Observable, of, Subject } from 'rxjs';
import { Store } from '@ngxs/store';
import { AbstractType, InjectFlags, InjectionToken, Injector, Type } from '@angular/core';
import { Subject } from 'rxjs';
export const mockActions = new Subject();
export const mockStore = {
selectSnapshot() {
return true;
},
select(): Observable<any> {
return of(null);
},
} as unknown as Store;
export class DummyInjector extends Injector {
constructor(public payload: { [key: string]: any }) {

7
npm/ng-packs/packages/core/src/lib/utils/environment-utils.ts

@ -1,10 +1,9 @@
import { HttpClient } from '@angular/common/http';
import { Injector } from '@angular/core';
import { Store } from '@ngxs/store';
import { catchError, tap } from 'rxjs/operators';
import { RestOccurError } from '../actions/rest.actions';
import { Environment, RemoteEnv } from '../models/environment';
import { EnvironmentService } from '../services/environment.service';
import { HttpErrorReporterService } from '../services/http-error-reporter.service';
import { deepMerge } from './object-utils';
export function getRemoteEnv(injector: Injector, environment: Partial<Environment>) {
@ -15,12 +14,12 @@ export function getRemoteEnv(injector: Injector, environment: Partial<Environmen
if (!url) return Promise.resolve();
const http = injector.get(HttpClient);
const store = injector.get(Store);
const httpErrorReporter = injector.get(HttpErrorReporterService);
return http
.request<Environment>(method, url, { headers })
.pipe(
catchError(err => store.dispatch(new RestOccurError(err))), // TODO: Condiser get handle function from a provider
catchError(err => httpErrorReporter.reportError(err)), // TODO: Condiser get handle function from a provider
tap(env => environmentService.setState(mergeEnvironments(environment, env, remoteEnv))),
)
.toPromise();

6
npm/ng-packs/packages/core/src/public-api.ts

@ -1,10 +1,5 @@
/*
* Public API Surface of core
*/
// export * from './lib/handlers';
export * from './lib/abstracts';
export * from './lib/actions';
export * from './lib/components';
export * from './lib/constants';
export * from './lib/core.module';
@ -24,7 +19,6 @@ export * from './lib/proxy/volo/abp/http/modeling';
export * from './lib/proxy/volo/abp/localization';
export * from './lib/proxy/volo/abp/models';
export * from './lib/services';
export * from './lib/states';
export * from './lib/strategies';
export * from './lib/tokens';
export * from './lib/utils';

2
npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.ts

@ -1,7 +1,6 @@
import { ConfigStateService, TrackByService } from '@abp/ng.core';
import { LocaleDirection } from '@abp/ng.theme.shared';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Store } from '@ngxs/store';
import { finalize } from 'rxjs/operators';
import { FeatureManagement } from '../../models/feature-management';
import { FeaturesService } from '../../proxy/feature-management/features.service';
@ -65,7 +64,6 @@ export class FeatureManagementComponent
constructor(
public readonly track: TrackByService,
protected service: FeaturesService,
protected store: Store,
protected configState: ConfigStateService,
) {}

3
npm/ng-packs/packages/feature-management/src/lib/feature-management.module.ts

@ -2,7 +2,6 @@ import { CoreModule } from '@abp/ng.core';
import { ThemeSharedModule } from '@abp/ng.theme.shared';
import { NgModule } from '@angular/core';
import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
import { NgxsModule } from '@ngxs/store';
import { FeatureManagementComponent } from './components/feature-management/feature-management.component';
import { FreeTextInputDirective } from './directives/free-text-input.directive';
@ -10,7 +9,7 @@ const exported = [FeatureManagementComponent, FreeTextInputDirective];
@NgModule({
declarations: [...exported],
imports: [CoreModule, ThemeSharedModule, NgbNavModule, NgxsModule.forFeature([])],
imports: [CoreModule, ThemeSharedModule, NgbNavModule],
exports: [...exported],
})
export class FeatureManagementModule {}

64
npm/ng-packs/packages/identity/src/lib/actions/identity.actions.ts

@ -1,64 +0,0 @@
import { Identity } from '../models/identity';
import { ABP, PagedAndSortedResultRequestDto } from '@abp/ng.core';
import {
GetIdentityUsersInput,
IdentityRoleCreateDto,
IdentityRoleUpdateDto,
IdentityUserCreateDto,
IdentityUserUpdateDto,
} from '../proxy/identity/models';
export class GetRoles {
static readonly type = '[Identity] Get Roles';
constructor(public payload?: PagedAndSortedResultRequestDto) {}
}
export class GetRoleById {
static readonly type = '[Identity] Get Role By Id';
constructor(public payload: string) {}
}
export class DeleteRole {
static readonly type = '[Identity] Delete Role';
constructor(public payload: string) {}
}
export class CreateRole {
static readonly type = '[Identity] Create Role';
constructor(public payload: IdentityRoleCreateDto) {}
}
export class UpdateRole {
static readonly type = '[Identity] Update Role';
constructor(public payload: IdentityRoleUpdateDto & { id: string }) {}
}
export class GetUsers {
static readonly type = '[Identity] Get Users';
constructor(public payload?: GetIdentityUsersInput) {}
}
export class GetUserById {
static readonly type = '[Identity] Get User By Id';
constructor(public payload: string) {}
}
export class DeleteUser {
static readonly type = '[Identity] Delete User';
constructor(public payload: string) {}
}
export class CreateUser {
static readonly type = '[Identity] Create User';
constructor(public payload: IdentityUserCreateDto) {}
}
export class UpdateUser {
static readonly type = '[Identity] Update User';
constructor(public payload: IdentityUserUpdateDto & { id: string }) {}
}
export class GetUserRoles {
static readonly type = '[Identity] Get User Roles';
constructor(public payload: string) {}
}

1
npm/ng-packs/packages/identity/src/lib/actions/index.ts

@ -1 +0,0 @@
export * from './identity.actions';

6
npm/ng-packs/packages/identity/src/lib/components/roles/roles.component.html

@ -5,15 +5,15 @@
<h5 class="card-title">{{ 'AbpIdentity::Roles' | abpLocalization }}</h5>
</div>
<div class="text-right col col-md-6">
<abp-page-toolbar [record]="data$ | async"></abp-page-toolbar>
<abp-page-toolbar [record]="data.items"></abp-page-toolbar>
</div>
</div>
</div>
<div class="card-body">
<abp-extensible-table
[data]="data$ | async"
[recordsTotal]="totalCount$ | async"
[data]="data.items"
[recordsTotal]="data.totalCount"
[list]="list"
></abp-extensible-table>
</div>

51
npm/ng-packs/packages/identity/src/lib/components/roles/roles.component.ts

@ -1,4 +1,4 @@
import { ListService, PagedAndSortedResultRequestDto } from '@abp/ng.core';
import { ListService, PagedAndSortedResultRequestDto, PagedResultDto } from '@abp/ng.core';
import { ePermissionManagementComponents } from '@abp/ng.permission-management';
import { Confirmation, ConfirmationService } from '@abp/ng.theme.shared';
import {
@ -6,21 +6,12 @@ import {
FormPropData,
generateFormFromProps,
} from '@abp/ng.theme.shared/extensions';
import { Component, ElementRef, Injector, OnInit, ViewChild } from '@angular/core';
import { Component, Injector, OnInit } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { Select, Store } from '@ngxs/store';
import { Observable } from 'rxjs';
import { finalize, pluck } from 'rxjs/operators';
import {
CreateRole,
DeleteRole,
GetRoleById,
GetRoles,
UpdateRole,
} from '../../actions/identity.actions';
import { finalize } from 'rxjs/operators';
import { eIdentityComponents } from '../../enums/components';
import { IdentityRoleService } from '../../proxy/identity/identity-role.service';
import { IdentityRoleDto } from '../../proxy/identity/models';
import { IdentityState } from '../../states/identity.state';
@Component({
selector: 'abp-roles',
@ -34,11 +25,7 @@ import { IdentityState } from '../../states/identity.state';
],
})
export class RolesComponent implements OnInit {
@Select(IdentityState.getRoles)
data$: Observable<IdentityRoleDto[]>;
@Select(IdentityState.getRolesTotalCount)
totalCount$: Observable<number>;
data: PagedResultDto<IdentityRoleDto> = { items: [], totalCount: 0 };
form: FormGroup;
@ -61,8 +48,8 @@ export class RolesComponent implements OnInit {
constructor(
public readonly list: ListService<PagedAndSortedResultRequestDto>,
protected confirmationService: ConfirmationService,
protected store: Store,
protected injector: Injector,
protected service: IdentityRoleService,
) {}
ngOnInit() {
@ -85,25 +72,21 @@ export class RolesComponent implements OnInit {
}
edit(id: string) {
this.store
.dispatch(new GetRoleById(id))
.pipe(pluck('IdentityState', 'selectedRole'))
.subscribe(selectedRole => {
this.selected = selectedRole;
this.openModal();
});
this.service.get(id).subscribe(res => {
this.selected = res;
this.openModal();
});
}
save() {
if (!this.form.valid) return;
this.modalBusy = true;
this.store
.dispatch(
this.selected.id
? new UpdateRole({ ...this.selected, ...this.form.value, id: this.selected.id })
: new CreateRole(this.form.value),
)
const { id } = this.selected;
(id
? this.service.update(id, { ...this.selected, ...this.form.value })
: this.service.create(this.form.value)
)
.pipe(finalize(() => (this.modalBusy = false)))
.subscribe(() => {
this.isModalVisible = false;
@ -118,13 +101,13 @@ export class RolesComponent implements OnInit {
})
.subscribe((status: Confirmation.Status) => {
if (status === Confirmation.Status.confirm) {
this.store.dispatch(new DeleteRole(id)).subscribe(() => this.list.get());
this.service.delete(id).subscribe(() => this.list.get());
}
});
}
private hookToQuery() {
this.list.hookToQuery(query => this.store.dispatch(new GetRoles(query))).subscribe();
this.list.hookToQuery(query => this.service.getList(query)).subscribe(res => (this.data = res));
}
openPermissionsModal(providerKey: string) {

6
npm/ng-packs/packages/identity/src/lib/components/users/users.component.html

@ -5,7 +5,7 @@
<h5 class="card-title">{{ 'AbpIdentity::Users' | abpLocalization }}</h5>
</div>
<div class="text-right col col-md-6">
<abp-page-toolbar [record]="data$ | async"></abp-page-toolbar>
<abp-page-toolbar [record]="data.items"></abp-page-toolbar>
</div>
</div>
</div>
@ -22,8 +22,8 @@
</div>
<abp-extensible-table
[data]="data$ | async"
[recordsTotal]="totalCount$ | async"
[data]="data.items"
[recordsTotal]="data.totalCount"
[list]="list"
></abp-extensible-table>
</div>

69
npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts

@ -1,4 +1,4 @@
import { ListService } from '@abp/ng.core';
import { ListService, PagedResultDto } from '@abp/ng.core';
import { ePermissionManagementComponents } from '@abp/ng.permission-management';
import { Confirmation, ConfirmationService } from '@abp/ng.theme.shared';
import {
@ -15,26 +15,14 @@ import {
ViewChild,
} from '@angular/core';
import { AbstractControl, FormArray, FormBuilder, FormGroup } from '@angular/forms';
import { Select, Store } from '@ngxs/store';
import { Observable } from 'rxjs';
import { finalize, pluck, switchMap, take } from 'rxjs/operators';
import {
CreateUser,
DeleteUser,
GetUserById,
GetUserRoles,
GetUsers,
UpdateUser,
} from '../../actions/identity.actions';
import { finalize, switchMap, tap } from 'rxjs/operators';
import { eIdentityComponents } from '../../enums/components';
import { Identity } from '../../models/identity';
import { IdentityUserService } from '../../proxy/identity/identity-user.service';
import {
GetIdentityUsersInput,
IdentityRoleDto,
IdentityUserDto,
} from '../../proxy/identity/models';
import { IdentityState } from '../../states/identity.state';
@Component({
selector: 'abp-users',
@ -48,11 +36,7 @@ import { IdentityState } from '../../states/identity.state';
],
})
export class UsersComponent implements OnInit {
@Select(IdentityState.getUsers)
data$: Observable<IdentityUserDto[]>;
@Select(IdentityState.getUsersTotalCount)
totalCount$: Observable<number>;
data: PagedResultDto<IdentityUserDto> = { items: [], totalCount: 0 };
@ViewChild('modalContent', { static: false })
modalContent: TemplateRef<any>;
@ -88,9 +72,8 @@ export class UsersComponent implements OnInit {
constructor(
public readonly list: ListService<GetIdentityUsersInput>,
protected confirmationService: ConfirmationService,
protected userService: IdentityUserService,
protected service: IdentityUserService,
protected fb: FormBuilder,
protected store: Store,
protected injector: Injector,
) {}
@ -102,7 +85,7 @@ export class UsersComponent implements OnInit {
const data = new FormPropData(this.injector, this.selected);
this.form = generateFormFromProps(data);
this.userService.getAssignableRoles().subscribe(({ items }) => {
this.service.getAssignableRoles().subscribe(({ items }) => {
this.roles = items;
this.form.addControl(
'roleNames',
@ -133,16 +116,14 @@ export class UsersComponent implements OnInit {
}
edit(id: string) {
this.store
.dispatch(new GetUserById(id))
this.service
.get(id)
.pipe(
switchMap(() => this.store.dispatch(new GetUserRoles(id))),
pluck('IdentityState'),
take(1),
tap(user => (this.selected = user)),
switchMap(() => this.service.getRoles(id)),
)
.subscribe((state: Identity.State) => {
this.selected = state.selectedUser;
this.selectedUserRoles = state.selectedUserRoles || [];
.subscribe(userRole => {
this.selectedUserRoles = userRole.items || [];
this.openModal();
});
}
@ -156,20 +137,16 @@ export class UsersComponent implements OnInit {
roleNames.filter(role => !!role[Object.keys(role)[0]]).map(role => Object.keys(role)[0]) ||
[];
this.store
.dispatch(
this.selected.id
? new UpdateUser({
...this.selected,
...this.form.value,
id: this.selected.id,
roleNames: mappedRoleNames,
})
: new CreateUser({
...this.form.value,
roleNames: mappedRoleNames,
}),
)
const { id } = this.selected;
(id
? this.service.update(id, {
...this.selected,
...this.form.value,
roleNames: mappedRoleNames,
})
: this.service.create({ ...this.form.value, roleNames: mappedRoleNames })
)
.pipe(finalize(() => (this.modalBusy = false)))
.subscribe(() => {
this.isModalVisible = false;
@ -184,7 +161,7 @@ export class UsersComponent implements OnInit {
})
.subscribe((status: Confirmation.Status) => {
if (status === Confirmation.Status.confirm) {
this.store.dispatch(new DeleteUser(id)).subscribe(() => this.list.get());
this.service.delete(id).subscribe(() => this.list.get());
}
});
}
@ -196,7 +173,7 @@ export class UsersComponent implements OnInit {
}
private hookToQuery() {
this.list.hookToQuery(query => this.store.dispatch(new GetUsers(query))).subscribe();
this.list.hookToQuery(query => this.service.getList(query)).subscribe(res => (this.data = res));
}
openPermissionsModal(providerKey: string) {

3
npm/ng-packs/packages/identity/src/lib/identity.module.ts

@ -5,13 +5,11 @@ import { UiExtensionsModule } from '@abp/ng.theme.shared/extensions';
import { ModuleWithProviders, NgModule, NgModuleFactory } from '@angular/core';
import { NgbDropdownModule, NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
import { NgxValidateCoreModule } from '@ngx-validate/core';
import { NgxsModule } from '@ngxs/store';
import { RolesComponent } from './components/roles/roles.component';
import { UsersComponent } from './components/users/users.component';
import { IdentityExtensionsGuard } from './guards/extensions.guard';
import { IdentityRoutingModule } from './identity-routing.module';
import { IdentityConfigOptions } from './models/config-options';
import { IdentityState } from './states/identity.state';
import {
IDENTITY_CREATE_FORM_PROP_CONTRIBUTORS,
IDENTITY_EDIT_FORM_PROP_CONTRIBUTORS,
@ -24,7 +22,6 @@ import {
declarations: [RolesComponent, UsersComponent],
exports: [RolesComponent, UsersComponent],
imports: [
NgxsModule.forFeature([IdentityState]),
CoreModule,
IdentityRoutingModule,
NgbNavModule,

12
npm/ng-packs/packages/identity/src/lib/models/identity.ts

@ -1,12 +0,0 @@
import { PagedResultDto } from '@abp/ng.core';
import { IdentityRoleDto, IdentityUserDto } from '../proxy/identity/models';
export namespace Identity {
export interface State {
roles: PagedResultDto<IdentityRoleDto>;
users: PagedResultDto<IdentityUserDto>;
selectedRole: IdentityRoleDto;
selectedUser: IdentityUserDto;
selectedUserRoles: IdentityRoleDto[];
}
}

1
npm/ng-packs/packages/identity/src/lib/models/index.ts

@ -1,2 +1 @@
export * from './config-options';
export * from './identity';

82
npm/ng-packs/packages/identity/src/lib/services/identity-state.service.ts

@ -1,82 +0,0 @@
import { ABP } from '@abp/ng.core';
import { Injectable } from '@angular/core';
import { Store } from '@ngxs/store';
import {
CreateRole,
CreateUser,
DeleteRole,
DeleteUser,
GetRoleById,
GetRoles,
GetUserById,
GetUsers,
UpdateRole,
UpdateUser,
GetUserRoles,
} from '../actions/identity.actions';
import { Identity } from '../models/identity';
import { IdentityState } from '../states/identity.state';
@Injectable({
providedIn: 'root',
})
export class IdentityStateService {
constructor(private store: Store) {}
getRoles() {
return this.store.selectSnapshot(IdentityState.getRoles);
}
getRolesTotalCount() {
return this.store.selectSnapshot(IdentityState.getRolesTotalCount);
}
getUsers() {
return this.store.selectSnapshot(IdentityState.getUsers);
}
getUsersTotalCount() {
return this.store.selectSnapshot(IdentityState.getUsersTotalCount);
}
dispatchGetRoles(...args: ConstructorParameters<typeof GetRoles>) {
return this.store.dispatch(new GetRoles(...args));
}
dispatchGetRoleById(...args: ConstructorParameters<typeof GetRoleById>) {
return this.store.dispatch(new GetRoleById(...args));
}
dispatchDeleteRole(...args: ConstructorParameters<typeof DeleteRole>) {
return this.store.dispatch(new DeleteRole(...args));
}
dispatchCreateRole(...args: ConstructorParameters<typeof CreateRole>) {
return this.store.dispatch(new CreateRole(...args));
}
dispatchUpdateRole(...args: ConstructorParameters<typeof UpdateRole>) {
return this.store.dispatch(new UpdateRole(...args));
}
dispatchGetUsers(...args: ConstructorParameters<typeof GetUsers>) {
return this.store.dispatch(new GetUsers(...args));
}
dispatchGetUserById(...args: ConstructorParameters<typeof GetUserById>) {
return this.store.dispatch(new GetUserById(...args));
}
dispatchDeleteUser(...args: ConstructorParameters<typeof DeleteUser>) {
return this.store.dispatch(new DeleteUser(...args));
}
dispatchCreateUser(...args: ConstructorParameters<typeof CreateUser>) {
return this.store.dispatch(new CreateUser(...args));
}
dispatchUpdateUser(...args: ConstructorParameters<typeof UpdateUser>) {
return this.store.dispatch(new UpdateUser(...args));
}
dispatchGetUserRoles(...args: ConstructorParameters<typeof GetUserRoles>) {
return this.store.dispatch(new GetUserRoles(...args));
}
}

1
npm/ng-packs/packages/identity/src/lib/services/index.ts

@ -1 +0,0 @@
export * from './identity-state.service';

138
npm/ng-packs/packages/identity/src/lib/states/identity.state.ts

@ -1,138 +0,0 @@
import { Injectable } from '@angular/core';
import { Action, Selector, State, StateContext } from '@ngxs/store';
import { pluck, tap } from 'rxjs/operators';
import {
CreateRole,
CreateUser,
DeleteRole,
DeleteUser,
GetRoleById,
GetRoles,
GetUserById,
GetUserRoles,
GetUsers,
UpdateRole,
UpdateUser,
} from '../actions/identity.actions';
import { Identity } from '../models/identity';
import { IdentityRoleService } from '../proxy/identity/identity-role.service';
import { IdentityUserService } from '../proxy/identity/identity-user.service';
import { IdentityRoleDto, IdentityUserDto } from '../proxy/identity/models';
@State<Identity.State>({
name: 'IdentityState',
defaults: { roles: {}, selectedRole: {}, users: {}, selectedUser: {} } as Identity.State,
})
@Injectable()
export class IdentityState {
@Selector()
static getRoles({ roles }: Identity.State): IdentityRoleDto[] {
return roles.items || [];
}
@Selector()
static getRolesTotalCount({ roles }: Identity.State): number {
return roles.totalCount || 0;
}
@Selector()
static getUsers({ users }: Identity.State): IdentityUserDto[] {
return users.items || [];
}
@Selector()
static getUsersTotalCount({ users }: Identity.State): number {
return users.totalCount || 0;
}
constructor(
private identityUserService: IdentityUserService,
private identityRoleService: IdentityRoleService,
) {}
@Action(GetRoles)
getRoles({ patchState }: StateContext<Identity.State>, { payload }: GetRoles) {
return this.identityRoleService.getList(payload).pipe(
tap(roles =>
patchState({
roles,
}),
),
);
}
@Action(GetRoleById)
getRole({ patchState }: StateContext<Identity.State>, { payload }: GetRoleById) {
return this.identityRoleService.get(payload).pipe(
tap(selectedRole =>
patchState({
selectedRole,
}),
),
);
}
@Action(DeleteRole)
deleteRole(_, { payload }: GetRoleById) {
return this.identityRoleService.delete(payload);
}
@Action(CreateRole)
addRole(_, { payload }: CreateRole) {
return this.identityRoleService.create(payload);
}
@Action(UpdateRole)
updateRole({ getState }: StateContext<Identity.State>, { payload }: UpdateRole) {
return this.identityRoleService.update(payload.id, { ...getState().selectedRole, ...payload });
}
@Action(GetUsers)
getUsers({ patchState }: StateContext<Identity.State>, { payload }: GetUsers) {
return this.identityUserService.getList(payload).pipe(
tap(users =>
patchState({
users,
}),
),
);
}
@Action(GetUserById)
getUser({ patchState }: StateContext<Identity.State>, { payload }: GetUserById) {
return this.identityUserService.get(payload).pipe(
tap(selectedUser =>
patchState({
selectedUser,
}),
),
);
}
@Action(DeleteUser)
deleteUser(_, { payload }: GetUserById) {
return this.identityUserService.delete(payload);
}
@Action(CreateUser)
addUser(_, { payload }: CreateUser) {
return this.identityUserService.create(payload);
}
@Action(UpdateUser)
updateUser({ getState }: StateContext<Identity.State>, { payload }: UpdateUser) {
return this.identityUserService.update(payload.id, { ...getState().selectedUser, ...payload });
}
@Action(GetUserRoles)
getUserRoles({ patchState }: StateContext<Identity.State>, { payload }: GetUserRoles) {
return this.identityUserService.getRoles(payload).pipe(
pluck('items'),
tap(selectedUserRoles =>
patchState({
selectedUserRoles,
}),
),
);
}
}

1
npm/ng-packs/packages/identity/src/lib/states/index.ts

@ -1 +0,0 @@
export * from './identity.state';

58
npm/ng-packs/packages/identity/src/lib/tests/identity-state.service.spec.ts

@ -1,58 +0,0 @@
import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/jest';
import { IdentityStateService } from '../services/identity-state.service';
import { IdentityState } from '../states/identity.state';
import { Store } from '@ngxs/store';
import * as IdentityActions from '../actions/identity.actions';
describe('IdentityStateService', () => {
let service: IdentityStateService;
let spectator: SpectatorService<IdentityStateService>;
let store: SpyObject<Store>;
const createService = createServiceFactory({ service: IdentityStateService, mocks: [Store] });
beforeEach(() => {
spectator = createService();
service = spectator.service;
store = spectator.inject(Store);
});
test('should have the all IdentityState static methods', () => {
const reg = /(?<=static )(.*)(?=\()/gm;
IdentityState.toString()
.match(reg)
.forEach(fnName => {
expect(service[fnName]).toBeTruthy();
const spy = jest.spyOn(store, 'selectSnapshot');
spy.mockClear();
const isDynamicSelector = IdentityState[fnName].name !== 'memoized';
if (isDynamicSelector) {
IdentityState[fnName] = jest.fn((...args) => args);
service[fnName]('test', 0, {});
expect(IdentityState[fnName]).toHaveBeenCalledWith('test', 0, {});
} else {
service[fnName]();
expect(spy).toHaveBeenCalledWith(IdentityState[fnName]);
}
});
});
test('should have a dispatch method for every IdentityState action', () => {
const reg = /(?<=dispatch)(\w+)(?=\()/gm;
IdentityStateService.toString()
.match(reg)
.forEach(fnName => {
expect(IdentityActions[fnName]).toBeTruthy();
const spy = jest.spyOn(store, 'dispatch');
spy.mockClear();
const params = Array.from(new Array(IdentityActions[fnName].length));
service[`dispatch${fnName}`](...params);
expect(spy).toHaveBeenCalledWith(new IdentityActions[fnName](...params));
});
});
});

3
npm/ng-packs/packages/identity/src/public-api.ts

@ -1,4 +1,3 @@
export * from './lib/actions';
export * from './lib/components';
export * from './lib/enums';
export * from './lib/guards';
@ -6,6 +5,4 @@ export * from './lib/identity.module';
export * from './lib/models';
export * from './lib/proxy/identity';
export * from './lib/proxy/users';
export * from './lib/services';
export * from './lib/states';
export * from './lib/tokens';

1
npm/ng-packs/packages/permission-management/src/lib/actions/index.ts

@ -1 +0,0 @@
export * from './permission-management.actions';

11
npm/ng-packs/packages/permission-management/src/lib/actions/permission-management.actions.ts

@ -1,11 +0,0 @@
import { ProviderInfoDto, UpdatePermissionsDto } from '../proxy/models';
export class GetPermissions {
static readonly type = '[PermissionManagement] Get Permissions';
constructor(public payload: ProviderInfoDto) {}
}
export class UpdatePermissions {
static readonly type = '[PermissionManagement] Update Permissions';
constructor(public payload: ProviderInfoDto & UpdatePermissionsDto) {}
}

21
npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.html

@ -1,8 +1,9 @@
<abp-modal [(visible)]="visible" (init)="initModal()" [busy]="modalBusy">
<ng-container *ngIf="{ entityName: entityName$ | async } as data">
<abp-modal [(visible)]="visible" [busy]="modalBusy">
<ng-container *ngIf="data.entityDisplayName">
<ng-template #abpHeader>
<h4>
{{ 'AbpPermissionManagement::Permissions' | abpLocalization }} - {{ data.entityName }}
{{ 'AbpPermissionManagement::Permissions' | abpLocalization }} -
{{ data.entityDisplayName }}
</h4>
</ng-template>
<ng-template #abpBody>
@ -24,16 +25,16 @@
<div class="row">
<div class="overflow-scroll col-md-4">
<ul class="nav nav-pills flex-column">
<li *ngFor="let group of groups$ | async; trackBy: trackByFn" class="nav-item">
<li *ngFor="let group of data.groups; trackBy: trackByFn" class="nav-item">
<a
*ngIf="{ assignedCount: getAssignedCount(group.name) } as data"
*ngIf="{ assignedCount: getAssignedCount(group.name) } as count"
class="nav-link pointer"
[class.active]="selectedGroup?.name === group?.name"
(click)="onChangeGroup(group)"
>
<div [class.font-weight-bold]="data.assignedCount">
<div [class.font-weight-bold]="count.assignedCount">
{{ group?.displayName }}
<span>({{ data.assignedCount }})</span>
<span>({{ count.assignedCount }})</span>
</div>
</a>
</li>
@ -58,11 +59,7 @@
</div>
<hr class="mb-3" />
<div
*ngFor="
let permission of selectedGroupPermissions$ | async;
let i = index;
trackBy: trackByFn
"
*ngFor="let permission of selectedGroupPermissions; let i = index; trackBy: trackByFn"
[ngStyle]="permission.style"
class="custom-checkbox custom-control mb-2"
>

133
npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts

@ -1,10 +1,8 @@
import { ConfigStateService, CurrentUserDto } from '@abp/ng.core';
import { LocaleDirection } from '@abp/ng.theme.shared';
import { Component, EventEmitter, Input, Output, TrackByFunction } from '@angular/core';
import { Select, Store } from '@ngxs/store';
import { Observable, of } from 'rxjs';
import { finalize, map, pluck, switchMap, take, tap } from 'rxjs/operators';
import { GetPermissions, UpdatePermissions } from '../actions/permission-management.actions';
import { of } from 'rxjs';
import { finalize, switchMap, tap } from 'rxjs/operators';
import { PermissionManagement } from '../models/permission-management';
import {
GetPermissionListResultDto,
@ -13,7 +11,7 @@ import {
ProviderInfoDto,
UpdatePermissionDto,
} from '../proxy/models';
import { PermissionManagementState } from '../states/permission-management.state';
import { PermissionsService } from '../proxy/permissions.service';
type PermissionWithStyle = PermissionGrantInfoDto & {
style: string;
@ -60,6 +58,7 @@ export class PermissionManagementComponent
this.openModal().subscribe(() => {
this._visible = true;
this.visibleChange.emit(true);
this.initModal();
});
} else {
this.selectedGroup = null;
@ -70,11 +69,7 @@ export class PermissionManagementComponent
@Output() readonly visibleChange = new EventEmitter<boolean>();
@Select(PermissionManagementState.getPermissionGroups)
groups$: Observable<PermissionGroupDto[]>;
@Select(PermissionManagementState.getEntityDisplayName)
entityName$: Observable<string>;
data: GetPermissionListResultDto = { groups: [], entityDisplayName: null };
selectedGroup: PermissionGroupDto;
@ -88,31 +83,28 @@ export class PermissionManagementComponent
trackByFn: TrackByFunction<PermissionGroupDto> = (_, item) => item.name;
get selectedGroupPermissions$(): Observable<PermissionWithStyle[]> {
get selectedGroupPermissions(): PermissionWithStyle[] {
if (!this.selectedGroup) return [];
const margin = `margin-${
(document.body.dir as LocaleDirection) === 'rtl' ? 'right' : 'left'
}.px`;
return this.groups$.pipe(
map(groups =>
this.selectedGroup
? groups.find(group => group.name === this.selectedGroup.name).permissions
: [],
),
map<PermissionGrantInfoDto[], PermissionWithStyle[]>(permissions =>
permissions.map(
permission =>
({
...permission,
style: { [margin]: findMargin(permissions, permission) },
isGranted: this.permissions.find(per => per.name === permission.name).isGranted,
} as any as PermissionWithStyle),
),
),
const permissions = this.data.groups.find(
group => group.name === this.selectedGroup.name,
).permissions;
return permissions.map(
permission =>
({
...permission,
style: { [margin]: findMargin(permissions, permission) },
isGranted: this.permissions.find(per => per.name === permission.name).isGranted,
} as unknown as PermissionWithStyle),
);
}
constructor(protected store: Store, protected configState: ConfigStateService) {}
constructor(protected service: PermissionsService, protected configState: ConfigStateService) {}
getChecked(name: string) {
return (this.permissions.find(per => per.name === name) || { isGranted: false }).isGranted;
@ -151,20 +143,18 @@ export class PermissionManagementComponent
}
setTabCheckboxState() {
this.selectedGroupPermissions$.pipe(take(1)).subscribe(permissions => {
const selectedPermissions = permissions.filter(per => per.isGranted);
const element = document.querySelector('#select-all-in-this-tabs') as any;
if (selectedPermissions.length === permissions.length) {
element.indeterminate = false;
this.selectThisTab = true;
} else if (selectedPermissions.length === 0) {
element.indeterminate = false;
this.selectThisTab = false;
} else {
element.indeterminate = true;
}
});
const selectedPermissions = this.selectedGroupPermissions.filter(per => per.isGranted);
const element = document.querySelector('#select-all-in-this-tabs') as any;
if (selectedPermissions.length === this.selectedGroupPermissions.length) {
element.indeterminate = false;
this.selectThisTab = true;
} else if (selectedPermissions.length === 0) {
element.indeterminate = false;
this.selectThisTab = false;
} else {
element.indeterminate = true;
}
}
setGrantCheckboxState() {
@ -183,19 +173,17 @@ export class PermissionManagementComponent
}
onClickSelectThisTab() {
this.selectedGroupPermissions$.pipe(take(1)).subscribe(permissions => {
permissions.forEach(permission => {
if (permission.isGranted && this.isGrantedByOtherProviderName(permission.grantedProviders))
return;
const index = this.permissions.findIndex(per => per.name === permission.name);
this.permissions = [
...this.permissions.slice(0, index),
{ ...this.permissions[index], isGranted: !this.selectThisTab },
...this.permissions.slice(index + 1),
];
});
this.selectedGroupPermissions.forEach(permission => {
if (permission.isGranted && this.isGrantedByOtherProviderName(permission.grantedProviders))
return;
const index = this.permissions.findIndex(per => per.name === permission.name);
this.permissions = [
...this.permissions.slice(0, index),
{ ...this.permissions[index], isGranted: !this.selectThisTab },
...this.permissions.slice(index + 1),
];
});
this.setGrantCheckboxState();
@ -217,9 +205,7 @@ export class PermissionManagementComponent
}
submit() {
const unchangedPermissions = getPermissions(
this.store.selectSnapshot(PermissionManagementState.getPermissionGroups),
);
const unchangedPermissions = getPermissions(this.data.groups);
const changedPermissions: UpdatePermissionDto[] = this.permissions
.filter(per =>
@ -236,14 +222,8 @@ export class PermissionManagementComponent
}
this.modalBusy = true;
this.store
.dispatch(
new UpdatePermissions({
providerKey: this.providerKey,
providerName: this.providerName,
permissions: changedPermissions,
}),
)
this.service
.update(this.providerName, this.providerKey, { permissions: changedPermissions })
.pipe(
switchMap(() =>
this.shouldFetchAppConfig() ? this.configState.refreshAppState() : of(null),
@ -260,20 +240,13 @@ export class PermissionManagementComponent
throw new Error('Provider Key and Provider Name are required.');
}
return this.store
.dispatch(
new GetPermissions({
providerKey: this.providerKey,
providerName: this.providerName,
}),
)
.pipe(
pluck('PermissionManagementState', 'permissionRes'),
tap((permissionRes: GetPermissionListResultDto) => {
this.selectedGroup = permissionRes.groups[0];
this.permissions = getPermissions(permissionRes.groups);
}),
);
return this.service.get(this.providerName, this.providerKey).pipe(
tap((permissionRes: GetPermissionListResultDto) => {
this.data = permissionRes;
this.selectedGroup = permissionRes.groups[0];
this.permissions = getPermissions(permissionRes.groups);
}),
);
}
initModal() {

4
npm/ng-packs/packages/permission-management/src/lib/permission-management.module.ts

@ -1,13 +1,11 @@
import { CoreModule } from '@abp/ng.core';
import { ThemeSharedModule } from '@abp/ng.theme.shared';
import { NgModule } from '@angular/core';
import { NgxsModule } from '@ngxs/store';
import { PermissionManagementComponent } from './components/permission-management.component';
import { PermissionManagementState } from './states/permission-management.state';
@NgModule({
declarations: [PermissionManagementComponent],
imports: [CoreModule, ThemeSharedModule, NgxsModule.forFeature([PermissionManagementState])],
imports: [CoreModule, ThemeSharedModule],
exports: [PermissionManagementComponent],
})
export class PermissionManagementModule {}

1
npm/ng-packs/packages/permission-management/src/lib/services/index.ts

@ -1 +0,0 @@
export * from './permission-management-state.service';

27
npm/ng-packs/packages/permission-management/src/lib/services/permission-management-state.service.ts

@ -1,27 +0,0 @@
import { Injectable } from '@angular/core';
import { Store } from '@ngxs/store';
import { PermissionManagementState } from '../states/permission-management.state';
import { PermissionManagement } from '../models';
import { GetPermissions, UpdatePermissions } from '../actions';
@Injectable({
providedIn: 'root',
})
export class PermissionManagementStateService {
constructor(private store: Store) {}
getPermissionGroups() {
return this.store.selectSnapshot(PermissionManagementState.getPermissionGroups);
}
getEntityDisplayName() {
return this.store.selectSnapshot(PermissionManagementState.getEntityDisplayName);
}
dispatchGetPermissions(...args: ConstructorParameters<typeof GetPermissions>) {
return this.store.dispatch(new GetPermissions(...args));
}
dispatchUpdatePermissions(...args: ConstructorParameters<typeof UpdatePermissions>) {
return this.store.dispatch(new UpdatePermissions(...args));
}
}

1
npm/ng-packs/packages/permission-management/src/lib/states/index.ts

@ -1 +0,0 @@
export * from './permission-management.state';

47
npm/ng-packs/packages/permission-management/src/lib/states/permission-management.state.ts

@ -1,47 +0,0 @@
import { Injectable } from '@angular/core';
import { Action, Selector, State, StateContext } from '@ngxs/store';
import { tap } from 'rxjs/operators';
import { GetPermissions, UpdatePermissions } from '../actions/permission-management.actions';
import { PermissionManagement } from '../models/permission-management';
import { ProviderInfoDto } from '../proxy/models';
import { PermissionsService } from '../proxy/permissions.service';
@State<PermissionManagement.State>({
name: 'PermissionManagementState',
defaults: { permissionRes: {} } as PermissionManagement.State,
})
@Injectable()
export class PermissionManagementState {
@Selector()
static getPermissionGroups({ permissionRes }: PermissionManagement.State) {
return permissionRes.groups || [];
}
@Selector()
static getEntityDisplayName({ permissionRes }: PermissionManagement.State): string {
return permissionRes.entityDisplayName;
}
constructor(private service: PermissionsService) {}
@Action(GetPermissions)
permissionManagementGet(
{ patchState }: StateContext<PermissionManagement.State>,
{ payload: { providerKey, providerName } = {} as ProviderInfoDto }: GetPermissions,
) {
return this.service.get(providerName, providerKey).pipe(
tap(permissionResponse =>
patchState({
permissionRes: permissionResponse,
}),
),
);
}
@Action(UpdatePermissions)
permissionManagementUpdate(_, { payload }: UpdatePermissions) {
return this.service.update(payload.providerName, payload.providerKey, {
permissions: payload.permissions,
});
}
}

60
npm/ng-packs/packages/permission-management/src/lib/tests/permission-management-state.service.spec.ts

@ -1,60 +0,0 @@
import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/jest';
import { PermissionManagementStateService } from '../services/permission-management-state.service';
import { PermissionManagementState } from '../states/permission-management.state';
import { Store } from '@ngxs/store';
import * as PermissionManagementActions from '../actions';
describe('PermissionManagementStateService', () => {
let service: PermissionManagementStateService;
let spectator: SpectatorService<PermissionManagementStateService>;
let store: SpyObject<Store>;
const createService = createServiceFactory({
service: PermissionManagementStateService,
mocks: [Store],
});
beforeEach(() => {
spectator = createService();
service = spectator.service;
store = spectator.inject(Store);
});
test('should have the all PermissionManagementState static methods', () => {
const reg = /(?<=static )(.*)(?=\()/gm;
PermissionManagementState.toString()
.match(reg)
.forEach(fnName => {
expect(service[fnName]).toBeTruthy();
const spy = jest.spyOn(store, 'selectSnapshot');
spy.mockClear();
const isDynamicSelector = PermissionManagementState[fnName].name !== 'memoized';
if (isDynamicSelector) {
PermissionManagementState[fnName] = jest.fn((...args) => args);
service[fnName]('test', 0, {});
expect(PermissionManagementState[fnName]).toHaveBeenCalledWith('test', 0, {});
} else {
service[fnName]();
expect(spy).toHaveBeenCalledWith(PermissionManagementState[fnName]);
}
});
});
test('should have a dispatch method for every PermissionManagementState action', () => {
const reg = /(?<=dispatch)(\w+)(?=\()/gm;
PermissionManagementStateService.toString()
.match(reg)
.forEach(fnName => {
expect(PermissionManagementActions[fnName]).toBeTruthy();
const spy = jest.spyOn(store, 'dispatch');
spy.mockClear();
const params = Array.from(new Array(PermissionManagementActions[fnName].length));
service[`dispatch${fnName}`](...params);
expect(spy).toHaveBeenCalledWith(new PermissionManagementActions[fnName](...params));
});
});
});

9
npm/ng-packs/packages/permission-management/src/public-api.ts

@ -1,12 +1,5 @@
/*
* Public API Surface of permission-management
*/
export * from './lib/permission-management.module';
export * from './lib/actions';
export * from './lib/components';
export * from './lib/enums/components';
export * from './lib/models';
export * from './lib/services';
export * from './lib/states';
export * from './lib/permission-management.module';
export * from './lib/proxy';

1
npm/ng-packs/packages/setting-management/src/lib/actions/index.ts

@ -1 +0,0 @@
export * from './setting-management.actions';

6
npm/ng-packs/packages/setting-management/src/lib/actions/setting-management.actions.ts

@ -1,6 +0,0 @@
import { ABP } from '@abp/ng.core';
export class SetSelectedSettingTab {
static readonly type = '[SettingManagement] Set Selected Tab';
constructor(public payload: ABP.Tab) {}
}

14
npm/ng-packs/packages/setting-management/src/lib/components/setting-management.component.ts

@ -1,9 +1,6 @@
import { ABP, SettingTabsService } from '@abp/ng.core';
import { Component, OnDestroy, OnInit, TrackByFunction } from '@angular/core';
import { Store } from '@ngxs/store';
import { Subscription } from 'rxjs';
import { SetSelectedSettingTab } from '../actions/setting-management.actions';
import { SettingManagementState } from '../states/setting-management.state';
@Component({
selector: 'abp-setting-management',
@ -13,18 +10,11 @@ export class SettingManagementComponent implements OnDestroy, OnInit {
private subscription = new Subscription();
settings: ABP.Tab[] = [];
set selected(value: ABP.Tab) {
this.store.dispatch(new SetSelectedSettingTab(value));
}
get selected(): ABP.Tab {
const value = this.store.selectSnapshot(SettingManagementState.getSelectedTab);
return value?.component ? value : this.settings[0] || ({} as ABP.Tab);
}
selected: ABP.Tab;
trackByFn: TrackByFunction<ABP.Tab> = (_, item) => item.name;
constructor(private store: Store, private settingTabsService: SettingTabsService) {}
constructor(private settingTabsService: SettingTabsService) {}
ngOnDestroy() {
this.subscription.unsubscribe();

1
npm/ng-packs/packages/setting-management/src/lib/models/index.ts

@ -1 +0,0 @@
export * from './setting-management';

7
npm/ng-packs/packages/setting-management/src/lib/models/setting-management.ts

@ -1,7 +0,0 @@
import { ABP } from '@abp/ng.core';
export namespace SettingManagement {
export interface State {
selectedTab?: ABP.Tab;
}
}

10
npm/ng-packs/packages/setting-management/src/lib/setting-management.module.ts

@ -2,21 +2,13 @@ import { PageModule } from '@abp/ng.components/page';
import { CoreModule, LazyModuleFactory } from '@abp/ng.core';
import { ThemeSharedModule } from '@abp/ng.theme.shared';
import { ModuleWithProviders, NgModule, NgModuleFactory } from '@angular/core';
import { NgxsModule } from '@ngxs/store';
import { SettingManagementComponent } from './components/setting-management.component';
import { SettingManagementRoutingModule } from './setting-management-routing.module';
import { SettingManagementState } from './states/setting-management.state';
@NgModule({
declarations: [SettingManagementComponent],
exports: [SettingManagementComponent],
imports: [
SettingManagementRoutingModule,
CoreModule,
ThemeSharedModule,
PageModule,
NgxsModule.forFeature([SettingManagementState]),
],
imports: [SettingManagementRoutingModule, CoreModule, ThemeSharedModule, PageModule],
})
export class SettingManagementModule {
static forChild(): ModuleWithProviders<SettingManagementModule> {

1
npm/ng-packs/packages/setting-management/src/lib/states/index.ts

@ -1 +0,0 @@
export * from './setting-management.state';

26
npm/ng-packs/packages/setting-management/src/lib/states/setting-management.state.ts

@ -1,26 +0,0 @@
import { Injectable } from '@angular/core';
import { Action, Selector, State, StateContext } from '@ngxs/store';
import { SetSelectedSettingTab } from '../actions/setting-management.actions';
import { SettingManagement } from '../models/setting-management';
@State<SettingManagement.State>({
name: 'SettingManagementState',
defaults: {},
})
@Injectable()
export class SettingManagementState {
@Selector()
static getSelectedTab({ selectedTab }: SettingManagement.State) {
return selectedTab;
}
@Action(SetSelectedSettingTab)
settingManagementAction(
{ patchState }: StateContext<SettingManagement.State>,
{ payload }: SetSelectedSettingTab,
) {
patchState({
selectedTab: payload,
});
}
}

1
npm/ng-packs/packages/tenant-management/src/lib/actions/index.ts

@ -1 +0,0 @@
export * from './tenant-management.actions';

26
npm/ng-packs/packages/tenant-management/src/lib/actions/tenant-management.actions.ts

@ -1,26 +0,0 @@
import { GetTenantsInput, TenantCreateDto, TenantUpdateDto } from '../proxy/models';
export class GetTenants {
static readonly type = '[TenantManagement] Get Tenant';
constructor(public payload?: GetTenantsInput) {}
}
export class GetTenantById {
static readonly type = '[TenantManagement] Get Tenant By Id';
constructor(public payload: string) {}
}
export class CreateTenant {
static readonly type = '[TenantManagement] Create Tenant';
constructor(public payload: TenantCreateDto) {}
}
export class UpdateTenant {
static readonly type = '[TenantManagement] Update Tenant';
constructor(public payload: TenantUpdateDto & { id: string }) {}
}
export class DeleteTenant {
static readonly type = '[TenantManagement] Delete Tenant';
constructor(public payload: string) {}
}

26
npm/ng-packs/packages/tenant-management/src/lib/components/tenants/tenants.component.html

@ -5,7 +5,7 @@
<h5 class="card-title">{{ 'AbpTenantManagement::Tenants' | abpLocalization }}</h5>
</div>
<div class="text-right col col-md-6">
<abp-page-toolbar [record]="data$ | async"></abp-page-toolbar>
<abp-page-toolbar [record]="data.items"></abp-page-toolbar>
</div>
</div>
</div>
@ -23,8 +23,8 @@
</div>
<abp-extensible-table
[data]="data$ | async"
[recordsTotal]="totalCount$ | async"
[data]="data.items"
[recordsTotal]="data.totalCount"
[list]="list"
></abp-extensible-table>
</div>
@ -32,29 +32,31 @@
<abp-modal size="md" [(visible)]="isModalVisible" [busy]="modalBusy">
<ng-template #abpHeader>
<h3>{{ selectedModalContent.title | abpLocalization }}</h3>
<h3>
{{
selected?.id
? 'AbpTenantManagement::Edit'
: ('AbpTenantManagement::NewTenant' | abpLocalization)
}}
</h3>
</ng-template>
<ng-template #abpBody>
<ng-container *ngTemplateOutlet="selectedModalContent?.template"></ng-container>
<form [formGroup]="tenantForm" (ngSubmit)="save()" validateOnSubmit>
<abp-extensible-form [selectedRecord]="selected"></abp-extensible-form>
</form>
</ng-template>
<ng-template #abpFooter>
<button abpClose type="button" class="btn btn-secondary">
{{ 'AbpTenantManagement::Cancel' | abpLocalization }}
</button>
<abp-button iconClass="fa fa-check" (click)="save()" [disabled]="isDisabledSaveButton">{{
<abp-button iconClass="fa fa-check" (click)="save()" [disabled]="tenantForm?.invalid">{{
'AbpTenantManagement::Save' | abpLocalization
}}</abp-button>
</ng-template>
</abp-modal>
<ng-template #tenantModalTemplate>
<form [formGroup]="tenantForm" (ngSubmit)="save()" validateOnSubmit>
<abp-extensible-form [selectedRecord]="selected"></abp-extensible-form>
</form>
</ng-template>
<abp-feature-management
*abpReplaceableTemplate="{
inputs: {

160
npm/ng-packs/packages/tenant-management/src/lib/components/tenants/tenants.component.ts

@ -1,33 +1,17 @@
import { ListService, PagedResultDto } from '@abp/ng.core';
import { eFeatureManagementComponents } from '@abp/ng.feature-management';
import { Confirmation, ConfirmationService, getPasswordValidators } from '@abp/ng.theme.shared';
import { Component, Injector, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Select, Store } from '@ngxs/store';
import { Observable } from 'rxjs';
import { finalize, pluck, switchMap, take } from 'rxjs/operators';
import {
CreateTenant,
DeleteTenant,
GetTenantById,
GetTenants,
UpdateTenant,
} from '../../actions/tenant-management.actions';
import { GetTenantsInput, TenantDto } from '../../proxy/models';
import { TenantService } from '../../proxy/tenant.service';
import { TenantManagementState } from '../../states/tenant-management.state';
import { Confirmation, ConfirmationService } from '@abp/ng.theme.shared';
import {
EXTENSIONS_IDENTIFIER,
FormPropData,
generateFormFromProps,
} from '@abp/ng.theme.shared/extensions';
import { Component, Injector, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { finalize } from 'rxjs/operators';
import { eTenantManagementComponents } from '../../enums/components';
interface SelectedModalContent {
type: 'saveConnStr' | 'saveTenant';
title: string;
template: TemplateRef<any>;
}
import { GetTenantsInput, TenantDto } from '../../proxy/models';
import { TenantService } from '../../proxy/tenant.service';
@Component({
selector: 'abp-tenants',
@ -41,24 +25,14 @@ interface SelectedModalContent {
],
})
export class TenantsComponent implements OnInit {
@Select(TenantManagementState.get)
data$: Observable<PagedResultDto<TenantDto>>;
@Select(TenantManagementState.getTenantsTotalCount)
totalCount$: Observable<number>;
data: PagedResultDto<TenantDto> = { items: [], totalCount: 0 };
selected: TenantDto;
tenantForm: FormGroup;
defaultConnectionStringForm: FormGroup;
defaultConnectionString: string;
isModalVisible: boolean;
selectedModalContent = {} as SelectedModalContent;
visibleFeatures = false;
providerKey: string;
@ -73,37 +47,6 @@ export class TenantsComponent implements OnInit {
return Boolean(this.selected.id);
}
get useSharedDatabase(): boolean {
return this.defaultConnectionStringForm.get('useSharedDatabase').value;
}
get connectionString(): string {
return this.defaultConnectionStringForm.get('defaultConnectionString').value;
}
@ViewChild('tenantModalTemplate')
tenantModalTemplate: TemplateRef<any>;
get isDisabledSaveButton(): boolean {
if (!this.selectedModalContent) return false;
if (
this.selectedModalContent.type === 'saveConnStr' &&
this.defaultConnectionStringForm &&
this.defaultConnectionStringForm.invalid
) {
return true;
} else if (
this.selectedModalContent.type === 'saveTenant' &&
this.tenantForm &&
this.tenantForm.invalid
) {
return true;
} else {
return false;
}
}
onVisibleFeaturesChange = (value: boolean) => {
this.visibleFeatures = value;
};
@ -112,9 +55,8 @@ export class TenantsComponent implements OnInit {
public readonly list: ListService<GetTenantsInput>,
private injector: Injector,
private confirmationService: ConfirmationService,
private tenantService: TenantService,
private service: TenantService,
private fb: FormBuilder,
private store: Store,
) {}
ngOnInit() {
@ -126,84 +68,30 @@ export class TenantsComponent implements OnInit {
this.tenantForm = generateFormFromProps(data);
}
private createDefaultConnectionStringForm() {
this.defaultConnectionStringForm = this.fb.group({
useSharedDatabase: this._useSharedDatabase,
defaultConnectionString: [this.defaultConnectionString || ''],
});
}
openModal(title: string, template: TemplateRef<any>, type: 'saveConnStr' | 'saveTenant') {
this.selectedModalContent = {
title,
template,
type,
};
this.isModalVisible = true;
}
addTenant() {
this.selected = {} as TenantDto;
this.createTenantForm();
this.openModal('AbpTenantManagement::NewTenant', this.tenantModalTemplate, 'saveTenant');
this.isModalVisible = true;
}
editTenant(id: string) {
this.store
.dispatch(new GetTenantById(id))
.pipe(pluck('TenantManagementState', 'selectedItem'))
.subscribe(selected => {
this.selected = selected;
this.createTenantForm();
this.openModal('AbpTenantManagement::Edit', this.tenantModalTemplate, 'saveTenant');
});
this.service.get(id).subscribe(res => {
this.selected = res;
this.createTenantForm();
this.isModalVisible = true;
});
}
save() {
const { type } = this.selectedModalContent;
if (!type) return;
if (type === 'saveTenant') this.saveTenant();
else if (type === 'saveConnStr') this.saveConnectionString();
}
saveConnectionString() {
if (this.modalBusy) return;
this.modalBusy = true;
if (this.useSharedDatabase || (!this.useSharedDatabase && !this.connectionString)) {
this.tenantService
.deleteDefaultConnectionString(this.selected.id)
.pipe(
take(1),
finalize(() => (this.modalBusy = false)),
)
.subscribe(() => {
this.isModalVisible = false;
});
} else {
this.tenantService
.updateDefaultConnectionString(this.selected.id, this.connectionString)
.pipe(
take(1),
finalize(() => (this.modalBusy = false)),
)
.subscribe(() => {
this.isModalVisible = false;
});
}
}
saveTenant() {
if (!this.tenantForm.valid || this.modalBusy) return;
this.modalBusy = true;
this.store
.dispatch(
this.selected.id
? new UpdateTenant({ ...this.selected, ...this.tenantForm.value, id: this.selected.id })
: new CreateTenant(this.tenantForm.value),
)
const { id } = this.selected;
(id
? this.service.update(id, { ...this.selected, ...this.tenantForm.value })
: this.service.create(this.tenantForm.value)
)
.pipe(finalize(() => (this.modalBusy = false)))
.subscribe(() => {
this.isModalVisible = false;
@ -222,13 +110,17 @@ export class TenantsComponent implements OnInit {
)
.subscribe((status: Confirmation.Status) => {
if (status === Confirmation.Status.confirm) {
this.store.dispatch(new DeleteTenant(id)).subscribe(() => this.list.get());
this.service.delete(id).subscribe(() => this.list.get());
}
});
}
hookToQuery() {
this.list.hookToQuery(query => this.store.dispatch(new GetTenants(query))).subscribe();
this.list
.hookToQuery(query => this.service.getList(query))
.subscribe(res => {
this.data = res;
});
}
onSharedDatabaseChange(value: boolean) {

3533
npm/ng-packs/packages/tenant-management/src/lib/proxy/generate-proxy.json

File diff suppressed because it is too large

16
npm/ng-packs/packages/tenant-management/src/lib/proxy/models.ts

@ -1,11 +1,7 @@
import type {
ExtensibleEntityDto,
ExtensibleObject,
PagedAndSortedResultRequestDto,
} from '@abp/ng.core';
import type { ExtensibleEntityDto, ExtensibleObject, PagedAndSortedResultRequestDto } from '@abp/ng.core';
export interface GetTenantsInput extends PagedAndSortedResultRequestDto {
filter: string;
filter?: string;
}
export interface TenantCreateDto extends TenantCreateOrUpdateDtoBase {
@ -18,8 +14,10 @@ export interface TenantCreateOrUpdateDtoBase extends ExtensibleObject {
}
export interface TenantDto extends ExtensibleEntityDto<string> {
name: string;
name?: string;
concurrencyStamp?: string;
}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface TenantUpdateDto extends TenantCreateOrUpdateDtoBase {}
export interface TenantUpdateDto extends TenantCreateOrUpdateDtoBase {
concurrencyStamp?: string;
}

4
npm/ng-packs/packages/tenant-management/src/lib/proxy/tenant.service.ts

@ -1,7 +1,7 @@
import type { GetTenantsInput, TenantCreateDto, TenantDto, TenantUpdateDto } from './models';
import { RestService } from '@abp/ng.core';
import type { PagedResultDto } from '@abp/ng.core';
import { RestService } from '@abp/ng.core';
import { Injectable } from '@angular/core';
import type { GetTenantsInput, TenantCreateDto, TenantDto, TenantUpdateDto } from './models';
@Injectable({
providedIn: 'root',

1
npm/ng-packs/packages/tenant-management/src/lib/services/index.ts

@ -1 +0,0 @@
export * from './tenant-management-state.service';

41
npm/ng-packs/packages/tenant-management/src/lib/services/tenant-management-state.service.ts

@ -1,41 +0,0 @@
import { Injectable } from '@angular/core';
import { Store } from '@ngxs/store';
import { TenantManagementState } from '../states/tenant-management.state';
import { ABP } from '@abp/ng.core';
import { GetTenants, GetTenantById, CreateTenant, UpdateTenant, DeleteTenant } from '../actions';
import { TenantManagement } from '../models';
@Injectable({
providedIn: 'root',
})
export class TenantManagementStateService {
constructor(private store: Store) {}
get() {
return this.store.selectSnapshot(TenantManagementState.get);
}
getTenantsTotalCount() {
return this.store.selectSnapshot(TenantManagementState.getTenantsTotalCount);
}
dispatchGetTenants(...args: ConstructorParameters<typeof GetTenants>) {
return this.store.dispatch(new GetTenants(...args));
}
dispatchGetTenantById(...args: ConstructorParameters<typeof GetTenantById>) {
return this.store.dispatch(new GetTenantById(...args));
}
dispatchCreateTenant(...args: ConstructorParameters<typeof CreateTenant>) {
return this.store.dispatch(new CreateTenant(...args));
}
dispatchUpdateTenant(...args: ConstructorParameters<typeof UpdateTenant>) {
return this.store.dispatch(new UpdateTenant(...args));
}
dispatchDeleteTenant(...args: ConstructorParameters<typeof DeleteTenant>) {
return this.store.dispatch(new DeleteTenant(...args));
}
}

1
npm/ng-packs/packages/tenant-management/src/lib/states/index.ts

@ -1 +0,0 @@
export * from './tenant-management.state';

70
npm/ng-packs/packages/tenant-management/src/lib/states/tenant-management.state.ts

@ -1,70 +0,0 @@
import { ABP, PagedResultDto } from '@abp/ng.core';
import { Action, Selector, State, StateContext } from '@ngxs/store';
import { tap } from 'rxjs/operators';
import {
CreateTenant,
DeleteTenant,
GetTenantById,
GetTenants,
UpdateTenant,
} from '../actions/tenant-management.actions';
import { TenantManagement } from '../models/tenant-management';
import { Injectable } from '@angular/core';
import { TenantService } from '../proxy/tenant.service';
import { TenantDto } from '../proxy/models';
@State<TenantManagement.State>({
name: 'TenantManagementState',
defaults: { result: {}, selectedItem: {} } as TenantManagement.State,
})
@Injectable()
export class TenantManagementState {
@Selector()
static get({ result }: TenantManagement.State): TenantDto[] {
return result.items || [];
}
@Selector()
static getTenantsTotalCount({ result }: TenantManagement.State): number {
return result.totalCount;
}
constructor(private service: TenantService) {}
@Action(GetTenants)
get({ patchState }: StateContext<TenantManagement.State>, { payload }: GetTenants) {
return this.service.getList(payload).pipe(
tap(result =>
patchState({
result,
}),
),
);
}
@Action(GetTenantById)
getById({ patchState }: StateContext<TenantManagement.State>, { payload }: GetTenantById) {
return this.service.get(payload).pipe(
tap(selectedItem =>
patchState({
selectedItem,
}),
),
);
}
@Action(DeleteTenant)
delete(_, { payload }: DeleteTenant) {
return this.service.delete(payload);
}
@Action(CreateTenant)
add(_, { payload }: CreateTenant) {
return this.service.create(payload);
}
@Action(UpdateTenant)
update({ getState }: StateContext<TenantManagement.State>, { payload }: UpdateTenant) {
return this.service.update(payload.id, { ...getState().selectedItem, ...payload });
}
}

3
npm/ng-packs/packages/tenant-management/src/lib/tenant-management.module.ts

@ -5,11 +5,9 @@ import { UiExtensionsModule } from '@abp/ng.theme.shared/extensions';
import { ModuleWithProviders, NgModule, NgModuleFactory } from '@angular/core';
import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
import { NgxValidateCoreModule } from '@ngx-validate/core';
import { NgxsModule } from '@ngxs/store';
import { TenantsComponent } from './components/tenants/tenants.component';
import { TenantManagementExtensionsGuard } from './guards/extensions.guard';
import { TenantManagementConfigOptions } from './models/config-options';
import { TenantManagementState } from './states/tenant-management.state';
import { TenantManagementRoutingModule } from './tenant-management-routing.module';
import {
TENANT_MANAGEMENT_CREATE_FORM_PROP_CONTRIBUTORS,
@ -24,7 +22,6 @@ import {
exports: [TenantsComponent],
imports: [
TenantManagementRoutingModule,
NgxsModule.forFeature([TenantManagementState]),
NgxValidateCoreModule,
CoreModule,
ThemeSharedModule,

61
npm/ng-packs/packages/tenant-management/src/lib/tests/tenant-management-state.service.spec.ts

@ -1,61 +0,0 @@
import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/jest';
import { TenantManagementStateService } from '../services/tenant-management-state.service';
import { TenantManagementState } from '../states/tenant-management.state';
import { Store } from '@ngxs/store';
import * as TenantManagementActions from '../actions';
describe('TenantManagementStateService', () => {
let service: TenantManagementStateService;
let spectator: SpectatorService<TenantManagementStateService>;
let store: SpyObject<Store>;
const createService = createServiceFactory({
service: TenantManagementStateService,
mocks: [Store],
});
beforeEach(() => {
spectator = createService();
service = spectator.service;
store = spectator.inject(Store);
});
test('should have the all TenantManagementState static methods', () => {
const reg = /(?<=static )(.*)(?=\()/gm;
TenantManagementState.toString()
.match(reg)
.forEach(fnName => {
expect(service[fnName]).toBeTruthy();
const spy = jest.spyOn(store, 'selectSnapshot');
spy.mockClear();
const isDynamicSelector = TenantManagementState[fnName].name !== 'memoized';
if (isDynamicSelector) {
TenantManagementState[fnName] = jest.fn((...args) => args);
service[fnName]('test', 0, {});
expect(TenantManagementState[fnName]).toHaveBeenCalledWith('test', 0, {});
} else {
service[fnName]();
expect(spy).toHaveBeenCalledWith(TenantManagementState[fnName]);
}
});
});
test('should have a dispatch method for every TenantManagementState action', () => {
const reg = /(?<=dispatch)(\w+)(?=\()/gm;
TenantManagementStateService.toString()
.match(reg)
.forEach(fnName => {
expect(TenantManagementActions[fnName]).toBeTruthy();
const spy = jest.spyOn(store, 'dispatch');
spy.mockClear();
const params = Array.from(new Array(TenantManagementActions[fnName].length));
service[`dispatch${fnName}`](...params);
expect(spy).toHaveBeenCalledWith(new TenantManagementActions[fnName](...params));
});
});
});

3
npm/ng-packs/packages/tenant-management/src/public-api.ts

@ -1,10 +1,7 @@
export * from './lib/actions';
export * from './lib/components';
export * from './lib/enums';
export * from './lib/guards';
export * from './lib/models';
export * from './lib/proxy';
export * from './lib/services';
export * from './lib/states';
export * from './lib/tenant-management.module';
export * from './lib/tokens';

3
npm/ng-packs/packages/theme-basic/src/lib/providers/styles.provider.ts

@ -1,6 +1,5 @@
import { ReplaceableComponentsService, CONTENT_STRATEGY, DomInsertionService } from '@abp/ng.core';
import { CONTENT_STRATEGY, DomInsertionService, ReplaceableComponentsService } from '@abp/ng.core';
import { APP_INITIALIZER } from '@angular/core';
import { Store } from '@ngxs/store';
import { AccountLayoutComponent } from '../components/account-layout/account-layout.component';
import { ApplicationLayoutComponent } from '../components/application-layout/application-layout.component';
import { EmptyLayoutComponent } from '../components/empty-layout/empty-layout.component';

2
npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb/breadcrumb.component.ts

@ -9,7 +9,7 @@ import {
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { map, startWith } from 'rxjs/operators';
import { eThemeSharedRouteNames } from '../../enums';
import { eThemeSharedRouteNames } from '../../enums/route-names';
@Component({
selector: 'abp-breadcrumb',

6
npm/ng-packs/packages/theme-shared/src/lib/components/http-error-wrapper/http-error-wrapper.component.ts

@ -1,4 +1,4 @@
import { Config, SubscriptionService } from '@abp/ng.core';
import { LocalizationParam, SubscriptionService } from '@abp/ng.core';
import {
AfterViewInit,
ApplicationRef,
@ -30,9 +30,9 @@ export class HttpErrorWrapperComponent implements AfterViewInit, OnDestroy, OnIn
status = 0;
title: Config.LocalizationParam = 'Oops!';
title: LocalizationParam = 'Oops!';
details: Config.LocalizationParam = 'Sorry, an error has occured.';
details: LocalizationParam = 'Sorry, an error has occured.';
customComponent: Type<any> = null;

44
npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts

@ -1,19 +1,22 @@
import { AuthService, LocalizationParam, RestOccurError, RouterEvents } from '@abp/ng.core';
import {
AuthService,
HttpErrorReporterService,
LocalizationParam,
RouterEvents,
} from '@abp/ng.core';
import { HttpErrorResponse } from '@angular/common/http';
import {
ApplicationRef,
ComponentFactoryResolver,
ComponentRef,
EmbeddedViewRef,
Inject,
Injectable,
Injector,
RendererFactory2,
} from '@angular/core';
import { NavigationError, ResolveEnd } from '@angular/router';
import { Actions, ofActionSuccessful } from '@ngxs/store';
import { Observable, of, Subject, throwError } from 'rxjs';
import { catchError, filter, map, switchMap } from 'rxjs/operators';
import { catchError, filter, switchMap } from 'rxjs/operators';
import { HttpErrorWrapperComponent } from '../components/http-error-wrapper/http-error-wrapper.component';
import { ErrorScreenErrorCodes, HttpErrorConfig } from '../models/common';
import { Confirmation } from '../models/confirmation';
@ -74,15 +77,21 @@ export class ErrorHandler {
throwError(err),
);
constructor(
protected actions: Actions,
protected routerEvents: RouterEvents,
protected confirmationService: ConfirmationService,
protected cfRes: ComponentFactoryResolver,
protected rendererFactory: RendererFactory2,
protected injector: Injector,
@Inject('HTTP_ERROR_CONFIG') protected httpErrorConfig: HttpErrorConfig,
) {
protected httpErrorReporter: HttpErrorReporterService;
protected routerEvents: RouterEvents;
protected confirmationService: ConfirmationService;
protected cfRes: ComponentFactoryResolver;
protected rendererFactory: RendererFactory2;
protected httpErrorConfig: HttpErrorConfig;
constructor(protected injector: Injector) {
this.httpErrorReporter = injector.get(HttpErrorReporterService);
this.routerEvents = injector.get(RouterEvents);
this.confirmationService = injector.get(ConfirmationService);
this.cfRes = injector.get(ComponentFactoryResolver);
this.rendererFactory = injector.get(RendererFactory2);
this.httpErrorConfig = injector.get('HTTP_ERROR_CONFIG');
this.listenToRestError();
this.listenToRouterError();
this.listenToRouterDataResolved();
@ -106,13 +115,8 @@ export class ErrorHandler {
}
protected listenToRestError() {
this.actions
.pipe(
ofActionSuccessful(RestOccurError),
map(action => action.payload),
filter(this.filterRestErrors),
switchMap(this.executeErrorHandler),
)
this.httpErrorReporter.reporter$
.pipe(filter(this.filterRestErrors), switchMap(this.executeErrorHandler))
.subscribe();
}

6
npm/ng-packs/packages/theme-shared/src/lib/models/confirmation.ts

@ -1,4 +1,4 @@
import { Config, LocalizationParam } from '@abp/ng.core';
import { LocalizationParam } from '@abp/ng.core';
export namespace Confirmation {
export interface Options {
@ -13,8 +13,8 @@ export namespace Confirmation {
}
export interface DialogData {
message: Config.LocalizationParam;
title?: Config.LocalizationParam;
message: LocalizationParam;
title?: LocalizationParam;
severity?: Severity;
options?: Partial<Options>;
}

18
npm/ng-packs/packages/theme-shared/src/lib/models/toaster.ts

@ -1,4 +1,4 @@
import { Config, LocalizationParam } from '@abp/ng.core';
import { LocalizationParam } from '@abp/ng.core';
export namespace Toaster {
export interface ToastOptions {
@ -32,23 +32,23 @@ export namespace Toaster {
remove: (id: number) => void;
clear: (containerKey?: string) => void;
info: (
message: Config.LocalizationParam,
title?: Config.LocalizationParam,
message: LocalizationParam,
title?: LocalizationParam,
options?: Partial<Toaster.ToastOptions>,
) => ToasterId;
success: (
message: Config.LocalizationParam,
title?: Config.LocalizationParam,
message: LocalizationParam,
title?: LocalizationParam,
options?: Partial<Toaster.ToastOptions>,
) => ToasterId;
warn: (
message: Config.LocalizationParam,
title?: Config.LocalizationParam,
message: LocalizationParam,
title?: LocalizationParam,
options?: Partial<Toaster.ToastOptions>,
) => ToasterId;
error: (
message: Config.LocalizationParam,
title?: Config.LocalizationParam,
message: LocalizationParam,
title?: LocalizationParam,
options?: Partial<Toaster.ToastOptions>,
) => ToasterId;
}

10
npm/ng-packs/packages/theme-shared/src/lib/tests/breadcrumb.component.spec.ts

@ -7,8 +7,7 @@ import {
} from '@abp/ng.core';
import { HttpClient } from '@angular/common/http';
import { RouterModule } from '@angular/router';
import { createRoutingFactory, SpectatorRouting, SpyObject } from '@ngneat/spectator/jest';
import { Store } from '@ngxs/store';
import { createRoutingFactory, SpectatorRouting } from '@ngneat/spectator/jest';
// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries
import { mockRoutesService } from '../../../../core/src/lib/tests/routes.service.spec';
import { BreadcrumbComponent } from '../components/breadcrumb/breadcrumb.component';
@ -21,13 +20,12 @@ const mockRoutes: ABP.Route[] = [
describe('BreadcrumbComponent', () => {
let spectator: SpectatorRouting<RouterOutletComponent>;
let routes: RoutesService;
let store: SpyObject<Store>;
const createRouting = createRoutingFactory({
component: RouterOutletComponent,
stubsEnabled: false,
detectChanges: false,
mocks: [Store, HttpClient],
mocks: [HttpClient],
providers: [
{ provide: CORE_OPTIONS, useValue: {} },
{
@ -58,15 +56,11 @@ describe('BreadcrumbComponent', () => {
beforeEach(() => {
spectator = createRouting();
routes = spectator.inject(RoutesService);
store = spectator.inject(Store);
});
it('should display the breadcrumb', async () => {
routes.add(mockRoutes);
await spectator.router.navigateByUrl('/identity/users');
// for abpLocalization
store.selectSnapshot.mockReturnValueOnce('Identity');
store.selectSnapshot.mockReturnValueOnce('Users');
spectator.detectChanges();
const elements = spectator.queryAll('li');

3
npm/ng-packs/packages/theme-shared/src/lib/tests/confirmation.service.spec.ts

@ -2,7 +2,6 @@ import { CoreTestingModule } from '@abp/ng.core/testing';
import { NgModule } from '@angular/core';
import { fakeAsync, tick } from '@angular/core/testing';
import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest';
import { NgxsModule } from '@ngxs/store';
import { timer } from 'rxjs';
import { ConfirmationComponent } from '../components';
import { Confirmation } from '../models';
@ -21,7 +20,7 @@ describe('ConfirmationService', () => {
let service: ConfirmationService;
const createService = createServiceFactory({
service: ConfirmationService,
imports: [NgxsModule.forRoot(), CoreTestingModule.withConfig(), MockModule],
imports: [CoreTestingModule.withConfig(), MockModule],
});
beforeEach(() => {

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

Loading…
Cancel
Save