Browse Source

refactor : migrate core package to inject() DI

pull/23262/head
Fahri Gedik 7 months ago
parent
commit
d55ea1b132
  1. 6
      npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts
  2. 12
      npm/ng-packs/packages/core/src/lib/components/replaceable-route-container.component.ts
  3. 6
      npm/ng-packs/packages/core/src/lib/directives/autofocus.directive.ts
  4. 10
      npm/ng-packs/packages/core/src/lib/directives/debounce.directive.ts
  5. 35
      npm/ng-packs/packages/core/src/lib/directives/for.directive.ts
  6. 30
      npm/ng-packs/packages/core/src/lib/directives/form-submit.directive.ts
  7. 6
      npm/ng-packs/packages/core/src/lib/directives/init.directive.ts
  8. 35
      npm/ng-packs/packages/core/src/lib/directives/permission.directive.ts
  9. 35
      npm/ng-packs/packages/core/src/lib/directives/replaceable-template.directive.ts
  10. 10
      npm/ng-packs/packages/core/src/lib/directives/stop-propagation.directive.ts
  11. 10
      npm/ng-packs/packages/core/src/lib/handlers/routes.handler.ts
  12. 5
      npm/ng-packs/packages/core/src/lib/interceptors/api.interceptor.ts
  13. 5
      npm/ng-packs/packages/core/src/lib/pipes/localization.pipe.ts
  14. 13
      npm/ng-packs/packages/core/src/lib/pipes/short-date-time.pipe.ts
  15. 13
      npm/ng-packs/packages/core/src/lib/pipes/short-date.pipe.ts
  16. 13
      npm/ng-packs/packages/core/src/lib/pipes/short-time.pipe.ts
  17. 5
      npm/ng-packs/packages/core/src/lib/pipes/to-injector.pipe.ts
  18. 8
      npm/ng-packs/packages/core/src/lib/proxy/pages/abp/multi-tenancy/abp-tenant.service.ts
  19. 8
      npm/ng-packs/packages/core/src/lib/proxy/volo/abp/asp-net-core/mvc/api-exploring/abp-api-definition.service.ts
  20. 8
      npm/ng-packs/packages/core/src/lib/proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-configuration.service.ts
  21. 8
      npm/ng-packs/packages/core/src/lib/proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-localization.service.ts
  22. 14
      npm/ng-packs/packages/core/src/lib/services/config-state.service.ts
  23. 5
      npm/ng-packs/packages/core/src/lib/services/content-projection.service.ts
  24. 6
      npm/ng-packs/packages/core/src/lib/services/http-wait.service.ts
  25. 6
      npm/ng-packs/packages/core/src/lib/services/lazy-load.service.ts
  26. 6
      npm/ng-packs/packages/core/src/lib/services/list.service.ts
  27. 17
      npm/ng-packs/packages/core/src/lib/services/localization.service.ts
  28. 16
      npm/ng-packs/packages/core/src/lib/services/multi-tenancy.service.ts
  29. 5
      npm/ng-packs/packages/core/src/lib/services/permission.service.ts
  30. 7
      npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts
  31. 15
      npm/ng-packs/packages/core/src/lib/services/rest.service.ts
  32. 8
      npm/ng-packs/packages/core/src/lib/services/router-wait.service.ts
  33. 8
      npm/ng-packs/packages/core/src/lib/services/routes.service.ts
  34. 8
      npm/ng-packs/packages/core/src/lib/services/session-state.service.ts
  35. 402
      npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts
  36. 352
      npm/ng-packs/packages/core/src/lib/tests/replaceable-template.directive.spec.ts
  37. 10
      npm/ng-packs/packages/core/testing/src/lib/services/mock-permission.service.ts
  38. 24
      npm/ng-packs/packages/core/testing/src/lib/services/mock-rest.service.ts

6
npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts

