diff --git a/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpSystemTextJsonSerializerProvider_Tests.cs b/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpSystemTextJsonSerializerProvider_Tests.cs index 143400c58c..dd4a366b03 100644 --- a/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpSystemTextJsonSerializerProvider_Tests.cs +++ b/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpSystemTextJsonSerializerProvider_Tests.cs @@ -26,6 +26,26 @@ namespace Volo.Abp.Json newJson.ShouldBe("{\"name\":\"abp\",\"isDeleted\":false}"); } + [Fact] + public void Serialize_Deserialize_With_Nullable_Boolean() + { + var json = "{\"name\":\"abp\",\"IsDeleted\":null}"; + var file = _jsonSerializer.Deserialize(json); + file.Name.ShouldBe("abp"); + file.IsDeleted.ShouldBeNull(); + + var newJson = _jsonSerializer.Serialize(file); + newJson.ShouldBe("{\"name\":\"abp\",\"isDeleted\":null}"); + + json = "{\"name\":\"abp\",\"IsDeleted\":\"true\"}"; + file = _jsonSerializer.Deserialize(json); + file.IsDeleted.ShouldNotBeNull(); + file.IsDeleted.Value.ShouldBeTrue(); + + newJson = _jsonSerializer.Serialize(file); + newJson.ShouldBe("{\"name\":\"abp\",\"isDeleted\":true}"); + } + [Fact] public void Serialize_Deserialize_With_Enum() { @@ -38,12 +58,38 @@ namespace Volo.Abp.Json newJson.ShouldBe("{\"name\":\"abp\",\"type\":2}"); } + [Fact] + public void Serialize_Deserialize_With_Nullable_Enum() + { + var json = "{\"name\":\"abp\",\"type\":null}"; + var file = _jsonSerializer.Deserialize(json); + file.Name.ShouldBe("abp"); + file.Type.ShouldBeNull(); + + var newJson = _jsonSerializer.Serialize(file); + newJson.ShouldBe("{\"name\":\"abp\",\"type\":null}"); + + json = "{\"name\":\"abp\",\"type\":\"Exe\"}"; + file = _jsonSerializer.Deserialize(json); + file.Type.ShouldNotBeNull(); + file.Type.ShouldBe(FileType.Exe); + + newJson = _jsonSerializer.Serialize(file); + newJson.ShouldBe("{\"name\":\"abp\",\"type\":2}"); + } + class FileWithBoolean { public string Name { get; set; } public bool IsDeleted { get; set; } + } + class FileWithNullableBoolean + { + public string Name { get; set; } + + public bool? IsDeleted { get; set; } } class FileWithEnum @@ -53,6 +99,12 @@ namespace Volo.Abp.Json public FileType Type { get; set; } } + class FileWithNullableEnum + { + public string Name { get; set; } + + public FileType? Type { get; set; } + } enum FileType { diff --git a/npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.ts b/npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.ts index 84e87f7ce7..de5cd23c89 100644 --- a/npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.ts +++ b/npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.ts @@ -1,6 +1,6 @@ import { ChangePassword, ProfileState } from '@abp/ng.core'; import { getPasswordValidators, ToasterService } from '@abp/ng.theme.shared'; -import { Component, OnInit } from '@angular/core'; +import { Component, Injector, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators, FormControl } from '@angular/forms'; import { comparePasswords, Validation } from '@ngx-validate/core'; import { Store } from '@ngxs/store'; @@ -32,6 +32,7 @@ export class ChangePasswordComponent }; constructor( + private injector: Injector, private fb: FormBuilder, private store: Store, private toasterService: ToasterService, @@ -40,7 +41,7 @@ export class ChangePasswordComponent ngOnInit(): void { this.hideCurrentPassword = !this.store.selectSnapshot(ProfileState.getProfile).hasPassword; - const passwordValidations = getPasswordValidators(this.store); + const passwordValidations = getPasswordValidators(this.injector); this.form = this.fb.group( { diff --git a/npm/ng-packs/packages/account/src/lib/components/register/register.component.ts b/npm/ng-packs/packages/account/src/lib/components/register/register.component.ts index 32dc096748..ea313cc30e 100644 --- a/npm/ng-packs/packages/account/src/lib/components/register/register.component.ts +++ b/npm/ng-packs/packages/account/src/lib/components/register/register.component.ts @@ -1,6 +1,6 @@ import { AuthService, ConfigState } from '@abp/ng.core'; import { getPasswordValidators, ToasterService } from '@abp/ng.theme.shared'; -import { Component, OnInit } from '@angular/core'; +import { Component, Injector, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { Store } from '@ngxs/store'; import { OAuthService } from 'angular-oauth2-oidc'; @@ -26,6 +26,7 @@ export class RegisterComponent implements OnInit { authWrapperKey = eAccountComponents.AuthWrapper; constructor( + private injector: Injector, private fb: FormBuilder, private accountService: AccountService, private oauthService: OAuthService, @@ -55,7 +56,7 @@ export class RegisterComponent implements OnInit { this.form = this.fb.group({ username: ['', [required, maxLength(255)]], - password: ['', [required, ...getPasswordValidators(this.store)]], + password: ['', [required, ...getPasswordValidators(this.injector)]], email: ['', [required, email]], }); } diff --git a/npm/ng-packs/packages/account/src/lib/guards/manage-profile.guard.ts b/npm/ng-packs/packages/account/src/lib/guards/manage-profile.guard.ts index 4a5b5a0709..4b71f39368 100644 --- a/npm/ng-packs/packages/account/src/lib/guards/manage-profile.guard.ts +++ b/npm/ng-packs/packages/account/src/lib/guards/manage-profile.guard.ts @@ -1,13 +1,13 @@ +import { EnvironmentService } from '@abp/ng.core'; import { Injectable } from '@angular/core'; -import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; -import { ConfigStateService } from '@abp/ng.core'; +import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router'; @Injectable() export class ManageProfileGuard implements CanActivate { - constructor(private configState: ConfigStateService) {} + constructor(private environment: EnvironmentService) {} canActivate(_: ActivatedRouteSnapshot, __: RouterStateSnapshot) { - const env = this.configState.getEnvironment(); + const env = this.environment.getEnvironment(); if (env.oAuthConfig.responseType === 'code') { window.location.href = `${env.oAuthConfig.issuer}/Account/Manage?returnUrl=${window.location.href}`; return false; diff --git a/npm/ng-packs/packages/core/src/lib/actions/config.actions.ts b/npm/ng-packs/packages/core/src/lib/actions/config.actions.ts index 535f3f3b81..c13e1e8a2e 100644 --- a/npm/ng-packs/packages/core/src/lib/actions/config.actions.ts +++ b/npm/ng-packs/packages/core/src/lib/actions/config.actions.ts @@ -1,10 +1,24 @@ 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) {} +} diff --git a/npm/ng-packs/packages/core/src/lib/actions/index.ts b/npm/ng-packs/packages/core/src/lib/actions/index.ts index 68436a5b0d..8a3b36e5b2 100644 --- a/npm/ng-packs/packages/core/src/lib/actions/index.ts +++ b/npm/ng-packs/packages/core/src/lib/actions/index.ts @@ -1,4 +1,4 @@ -export * from './config.actions'; +export { SetEnvironment, GetAppConfiguration } from './config.actions'; export * from './loader.actions'; export * from './profile.actions'; export * from './replaceable-components.actions'; diff --git a/npm/ng-packs/packages/core/src/lib/core.module.ts b/npm/ng-packs/packages/core/src/lib/core.module.ts index 6e1e9147b3..bbe3de7299 100644 --- a/npm/ng-packs/packages/core/src/lib/core.module.ts +++ b/npm/ng-packs/packages/core/src/lib/core.module.ts @@ -1,10 +1,10 @@ import { APP_BASE_HREF, CommonModule } from '@angular/common'; -import { HttpClientModule, HTTP_INTERCEPTORS, HttpClientXsrfModule } from '@angular/common/http'; +import { HttpClientModule, HttpClientXsrfModule, HTTP_INTERCEPTORS } from '@angular/common/http'; import { APP_INITIALIZER, Injector, ModuleWithProviders, NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { RouterModule } from '@angular/router'; import { NgxsRouterPluginModule } from '@ngxs/router-plugin'; -import { NgxsModule, NGXS_PLUGINS } from '@ngxs/store'; +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,17 +27,15 @@ import { LocalizationModule } from './localization.module'; import { ABP } from './models/common'; import { LocalizationPipe, MockLocalizationPipe } from './pipes/localization.pipe'; import { SortPipe } from './pipes/sort.pipe'; -import { ConfigPlugin, NGXS_CONFIG_PLUGIN_OPTIONS } from './plugins/config.plugin'; import { LocaleProvider } from './providers/locale.provider'; import { LocalizationService } from './services/localization.service'; -import { ConfigState } from './states/config.state'; import { ProfileState } from './states/profile.state'; import { ReplaceableComponentsState } from './states/replaceable-components.state'; +import { oAuthStorage } from './strategies/auth-flow.strategy'; import { coreOptionsFactory, CORE_OPTIONS } from './tokens/options.token'; import { noop } from './utils/common-utils'; import './utils/date-extensions'; import { getInitialData, localeInitializer } from './utils/initial-utils'; -import { oAuthStorage } from './strategies/auth-flow.strategy'; export function storageFactory(): OAuthStorage { return oAuthStorage; @@ -115,7 +113,7 @@ export class BaseCoreModule {} imports: [ BaseCoreModule, LocalizationModule, - NgxsModule.forFeature([ReplaceableComponentsState, ProfileState, ConfigState]), + NgxsModule.forFeature([ReplaceableComponentsState, ProfileState]), NgxsRouterPluginModule.forRoot(), OAuthModule.forRoot(), HttpClientXsrfModule.withOptions({ @@ -164,15 +162,6 @@ export class CoreModule { ngModule: RootCoreModule, providers: [ LocaleProvider, - { - provide: NGXS_PLUGINS, - useClass: ConfigPlugin, - multi: true, - }, - { - provide: NGXS_CONFIG_PLUGIN_OPTIONS, - useValue: { environment: options.environment }, - }, { provide: 'CORE_OPTIONS', useValue: options, diff --git a/npm/ng-packs/packages/core/src/lib/handlers/oauth-configuration.handler.ts b/npm/ng-packs/packages/core/src/lib/handlers/oauth-configuration.handler.ts index e0505d2f2d..f82e382fe7 100644 --- a/npm/ng-packs/packages/core/src/lib/handlers/oauth-configuration.handler.ts +++ b/npm/ng-packs/packages/core/src/lib/handlers/oauth-configuration.handler.ts @@ -1,10 +1,9 @@ import { Inject, Injectable } from '@angular/core'; -import { Actions, ofActionSuccessful } from '@ngxs/store'; import { OAuthService } from 'angular-oauth2-oidc'; import compare from 'just-compare'; import { filter, map } from 'rxjs/operators'; -import { SetEnvironment } from '../actions/config.actions'; import { ABP } from '../models/common'; +import { EnvironmentService } from '../services/environment.service'; import { CORE_OPTIONS } from '../tokens/options.token'; @Injectable({ @@ -12,18 +11,18 @@ import { CORE_OPTIONS } from '../tokens/options.token'; }) export class OAuthConfigurationHandler { constructor( - private actions: Actions, private oAuthService: OAuthService, + private environmentService: EnvironmentService, @Inject(CORE_OPTIONS) private options: ABP.Root, ) { this.listenToSetEnvironment(); } private listenToSetEnvironment() { - this.actions - .pipe(ofActionSuccessful(SetEnvironment)) + this.environmentService + .createOnUpdateStream(state => state) .pipe( - map(({ environment }: SetEnvironment) => environment.oAuthConfig), + map(environment => environment.oAuthConfig), filter(config => !compare(config, this.options.environment.oAuthConfig)), ) .subscribe(config => { diff --git a/npm/ng-packs/packages/core/src/lib/models/common.ts b/npm/ng-packs/packages/core/src/lib/models/common.ts index 6e4eead683..652cc07ea3 100644 --- a/npm/ng-packs/packages/core/src/lib/models/common.ts +++ b/npm/ng-packs/packages/core/src/lib/models/common.ts @@ -2,11 +2,11 @@ import { EventEmitter, Type } from '@angular/core'; import { Router } from '@angular/router'; import { Subject } from 'rxjs'; import { eLayoutType } from '../enums/common'; -import { Config } from './config'; +import { Environment } from './environment'; export namespace ABP { export interface Root { - environment: Partial; + environment: Partial; registerLocaleFn: (locale: string) => Promise; skipGetAppConfiguration?: boolean; sendNullsAsQueryParam?: boolean; diff --git a/npm/ng-packs/packages/core/src/lib/models/config.ts b/npm/ng-packs/packages/core/src/lib/models/config.ts index be2c128d37..70b8904bf3 100644 --- a/npm/ng-packs/packages/core/src/lib/models/config.ts +++ b/npm/ng-packs/packages/core/src/lib/models/config.ts @@ -3,6 +3,9 @@ import { AuthConfig } from 'angular-oauth2-oidc'; import { ApplicationConfiguration } from './application-configuration'; import { ABP } from './common'; +/** + * @deprecated Use ApplicationConfiguration.Response instead. To be deleted in v5.0. + */ export namespace Config { export type State = ApplicationConfiguration.Response & ABP.Root & { environment: Environment }; diff --git a/npm/ng-packs/packages/core/src/lib/models/environment.ts b/npm/ng-packs/packages/core/src/lib/models/environment.ts new file mode 100644 index 0000000000..b29f0d338d --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/models/environment.ts @@ -0,0 +1,40 @@ +import { AuthConfig } from 'angular-oauth2-oidc'; +import { ABP } from './common'; + +export interface Environment { + apis: Apis; + application: ApplicationInfo; + hmr?: boolean; + test?: boolean; + localization?: { defaultResourceName?: string }; + oAuthConfig: AuthConfig; + production: boolean; + remoteEnv?: RemoteEnv; +} + +export interface ApplicationInfo { + name: string; + baseUrl?: string; + logoUrl?: string; +} + +export type ApiConfig = { + [key: string]: string; + url: string; +} & Partial<{ + rootNamespace: string; +}>; + +export interface Apis { + [key: string]: ApiConfig; + default: ApiConfig; +} + +export type customMergeFn = (localEnv: Partial, remoteEnv: any) => Environment; + +export interface RemoteEnv { + url: string; + mergeStrategy: 'deepmerge' | 'overwrite' | customMergeFn; + method?: string; + headers?: ABP.Dictionary; +} diff --git a/npm/ng-packs/packages/core/src/lib/models/index.ts b/npm/ng-packs/packages/core/src/lib/models/index.ts index e5776a682e..bdcc74f1ce 100644 --- a/npm/ng-packs/packages/core/src/lib/models/index.ts +++ b/npm/ng-packs/packages/core/src/lib/models/index.ts @@ -2,7 +2,9 @@ export * from './application-configuration'; export * from './common'; export * from './config'; export * from './dtos'; +export * from './environment'; export * from './find-tenant-result-dto'; +export * from './localization'; export * from './profile'; export * from './replaceable-components'; export * from './rest'; diff --git a/npm/ng-packs/packages/core/src/lib/models/localization.ts b/npm/ng-packs/packages/core/src/lib/models/localization.ts new file mode 100644 index 0000000000..1d0e4d91c2 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/models/localization.ts @@ -0,0 +1,6 @@ +export interface LocalizationWithDefault { + key: string; + defaultValue: string; +} + +export type LocalizationParam = string | LocalizationWithDefault; diff --git a/npm/ng-packs/packages/core/src/lib/pipes/localization.pipe.ts b/npm/ng-packs/packages/core/src/lib/pipes/localization.pipe.ts index 79fa5a81ec..cc84cfebdd 100644 --- a/npm/ng-packs/packages/core/src/lib/pipes/localization.pipe.ts +++ b/npm/ng-packs/packages/core/src/lib/pipes/localization.pipe.ts @@ -1,26 +1,23 @@ import { Injectable, Pipe, PipeTransform } from '@angular/core'; -import { Store } from '@ngxs/store'; import { Config } from '../models'; -import { ConfigState } from '../states'; +import { LocalizationService } from '../services/localization.service'; @Injectable() @Pipe({ name: 'abpLocalization', }) export class LocalizationPipe implements PipeTransform { - constructor(private store: Store) {} + constructor(private localization: LocalizationService) {} transform( value: string | Config.LocalizationWithDefault = '', ...interpolateParams: string[] ): string { - return this.store.selectSnapshot( - ConfigState.getLocalization( - value, - ...interpolateParams.reduce( - (acc, val) => (Array.isArray(val) ? [...acc, ...val] : [...acc, val]), - [], - ), + return this.localization.instant( + value, + ...interpolateParams.reduce( + (acc, val) => (Array.isArray(val) ? [...acc, ...val] : [...acc, val]), + [], ), ); } diff --git a/npm/ng-packs/packages/core/src/lib/plugins/config.plugin.ts b/npm/ng-packs/packages/core/src/lib/plugins/config.plugin.ts deleted file mode 100644 index 9ca46c9277..0000000000 --- a/npm/ng-packs/packages/core/src/lib/plugins/config.plugin.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Inject, Injectable, InjectionToken } from '@angular/core'; -import { - actionMatcher, - InitState, - NgxsNextPluginFn, - NgxsPlugin, - setValue, - UpdateState, -} from '@ngxs/store'; -import { ABP } from '../models/common'; - -export const NGXS_CONFIG_PLUGIN_OPTIONS = new InjectionToken('NGXS_CONFIG_PLUGIN_OPTIONS'); - -@Injectable() -export class ConfigPlugin implements NgxsPlugin { - private initialized = false; - - constructor(@Inject(NGXS_CONFIG_PLUGIN_OPTIONS) private options: ABP.Root) {} - - handle(state: any, event: any, next: NgxsNextPluginFn) { - const matches = actionMatcher(event); - const isInitAction = matches(InitState) || matches(UpdateState); - - if (isInitAction && !this.initialized) { - state = setValue(state, 'ConfigState', { - ...(state.ConfigState && { ...state.ConfigState }), - ...this.options, - }); - - this.initialized = true; - } - - return next(state, event); - } -} diff --git a/npm/ng-packs/packages/core/src/lib/plugins/index.ts b/npm/ng-packs/packages/core/src/lib/plugins/index.ts deleted file mode 100644 index 42ab2f291c..0000000000 --- a/npm/ng-packs/packages/core/src/lib/plugins/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './config.plugin'; diff --git a/npm/ng-packs/packages/core/src/lib/services/application-configuration.service.ts b/npm/ng-packs/packages/core/src/lib/services/application-configuration.service.ts index 39b893501c..5be97fbb64 100644 --- a/npm/ng-packs/packages/core/src/lib/services/application-configuration.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/application-configuration.service.ts @@ -1,20 +1,19 @@ import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; -import { Rest } from '../models/rest'; import { ApplicationConfiguration } from '../models/application-configuration'; +import { Rest } from '../models/rest'; +import { EnvironmentService } from './environment.service'; import { RestService } from './rest.service'; -import { Store } from '@ngxs/store'; -import { ConfigState } from '../states/config.state'; @Injectable({ providedIn: 'root', }) export class ApplicationConfigurationService { get apiName(): string { - return this.store.selectSnapshot(ConfigState.getDeep('environment.application.name')); + return this.environment.getEnvironment().application?.name; } - constructor(private rest: RestService, private store: Store) {} + constructor(private rest: RestService, private environment: EnvironmentService) {} getConfiguration(): Observable { const request: Rest.Request = { diff --git a/npm/ng-packs/packages/core/src/lib/services/auth.service.ts b/npm/ng-packs/packages/core/src/lib/services/auth.service.ts index d694691701..1c1bd31119 100644 --- a/npm/ng-packs/packages/core/src/lib/services/auth.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/auth.service.ts @@ -1,15 +1,15 @@ import { HttpHeaders } from '@angular/common/http'; import { Inject, Injectable, Injector, Optional } from '@angular/core'; import { Navigate } from '@ngxs/router-plugin'; -import { Actions, ofActionSuccessful, Store } from '@ngxs/store'; +import { Store } from '@ngxs/store'; import { OAuthService } from 'angular-oauth2-oidc'; import { from, Observable } from 'rxjs'; import { switchMap, take, tap } from 'rxjs/operators'; import snq from 'snq'; -import { GetAppConfiguration, SetEnvironment } from '../actions/config.actions'; -import { ConfigState } from '../states/config.state'; import { AuthFlowStrategy, AUTH_FLOW_STRATEGY } from '../strategies/auth-flow.strategy'; -import { RestService } from './rest.service'; +import { ApplicationConfigurationService } from './application-configuration.service'; +import { ConfigStateService } from './config-state.service'; +import { EnvironmentService } from './environment.service'; import { SessionStateService } from './session-state.service'; @Injectable({ @@ -24,12 +24,13 @@ export class AuthService { } constructor( - private actions: Actions, + private environment: EnvironmentService, private injector: Injector, - private rest: RestService, private oAuthService: OAuthService, private store: Store, private sessionState: SessionStateService, + private configState: ConfigStateService, + private appConfigService: ApplicationConfigurationService, @Optional() @Inject('ACCOUNT_OPTIONS') private options: any, ) { this.setStrategy(); @@ -37,9 +38,7 @@ export class AuthService { } private setStrategy = () => { - const flow = - this.store.selectSnapshot(ConfigState.getDeep('environment.oAuthConfig.responseType')) || - 'password'; + const flow = this.environment.getEnvironment().oAuthConfig.responseType || 'password'; if (this.flow === flow) return; if (this.strategy) this.strategy.destroy(); @@ -52,7 +51,7 @@ export class AuthService { }; private listenToSetEnvironment() { - this.actions.pipe(ofActionSuccessful(SetEnvironment)).subscribe(this.setStrategy); + this.environment.createOnUpdateStream(state => state.oAuthConfig).subscribe(this.setStrategy); } login(username: string, password: string): Observable { @@ -65,7 +64,9 @@ export class AuthService { new HttpHeaders({ ...(tenant && tenant.id && { __tenant: tenant.id }) }), ), ).pipe( - switchMap(() => this.store.dispatch(new GetAppConfiguration())), + switchMap(() => + this.appConfigService.getConfiguration().pipe(tap(res => this.configState.setState(res))), + ), tap(() => { const redirectUrl = snq(() => window.history.state.redirectUrl) || (this.options || {}).redirectUrl || '/'; diff --git a/npm/ng-packs/packages/core/src/lib/services/config-state.service.ts b/npm/ng-packs/packages/core/src/lib/services/config-state.service.ts index e7bbc8599d..ed544fec2b 100644 --- a/npm/ng-packs/packages/core/src/lib/services/config-state.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/config-state.service.ts @@ -1,68 +1,124 @@ import { Injectable } from '@angular/core'; -import { Store } from '@ngxs/store'; -import { GetAppConfiguration, SetEnvironment } from '../actions/config.actions'; -import { ConfigState } from '../states'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { ApplicationConfiguration } from '../models/application-configuration'; +import { InternalStore } from '../utils/internal-store-utils'; @Injectable({ providedIn: 'root', }) export class ConfigStateService { - constructor(private store: Store) {} + private readonly store = new InternalStore({} as ApplicationConfiguration.Response); - getAll() { - return this.store.selectSnapshot(ConfigState.getAll); + get createOnUpdateStream() { + return this.store.sliceUpdate; } - getApplicationInfo() { - return this.store.selectSnapshot(ConfigState.getApplicationInfo); + setState = (state: ApplicationConfiguration.Response) => { + this.store.patch(state); + }; + + getOne$(key: string) { + return this.store.sliceState(state => state[key]); } - getEnvironment() { - return this.store.selectSnapshot(ConfigState.getEnvironment); + getOne(key: string) { + return this.store.state[key]; } - getOne(...args: Parameters) { - return this.store.selectSnapshot(ConfigState.getOne(...args)); + getAll$(): Observable { + return this.store.sliceState(state => state); } - getDeep(...args: Parameters) { - return this.store.selectSnapshot(ConfigState.getDeep(...args)); + getAll(): ApplicationConfiguration.Response { + return this.store.state; } - getApiUrl(...args: Parameters) { - return this.store.selectSnapshot(ConfigState.getApiUrl(...args)); + getDeep$(keys: string[] | string) { + keys = splitKeys(keys); + + return this.store + .sliceState(state => state) + .pipe( + map(state => { + return (keys as string[]).reduce((acc, val) => { + if (acc) { + return acc[val]; + } + + return undefined; + }, state); + }), + ); + } + + getDeep(keys: string[] | string) { + keys = splitKeys(keys); + + return (keys as string[]).reduce((acc, val) => { + if (acc) { + return acc[val]; + } + + return undefined; + }, this.store.state); } - getFeature(...args: Parameters) { - return this.store.selectSnapshot(ConfigState.getFeature(...args)); + getFeature(key: string) { + return this.store.state.features?.values?.[key]; } - getSetting(...args: Parameters) { - return this.store.selectSnapshot(ConfigState.getSetting(...args)); + getFeature$(key: string) { + return this.store.sliceState(state => state.features?.values?.[key]); } - getSettings(...args: Parameters) { - return this.store.selectSnapshot(ConfigState.getSettings(...args)); + getSetting(key: string) { + return this.store.state.setting?.values?.[key]; } - /** @deprecated Use PermissionService's getGrantedPolicySnapshot method */ - getGrantedPolicy(...args: Parameters) { - return this.store.selectSnapshot(ConfigState.getGrantedPolicy(...args)); + getSetting$(key: string) { + return this.store.sliceState(state => state.setting?.values?.[key]); } - getLocalization(...args: Parameters) { - return this.store.selectSnapshot(ConfigState.getLocalization(...args)); + getSettings(keyword?: string) { + const settings = this.store.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; + }, {}); } - getLocalizationResource(...args: Parameters) { - return this.store.selectSnapshot(ConfigState.getLocalizationResource(...args)); + getSettings$(keyword?: string) { + return this.store + .sliceState(state => state.setting?.values) + .pipe( + map((settings = {}) => { + 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; + }, {}); + }), + ); } +} - dispatchGetAppConfiguration() { - return this.store.dispatch(new GetAppConfiguration()); +function splitKeys(keys: string[] | string): string[] { + if (typeof keys === 'string') { + keys = keys.split('.'); } - dispatchSetEnvironment(...args: ConstructorParameters) { - return this.store.dispatch(new SetEnvironment(...args)); + if (!Array.isArray(keys)) { + throw new Error('The argument must be a dot string or an string array.'); } + + return keys; } diff --git a/npm/ng-packs/packages/core/src/lib/services/environment.service.ts b/npm/ng-packs/packages/core/src/lib/services/environment.service.ts new file mode 100644 index 0000000000..5cf975ae5f --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/services/environment.service.ts @@ -0,0 +1,36 @@ +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { Apis, Environment } from '../models/environment'; +import { InternalStore } from '../utils/internal-store-utils'; + +@Injectable({ providedIn: 'root' }) +export class EnvironmentService { + private readonly store = new InternalStore({} as Environment); + + get createOnUpdateStream() { + return this.store.sliceUpdate; + } + + getEnvironment$(): Observable { + return this.store.sliceState(state => state); + } + + getEnvironment(): Environment { + return this.store.state; + } + + getApiUrl(key?: string) { + return (this.store.state.apis[key || 'default'] || this.store.state.apis.default).url; + } + + getApiUrl$(key?: string) { + return this.store + .sliceState(state => state.apis) + .pipe(map((apis: Apis) => (apis[key || 'default'] || apis.default).url)); + } + + setState(environment: Environment) { + this.store.patch(environment); + } +} diff --git a/npm/ng-packs/packages/core/src/lib/services/index.ts b/npm/ng-packs/packages/core/src/lib/services/index.ts index 670a2fe5f1..5b294c4905 100644 --- a/npm/ng-packs/packages/core/src/lib/services/index.ts +++ b/npm/ng-packs/packages/core/src/lib/services/index.ts @@ -3,6 +3,7 @@ export * from './auth.service'; export * from './config-state.service'; export * from './content-projection.service'; export * from './dom-insertion.service'; +export * from './environment.service'; export * from './lazy-load.service'; export * from './list.service'; export * from './localization.service'; diff --git a/npm/ng-packs/packages/core/src/lib/services/localization.service.ts b/npm/ng-packs/packages/core/src/lib/services/localization.service.ts index 96f9dd5ce8..b49056f0b5 100644 --- a/npm/ng-packs/packages/core/src/lib/services/localization.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/localization.service.ts @@ -1,19 +1,19 @@ import { registerLocaleData } from '@angular/common'; -import { Injectable, Injector, NgZone, Optional, SkipSelf } from '@angular/core'; -import { ActivatedRouteSnapshot, Router } from '@angular/router'; +import { Injectable, Injector, isDevMode, NgZone, Optional, SkipSelf } from '@angular/core'; +import { Router } from '@angular/router'; import { Store } from '@ngxs/store'; -import { noop, Observable, of, Subject } from 'rxjs'; -import { filter, map, mapTo, switchMap } from 'rxjs/operators'; -import { GetAppConfiguration } from '../actions/config.actions'; +import { noop, Observable, Subject } from 'rxjs'; +import { filter, map, mapTo, switchMap, tap } from 'rxjs/operators'; +import { ApplicationConfiguration } from '../models/application-configuration'; import { ABP } from '../models/common'; import { Config } from '../models/config'; -import { ConfigState } from '../states/config.state'; import { CORE_OPTIONS } from '../tokens/options.token'; import { createLocalizer, createLocalizerWithFallback } from '../utils/localization-utils'; +import { interpolate } from '../utils/string-utils'; +import { ApplicationConfigurationService } from './application-configuration.service'; +import { ConfigStateService } from './config-state.service'; import { SessionStateService } from './session-state.service'; -type ShouldReuseRoute = (future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot) => boolean; - @Injectable({ providedIn: 'root' }) export class LocalizationService { private latestLang = this.sessionState.getLanguage(); @@ -38,6 +38,8 @@ export class LocalizationService { @Optional() @SkipSelf() otherInstance: LocalizationService, + private configState: ConfigStateService, + private appConfigService: ApplicationConfigurationService, ) { if (otherInstance) throw new Error('LocalizationService should have only one instance.'); @@ -49,12 +51,14 @@ export class LocalizationService { .onLanguageChange$() .pipe( filter( - lang => - this.store.selectSnapshot( - ConfigState.getDeep('localization.currentCulture.cultureName'), - ) !== lang, + lang => this.configState.getDeep('localization.currentCulture.cultureName') !== lang, + ), + switchMap(lang => + this.appConfigService + .getConfiguration() + .pipe(tap(res => this.configState.setState(res))) + .pipe(mapTo(lang)), ), - switchMap(lang => this.store.dispatch(new GetAppConfiguration()).pipe(mapTo(lang))), ) .subscribe(lang => { this.registerLocale(lang); @@ -90,11 +94,17 @@ export class LocalizationService { key: string | Config.LocalizationWithDefault, ...interpolateParams: string[] ): Observable { - return this.store.select(ConfigState.getLocalization(key, ...interpolateParams)); + return this.configState + .getAll$() + .pipe(map(state => getLocalization(state, key, ...interpolateParams))); } getResource(resourceName: string) { - return this.store.select(ConfigState.getLocalizationResource(resourceName)); + return this.configState.getDeep(`localization.values.${resourceName}`); + } + + getResource$(resourceName: string) { + return this.configState.getDeep$(`localization.values.${resourceName}`); } /** @@ -103,18 +113,18 @@ export class LocalizationService { * @param interpolateParams Values to intepolate. */ instant(key: string | Config.LocalizationWithDefault, ...interpolateParams: string[]): string { - return this.store.selectSnapshot(ConfigState.getLocalization(key, ...interpolateParams)); + return getLocalization(this.configState.getAll(), key, ...interpolateParams); } localize(resourceName: string, key: string, defaultValue: string): Observable { - return this.store.select(ConfigState.getOne('localization')).pipe( + return this.configState.getOne$('localization').pipe( map(createLocalizer), map(localize => localize(resourceName, key, defaultValue)), ); } localizeSync(resourceName: string, key: string, defaultValue: string): string { - const localization = this.store.selectSnapshot(ConfigState.getOne('localization')); + const localization = this.configState.getOne('localization'); return createLocalizer(localization)(resourceName, key, defaultValue); } @@ -123,14 +133,70 @@ export class LocalizationService { keys: string[], defaultValue: string, ): Observable { - return this.store.select(ConfigState.getOne('localization')).pipe( + return this.configState.getOne$('localization').pipe( map(createLocalizerWithFallback), map(localizeWithFallback => localizeWithFallback(resourceNames, keys, defaultValue)), ); } localizeWithFallbackSync(resourceNames: string[], keys: string[], defaultValue: string): string { - const localization = this.store.selectSnapshot(ConfigState.getOne('localization')); + const localization = this.configState.getOne('localization'); return createLocalizerWithFallback(localization)(resourceNames, keys, defaultValue); } } + +function getLocalization( + state: ApplicationConfiguration.Response, + 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 warn = (message: string) => { + if (isDevMode) 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.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); +} diff --git a/npm/ng-packs/packages/core/src/lib/services/permission.service.ts b/npm/ng-packs/packages/core/src/lib/services/permission.service.ts index 522d8b57ae..eb06fad098 100644 --- a/npm/ng-packs/packages/core/src/lib/services/permission.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/permission.service.ts @@ -1,13 +1,12 @@ -import { ConfigState } from '../states'; -import { Store } from '@ngxs/store'; +import { Injectable } from '@angular/core'; import { map } from 'rxjs/operators'; -import { ApplicationConfiguration } from '../models/application-configuration'; import snq from 'snq'; -import { Injectable } from '@angular/core'; +import { ApplicationConfiguration } from '../models/application-configuration'; +import { ConfigStateService } from './config-state.service'; @Injectable({ providedIn: 'root' }) export class PermissionService { - constructor(private store: Store) {} + constructor(private configState: ConfigStateService) {} getGrantedPolicy$(key: string) { return this.getStream().pipe(map(policies => this.isPolicyGranted(key, policies))); @@ -43,11 +42,11 @@ export class PermissionService { } private getStream() { - return this.store.select(ConfigState).pipe(map(this.mapToPolicies)); + return this.configState.getAll$().pipe(map(this.mapToPolicies)); } private getSnapshot() { - return this.mapToPolicies(this.store.selectSnapshot(ConfigState)); + return this.mapToPolicies(this.configState.getAll()); } private mapToPolicies(applicationConfiguration: ApplicationConfiguration.Response) { diff --git a/npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts b/npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts index 29c7ddf07e..df0f9231e9 100644 --- a/npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts @@ -8,7 +8,7 @@ import { reloadRoute } from '../utils/route-utils'; @Injectable({ providedIn: 'root' }) export class ReplaceableComponentsService { - private store: InternalStore; + private readonly store: InternalStore; get replaceableComponents$(): Observable { return this.store.sliceState(state => state); diff --git a/npm/ng-packs/packages/core/src/lib/services/rest.service.ts b/npm/ng-packs/packages/core/src/lib/services/rest.service.ts index 4ece11a43f..bd2c41cd29 100644 --- a/npm/ng-packs/packages/core/src/lib/services/rest.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/rest.service.ts @@ -5,10 +5,10 @@ import { Observable, throwError } from 'rxjs'; import { catchError } from 'rxjs/operators'; import { RestOccurError } from '../actions/rest.actions'; import { Rest } from '../models/rest'; -import { ConfigState } from '../states/config.state'; import { isUndefinedOrEmptyString } from '../utils/common-utils'; import { ABP } from '../models/common'; import { CORE_OPTIONS } from '../tokens/options.token'; +import { EnvironmentService } from './environment.service'; @Injectable({ providedIn: 'root', @@ -18,10 +18,11 @@ export class RestService { @Inject(CORE_OPTIONS) private options: ABP.Root, private http: HttpClient, private store: Store, + private environment: EnvironmentService, ) {} private getApiFromStore(apiName: string): string { - return this.store.selectSnapshot(ConfigState.getApiUrl(apiName)); + return this.environment.getApiUrl(apiName); } handleError(err: any): Observable { diff --git a/npm/ng-packs/packages/core/src/lib/services/routes.service.ts b/npm/ng-packs/packages/core/src/lib/services/routes.service.ts index 3ce32401d2..3d572b457c 100644 --- a/npm/ng-packs/packages/core/src/lib/services/routes.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/routes.service.ts @@ -1,10 +1,10 @@ import { Injectable, Injector, OnDestroy } from '@angular/core'; -import { Actions, ofActionSuccessful } from '@ngxs/store'; +import { Actions } from '@ngxs/store'; import { BehaviorSubject, Observable, Subscription } from 'rxjs'; -import { GetAppConfiguration } from '../actions/config.actions'; import { ABP } from '../models/common'; import { pushValueTo } from '../utils/array-utils'; import { BaseTreeNode, createTreeFromList, TreeNode } from '../utils/tree-utils'; +import { ConfigStateService } from './config-state.service'; import { PermissionService } from './permission.service'; export abstract class AbstractTreeService { @@ -152,9 +152,9 @@ export abstract class AbstractNavTreeService constructor(protected injector: Injector) { super(); - this.actions = injector.get(Actions); - this.subscription = this.actions - .pipe(ofActionSuccessful(GetAppConfiguration)) + const configState = this.injector.get(ConfigStateService); + this.subscription = configState + .createOnUpdateStream(state => state) .subscribe(() => this.refresh()); this.permissionService = injector.get(PermissionService); } diff --git a/npm/ng-packs/packages/core/src/lib/services/session-state.service.ts b/npm/ng-packs/packages/core/src/lib/services/session-state.service.ts index 9d2cc24ed2..4f7194024e 100644 --- a/npm/ng-packs/packages/core/src/lib/services/session-state.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/session-state.service.ts @@ -1,8 +1,10 @@ import { Injectable } from '@angular/core'; +import compare from 'just-compare'; +import { filter, take } from 'rxjs/operators'; import { ApplicationConfiguration } from '../models/application-configuration'; import { Session } from '../models/session'; import { InternalStore } from '../utils/internal-store-utils'; -import compare from 'just-compare'; +import { ConfigStateService } from './config-state.service'; export interface SessionDetail { openedTabCount: number; @@ -20,8 +22,9 @@ export class SessionStateService { localStorage.setItem('abpSession', JSON.stringify(this.store.state)); }; - constructor() { + constructor(private configState: ConfigStateService) { this.init(); + this.setInitialLanguage(); } private init() { @@ -33,6 +36,24 @@ export class SessionStateService { this.store.sliceUpdate(state => state).subscribe(this.updateLocalStorage); } + private setInitialLanguage() { + if (this.getLanguage()) return; + + this.configState + .getDeep$('localization.currentCulture.cultureName') + .pipe( + filter(cultureName => !!cultureName), + take(1), + ) + .subscribe(lang => { + if (lang.includes(';')) { + lang = lang.split(';')[0]; + } + + this.setLanguage(lang); + }); + } + onLanguageChange$() { return this.store.sliceUpdate(state => state.language); } @@ -67,5 +88,6 @@ export class SessionStateService { if (language === this.store.state.language) return; this.store.patch({ language }); + document.documentElement.setAttribute('lang', language); } } diff --git a/npm/ng-packs/packages/core/src/lib/states/config.state.ts b/npm/ng-packs/packages/core/src/lib/states/config.state.ts index d11c7b44c9..9531a2b7a8 100644 --- a/npm/ng-packs/packages/core/src/lib/states/config.state.ts +++ b/npm/ng-packs/packages/core/src/lib/states/config.state.ts @@ -2,15 +2,21 @@ import { HttpClient, HttpErrorResponse } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Action, createSelector, Selector, State, StateContext, Store } from '@ngxs/store'; import { of, throwError } from 'rxjs'; -import { catchError, switchMap, tap } from 'rxjs/operators'; +import { catchError, distinctUntilChanged, switchMap, tap } from 'rxjs/operators'; import snq from 'snq'; -import { GetAppConfiguration, SetEnvironment } from '../actions/config.actions'; +import { GetAppConfiguration, PatchConfigState, SetEnvironment } from '../actions/config.actions'; import { RestOccurError } from '../actions/rest.actions'; import { ApplicationConfiguration } from '../models/application-configuration'; import { Config } from '../models/config'; +import { ConfigStateService } from '../services/config-state.service'; +import { EnvironmentService } from '../services/environment.service'; import { SessionStateService } from '../services/session-state.service'; import { interpolate } from '../utils/string-utils'; +import compare from 'just-compare'; +/** + * @deprecated Use ConfigStateService instead. To be deleted in v5.0. + */ @State({ name: 'ConfigState', defaults: {} as Config.State, @@ -198,7 +204,6 @@ export class ConfigState { return defaultValue || sourceKey; } - // [TODO]: next line should be removed in v3.2, breaking change!!! interpolateParams = interpolateParams.filter(params => params != null); if (localization) localization = interpolate(localization, interpolateParams); @@ -214,7 +219,26 @@ export class ConfigState { private http: HttpClient, private store: Store, private sessionState: SessionStateService, - ) {} + 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) { @@ -223,22 +247,7 @@ export class ConfigState { return this.http .get(`${api}/api/abp/application-configuration`) .pipe( - tap(configuration => - patchState({ - ...configuration, - }), - ), - switchMap(configuration => { - if (this.sessionState.getLanguage()) return of(null); - - let lang = configuration.localization.currentCulture.cultureName; - if (lang.includes(';')) { - lang = lang.split(';')[0]; - } - - document.documentElement.setAttribute('lang', lang); - return of(null).pipe(tap(() => this.sessionState.setLanguage(lang))); - }), + tap(configuration => this.configState.setState(configuration)), catchError((err: HttpErrorResponse) => { dispatch(new RestOccurError(err)); return throwError(err); @@ -247,7 +256,12 @@ export class ConfigState { } @Action(SetEnvironment) - setEnvironment({ patchState }: StateContext, { environment }: SetEnvironment) { - return patchState({ environment }); + setEnvironment(_, { environment }: SetEnvironment) { + return this.environmentService.setState(environment); + } + + @Action(PatchConfigState) + setConfig({ patchState, getState }: StateContext, { state }: PatchConfigState) { + patchState({ ...getState(), ...state }); } } diff --git a/npm/ng-packs/packages/core/src/lib/strategies/auth-flow.strategy.ts b/npm/ng-packs/packages/core/src/lib/strategies/auth-flow.strategy.ts index e301ad19cb..60b2cd9702 100644 --- a/npm/ng-packs/packages/core/src/lib/strategies/auth-flow.strategy.ts +++ b/npm/ng-packs/packages/core/src/lib/strategies/auth-flow.strategy.ts @@ -4,10 +4,11 @@ import { Store } from '@ngxs/store'; import { AuthConfig, OAuthService, OAuthStorage } from 'angular-oauth2-oidc'; import { Observable, of } from 'rxjs'; import { switchMap, tap } from 'rxjs/operators'; -import { GetAppConfiguration } from '../actions/config.actions'; import { RestOccurError } from '../actions/rest.actions'; +import { ApplicationConfigurationService } from '../services/application-configuration.service'; +import { ConfigStateService } from '../services/config-state.service'; +import { EnvironmentService } from '../services/environment.service'; import { RestService } from '../services/rest.service'; -import { ConfigState } from '../states/config.state'; export const oAuthStorage = localStorage; @@ -15,6 +16,9 @@ export abstract class AuthFlowStrategy { abstract readonly isInternalAuth: boolean; protected store: Store; + protected environment: EnvironmentService; + protected configState: ConfigStateService; + protected appConfigService: ApplicationConfigurationService; protected oAuthService: OAuthService; protected oAuthConfig: AuthConfig; abstract checkIfInternalAuth(): boolean; @@ -26,13 +30,16 @@ export abstract class AuthFlowStrategy { constructor(protected injector: Injector) { this.store = injector.get(Store); + this.environment = injector.get(EnvironmentService); + this.configState = injector.get(ConfigStateService); + this.appConfigService = injector.get(ApplicationConfigurationService); this.oAuthService = injector.get(OAuthService); - this.oAuthConfig = this.store.selectSnapshot(ConfigState.getDeep('environment.oAuthConfig')); + this.oAuthConfig = this.environment.getEnvironment().oAuthConfig; } async init(): Promise { const shouldClear = shouldStorageClear( - this.store.selectSnapshot(ConfigState.getDeep('environment.oAuthConfig.clientId')), + this.environment.getEnvironment().oAuthConfig.clientId, oAuthStorage, ); if (shouldClear) clearOAuthStorage(oAuthStorage); @@ -91,7 +98,7 @@ export class AuthPasswordFlowStrategy extends AuthFlowStrategy { logout() { const rest = this.injector.get(RestService); - const issuer = this.store.selectSnapshot(ConfigState.getDeep('environment.oAuthConfig.issuer')); + const issuer = this.environment.getEnvironment().oAuthConfig.issuer; return rest .request( { @@ -103,7 +110,9 @@ export class AuthPasswordFlowStrategy extends AuthFlowStrategy { ) .pipe( tap(() => this.oAuthService.logOut()), - switchMap(() => this.store.dispatch(new GetAppConfiguration())), + switchMap(() => + this.appConfigService.getConfiguration().pipe(tap(res => this.configState.setState(res))), + ), ); } diff --git a/npm/ng-packs/packages/core/src/lib/utils/environment-utils.ts b/npm/ng-packs/packages/core/src/lib/utils/environment-utils.ts index 47a8f18b42..6dc9a7de16 100644 --- a/npm/ng-packs/packages/core/src/lib/utils/environment-utils.ts +++ b/npm/ng-packs/packages/core/src/lib/utils/environment-utils.ts @@ -1,42 +1,44 @@ import { HttpClient } from '@angular/common/http'; import { Injector } from '@angular/core'; import { Store } from '@ngxs/store'; -import { catchError, switchMap } from 'rxjs/operators'; -import { SetEnvironment } from '../actions/config.actions'; -import { Config } from '../models/config'; +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 { deepMerge } from './object-utils'; -export function getRemoteEnv(injector: Injector, environment: Partial) { +export function getRemoteEnv(injector: Injector, environment: Partial) { + const environmentService = injector.get(EnvironmentService); + const { remoteEnv } = environment; - const { headers = {}, method = 'GET', url } = remoteEnv || ({} as Config.RemoteEnv); + const { headers = {}, method = 'GET', url } = remoteEnv || ({} as RemoteEnv); if (!url) return Promise.resolve(); const http = injector.get(HttpClient); const store = injector.get(Store); return http - .request(method, url, { headers }) + .request(method, url, { headers }) .pipe( catchError(err => store.dispatch(new RestOccurError(err))), // TODO: Condiser get handle function from a provider - switchMap(env => store.dispatch(mergeEnvironments(environment, env, remoteEnv))), + tap(env => environmentService.setState(mergeEnvironments(environment, env, remoteEnv))), ) .toPromise(); } function mergeEnvironments( - local: Partial, + local: Partial, remote: any, - config: Config.RemoteEnv, -) { + config: RemoteEnv, +): Environment { switch (config.mergeStrategy) { case 'deepmerge': - return new SetEnvironment(deepMerge(local, remote)); + return deepMerge(local, remote); case 'overwrite': case null: case undefined: - return new SetEnvironment(remote); + return remote; default: - return new SetEnvironment(config.mergeStrategy(local, remote)); + return config.mergeStrategy(local, remote); } } diff --git a/npm/ng-packs/packages/core/src/lib/utils/initial-utils.ts b/npm/ng-packs/packages/core/src/lib/utils/initial-utils.ts index f34f46e6f0..c4b8e012ab 100644 --- a/npm/ng-packs/packages/core/src/lib/utils/initial-utils.ts +++ b/npm/ng-packs/packages/core/src/lib/utils/initial-utils.ts @@ -3,12 +3,14 @@ import { Injector } from '@angular/core'; import { Store } from '@ngxs/store'; import { OAuthService } from 'angular-oauth2-oidc'; import { tap } from 'rxjs/operators'; -import { GetAppConfiguration } from '../actions/config.actions'; import { ApplicationConfiguration } from '../models/application-configuration'; import { ABP } from '../models/common'; +import { Environment } from '../models/environment'; +import { ApplicationConfigurationService } from '../services/application-configuration.service'; import { AuthService } from '../services/auth.service'; +import { ConfigStateService } from '../services/config-state.service'; +import { EnvironmentService } from '../services/environment.service'; import { SessionStateService } from '../services/session-state.service'; -import { ConfigState } from '../states/config.state'; import { clearOAuthStorage } from '../strategies/auth-flow.strategy'; import { CORE_OPTIONS } from '../tokens/options.token'; import { getRemoteEnv } from './environment-utils'; @@ -16,22 +18,26 @@ import { parseTenantFromUrl } from './multi-tenancy-utils'; export function getInitialData(injector: Injector) { const fn = async () => { - const store: Store = injector.get(Store); + const environmentService = injector.get(EnvironmentService); + const configState = injector.get(ConfigStateService); + const appConfigService = injector.get(ApplicationConfigurationService); const options = injector.get(CORE_OPTIONS) as ABP.Root; + environmentService.setState(options.environment as Environment); await getRemoteEnv(injector, options.environment); await parseTenantFromUrl(injector); await injector.get(AuthService).init(); if (options.skipGetAppConfiguration) return; - return store - .dispatch(new GetAppConfiguration()) + return appConfigService + .getConfiguration() .pipe( - tap(() => checkAccessToken(store, injector)), + tap(res => configState.setState(res)), + tap(() => checkAccessToken(injector)), tap(() => { - const currentTenant = store.selectSnapshot( - ConfigState.getDeep('currentTenant'), + const currentTenant = configState.getOne( + 'currentTenant', ) as ApplicationConfiguration.CurrentTenant; if (!currentTenant?.id) return; @@ -44,9 +50,10 @@ export function getInitialData(injector: Injector) { return fn; } -export function checkAccessToken(store: Store, injector: Injector) { +export function checkAccessToken(injector: Injector) { + const configState = injector.get(ConfigStateService); const oAuth = injector.get(OAuthService); - if (oAuth.hasValidAccessToken() && !store.selectSnapshot(ConfigState.getDeep('currentUser.id'))) { + if (oAuth.hasValidAccessToken() && !configState.getDeep('currentUser.id')) { clearOAuthStorage(); } } diff --git a/npm/ng-packs/packages/core/src/lib/utils/multi-tenancy-utils.ts b/npm/ng-packs/packages/core/src/lib/utils/multi-tenancy-utils.ts index eca1bd3dae..ede1a1cbc8 100644 --- a/npm/ng-packs/packages/core/src/lib/utils/multi-tenancy-utils.ts +++ b/npm/ng-packs/packages/core/src/lib/utils/multi-tenancy-utils.ts @@ -1,11 +1,10 @@ import { Injector } from '@angular/core'; -import { Store } from '@ngxs/store'; import clone from 'just-clone'; +import { of } from 'rxjs'; import { switchMap, tap } from 'rxjs/operators'; -import { SetEnvironment } from '../actions'; -import { Config } from '../models/config'; +import { Environment } from '../models/environment'; +import { EnvironmentService } from '../services/environment.service'; import { MultiTenancyService } from '../services/multi-tenancy.service'; -import { ConfigState } from '../states/config.state'; import { createTokenParser } from './string-utils'; const tenancyPlaceholder = '{0}'; @@ -19,17 +18,17 @@ function getCurrentTenancyName(appBaseUrl: string): string { } export async function parseTenantFromUrl(injector: Injector) { - const store: Store = injector.get(Store); + const environmentService = injector.get(EnvironmentService); const multiTenancyService = injector.get(MultiTenancyService); - const environment = store.selectSnapshot(ConfigState.getOne('environment')) as Config.Environment; - const { baseUrl = '' } = environment.application; + const baseUrl = environmentService.getEnvironment()?.application?.baseUrl || ''; const tenancyName = getCurrentTenancyName(baseUrl); if (tenancyName) { multiTenancyService.isTenantBoxVisible = false; + setEnvironment(injector, tenancyName); - return setEnvironment(store, tenancyName) + return of(null) .pipe( switchMap(() => multiTenancyService.findTenantByName(tenancyName, { __tenant: '' })), tap(res => { @@ -44,10 +43,10 @@ export async function parseTenantFromUrl(injector: Injector) { return Promise.resolve(); } -function setEnvironment(store: Store, tenancyName: string) { - const environment = clone( - store.selectSnapshot(ConfigState.getOne('environment')), - ) as Config.Environment; +function setEnvironment(injector: Injector, tenancyName: string) { + const environmentService = injector.get(EnvironmentService); + + const environment = clone(environmentService.getEnvironment()) as Environment; if (environment.application.baseUrl) { environment.application.baseUrl = environment.application.baseUrl.replace( @@ -70,5 +69,5 @@ function setEnvironment(store: Store, tenancyName: string) { }); }); - return store.dispatch(new SetEnvironment(environment)); + return environmentService.setState(environment); } diff --git a/npm/ng-packs/packages/core/src/public-api.ts b/npm/ng-packs/packages/core/src/public-api.ts index b21dbaf3be..ec8fddfea8 100644 --- a/npm/ng-packs/packages/core/src/public-api.ts +++ b/npm/ng-packs/packages/core/src/public-api.ts @@ -15,7 +15,6 @@ export * from './lib/guards'; export * from './lib/interceptors'; export * from './lib/models'; export * from './lib/pipes'; -export * from './lib/plugins'; export * from './lib/services'; export * from './lib/states'; export * from './lib/strategies'; diff --git a/npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.ts b/npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.ts index 71e5ae834e..1cde2f2081 100644 --- a/npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.ts +++ b/npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.ts @@ -1,8 +1,8 @@ -import { TrackByService, GetAppConfiguration } from '@abp/ng.core'; +import { ApplicationConfigurationService, 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 { finalize, tap } from 'rxjs/operators'; import { FeatureManagement } from '../../models/feature-management'; import { FeaturesService } from '../../proxy/feature-management/features.service'; import { @@ -65,6 +65,8 @@ export class FeatureManagementComponent public readonly track: TrackByService, protected service: FeaturesService, protected store: Store, + protected configState: ConfigStateService, + protected appConfigService: ApplicationConfigurationService, ) {} openModal() { @@ -115,7 +117,10 @@ export class FeatureManagementComponent if (!this.providerKey) { // to refresh host's features - this.store.dispatch(new GetAppConfiguration()); + this.appConfigService + .getConfiguration() + .pipe(tap(res => this.configState.setState(res))) + .subscribe(); } }); } diff --git a/npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts b/npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts index 5b36b7351e..c302b14cea 100644 --- a/npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts +++ b/npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts @@ -1,7 +1,14 @@ import { ListService } from '@abp/ng.core'; import { ePermissionManagementComponents } from '@abp/ng.permission-management'; import { Confirmation, ConfirmationService, getPasswordValidators } from '@abp/ng.theme.shared'; -import { Component, OnInit, TemplateRef, TrackByFunction, ViewChild } from '@angular/core'; +import { + Component, + Injector, + OnInit, + TemplateRef, + TrackByFunction, + ViewChild, +} from '@angular/core'; import { AbstractControl, FormArray, @@ -23,7 +30,6 @@ import { 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 { GetIdentityUsersInput, @@ -78,6 +84,7 @@ export class UsersComponent implements OnInit { constructor( public readonly list: ListService, + private injector: Injector, private confirmationService: ConfirmationService, private fb: FormBuilder, private store: Store, @@ -115,7 +122,7 @@ export class UsersComponent implements OnInit { ), }); - const passwordValidators = getPasswordValidators(this.store); + const passwordValidators = getPasswordValidators(this.injector); this.form.addControl('password', new FormControl('', [...passwordValidators])); diff --git a/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts b/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts index 16fb9fe9e2..8ee4fa113c 100644 --- a/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts +++ b/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts @@ -1,4 +1,8 @@ -import { ApplicationConfiguration, ConfigState, GetAppConfiguration } from '@abp/ng.core'; +import { + ApplicationConfiguration, + ApplicationConfigurationService, + ConfigStateService, +} from '@abp/ng.core'; import { LocaleDirection } from '@abp/ng.theme.shared'; import { Component, EventEmitter, Input, Output, Renderer2, TrackByFunction } from '@angular/core'; import { Select, Store } from '@ngxs/store'; @@ -105,7 +109,11 @@ export class PermissionManagementComponent ); } - constructor(private store: Store, private renderer: Renderer2) {} + constructor( + protected store: Store, + protected configState: ConfigStateService, + protected appConfigService: ApplicationConfigurationService, + ) {} getChecked(name: string) { return (this.permissions.find(per => per.name === name) || { isGranted: false }).isGranted; @@ -239,7 +247,11 @@ export class PermissionManagementComponent ) .pipe( switchMap(() => - this.shouldFetchAppConfig() ? this.store.dispatch(GetAppConfiguration) : of(null), + this.shouldFetchAppConfig() + ? this.appConfigService + .getConfiguration() + .pipe(tap(res => this.configState.setState(res))) + : of(null), ), finalize(() => (this.modalBusy = false)), ) @@ -282,8 +294,8 @@ export class PermissionManagementComponent } shouldFetchAppConfig() { - const currentUser = this.store.selectSnapshot( - ConfigState.getOne('currentUser'), + const currentUser = this.configState.getOne( + 'currentUser', ) as ApplicationConfiguration.CurrentUser; if (this.providerName === 'R') return currentUser.roles.some(role => role === this.providerKey); diff --git a/npm/ng-packs/packages/tenant-management/src/lib/components/tenants/tenants.component.ts b/npm/ng-packs/packages/tenant-management/src/lib/components/tenants/tenants.component.ts index 1339d9ebc8..460811dfab 100644 --- a/npm/ng-packs/packages/tenant-management/src/lib/components/tenants/tenants.component.ts +++ b/npm/ng-packs/packages/tenant-management/src/lib/components/tenants/tenants.component.ts @@ -1,7 +1,7 @@ -import { ABP, ListService, PagedResultDto } from '@abp/ng.core'; +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, OnInit, TemplateRef, ViewChild } from '@angular/core'; +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'; @@ -101,6 +101,7 @@ export class TenantsComponent implements OnInit { constructor( public readonly list: ListService, + private injector: Injector, private confirmationService: ConfirmationService, private tenantService: TenantManagementService, private fb: FormBuilder, @@ -115,7 +116,7 @@ export class TenantsComponent implements OnInit { const tenantForm = this.fb.group({ name: [this.selected.name || '', [Validators.required, Validators.maxLength(256)]], adminEmailAddress: [null, [Validators.required, Validators.maxLength(256), Validators.email]], - adminPassword: [null, [Validators.required, ...getPasswordValidators(this.store)]], + adminPassword: [null, [Validators.required, ...getPasswordValidators(this.injector)]], }); if (this.hasSelectedTenant) { diff --git a/npm/ng-packs/packages/theme-basic/src/lib/components/logo/logo.component.ts b/npm/ng-packs/packages/theme-basic/src/lib/components/logo/logo.component.ts index 6f8365c3e3..95a532435b 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/components/logo/logo.component.ts +++ b/npm/ng-packs/packages/theme-basic/src/lib/components/logo/logo.component.ts @@ -1,6 +1,5 @@ -import { Config, ConfigState } from '@abp/ng.core'; +import { ApplicationInfo, EnvironmentService } from '@abp/ng.core'; import { Component } from '@angular/core'; -import { Store } from '@ngxs/store'; @Component({ selector: 'abp-logo', @@ -21,9 +20,9 @@ import { Store } from '@ngxs/store'; `, }) export class LogoComponent { - get appInfo(): Config.Application { - return this.store.selectSnapshot(ConfigState.getApplicationInfo); + get appInfo(): ApplicationInfo { + return this.environment.getEnvironment().application; } - constructor(private store: Store) {} + constructor(private environment: EnvironmentService) {} } diff --git a/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/current-user.component.ts b/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/current-user.component.ts index 6f362b0ee8..804cf5dc32 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/current-user.component.ts +++ b/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/current-user.component.ts @@ -1,7 +1,6 @@ -import { ApplicationConfiguration, ConfigState, AuthService } from '@abp/ng.core'; +import { ApplicationConfiguration, AuthService, ConfigStateService } from '@abp/ng.core'; import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; -import { Select } from '@ngxs/store'; import { Observable } from 'rxjs'; @Component({ @@ -48,14 +47,19 @@ import { Observable } from 'rxjs'; `, }) export class CurrentUserComponent implements OnInit { - @Select(ConfigState.getOne('currentUser')) - currentUser$: Observable; + currentUser$: Observable = this.configState.getOne$( + 'currentUser', + ); get smallScreen(): boolean { return window.innerWidth < 992; } - constructor(private authService: AuthService, private router: Router) {} + constructor( + private authService: AuthService, + private router: Router, + private configState: ConfigStateService, + ) {} ngOnInit() {} diff --git a/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/languages.component.ts b/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/languages.component.ts index fae3fa0cf4..6df8beef7a 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/languages.component.ts +++ b/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/languages.component.ts @@ -1,6 +1,5 @@ -import { ApplicationConfiguration, ConfigState, SessionStateService } from '@abp/ng.core'; -import { Component, OnInit } from '@angular/core'; -import { Select } from '@ngxs/store'; +import { ApplicationConfiguration, ConfigStateService, SessionStateService } from '@abp/ng.core'; +import { Component } from '@angular/core'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import snq from 'snq'; @@ -44,13 +43,14 @@ import snq from 'snq'; `, }) -export class LanguagesComponent implements OnInit { +export class LanguagesComponent { get smallScreen(): boolean { return window.innerWidth < 992; } - @Select(ConfigState.getDeep('localization.languages')) - languages$: Observable; + languages$: Observable = this.configState.getDeep$( + 'localization.languages', + ); get defaultLanguage$(): Observable { return this.languages$.pipe( @@ -78,9 +78,7 @@ export class LanguagesComponent implements OnInit { return this.sessionState.getLanguage(); } - constructor(private sessionState: SessionStateService) {} - - ngOnInit() {} + constructor(private sessionState: SessionStateService, private configState: ConfigStateService) {} onChangeLang(cultureName: string) { this.sessionState.setLanguage(cultureName); diff --git a/npm/ng-packs/packages/theme-shared/extensions/src/lib/utils/state.util.ts b/npm/ng-packs/packages/theme-shared/extensions/src/lib/utils/state.util.ts index 9ae9784e26..47f80c3fa1 100644 --- a/npm/ng-packs/packages/theme-shared/extensions/src/lib/utils/state.util.ts +++ b/npm/ng-packs/packages/theme-shared/extensions/src/lib/utils/state.util.ts @@ -1,5 +1,4 @@ -import { ABP, ApplicationConfiguration } from '@abp/ng.core'; -import { createSelector, Store } from '@ngxs/store'; +import { ABP, ApplicationConfiguration, ConfigStateService } from '@abp/ng.core'; import { Observable, pipe, zip } from 'rxjs'; import { filter, map, switchMap, take } from 'rxjs/operators'; import { ePropType } from '../enums/props.enum'; @@ -12,51 +11,59 @@ import { createDisplayNameLocalizationPipeKeyGenerator } from './localization.ut import { createExtraPropertyValueResolver } from './props.util'; import { getValidatorsFromProperty } from './validation.util'; -const selectConfig = (state: any) => state.ConfigState; - -const selectObjectExtensions = createSelector( - [selectConfig], - (config: ObjectExtensions.Config) => config.objectExtensions, -); - -const selectLocalization = createSelector( - [selectConfig], - (config: ApplicationConfiguration.Response) => config.localization, -); - -const selectEnums = createSelector( - [selectObjectExtensions, selectLocalization], - (extensions: ObjectExtensions.Item) => - Object.keys(extensions.enums).reduce((acc, key) => { - const { fields, localizationResource } = extensions.enums[key]; - acc[key] = { - fields, - localizationResource, - transformed: createEnum(fields), - }; - return acc; - }, {} as ObjectExtensions.Enums), -); - -const createObjectExtensionEntitiesSelector = (moduleKey: ModuleKey) => - createSelector([selectObjectExtensions], (extensions: ObjectExtensions.Item) => { - if (!extensions) return null; - - return (extensions.modules[moduleKey] || ({} as ObjectExtensions.Module)).entities; - }); - -export function getObjectExtensionEntitiesFromStore(store: Store, moduleKey: ModuleKey) { - return store.select(createObjectExtensionEntitiesSelector(moduleKey)).pipe( +function selectObjectExtensions( + configState: ConfigStateService, +): Observable { + return configState.getOne$('objectExtensions'); +} + +function selectLocalization( + configState: ConfigStateService, +): Observable { + return configState.getOne$('localization'); +} + +function selectEnums( + configState: ConfigStateService, +): Observable> { + return selectObjectExtensions(configState).pipe( + map((extensions: ObjectExtensions.Item) => + Object.keys(extensions.enums).reduce((acc, key) => { + const { fields, localizationResource } = extensions.enums[key]; + acc[key] = { + fields, + localizationResource, + transformed: createEnum(fields), + }; + return acc; + }, {} as ObjectExtensions.Enums), + ), + ); +} + +export function getObjectExtensionEntitiesFromStore( + configState: ConfigStateService, + moduleKey: ModuleKey, +) { + return selectObjectExtensions(configState).pipe( + map(extensions => { + if (!extensions) return null; + + return (extensions.modules[moduleKey] || ({} as ObjectExtensions.Module)).entities; + }), map(entities => (isUndefined(entities) ? {} : entities)), filter(Boolean), take(1), ); } -export function mapEntitiesToContributors(store: Store, resource: string) { +export function mapEntitiesToContributors( + configState: ConfigStateService, + resource: string, +) { return pipe( switchMap(entities => - zip(store.select(selectLocalization), store.select(selectEnums)).pipe( + zip(selectLocalization(configState), selectEnums(configState)).pipe( map(([localization, enums]) => { const generateDisplayName = createDisplayNameLocalizationPipeKeyGenerator(localization); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/utils/date-parser-formatter.ts b/npm/ng-packs/packages/theme-shared/src/lib/utils/date-parser-formatter.ts index 9525f4be3a..cf24e4d82c 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/utils/date-parser-formatter.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/utils/date-parser-formatter.ts @@ -1,8 +1,7 @@ -import { ApplicationConfiguration, ConfigState } from '@abp/ng.core'; +import { ApplicationConfiguration, ConfigStateService } from '@abp/ng.core'; import { DatePipe } from '@angular/common'; import { Injectable, Optional } from '@angular/core'; import { NgbDateParserFormatter, NgbDateStruct } from '@ng-bootstrap/ng-bootstrap'; -import { Store } from '@ngxs/store'; function padNumber(value: number) { if (isNumber(value)) { @@ -22,7 +21,7 @@ function toInteger(value: any): number { @Injectable() export class DateParserFormatter extends NgbDateParserFormatter { - constructor(@Optional() private datePipe: DatePipe, private store: Store) { + constructor(@Optional() private datePipe: DatePipe, private configState: ConfigStateService) { super(); } @@ -50,8 +49,8 @@ export class DateParserFormatter extends NgbDateParserFormatter { } format(date: NgbDateStruct): string { - const { shortDatePattern } = (this.store.selectSnapshot( - ConfigState.getOne('localization'), + const { shortDatePattern } = (this.configState.getOne( + 'localization', ) as ApplicationConfiguration.Localization).currentCulture.dateTimeFormat; if (date && this.datePipe) { diff --git a/npm/ng-packs/packages/theme-shared/src/lib/utils/validation-utils.ts b/npm/ng-packs/packages/theme-shared/src/lib/utils/validation-utils.ts index 966135fad7..a9ec1d4270 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/utils/validation-utils.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/utils/validation-utils.ts @@ -1,12 +1,12 @@ -import { Store } from '@ngxs/store'; -import { ABP, ConfigState } from '@abp/ng.core'; +import { ABP, ConfigStateService } from '@abp/ng.core'; +import { Injector } from '@angular/core'; +import { ValidatorFn, Validators } from '@angular/forms'; import { PasswordRules, validatePassword } from '@ngx-validate/core'; -import { Validators, ValidatorFn } from '@angular/forms'; const { minLength, maxLength } = Validators; -export function getPasswordValidators(store: Store): ValidatorFn[] { - const getRule = getRuleFn(store); +export function getPasswordValidators(injector: Injector): ValidatorFn[] { + const getRule = getRuleFn(injector); const passwordRulesArr = [] as PasswordRules; let requiredLength = 1; @@ -34,11 +34,11 @@ export function getPasswordValidators(store: Store): ValidatorFn[] { return [validatePassword(passwordRulesArr), minLength(requiredLength), maxLength(128)]; } -function getRuleFn(store: Store) { +function getRuleFn(injector: Injector) { + const configState = injector.get(ConfigStateService); + return (key: string) => { - const passwordRules: ABP.Dictionary = store.selectSnapshot( - ConfigState.getSettings('Identity.Password'), - ); + const passwordRules: ABP.Dictionary = configState.getSettings('Identity.Password'); return (passwordRules[`Abp.Identity.Password.${key}`] || '').toLowerCase(); };