@ -1,4 +1,4 @@
import { Component, inject, isDevMode, OnInit, Optional, SkipSelf, Type } from '@angular/core';
import { Component, inject, isDevMode, OnInit, Type } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { eLayoutType } from '../enums/common';
import { ABP } from '../models';
@ -39,7 +39,9 @@ export class DynamicLayoutComponent implements OnInit {
protected readonly routerEvents = inject(RouterEvents);
protected readonly environment = inject(EnvironmentService);
constructor(@Optional() @SkipSelf() dynamicLayoutComponent: DynamicLayoutComponent) {
constructor() {
const dynamicLayoutComponent = inject(DynamicLayoutComponent, { optional: true, skipSelf: true })!;
if (dynamicLayoutComponent) {
if (isDevMode()) console.warn('DynamicLayoutComponent must be used only in AppComponent.');
return;

12
npm/ng-packs/packages/core/src/lib/components/replaceable-route-container.component.ts

@ -1,4 +1,4 @@
import { Component, OnInit, Type } from '@angular/core';
import { Component, OnInit, Type, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { distinctUntilChanged } from 'rxjs/operators';
import { ReplaceableComponents } from '../models/replaceable-components';
@ -15,18 +15,16 @@ import { CommonModule } from '@angular/common';
imports: [CommonModule],
})
export class ReplaceableRouteContainerComponent implements OnInit {
private route = inject(ActivatedRoute);
private replaceableComponents = inject(ReplaceableComponentsService);
private subscription = inject(SubscriptionService);
defaultComponent!: Type<any>;
componentKey!: string;
externalComponent?: Type<any>;
constructor(
private route: ActivatedRoute,
private replaceableComponents: ReplaceableComponentsService,
private subscription: SubscriptionService,
) {}
ngOnInit() {
this.defaultComponent = this.route.snapshot.data.replaceableComponent.defaultComponent;
this.componentKey = (

6
npm/ng-packs/packages/core/src/lib/directives/autofocus.directive.ts

@ -1,9 +1,11 @@
import { AfterViewInit, Directive, ElementRef, Input } from '@angular/core';
import { AfterViewInit, Directive, ElementRef, Input, inject } from '@angular/core';
@Directive({
selector: '[autofocus]',
})
export class AutofocusDirective implements AfterViewInit {
private elRef = inject(ElementRef);
private _delay = 0;
@Input('autofocus')
@ -15,8 +17,6 @@ export class AutofocusDirective implements AfterViewInit {
return this._delay;
}
constructor(private elRef: ElementRef) {}
ngAfterViewInit(): void {
setTimeout(() => this.elRef.nativeElement.focus(), this.delay as number);
}

10
npm/ng-packs/packages/core/src/lib/directives/debounce.directive.ts

@ -1,4 +1,4 @@
import { Directive, ElementRef, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { Directive, ElementRef, EventEmitter, Input, OnInit, Output, inject } from '@angular/core';
import { fromEvent } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
import { SubscriptionService } from '../services/subscription.service';
@ -8,15 +8,13 @@ import { SubscriptionService } from '../services/subscription.service';
providers: [SubscriptionService],
})
export class InputEventDebounceDirective implements OnInit {
private el = inject(ElementRef);
private subscription = inject(SubscriptionService);
@Input() debounce = 300;
@Output('input.debounce') readonly debounceEvent = new EventEmitter<Event>();
constructor(
private el: ElementRef,
private subscription: SubscriptionService,
) {}
ngOnInit(): void {
const input$ = fromEvent<InputEvent>(this.el.nativeElement, 'input').pipe(
debounceTime(this.debounce),

35
npm/ng-packs/packages/core/src/lib/directives/for.directive.ts

@ -1,15 +1,16 @@
import {
Directive,
EmbeddedViewRef,
Input,
IterableChangeRecord,
IterableChanges,
IterableDiffer,
IterableDiffers,
OnChanges,
TemplateRef,
TrackByFunction,
ViewContainerRef,
import {
Directive,
EmbeddedViewRef,
Input,
IterableChangeRecord,
IterableChanges,
IterableDiffer,
IterableDiffers,
OnChanges,
TemplateRef,
TrackByFunction,
ViewContainerRef,
inject
} from '@angular/core';
import clone from 'just-clone';
import compare from 'just-compare';
@ -36,6 +37,10 @@ class RecordView {
selector: '[abpFor]',
})
export class ForDirective implements OnChanges {
private tempRef = inject<TemplateRef<AbpForContext>>(TemplateRef);
private vcRef = inject(ViewContainerRef);
private differs = inject(IterableDiffers);
// eslint-disable-next-line @angular-eslint/no-input-rename
@Input('abpForOf')
items!: any[];
@ -73,12 +78,6 @@ export class ForDirective implements OnChanges {
return this.trackBy || ((index: number, item: any) => (item as any).id || index);
}
constructor(
private tempRef: TemplateRef<AbpForContext>,
private vcRef: ViewContainerRef,
private differs: IterableDiffers,
) {}
private iterateOverAppliedOperations(changes: IterableChanges<any>) {
const rw: RecordView[] = [];

30
npm/ng-packs/packages/core/src/lib/directives/form-submit.directive.ts

@ -1,12 +1,12 @@
import {
ChangeDetectorRef,
Directive,
ElementRef,
EventEmitter,
Input,
OnInit,
Output,
Self,
import {
ChangeDetectorRef,
Directive,
ElementRef,
EventEmitter,
Input,
OnInit,
Output,
inject
} from '@angular/core';
import { FormGroupDirective, UntypedFormControl, UntypedFormGroup } from '@angular/forms';
import { fromEvent } from 'rxjs';
@ -22,6 +22,11 @@ type Controls = { [key: string]: UntypedFormControl } | UntypedFormGroup[];
providers: [SubscriptionService],
})
export class FormSubmitDirective implements OnInit {
private formGroupDirective = inject(FormGroupDirective, { self: true });
private host = inject<ElementRef<HTMLFormElement>>(ElementRef);
private cdRef = inject(ChangeDetectorRef);
private subscription = inject(SubscriptionService);
@Input()
debounce = 200;
@ -36,13 +41,6 @@ export class FormSubmitDirective implements OnInit {
executedNgSubmit = false;
constructor(
@Self() private formGroupDirective: FormGroupDirective,
private host: ElementRef<HTMLFormElement>,
private cdRef: ChangeDetectorRef,
private subscription: SubscriptionService,
) {}
ngOnInit() {
this.subscription.addOne(this.formGroupDirective.ngSubmit, () => {
if (this.markAsDirtyWhenSubmit) {

6
npm/ng-packs/packages/core/src/lib/directives/init.directive.ts

@ -1,12 +1,12 @@
import { Directive, Output, EventEmitter, ElementRef, AfterViewInit } from '@angular/core';
import { Directive, Output, EventEmitter, ElementRef, AfterViewInit, inject } from '@angular/core';
@Directive({
selector: '[abpInit]',
})
export class InitDirective implements AfterViewInit {
@Output('abpInit') readonly init = new EventEmitter<ElementRef<any>>();
private elRef = inject(ElementRef);
constructor(private elRef: ElementRef) {}
@Output('abpInit') readonly init = new EventEmitter<ElementRef<any>>();
ngAfterViewInit() {
this.init.emit(this.elRef);

35
npm/ng-packs/packages/core/src/lib/directives/permission.directive.ts

@ -1,14 +1,13 @@
import {
AfterViewInit,
ChangeDetectorRef,
Directive,
Inject,
Input,
OnChanges,
OnDestroy,
Optional,
TemplateRef,
ViewContainerRef,
import {
AfterViewInit,
ChangeDetectorRef,
Directive,
Input,
OnChanges,
OnDestroy,
TemplateRef,
ViewContainerRef,
inject
} from '@angular/core';
import { ReplaySubject, Subscription } from 'rxjs';
import { distinctUntilChanged, take } from 'rxjs/operators';
@ -20,6 +19,12 @@ import { QueueManager } from '../utils/queue';
selector: '[abpPermission]',
})
export class PermissionDirective implements OnDestroy, OnChanges, AfterViewInit {
private templateRef = inject<TemplateRef<any>>(TemplateRef, { optional: true })!;
private vcRef = inject(ViewContainerRef);
private permissionService = inject(PermissionService);
private cdRef = inject(ChangeDetectorRef);
queue = inject<QueueManager>(QUEUE_MANAGER);
@Input('abpPermission') condition: string | undefined;
@Input('abpPermissionRunChangeDetection') runChangeDetection = true;
@ -30,14 +35,6 @@ export class PermissionDirective implements OnDestroy, OnChanges, AfterViewInit
rendered = false;
constructor(
@Optional() private templateRef: TemplateRef<any>,
private vcRef: ViewContainerRef,
private permissionService: PermissionService,
private cdRef: ChangeDetectorRef,
@Inject(QUEUE_MANAGER) public queue: QueueManager,
) {}
private check() {
if (this.subscription) {
this.subscription.unsubscribe();

35
npm/ng-packs/packages/core/src/lib/directives/replaceable-template.directive.ts

@ -1,13 +1,14 @@
import {
Directive,
Injector,
Input,
OnChanges,
OnInit,
SimpleChanges,
TemplateRef,
Type,
ViewContainerRef,
import {
Directive,
Injector,
Input,
OnChanges,
OnInit,
SimpleChanges,
TemplateRef,
Type,
ViewContainerRef,
inject
} from '@angular/core';
import compare from 'just-compare';
import { Subscription } from 'rxjs';
@ -22,6 +23,12 @@ import { SubscriptionService } from '../services/subscription.service';
providers: [SubscriptionService],
})
export class ReplaceableTemplateDirective implements OnInit, OnChanges {
private injector = inject(Injector);
private templateRef = inject<TemplateRef<any>>(TemplateRef);
private vcRef = inject(ViewContainerRef);
private replaceableComponents = inject(ReplaceableComponentsService);
private subscription = inject(SubscriptionService);
@Input('abpReplaceableTemplate')
data!: ReplaceableComponents.ReplaceableTemplateDirectiveInput<any, any>;
@ -40,13 +47,7 @@ export class ReplaceableTemplateDirective implements OnInit, OnChanges {
initialized = false;
constructor(
private injector: Injector,
private templateRef: TemplateRef<any>,
private vcRef: ViewContainerRef,
private replaceableComponents: ReplaceableComponentsService,
private subscription: SubscriptionService,
) {
constructor() {
this.context = {
initTemplate: (ref: any) => {
this.resetDefaultComponent();

10
npm/ng-packs/packages/core/src/lib/directives/stop-propagation.directive.ts

@ -1,4 +1,4 @@
import { Directive, ElementRef, EventEmitter, OnInit, Output } from '@angular/core';
import { Directive, ElementRef, EventEmitter, OnInit, Output, inject } from '@angular/core';
import { fromEvent } from 'rxjs';
import { SubscriptionService } from '../services/subscription.service';
@ -7,12 +7,10 @@ import { SubscriptionService } from '../services/subscription.service';
providers: [SubscriptionService],
})
export class StopPropagationDirective implements OnInit {
@Output('click.stop') readonly stopPropEvent = new EventEmitter<MouseEvent>();
private el = inject(ElementRef);
private subscription = inject(SubscriptionService);
constructor(
private el: ElementRef,
private subscription: SubscriptionService,
) {}
@Output('click.stop') readonly stopPropEvent = new EventEmitter<MouseEvent>();
ngOnInit(): void {
this.subscription.addOne(fromEvent<MouseEvent>(this.el.nativeElement, 'click'), event => {

10
npm/ng-packs/packages/core/src/lib/handlers/routes.handler.ts

@ -1,4 +1,4 @@
import { Injectable, Optional } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { Route, Router } from '@angular/router';
import { ABP } from '../models';
import { RoutesService } from '../services/routes.service';
@ -7,10 +7,10 @@ import { RoutesService } from '../services/routes.service';
providedIn: 'root',
})
export class RoutesHandler {
constructor(
private routes: RoutesService,
@Optional() private router: Router,
) {
private routes = inject(RoutesService);
private router = inject(Router, { optional: true })!;
constructor() {
this.addRoutes();
}

5
npm/ng-packs/packages/core/src/lib/interceptors/api.interceptor.ts

@ -1,5 +1,5 @@
import { HttpHandler, HttpHeaders, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { finalize } from 'rxjs/operators';
import { HttpWaitService } from '../services';
@ -7,7 +7,8 @@ import { HttpWaitService } from '../services';
providedIn: 'root',
})
export class ApiInterceptor implements IApiInterceptor {
constructor(private httpWaitService: HttpWaitService) {}
private httpWaitService = inject(HttpWaitService);
getAdditionalHeaders(existingHeaders?: HttpHeaders) {
return existingHeaders || new HttpHeaders();

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

@ -1,4 +1,4 @@
import { Injectable, Pipe, PipeTransform } from '@angular/core';
import { Injectable, Pipe, PipeTransform, inject } from '@angular/core';
import { LocalizationWithDefault } from '../models/localization';
import { LocalizationService } from '../services/localization.service';
@ -7,7 +7,8 @@ import { LocalizationService } from '../services/localization.service';
name: 'abpLocalization',
})
export class LocalizationPipe implements PipeTransform {
constructor(private localization: LocalizationService) {}
private localization = inject(LocalizationService);
transform(
value: string | LocalizationWithDefault = '',

13
npm/ng-packs/packages/core/src/lib/pipes/short-date-time.pipe.ts

@ -1,5 +1,5 @@
import { DatePipe, DATE_PIPE_DEFAULT_TIMEZONE } from '@angular/common';
import { Inject, LOCALE_ID, Optional, Pipe, PipeTransform } from '@angular/core';
import { LOCALE_ID, Pipe, PipeTransform, inject } from '@angular/core';
import { ConfigStateService } from '../services';
import { getShortDateShortTimeFormat } from '../utils/date-utils';
@ -8,11 +8,12 @@ import { getShortDateShortTimeFormat } from '../utils/date-utils';
pure: true,
})
export class ShortDateTimePipe extends DatePipe implements PipeTransform {
constructor(
private configStateService: ConfigStateService,
@Inject(LOCALE_ID) locale: string,
@Inject(DATE_PIPE_DEFAULT_TIMEZONE) @Optional() defaultTimezone?: string | null,
) {
private configStateService = inject(ConfigStateService);
constructor() {
const locale = inject(LOCALE_ID);
const defaultTimezone = inject(DATE_PIPE_DEFAULT_TIMEZONE, { optional: true });
super(locale, defaultTimezone);
}

13
npm/ng-packs/packages/core/src/lib/pipes/short-date.pipe.ts

@ -1,5 +1,5 @@
import { DatePipe, DATE_PIPE_DEFAULT_TIMEZONE } from '@angular/common';
import { Inject, LOCALE_ID, Optional, Pipe, PipeTransform } from '@angular/core';
import { LOCALE_ID, Pipe, PipeTransform, inject } from '@angular/core';
import { ConfigStateService } from '../services';
import { getShortDateFormat } from '../utils/date-utils';
@ -8,11 +8,12 @@ import { getShortDateFormat } from '../utils/date-utils';
pure: true,
})
export class ShortDatePipe extends DatePipe implements PipeTransform {
constructor(
private configStateService: ConfigStateService,
@Inject(LOCALE_ID) locale: string,
@Inject(DATE_PIPE_DEFAULT_TIMEZONE) @Optional() defaultTimezone?: string | null,
) {
private configStateService = inject(ConfigStateService);
constructor() {
const locale = inject(LOCALE_ID);
const defaultTimezone = inject(DATE_PIPE_DEFAULT_TIMEZONE, { optional: true });
super(locale, defaultTimezone);
}

13
npm/ng-packs/packages/core/src/lib/pipes/short-time.pipe.ts

@ -1,5 +1,5 @@
import { DatePipe, DATE_PIPE_DEFAULT_TIMEZONE } from '@angular/common';
import { Inject, LOCALE_ID, Optional, Pipe, PipeTransform } from '@angular/core';
import { LOCALE_ID, Pipe, PipeTransform, inject } from '@angular/core';
import { ConfigStateService } from '../services';
import { getShortTimeFormat } from '../utils/date-utils';
@ -8,11 +8,12 @@ import { getShortTimeFormat } from '../utils/date-utils';
pure: true,
})
export class ShortTimePipe extends DatePipe implements PipeTransform {
constructor(
private configStateService: ConfigStateService,
@Inject(LOCALE_ID) locale: string,
@Inject(DATE_PIPE_DEFAULT_TIMEZONE) @Optional() defaultTimezone?: string | null,
) {
private configStateService = inject(ConfigStateService);
constructor() {
const locale = inject(LOCALE_ID);
const defaultTimezone = inject(DATE_PIPE_DEFAULT_TIMEZONE, { optional: true });
super(locale, defaultTimezone);
}

5
npm/ng-packs/packages/core/src/lib/pipes/to-injector.pipe.ts

@ -1,4 +1,4 @@
import { InjectionToken, Injector, Pipe, PipeTransform } from '@angular/core';
import { InjectionToken, Injector, Pipe, PipeTransform, inject } from '@angular/core';
export const INJECTOR_PIPE_DATA_TOKEN = new InjectionToken<PipeTransform>(
'INJECTOR_PIPE_DATA_TOKEN',
@ -8,7 +8,8 @@ export const INJECTOR_PIPE_DATA_TOKEN = new InjectionToken<PipeTransform>(
name: 'toInjector',
})
export class ToInjectorPipe implements PipeTransform {
constructor(private injector: Injector) {}
private injector = inject(Injector);
transform(
value: any,
token: InjectionToken<any> = INJECTOR_PIPE_DATA_TOKEN,

8
npm/ng-packs/packages/core/src/lib/proxy/pages/abp/multi-tenancy/abp-tenant.service.ts

@ -1,12 +1,14 @@
import { RestService } from '../../../../services';
import { Rest } from '../../../../models';
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import type { FindTenantResultDto } from '../../../volo/abp/asp-net-core/mvc/multi-tenancy/models';
@Injectable({
providedIn: 'root',
})
export class AbpTenantService {
export class AbpTenantService {
private restService = inject(RestService);
apiName = 'abp';
@ -24,6 +26,4 @@ export class AbpTenantService {
url: `/api/abp/multi-tenancy/tenants/by-name/${name}`,
},
{ apiName: this.apiName,...config });
constructor(private restService: RestService) {}
}

8
npm/ng-packs/packages/core/src/lib/proxy/volo/abp/asp-net-core/mvc/api-exploring/abp-api-definition.service.ts

@ -1,12 +1,14 @@
import { RestService } from '../../../../../../services';
import { Rest } from '../../../../../../models';
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import type { ApplicationApiDescriptionModel, ApplicationApiDescriptionModelRequestDto } from '../../../http/modeling/models';
@Injectable({
providedIn: 'root',
})
export class AbpApiDefinitionService {
export class AbpApiDefinitionService {
private restService = inject(RestService);
apiName = 'abp';
@ -17,6 +19,4 @@ export class AbpApiDefinitionService {
params: { includeTypes: model.includeTypes },
},
{ apiName: this.apiName,...config });
constructor(private restService: RestService) {}
}

8
npm/ng-packs/packages/core/src/lib/proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-configuration.service.ts

@ -1,12 +1,14 @@
import type { ApplicationConfigurationDto, ApplicationConfigurationRequestOptions } from './models';
import { RestService } from '../../../../../../services';
import { Rest } from '../../../../../../models';
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class AbpApplicationConfigurationService {
export class AbpApplicationConfigurationService {
private restService = inject(RestService);
apiName = 'abp';
@ -17,6 +19,4 @@ export class AbpApplicationConfigurationService {
params: { includeLocalizationResources: options.includeLocalizationResources },
},
{ apiName: this.apiName, ...config });
constructor(private restService: RestService) { }
}

8
npm/ng-packs/packages/core/src/lib/proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-localization.service.ts

@ -1,12 +1,14 @@
import type { ApplicationLocalizationDto, ApplicationLocalizationRequestDto } from './models';
import { RestService } from '../../../../../../services';
import { Rest } from '../../../../../../models';
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class AbpApplicationLocalizationService {
export class AbpApplicationLocalizationService {
private restService = inject(RestService);
apiName = 'abp';
@ -17,6 +19,4 @@ export class AbpApplicationLocalizationService {
params: { cultureName: input.cultureName, onlyDynamics: input.onlyDynamics },
},
{ apiName: this.apiName,...config });
constructor(private restService: RestService) {}
}

14
npm/ng-packs/packages/core/src/lib/services/config-state.service.ts

@ -1,4 +1,4 @@
import { Inject, Injectable, Optional } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { map, switchMap, take, tap } from 'rxjs/operators';
import { AbpApplicationConfigurationService } from '../proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-configuration.service';
@ -15,6 +15,10 @@ import { InternalStore } from '../utils/internal-store-utils';
providedIn: 'root',
})
export class ConfigStateService {
private abpConfigService = inject(AbpApplicationConfigurationService);
private abpApplicationLocalizationService = inject(AbpApplicationLocalizationService);
private readonly includeLocalizationResources = inject(INCUDE_LOCALIZATION_RESOURCES_TOKEN, { optional: true });
private updateSubject = new Subject<void>();
private readonly store = new InternalStore({} as ApplicationConfigurationDto);
@ -27,13 +31,7 @@ export class ConfigStateService {
get createOnUpdateStream() {
return this.store.sliceUpdate;
}
constructor(
private abpConfigService: AbpApplicationConfigurationService,
private abpApplicationLocalizationService: AbpApplicationLocalizationService,
@Optional()
@Inject(INCUDE_LOCALIZATION_RESOURCES_TOKEN)
private readonly includeLocalizationResources: boolean | null,
) {
constructor() {
this.initUpdateStream();
}

5
npm/ng-packs/packages/core/src/lib/services/content-projection.service.ts

@ -1,9 +1,10 @@
import { Injectable, Injector, TemplateRef, Type } from '@angular/core';
import { Injectable, Injector, TemplateRef, Type, inject } from '@angular/core';
import { ProjectionStrategy } from '../strategies/projection.strategy';
@Injectable({ providedIn: 'root' })
export class ContentProjectionService {
constructor(private injector: Injector) {}
private injector = inject(Injector);
projectContent<T extends Type<any> | TemplateRef<any>>(
projectionStrategy: ProjectionStrategy<T>,

6
npm/ng-packs/packages/core/src/lib/services/http-wait.service.ts

@ -1,4 +1,4 @@
import { Injectable, Injector } from '@angular/core';
import { Injectable, Injector, inject } from '@angular/core';
import { HttpRequest } from '@angular/common/http';
import { InternalStore } from '../utils/internal-store-utils';
import { getPathName } from '../utils/http-utils';
@ -26,7 +26,9 @@ export class HttpWaitService {
private delay: number;
private destroy$ = new Subject<void>();
constructor(injector: Injector) {
constructor() {
const injector = inject(Injector);
this.delay = injector.get(LOADER_DELAY, 500);
}

6
npm/ng-packs/packages/core/src/lib/services/lazy-load.service.ts

@ -1,4 +1,4 @@
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { concat, Observable, of, pipe, throwError } from 'rxjs';
import { delay, retryWhen, shareReplay, take, tap } from 'rxjs/operators';
import { LoadingStrategy } from '../strategies';
@ -8,9 +8,9 @@ import { ResourceWaitService } from './resource-wait.service';
providedIn: 'root',
})
export class LazyLoadService {
readonly loaded = new Map<string, HTMLScriptElement | HTMLLinkElement | null>();
private resourceWaitService = inject(ResourceWaitService);
constructor(private resourceWaitService: ResourceWaitService) {}
readonly loaded = new Map<string, HTMLScriptElement | HTMLLinkElement | null>();
load(strategy: LoadingStrategy, retryTimes?: number, retryDelay?: number): Observable<Event> {
if (this.loaded.has(strategy.path)) return of(new CustomEvent('load'));

6
npm/ng-packs/packages/core/src/lib/services/list.service.ts

@ -1,4 +1,4 @@
import { Injectable, Injector, OnDestroy } from '@angular/core';
import { Injectable, Injector, OnDestroy, inject } from '@angular/core';
import {
EMPTY,
BehaviorSubject,
@ -119,7 +119,9 @@ export class ListService<QueryParamsType = ABP.PageQueryParams | any> implements
this.next();
};
constructor(injector: Injector) {
constructor() {
const injector = inject(Injector);
const delay = injector.get(LIST_QUERY_DEBOUNCE_TIME, 300);
this.delay = delay ? debounceTime(delay) : tap();
this.get();

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

@ -1,5 +1,5 @@
import { registerLocaleData } from '@angular/common';
import { Injectable, Injector, isDevMode, Optional, SkipSelf } from '@angular/core';
import { Injectable, Injector, isDevMode, inject } from '@angular/core';
import { BehaviorSubject, combineLatest, from, Observable, Subject } from 'rxjs';
import { filter, map, switchMap } from 'rxjs/operators';
import { ABP } from '../models/common';
@ -17,6 +17,10 @@ import { SessionStateService } from './session-state.service';
@Injectable({ providedIn: 'root' })
export class LocalizationService {
private sessionState = inject(SessionStateService);
private injector = inject(Injector);
private configState = inject(ConfigStateService);
private latestLang = this.sessionState.getLanguage();
private _languageChange$ = new Subject<string>();
@ -44,14 +48,9 @@ export class LocalizationService {
return this._languageChange$.asObservable();
}
constructor(
private sessionState: SessionStateService,
private injector: Injector,
@Optional()
@SkipSelf()
otherInstance: LocalizationService,
private configState: ConfigStateService,
) {
constructor() {
const otherInstance = inject(LocalizationService, { optional: true, skipSelf: true })!;
if (otherInstance) throw new Error('LocalizationService should have only one instance.');
this.listenToSetLanguage();

16
npm/ng-packs/packages/core/src/lib/services/multi-tenancy.service.ts

@ -1,4 +1,4 @@
import { Inject, Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { map, switchMap } from 'rxjs/operators';
import { AbpTenantService } from '../proxy/pages/abp/multi-tenancy';
import {
@ -12,6 +12,12 @@ import { SessionStateService } from './session-state.service';
@Injectable({ providedIn: 'root' })
export class MultiTenancyService {
private restService = inject(RestService);
private sessionState = inject(SessionStateService);
private tenantService = inject(AbpTenantService);
private configStateService = inject(ConfigStateService);
tenantKey = inject(TENANT_KEY);
domainTenant: CurrentTenantDto | null = null;
isTenantBoxVisible = true;
@ -23,14 +29,6 @@ export class MultiTenancyService {
return this.configStateService.refreshAppState().pipe(map(_ => tenant));
};
constructor(
private restService: RestService,
private sessionState: SessionStateService,
private tenantService: AbpTenantService,
private configStateService: ConfigStateService,
@Inject(TENANT_KEY) public tenantKey: string,
) { }
setTenantByName(tenantName: string) {
return this.tenantService
.findTenantByName(tenantName)

5
npm/ng-packs/packages/core/src/lib/services/permission.service.ts

@ -1,4 +1,4 @@
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { map } from 'rxjs/operators';
import { ABP } from '../models/common';
import { ApplicationConfigurationDto } from '../proxy/volo/abp/asp-net-core/mvc/application-configurations/models';
@ -6,7 +6,8 @@ import { ConfigStateService } from './config-state.service';
@Injectable({ providedIn: 'root' })
export class PermissionService {
constructor(protected configState: ConfigStateService) {}
protected configState = inject(ConfigStateService);
getGrantedPolicy$(key: string) {
return this.getStream().pipe(

7
npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts

@ -1,4 +1,4 @@
import { Injectable, NgZone } from '@angular/core';
import { Injectable, NgZone, inject } from '@angular/core';
import { Router } from '@angular/router';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@ -8,6 +8,9 @@ import { reloadRoute } from '../utils/route-utils';
@Injectable({ providedIn: 'root' })
export class ReplaceableComponentsService {
private ngZone = inject(NgZone);
private router = inject(Router);
private readonly store: InternalStore<ReplaceableComponents.ReplaceableComponent[]>;
get replaceableComponents$(): Observable<ReplaceableComponents.ReplaceableComponent[]> {
@ -22,7 +25,7 @@ export class ReplaceableComponentsService {
return this.store.sliceUpdate(state => state);
}
constructor(private ngZone: NgZone, private router: Router) {
constructor() {
this.store = new InternalStore([] as ReplaceableComponents.ReplaceableComponent[]);
}

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

@ -1,5 +1,5 @@
import { HttpClient, HttpParameterCodec, HttpParams, HttpRequest } from '@angular/common/http';
import { Inject, Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { ExternalHttpClient } from '../clients/http.client';
@ -14,13 +14,12 @@ import { HttpErrorReporterService } from './http-error-reporter.service';
providedIn: 'root',
})
export class RestService {
constructor(
@Inject(CORE_OPTIONS) protected options: ABP.Root,
protected http: HttpClient,
protected externalHttp: ExternalHttpClient,
protected environment: EnvironmentService,
protected httpErrorReporter: HttpErrorReporterService,
) { }
protected options = inject<ABP.Root>(CORE_OPTIONS);
protected http = inject(HttpClient);
protected externalHttp = inject(ExternalHttpClient);
protected environment = inject(EnvironmentService);
protected httpErrorReporter = inject(HttpErrorReporterService);
protected getApiFromStore(apiName: string | undefined): string {
return this.environment.getApiUrl(apiName);

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

@ -1,4 +1,4 @@
import { Injectable, Injector } from '@angular/core';
import { Injectable, Injector, inject } from '@angular/core';
import { NavigationStart } from '@angular/router';
import { of, Subject, timer } from 'rxjs';
import { map, mapTo, switchMap, takeUntil, tap } from 'rxjs/operators';
@ -14,10 +14,14 @@ export interface RouterWaitState {
providedIn: 'root',
})
export class RouterWaitService {
private routerEvents = inject(RouterEvents);
private store = new InternalStore<RouterWaitState>({ loading: false });
private destroy$ = new Subject<void>();
private delay: number;
constructor(private routerEvents: RouterEvents, injector: Injector) {
constructor() {
const injector = inject(Injector);
this.delay = injector.get(LOADER_DELAY, 500);
this.updateLoadingStatusOnNavigationEvents();
}

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

@ -1,4 +1,4 @@
import { Injectable, Injector, OnDestroy } from '@angular/core';
import { Injectable, Injector, OnDestroy, inject } from '@angular/core';
import { BehaviorSubject, Observable, Subscription, map } from 'rxjs';
import { ABP } from '../models/common';
import { OTHERS_GROUP } from '../tokens';
@ -201,6 +201,8 @@ export abstract class AbstractNavTreeService<T extends ABP.Nav>
extends AbstractTreeService<T>
implements OnDestroy
{
protected injector = inject(Injector);
private subscription: Subscription;
private permissionService: PermissionService;
private compareFunc;
@ -211,8 +213,10 @@ export abstract class AbstractNavTreeService<T extends ABP.Nav>
return this.compareFunc(a, b);
};
constructor(protected injector: Injector) {
constructor() {
super();
const injector = this.injector;
const configState = this.injector.get(ConfigStateService);
this.subscription = configState
.createOnUpdateStream(state => state)

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

@ -12,6 +12,9 @@ import { AbpLocalStorageService } from './local-storage.service';
providedIn: 'root',
})
export class SessionStateService {
private configState = inject(ConfigStateService);
private localStorageService = inject(AbpLocalStorageService);
private readonly store = new InternalStore({} as Session.State);
protected readonly document = inject(DOCUMENT);
@ -19,10 +22,7 @@ export class SessionStateService {
this.localStorageService.setItem('abpSession', JSON.stringify(this.store.state));
};
constructor(
private configState: ConfigStateService,
private localStorageService: AbpLocalStorageService,
) {
constructor() {
this.init();
this.setInitialLanguage();
}

402
npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts

@ -1,201 +1,201 @@
import { HttpClient } from '@angular/common/http';
import { Component, NgModule } from '@angular/core';
import { ActivatedRoute, RouterModule } from '@angular/router';
import { createRoutingFactory, SpectatorRouting } from '@ngneat/spectator/jest';
import { DynamicLayoutComponent, RouterOutletComponent } from '../components';
import { eLayoutType } from '../enums/common';
import { ABP } from '../models';
import { AbpApplicationConfigurationService } from '../proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-configuration.service';
import { ReplaceableComponentsService, RoutesService } from '../services';
import { mockRoutesService } from './routes.service.spec';
@Component({
selector: 'abp-layout-application',
template: '<router-outlet></router-outlet>',
})
class DummyApplicationLayoutComponent {}
@Component({
selector: 'abp-layout-account',
template: '<router-outlet></router-outlet>',
})
class DummyAccountLayoutComponent {}
@Component({
selector: 'abp-layout-empty',
template: '<router-outlet></router-outlet>',
})
class DummyEmptyLayoutComponent {}
const LAYOUTS = [
DummyApplicationLayoutComponent,
DummyAccountLayoutComponent,
DummyEmptyLayoutComponent,
];
@NgModule({
imports: [RouterModule],
declarations: [...LAYOUTS],
})
class DummyLayoutModule {}
@Component({
selector: 'abp-dummy',
template: '{{route.snapshot.data?.name}} works!',
})
class DummyComponent {
constructor(public route: ActivatedRoute) {}
}
const routes: ABP.Route[] = [
{
path: '',
name: 'Root',
},
{
path: '/parentWithLayout',
name: 'ParentWithLayout',
parentName: 'Root',
layout: eLayoutType.application,
},
{
path: '/parentWithLayout/childWithoutLayout',
name: 'ChildWithoutLayout',
parentName: 'ParentWithLayout',
},
{
path: '/parentWithLayout/childWithLayout',
name: 'ChildWithLayout',
parentName: 'ParentWithLayout',
layout: eLayoutType.account,
},
{
path: '/withData',
name: 'WithData',
layout: eLayoutType.application,
},
];
describe('DynamicLayoutComponent', () => {
const createComponent = createRoutingFactory({
component: RouterOutletComponent,
stubsEnabled: false,
declarations: [DummyComponent, DynamicLayoutComponent],
mocks: [AbpApplicationConfigurationService, HttpClient],
providers: [
{
provide: RoutesService,
useFactory: () => mockRoutesService(),
},
ReplaceableComponentsService,
],
imports: [RouterModule, DummyLayoutModule],
routes: [
{ path: '', component: RouterOutletComponent },
{
path: 'parentWithLayout',
component: DynamicLayoutComponent,
children: [
{
path: 'childWithoutLayout',
component: DummyComponent,
data: { name: 'childWithoutLayout' },
},
{
path: 'childWithLayout',
component: DummyComponent,
data: { name: 'childWithLayout' },
},
],
},
{
path: 'withData',
component: DynamicLayoutComponent,
children: [
{
path: '',
component: DummyComponent,
data: { name: 'withData' },
},
],
data: { layout: eLayoutType.empty },
},
{
path: 'withoutLayout',
component: DynamicLayoutComponent,
children: [
{
path: '',
component: DummyComponent,
data: { name: 'withoutLayout' },
},
],
data: { layout: null },
},
],
});
let spectator: SpectatorRouting<RouterOutletComponent>;
let replaceableComponents: ReplaceableComponentsService;
beforeEach(async () => {
spectator = createComponent();
replaceableComponents = spectator.inject(ReplaceableComponentsService);
const routesService = spectator.inject(RoutesService);
routesService.add(routes);
replaceableComponents.add({
key: 'Theme.ApplicationLayoutComponent',
component: DummyApplicationLayoutComponent,
});
replaceableComponents.add({
key: 'Theme.AccountLayoutComponent',
component: DummyAccountLayoutComponent,
});
replaceableComponents.add({
key: 'Theme.EmptyLayoutComponent',
component: DummyEmptyLayoutComponent,
});
});
it('should handle application layout from parent abp route and display it', async () => {
spectator.router.navigateByUrl('/parentWithLayout/childWithoutLayout');
await spectator.fixture.whenStable();
spectator.detectComponentChanges();
expect(spectator.query('abp-dynamic-layout')).toBeTruthy();
expect(spectator.query('abp-layout-application')).toBeTruthy();
});
it('should handle account layout from own property and display it', async () => {
spectator.router.navigateByUrl('/parentWithLayout/childWithLayout');
await spectator.fixture.whenStable();
spectator.detectComponentChanges();
expect(spectator.query('abp-layout-account')).toBeTruthy();
});
it('should handle empty layout from route data and display it', async () => {
spectator.router.navigateByUrl('/withData');
await spectator.fixture.whenStable();
spectator.detectComponentChanges();
expect(spectator.query('abp-layout-empty')).toBeTruthy();
});
it('should display empty layout when layout is null', async () => {
spectator.router.navigateByUrl('/withoutLayout');
await spectator.fixture.whenStable();
spectator.detectComponentChanges();
expect(spectator.query('abp-layout-empty')).toBeTruthy();
});
it('should not display any layout when layouts are empty', async () => {
const spy = jest.spyOn(replaceableComponents, 'get');
spy.mockReturnValue(null);
spectator.detectChanges();
spectator.router.navigateByUrl('/withoutLayout');
await spectator.fixture.whenStable();
spectator.detectComponentChanges();
expect(spectator.query('abp-layout-empty')).toBeFalsy();
});
});
import { HttpClient } from '@angular/common/http';
import { Component, NgModule, inject as inject_1 } from '@angular/core';
import { ActivatedRoute, RouterModule } from '@angular/router';
import { createRoutingFactory, SpectatorRouting } from '@ngneat/spectator/jest';
import { DynamicLayoutComponent, RouterOutletComponent } from '../components';
import { eLayoutType } from '../enums/common';
import { ABP } from '../models';
import { AbpApplicationConfigurationService } from '../proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-configuration.service';
import { ReplaceableComponentsService, RoutesService } from '../services';
import { mockRoutesService } from './routes.service.spec';
@Component({
selector: 'abp-layout-application',
template: '<router-outlet></router-outlet>',
})
class DummyApplicationLayoutComponent {}
@Component({
selector: 'abp-layout-account',
template: '<router-outlet></router-outlet>',
})
class DummyAccountLayoutComponent {}
@Component({
selector: 'abp-layout-empty',
template: '<router-outlet></router-outlet>',
})
class DummyEmptyLayoutComponent {}
const LAYOUTS = [
DummyApplicationLayoutComponent,
DummyAccountLayoutComponent,
DummyEmptyLayoutComponent,
];
@NgModule({
imports: [RouterModule],
declarations: [...LAYOUTS],
})
class DummyLayoutModule {}
@Component({
selector: 'abp-dummy',
template: '{{route.snapshot.data?.name}} works!',
})
class DummyComponent { route = inject_1(ActivatedRoute);
}
const routes: ABP.Route[] = [
{
path: '',
name: 'Root',
},
{
path: '/parentWithLayout',
name: 'ParentWithLayout',
parentName: 'Root',
layout: eLayoutType.application,
},
{
path: '/parentWithLayout/childWithoutLayout',
name: 'ChildWithoutLayout',
parentName: 'ParentWithLayout',
},
{
path: '/parentWithLayout/childWithLayout',
name: 'ChildWithLayout',
parentName: 'ParentWithLayout',
layout: eLayoutType.account,
},
{
path: '/withData',
name: 'WithData',
layout: eLayoutType.application,
},
];
describe('DynamicLayoutComponent', () => {
const createComponent = createRoutingFactory({
component: RouterOutletComponent,
stubsEnabled: false,
declarations: [DummyComponent, DynamicLayoutComponent],
mocks: [AbpApplicationConfigurationService, HttpClient],
providers: [
{
provide: RoutesService,
useFactory: () => mockRoutesService(),
},
ReplaceableComponentsService,
],
imports: [RouterModule, DummyLayoutModule],
routes: [
{ path: '', component: RouterOutletComponent },
{
path: 'parentWithLayout',
component: DynamicLayoutComponent,
children: [
{
path: 'childWithoutLayout',
component: DummyComponent,
data: { name: 'childWithoutLayout' },
},
{
path: 'childWithLayout',
component: DummyComponent,
data: { name: 'childWithLayout' },
},
],
},
{
path: 'withData',
component: DynamicLayoutComponent,
children: [
{
path: '',
component: DummyComponent,
data: { name: 'withData' },
},
],
data: { layout: eLayoutType.empty },
},
{
path: 'withoutLayout',
component: DynamicLayoutComponent,
children: [
{
path: '',
component: DummyComponent,
data: { name: 'withoutLayout' },
},
],
data: { layout: null },
},
],
});
let spectator: SpectatorRouting<RouterOutletComponent>;
let replaceableComponents: ReplaceableComponentsService;
beforeEach(async () => {
spectator = createComponent();
replaceableComponents = spectator.inject(ReplaceableComponentsService);
const routesService = spectator.inject(RoutesService);
routesService.add(routes);
replaceableComponents.add({
key: 'Theme.ApplicationLayoutComponent',
component: DummyApplicationLayoutComponent,
});
replaceableComponents.add({
key: 'Theme.AccountLayoutComponent',
component: DummyAccountLayoutComponent,
});
replaceableComponents.add({
key: 'Theme.EmptyLayoutComponent',
component: DummyEmptyLayoutComponent,
});
});
it('should handle application layout from parent abp route and display it', async () => {
spectator.router.navigateByUrl('/parentWithLayout/childWithoutLayout');
await spectator.fixture.whenStable();
spectator.detectComponentChanges();
expect(spectator.query('abp-dynamic-layout')).toBeTruthy();
expect(spectator.query('abp-layout-application')).toBeTruthy();
});
it('should handle account layout from own property and display it', async () => {
spectator.router.navigateByUrl('/parentWithLayout/childWithLayout');
await spectator.fixture.whenStable();
spectator.detectComponentChanges();
expect(spectator.query('abp-layout-account')).toBeTruthy();
});
it('should handle empty layout from route data and display it', async () => {
spectator.router.navigateByUrl('/withData');
await spectator.fixture.whenStable();
spectator.detectComponentChanges();
expect(spectator.query('abp-layout-empty')).toBeTruthy();
});
it('should display empty layout when layout is null', async () => {
spectator.router.navigateByUrl('/withoutLayout');
await spectator.fixture.whenStable();
spectator.detectComponentChanges();
expect(spectator.query('abp-layout-empty')).toBeTruthy();
});
it('should not display any layout when layouts are empty', async () => {
const spy = jest.spyOn(replaceableComponents, 'get');
spy.mockReturnValue(null);
spectator.detectChanges();
spectator.router.navigateByUrl('/withoutLayout');
await spectator.fixture.whenStable();
spectator.detectComponentChanges();
expect(spectator.query('abp-layout-empty')).toBeFalsy();
});
});

352
npm/ng-packs/packages/core/src/lib/tests/replaceable-template.directive.spec.ts

@ -1,178 +1,174 @@
import { Component, EventEmitter, Inject, Input, Optional, Output } from '@angular/core';
import { Router } from '@angular/router';
import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/jest';
import { BehaviorSubject } from 'rxjs';
import { ReplaceableTemplateDirective } from '../directives/replaceable-template.directive';
import { ReplaceableComponents } from '../models/replaceable-components';
import { ReplaceableComponentsService } from '../services/replaceable-components.service';
@Component({
selector: 'abp-default-component',
template: ' <p>default</p> ',
exportAs: 'abpDefaultComponent',
})
class DefaultComponent {
@Input()
oneWay;
@Input()
twoWay: boolean;
@Output()
readonly twoWayChange = new EventEmitter<boolean>();
@Output()
readonly someOutput = new EventEmitter<string>();
setTwoWay(value) {
this.twoWay = value;
this.twoWayChange.emit(value);
}
}
@Component({
selector: 'abp-external-component',
template: ' <p>external</p> ',
})
class ExternalComponent {
constructor(
@Optional()
@Inject('REPLACEABLE_DATA')
public data: ReplaceableComponents.ReplaceableTemplateData<any, any>,
) {}
}
describe('ReplaceableTemplateDirective', () => {
let spectator: SpectatorDirective<ReplaceableTemplateDirective>;
const get$Res = new BehaviorSubject(undefined);
const createDirective = createDirectiveFactory({
directive: ReplaceableTemplateDirective,
declarations: [DefaultComponent, ExternalComponent],
entryComponents: [ExternalComponent],
mocks: [Router],
providers: [{ provide: ReplaceableComponentsService, useValue: { get$: () => get$Res } }],
});
describe('without external component', () => {
const twoWayChange = jest.fn(a => a);
const someOutput = jest.fn(a => a);
beforeEach(() => {
spectator = createDirective(
`
<div *abpReplaceableTemplate="{inputs: {oneWay: {value: oneWay}, twoWay: {value: twoWay, twoWay: true}}, outputs: {twoWayChange: twoWayChange, someOutput: someOutput}, componentKey: 'TestModule.TestComponent'}; let initTemplate = initTemplate">
<abp-default-component #defaultComponent="abpDefaultComponent"></abp-default-component>
</div>
`,
{
hostProps: {
oneWay: { label: 'Test' },
twoWay: false,
twoWayChange,
someOutput,
},
},
);
const component = spectator.query(DefaultComponent);
spectator.directive.context.initTemplate(component);
spectator.detectChanges();
});
afterEach(() => twoWayChange.mockClear());
it('should display the default template when store response is undefined', () => {
expect(spectator.query('abp-default-component')).toBeTruthy();
});
it('should be setted inputs and outputs', () => {
const component = spectator.query(DefaultComponent);
expect(component.oneWay).toEqual({ label: 'Test' });
expect(component.twoWay).toEqual(false);
});
it('should change the component inputs', () => {
const component = spectator.query(DefaultComponent);
spectator.setHostInput({ oneWay: 'test' });
component.setTwoWay(true);
component.someOutput.emit('someOutput emitted');
expect(component.oneWay).toBe('test');
expect(twoWayChange).toHaveBeenCalledWith(true);
expect(someOutput).toHaveBeenCalledWith('someOutput emitted');
});
});
describe('with external component', () => {
const twoWayChange = jest.fn(a => a);
const someOutput = jest.fn(a => a);
beforeEach(() => {
spectator = createDirective(
`
<div *abpReplaceableTemplate="{inputs: {oneWay: {value: oneWay}, twoWay: {value: twoWay, twoWay: true}}, outputs: {twoWayChange: twoWayChange, someOutput: someOutput}, componentKey: 'TestModule.TestComponent'}; let initTemplate = initTemplate">
<abp-default-component #defaultComponent="abpDefaultComponent"></abp-default-component>
</div>
`,
{ hostProps: { oneWay: { label: 'Test' }, twoWay: false, twoWayChange, someOutput } },
);
get$Res.next({ component: ExternalComponent, key: 'TestModule.TestComponent' });
});
afterEach(() => twoWayChange.mockClear());
it('should display the external component', () => {
expect(spectator.query('p')).toHaveText('external');
});
it('should be injected the data object', () => {
const externalComponent = spectator.query(ExternalComponent);
expect(externalComponent.data).toEqual({
componentKey: 'TestModule.TestComponent',
inputs: { oneWay: { label: 'Test' }, twoWay: false },
outputs: { someOutput, twoWayChange },
});
});
it('should be worked all data properties', () => {
const externalComponent = spectator.query(ExternalComponent);
spectator.setHostInput({ oneWay: 'test' });
externalComponent.data.inputs.twoWay = true;
externalComponent.data.outputs.someOutput('someOutput emitted');
expect(externalComponent.data.inputs.oneWay).toBe('test');
expect(twoWayChange).toHaveBeenCalledWith(true);
expect(someOutput).toHaveBeenCalledWith('someOutput emitted');
spectator.setHostInput({ twoWay: 'twoWay test' });
expect(externalComponent.data.inputs.twoWay).toBe('twoWay test');
});
it('should be worked correctly the default component when the external component has been removed from store', () => {
expect(spectator.query('p')).toHaveText('external');
const externalComponent = spectator.query(ExternalComponent);
spectator.setHostInput({ oneWay: 'test' });
externalComponent.data.inputs.twoWay = true;
get$Res.next({ component: null, key: 'TestModule.TestComponent' });
spectator.detectChanges();
const component = spectator.query(DefaultComponent);
spectator.directive.context.initTemplate(component);
expect(spectator.query('abp-default-component')).toBeTruthy();
expect(component.oneWay).toEqual('test');
expect(component.twoWay).toEqual(true);
});
it('should reset default component subscriptions', () => {
get$Res.next({ component: null, key: 'TestModule.TestComponent' });
const component = spectator.query(DefaultComponent);
spectator.directive.context.initTemplate(component);
spectator.detectChanges();
const unsubscribe = jest.fn(() => {});
spectator.directive.defaultComponentSubscriptions.twoWayChange.unsubscribe = unsubscribe;
get$Res.next({ component: ExternalComponent, key: 'TestModule.TestComponent' });
expect(unsubscribe).toHaveBeenCalled();
});
});
});
import { Component, EventEmitter, Input, Output, inject } from '@angular/core';
import { Router } from '@angular/router';
import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/jest';
import { BehaviorSubject } from 'rxjs';
import { ReplaceableTemplateDirective } from '../directives/replaceable-template.directive';
import { ReplaceableComponents } from '../models/replaceable-components';
import { ReplaceableComponentsService } from '../services/replaceable-components.service';
@Component({
selector: 'abp-default-component',
template: ' <p>default</p> ',
exportAs: 'abpDefaultComponent',
})
class DefaultComponent {
@Input()
oneWay;
@Input()
twoWay: boolean;
@Output()
readonly twoWayChange = new EventEmitter<boolean>();
@Output()
readonly someOutput = new EventEmitter<string>();
setTwoWay(value) {
this.twoWay = value;
this.twoWayChange.emit(value);
}
}
@Component({
selector: 'abp-external-component',
template: ' <p>external</p> ',
})
class ExternalComponent { data = inject<ReplaceableComponents.ReplaceableTemplateData<any, any>>('REPLACEABLE_DATA' as any, { optional: true })!;
}
describe('ReplaceableTemplateDirective', () => {
let spectator: SpectatorDirective<ReplaceableTemplateDirective>;
const get$Res = new BehaviorSubject(undefined);
const createDirective = createDirectiveFactory({
directive: ReplaceableTemplateDirective,
declarations: [DefaultComponent, ExternalComponent],
entryComponents: [ExternalComponent],
mocks: [Router],
providers: [{ provide: ReplaceableComponentsService, useValue: { get$: () => get$Res } }],
});
describe('without external component', () => {
const twoWayChange = jest.fn(a => a);
const someOutput = jest.fn(a => a);
beforeEach(() => {
spectator = createDirective(
`
<div *abpReplaceableTemplate="{inputs: {oneWay: {value: oneWay}, twoWay: {value: twoWay, twoWay: true}}, outputs: {twoWayChange: twoWayChange, someOutput: someOutput}, componentKey: 'TestModule.TestComponent'}; let initTemplate = initTemplate">
<abp-default-component #defaultComponent="abpDefaultComponent"></abp-default-component>
</div>
`,
{
hostProps: {
oneWay: { label: 'Test' },
twoWay: false,
twoWayChange,
someOutput,
},
},
);
const component = spectator.query(DefaultComponent);
spectator.directive.context.initTemplate(component);
spectator.detectChanges();
});
afterEach(() => twoWayChange.mockClear());
it('should display the default template when store response is undefined', () => {
expect(spectator.query('abp-default-component')).toBeTruthy();
});
it('should be setted inputs and outputs', () => {
const component = spectator.query(DefaultComponent);
expect(component.oneWay).toEqual({ label: 'Test' });
expect(component.twoWay).toEqual(false);
});
it('should change the component inputs', () => {
const component = spectator.query(DefaultComponent);
spectator.setHostInput({ oneWay: 'test' });
component.setTwoWay(true);
component.someOutput.emit('someOutput emitted');
expect(component.oneWay).toBe('test');
expect(twoWayChange).toHaveBeenCalledWith(true);
expect(someOutput).toHaveBeenCalledWith('someOutput emitted');
});
});
describe('with external component', () => {
const twoWayChange = jest.fn(a => a);
const someOutput = jest.fn(a => a);
beforeEach(() => {
spectator = createDirective(
`
<div *abpReplaceableTemplate="{inputs: {oneWay: {value: oneWay}, twoWay: {value: twoWay, twoWay: true}}, outputs: {twoWayChange: twoWayChange, someOutput: someOutput}, componentKey: 'TestModule.TestComponent'}; let initTemplate = initTemplate">
<abp-default-component #defaultComponent="abpDefaultComponent"></abp-default-component>
</div>
`,
{ hostProps: { oneWay: { label: 'Test' }, twoWay: false, twoWayChange, someOutput } },
);
get$Res.next({ component: ExternalComponent, key: 'TestModule.TestComponent' });
});
afterEach(() => twoWayChange.mockClear());
it('should display the external component', () => {
expect(spectator.query('p')).toHaveText('external');
});
it('should be injected the data object', () => {
const externalComponent = spectator.query(ExternalComponent);
expect(externalComponent.data).toEqual({
componentKey: 'TestModule.TestComponent',
inputs: { oneWay: { label: 'Test' }, twoWay: false },
outputs: { someOutput, twoWayChange },
});
});
it('should be worked all data properties', () => {
const externalComponent = spectator.query(ExternalComponent);
spectator.setHostInput({ oneWay: 'test' });
externalComponent.data.inputs.twoWay = true;
externalComponent.data.outputs.someOutput('someOutput emitted');
expect(externalComponent.data.inputs.oneWay).toBe('test');
expect(twoWayChange).toHaveBeenCalledWith(true);
expect(someOutput).toHaveBeenCalledWith('someOutput emitted');
spectator.setHostInput({ twoWay: 'twoWay test' });
expect(externalComponent.data.inputs.twoWay).toBe('twoWay test');
});
it('should be worked correctly the default component when the external component has been removed from store', () => {
expect(spectator.query('p')).toHaveText('external');
const externalComponent = spectator.query(ExternalComponent);
spectator.setHostInput({ oneWay: 'test' });
externalComponent.data.inputs.twoWay = true;
get$Res.next({ component: null, key: 'TestModule.TestComponent' });
spectator.detectChanges();
const component = spectator.query(DefaultComponent);
spectator.directive.context.initTemplate(component);
expect(spectator.query('abp-default-component')).toBeTruthy();
expect(component.oneWay).toEqual('test');
expect(component.twoWay).toEqual(true);
});
it('should reset default component subscriptions', () => {
get$Res.next({ component: null, key: 'TestModule.TestComponent' });
const component = spectator.query(DefaultComponent);
spectator.directive.context.initTemplate(component);
spectator.detectChanges();
const unsubscribe = jest.fn(() => {});
spectator.directive.defaultComponentSubscriptions.twoWayChange.unsubscribe = unsubscribe;
get$Res.next({ component: ExternalComponent, key: 'TestModule.TestComponent' });
expect(unsubscribe).toHaveBeenCalled();
});
});
});

10
npm/ng-packs/packages/core/testing/src/lib/services/mock-permission.service.ts

@ -1,12 +1,18 @@
import { ConfigStateService, PermissionService } from '@abp/ng.core';
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class MockPermissionService extends PermissionService {
constructor(protected configState: ConfigStateService) {
protected configState: ConfigStateService;
constructor() {
const configState = inject(ConfigStateService);
super(configState);
this.configState = configState;
this.grantAllPolicies();
}

24
npm/ng-packs/packages/core/testing/src/lib/services/mock-rest.service.ts

@ -7,20 +7,30 @@ import {
RestService,
} from '@abp/ng.core';
import { HttpClient } from '@angular/common/http';
import { Inject, Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { Observable, throwError } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class MockRestService extends RestService {
constructor(
@Inject(CORE_OPTIONS) protected options: ABP.Root,
protected http: HttpClient,
protected externalhttp: ExternalHttpClient,
protected environment: EnvironmentService,
) {
protected options: ABP.Root;
protected http: HttpClient;
protected externalhttp: ExternalHttpClient;
protected environment: EnvironmentService;
constructor() {
const options = inject<ABP.Root>(CORE_OPTIONS);
const http = inject(HttpClient);
const externalhttp = inject(ExternalHttpClient);
const environment = inject(EnvironmentService);
super(options, http,externalhttp, environment, null as unknown as HttpErrorReporterService);
this.options = options;
this.http = http;
this.externalhttp = externalhttp;
this.environment = environment;
}
handleError(err: any): Observable<any> {

Loading…
Cancel
Save