Browse Source

Merge pull request #6116 from abpframework/feat/5606

Removed the SessionState
pull/6122/head
Levent Arman Özak 6 years ago
committed by GitHub
parent
commit
71bc369bca
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      docs/en/UI/Angular/Localization.md
  2. 1
      npm/ng-packs/package.json
  3. 6
      npm/ng-packs/packages/account/src/lib/components/login/login.component.ts
  4. 13
      npm/ng-packs/packages/account/src/lib/components/tenant-box/tenant-box.component.ts
  5. 1
      npm/ng-packs/packages/core/ng-package.json
  6. 1
      npm/ng-packs/packages/core/package.json
  7. 1
      npm/ng-packs/packages/core/src/lib/actions/index.ts
  8. 18
      npm/ng-packs/packages/core/src/lib/actions/session.actions.ts
  9. 2
      npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts
  10. 21
      npm/ng-packs/packages/core/src/lib/core.module.ts
  11. 18
      npm/ng-packs/packages/core/src/lib/interceptors/api.interceptor.ts
  12. 7
      npm/ng-packs/packages/core/src/lib/models/application-configuration.ts
  13. 2
      npm/ng-packs/packages/core/src/lib/models/common.ts
  14. 4
      npm/ng-packs/packages/core/src/lib/models/session.ts
  15. 5
      npm/ng-packs/packages/core/src/lib/services/auth.service.ts
  16. 40
      npm/ng-packs/packages/core/src/lib/services/localization.service.ts
  17. 7
      npm/ng-packs/packages/core/src/lib/services/multi-tenancy.service.ts
  18. 73
      npm/ng-packs/packages/core/src/lib/services/session-state.service.ts
  19. 13
      npm/ng-packs/packages/core/src/lib/states/config.state.ts
  20. 1
      npm/ng-packs/packages/core/src/lib/states/index.ts
  21. 133
      npm/ng-packs/packages/core/src/lib/states/session.state.ts
  22. 14
      npm/ng-packs/packages/core/src/lib/tests/api.interceptor.spec.ts
  23. 15
      npm/ng-packs/packages/core/src/lib/tests/config.state.spec.ts
  24. 6
      npm/ng-packs/packages/core/src/lib/tests/initial-utils.spec.ts
  25. 36
      npm/ng-packs/packages/core/src/lib/tests/localization.service.spec.ts
  26. 61
      npm/ng-packs/packages/core/src/lib/tests/session-state.service.spec.ts
  27. 70
      npm/ng-packs/packages/core/src/lib/tests/session.state.spec.ts
  28. 18
      npm/ng-packs/packages/core/src/lib/utils/initial-utils.ts
  29. 12
      npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/languages.component.ts
  30. 2
      npm/ng-packs/packages/theme-shared/extensions/src/lib/utils/enum.util.ts
  31. 20
      npm/ng-packs/packages/theme-shared/extensions/src/tests/enum.util.spec.ts
  32. 11
      npm/ng-packs/packages/theme-shared/src/lib/handlers/lazy-style.handler.ts
  33. 6
      npm/ng-packs/packages/theme-shared/src/lib/tests/lazy-style.handler.spec.ts
  34. 2
      npm/ng-packs/yarn.lock

4
docs/en/UI/Angular/Localization.md

@ -309,10 +309,12 @@ import { registerLocale } from './register-locale';
] ]
``` ```
After this custom `registerLocale` function, only en and fr locale files will be created as chunks: After this custom `registerLocale` function, since the en and fr added to the `webpackInclude`, only en and fr locale files will be created as chunks:
![locale chunks](https://user-images.githubusercontent.com/34455572/98203212-acaa2100-1f44-11eb-85af-4eb66d296326.png) ![locale chunks](https://user-images.githubusercontent.com/34455572/98203212-acaa2100-1f44-11eb-85af-4eb66d296326.png)
Which locale files you add to `webpackInclude` magic comment, they will be included in the bundle
## See Also ## See Also

1
npm/ng-packs/package.json

@ -61,7 +61,6 @@
"@ngxs/devtools-plugin": "^3.7.0", "@ngxs/devtools-plugin": "^3.7.0",
"@ngxs/logger-plugin": "^3.7.0", "@ngxs/logger-plugin": "^3.7.0",
"@ngxs/router-plugin": "^3.7.0", "@ngxs/router-plugin": "^3.7.0",
"@ngxs/storage-plugin": "^3.7.0",
"@ngxs/store": "^3.7.0", "@ngxs/store": "^3.7.0",
"@schematics/angular": "~10.0.5", "@schematics/angular": "~10.0.5",
"@swimlane/ngx-datatable": "^17.1.0", "@swimlane/ngx-datatable": "^17.1.0",

6
npm/ng-packs/packages/account/src/lib/components/login/login.component.ts

@ -1,4 +1,4 @@
import { AuthService, SetRemember, ConfigState } from '@abp/ng.core'; import { AuthService, ConfigState } from '@abp/ng.core';
import { ToasterService } from '@abp/ng.theme.shared'; import { ToasterService } from '@abp/ng.theme.shared';
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@ -65,8 +65,6 @@ export class LoginComponent implements OnInit {
}), }),
finalize(() => (this.inProgress = false)), finalize(() => (this.inProgress = false)),
) )
.subscribe(() => { .subscribe(() => {});
this.store.dispatch(new SetRemember(this.form.get('remember').value));
});
} }
} }

13
npm/ng-packs/packages/account/src/lib/components/tenant-box/tenant-box.component.ts

@ -1,4 +1,4 @@
import { ABP, GetAppConfiguration, SessionState, SetTenant } from '@abp/ng.core'; import { ABP, GetAppConfiguration, SessionStateService } from '@abp/ng.core';
import { ToasterService } from '@abp/ng.theme.shared'; import { ToasterService } from '@abp/ng.theme.shared';
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { Select, Store } from '@ngxs/store'; import { Select, Store } from '@ngxs/store';
@ -13,8 +13,7 @@ import { AccountService } from '../../services/account.service';
}) })
export class TenantBoxComponent export class TenantBoxComponent
implements Account.TenantBoxComponentInputs, Account.TenantBoxComponentOutputs { implements Account.TenantBoxComponentInputs, Account.TenantBoxComponentOutputs {
@Select(SessionState.getTenant) currentTenant$ = this.sessionState.getTenant$();
currentTenant$: Observable<ABP.BasicItem>;
name: string; name: string;
@ -26,11 +25,12 @@ export class TenantBoxComponent
private store: Store, private store: Store,
private toasterService: ToasterService, private toasterService: ToasterService,
private accountService: AccountService, private accountService: AccountService,
private sessionState: SessionStateService,
) {} ) {}
onSwitch() { onSwitch() {
const tenant = this.store.selectSnapshot(SessionState.getTenant); const tenant = this.sessionState.getTenant;
this.name = (tenant || ({} as ABP.BasicItem)).name; this.name = tenant?.name;
this.isModalVisible = true; this.isModalVisible = true;
} }
@ -57,7 +57,8 @@ export class TenantBoxComponent
} }
private setTenant(tenant: ABP.BasicItem) { private setTenant(tenant: ABP.BasicItem) {
return this.store.dispatch([new SetTenant(tenant), new GetAppConfiguration()]); this.sessionState.setTenant(tenant);
return this.store.dispatch(new GetAppConfiguration());
} }
private showError() { private showError() {

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

@ -9,7 +9,6 @@
"@abp/utils", "@abp/utils",
"@angular/localize", "@angular/localize",
"@ngxs/router-plugin", "@ngxs/router-plugin",
"@ngxs/storage-plugin",
"@ngxs/store", "@ngxs/store",
"angular-oauth2-oidc", "angular-oauth2-oidc",
"just-compare", "just-compare",

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

@ -10,7 +10,6 @@
"@abp/utils": "^3.3.1", "@abp/utils": "^3.3.1",
"@angular/localize": "~10.0.10", "@angular/localize": "~10.0.10",
"@ngxs/router-plugin": "^3.7.0", "@ngxs/router-plugin": "^3.7.0",
"@ngxs/storage-plugin": "^3.7.0",
"@ngxs/store": "^3.7.0", "@ngxs/store": "^3.7.0",
"angular-oauth2-oidc": "^10.0.0", "angular-oauth2-oidc": "^10.0.0",
"just-clone": "^3.1.0", "just-clone": "^3.1.0",

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

@ -3,4 +3,3 @@ export * from './loader.actions';
export * from './profile.actions'; export * from './profile.actions';
export * from './replaceable-components.actions'; export * from './replaceable-components.actions';
export * from './rest.actions'; export * from './rest.actions';
export * from './session.actions';

18
npm/ng-packs/packages/core/src/lib/actions/session.actions.ts

@ -1,18 +0,0 @@
import { ABP } from '../models';
export class SetLanguage {
static readonly type = '[Session] Set Language';
constructor(public payload: string, public dispatchAppConfiguration?: boolean) {}
}
export class SetTenant {
static readonly type = '[Session] Set Tenant';
constructor(public payload: ABP.BasicItem) {}
}
export class ModifyOpenedTabCount {
static readonly type = '[Session] Modify Opened Tab Count';
constructor(public operation: 'increase' | 'decrease') {}
}
export class SetRemember {
static readonly type = '[Session] Set Remember';
constructor(public payload: boolean) {}
}

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

@ -74,7 +74,7 @@ export class DynamicLayoutComponent {
} }
private listenToLanguageChange() { private listenToLanguageChange() {
this.subscription.addOne(this.localizationService.languageChange, () => { this.subscription.addOne(this.localizationService.languageChange$, () => {
this.isLayoutVisible = false; this.isLayoutVisible = false;
setTimeout(() => (this.isLayoutVisible = true), 0); setTimeout(() => (this.isLayoutVisible = true), 0);
}); });

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

@ -4,11 +4,6 @@ import { APP_INITIALIZER, Injector, ModuleWithProviders, NgModule } from '@angul
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { NgxsRouterPluginModule } from '@ngxs/router-plugin'; import { NgxsRouterPluginModule } from '@ngxs/router-plugin';
import {
NgxsStoragePluginModule,
NGXS_STORAGE_PLUGIN_OPTIONS,
StorageOption,
} from '@ngxs/storage-plugin';
import { NgxsModule, NGXS_PLUGINS } from '@ngxs/store'; import { NgxsModule, NGXS_PLUGINS } from '@ngxs/store';
import { OAuthModule, OAuthStorage } from 'angular-oauth2-oidc'; import { OAuthModule, OAuthStorage } from 'angular-oauth2-oidc';
import { AbstractNgModelComponent } from './abstracts/ng-model.component'; import { AbstractNgModelComponent } from './abstracts/ng-model.component';
@ -38,7 +33,6 @@ import { LocalizationService } from './services/localization.service';
import { ConfigState } from './states/config.state'; import { ConfigState } from './states/config.state';
import { ProfileState } from './states/profile.state'; import { ProfileState } from './states/profile.state';
import { ReplaceableComponentsState } from './states/replaceable-components.state'; import { ReplaceableComponentsState } from './states/replaceable-components.state';
import { SessionState } from './states/session.state';
import { coreOptionsFactory, CORE_OPTIONS } from './tokens/options.token'; import { coreOptionsFactory, CORE_OPTIONS } from './tokens/options.token';
import { noop } from './utils/common-utils'; import { noop } from './utils/common-utils';
import './utils/date-extensions'; import './utils/date-extensions';
@ -121,9 +115,8 @@ export class BaseCoreModule {}
imports: [ imports: [
BaseCoreModule, BaseCoreModule,
LocalizationModule, LocalizationModule,
NgxsModule.forFeature([ReplaceableComponentsState, ProfileState, SessionState, ConfigState]), NgxsModule.forFeature([ReplaceableComponentsState, ProfileState, ConfigState]),
NgxsRouterPluginModule.forRoot(), NgxsRouterPluginModule.forRoot(),
NgxsStoragePluginModule.forRoot(),
OAuthModule.forRoot(), OAuthModule.forRoot(),
HttpClientXsrfModule.withOptions({ HttpClientXsrfModule.withOptions({
cookieName: 'XSRF-TOKEN', cookieName: 'XSRF-TOKEN',
@ -225,18 +218,6 @@ export class CoreModule {
useFactory: noop, useFactory: noop,
}, },
{ provide: OAuthStorage, useFactory: storageFactory }, { provide: OAuthStorage, useFactory: storageFactory },
{
provide: NGXS_STORAGE_PLUGIN_OPTIONS,
useValue: {
storage: StorageOption.LocalStorage,
serialize: JSON.stringify,
deserialize: JSON.parse,
beforeSerialize: ngxsStoragePluginSerialize,
afterDeserialize: ngxsStoragePluginSerialize,
...options.ngxsStoragePluginOptions,
key: [...(options.ngxsStoragePluginOptions?.key || []), 'SessionState'],
},
},
], ],
}; };
} }

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

@ -1,16 +1,20 @@
import { HttpHandler, HttpHeaders, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpHandler, HttpRequest, HttpHeaders } from '@angular/common/http';
import { OAuthService } from 'angular-oauth2-oidc';
import { Store } from '@ngxs/store'; import { Store } from '@ngxs/store';
import { SessionState } from '../states'; import { OAuthService } from 'angular-oauth2-oidc';
import { StartLoader, StopLoader } from '../actions/loader.actions';
import { finalize } from 'rxjs/operators'; import { finalize } from 'rxjs/operators';
import { StartLoader, StopLoader } from '../actions/loader.actions';
import { SessionStateService } from '../services/session-state.service';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class ApiInterceptor implements HttpInterceptor { export class ApiInterceptor implements HttpInterceptor {
constructor(private oAuthService: OAuthService, private store: Store) {} constructor(
private oAuthService: OAuthService,
private store: Store,
private sessionState: SessionStateService,
) {}
intercept(request: HttpRequest<any>, next: HttpHandler) { intercept(request: HttpRequest<any>, next: HttpHandler) {
this.store.dispatch(new StartLoader(request)); this.store.dispatch(new StartLoader(request));
@ -32,12 +36,12 @@ export class ApiInterceptor implements HttpInterceptor {
headers['Authorization'] = `Bearer ${token}`; headers['Authorization'] = `Bearer ${token}`;
} }
const lang = this.store.selectSnapshot(SessionState.getLanguage); const lang = this.sessionState.getLanguage();
if (!existingHeaders?.has('Accept-Language') && lang) { if (!existingHeaders?.has('Accept-Language') && lang) {
headers['Accept-Language'] = lang; headers['Accept-Language'] = lang;
} }
const tenant = this.store.selectSnapshot(SessionState.getTenant); const tenant = this.sessionState.getTenant();
if (!existingHeaders?.has('__tenant') && tenant) { if (!existingHeaders?.has('__tenant') && tenant) {
headers['__tenant'] = tenant.id; headers['__tenant'] = tenant.id;
} }

7
npm/ng-packs/packages/core/src/lib/models/application-configuration.ts

@ -6,6 +6,7 @@ export namespace ApplicationConfiguration {
auth: Auth; auth: Auth;
setting: Value; setting: Value;
currentUser: CurrentUser; currentUser: CurrentUser;
currentTenant: CurrentTenant;
features: Value; features: Value;
} }
@ -74,4 +75,10 @@ export namespace ApplicationConfiguration {
phoneNumberVerified: boolean; phoneNumberVerified: boolean;
surName: string; surName: string;
} }
export interface CurrentTenant {
id: string;
name: string;
isAvailable?: boolean;
}
} }

2
npm/ng-packs/packages/core/src/lib/models/common.ts

@ -1,6 +1,5 @@
import { EventEmitter, Type } from '@angular/core'; import { EventEmitter, Type } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { NgxsStoragePluginOptions } from '@ngxs/storage-plugin';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { eLayoutType } from '../enums/common'; import { eLayoutType } from '../enums/common';
import { Config } from './config'; import { Config } from './config';
@ -11,7 +10,6 @@ export namespace ABP {
registerLocaleFn: (locale: string) => Promise<any>; registerLocaleFn: (locale: string) => Promise<any>;
skipGetAppConfiguration?: boolean; skipGetAppConfiguration?: boolean;
sendNullsAsQueryParam?: boolean; sendNullsAsQueryParam?: boolean;
ngxsStoragePluginOptions?: NgxsStoragePluginOptions & { key?: string[] };
} }
export interface Test { export interface Test {

4
npm/ng-packs/packages/core/src/lib/models/session.ts

@ -1,9 +1,9 @@
import { ABP } from '../models'; import { ApplicationConfiguration } from './application-configuration';
export namespace Session { export namespace Session {
export interface State { export interface State {
language: string; language: string;
tenant: ABP.BasicItem; tenant: ApplicationConfiguration.CurrentTenant;
sessionDetail: SessionDetail; sessionDetail: SessionDetail;
} }

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

@ -8,9 +8,9 @@ import { switchMap, take, tap } from 'rxjs/operators';
import snq from 'snq'; import snq from 'snq';
import { GetAppConfiguration, SetEnvironment } from '../actions/config.actions'; import { GetAppConfiguration, SetEnvironment } from '../actions/config.actions';
import { ConfigState } from '../states/config.state'; import { ConfigState } from '../states/config.state';
import { SessionState } from '../states/session.state';
import { AuthFlowStrategy, AUTH_FLOW_STRATEGY } from '../strategies/auth-flow.strategy'; import { AuthFlowStrategy, AUTH_FLOW_STRATEGY } from '../strategies/auth-flow.strategy';
import { RestService } from './rest.service'; import { RestService } from './rest.service';
import { SessionStateService } from './session-state.service';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
@ -29,6 +29,7 @@ export class AuthService {
private rest: RestService, private rest: RestService,
private oAuthService: OAuthService, private oAuthService: OAuthService,
private store: Store, private store: Store,
private sessionState: SessionStateService,
@Optional() @Inject('ACCOUNT_OPTIONS') private options: any, @Optional() @Inject('ACCOUNT_OPTIONS') private options: any,
) { ) {
this.setStrategy(); this.setStrategy();
@ -55,7 +56,7 @@ export class AuthService {
} }
login(username: string, password: string): Observable<any> { login(username: string, password: string): Observable<any> {
const tenant = this.store.selectSnapshot(SessionState.getTenant); const tenant = this.sessionState.getTenant();
return from( return from(
this.oAuthService.fetchTokenUsingPasswordFlow( this.oAuthService.fetchTokenUsingPasswordFlow(

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

@ -1,36 +1,37 @@
import { registerLocaleData } from '@angular/common'; import { registerLocaleData } from '@angular/common';
import { Injectable, Injector, NgZone, Optional, SkipSelf } from '@angular/core'; import { Injectable, Injector, NgZone, Optional, SkipSelf } from '@angular/core';
import { ActivatedRouteSnapshot, Router } from '@angular/router'; import { ActivatedRouteSnapshot, Router } from '@angular/router';
import { Actions, ofActionSuccessful, Store } from '@ngxs/store'; import { Store } from '@ngxs/store';
import { noop, Observable } from 'rxjs'; import { noop, Observable, of, Subject } from 'rxjs';
import { filter, map } from 'rxjs/operators'; import { filter, map, mapTo, switchMap } from 'rxjs/operators';
import { SetLanguage } from '../actions/session.actions'; import { GetAppConfiguration } from '../actions/config.actions';
import { ABP } from '../models/common'; import { ABP } from '../models/common';
import { Config } from '../models/config'; import { Config } from '../models/config';
import { ConfigState } from '../states/config.state'; import { ConfigState } from '../states/config.state';
import { CORE_OPTIONS } from '../tokens/options.token'; import { CORE_OPTIONS } from '../tokens/options.token';
import { createLocalizer, createLocalizerWithFallback } from '../utils/localization-utils'; import { createLocalizer, createLocalizerWithFallback } from '../utils/localization-utils';
import { SessionStateService } from './session-state.service';
type ShouldReuseRoute = (future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot) => boolean; type ShouldReuseRoute = (future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot) => boolean;
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class LocalizationService { export class LocalizationService {
private latestLang = this.sessionState.getLanguage();
private _languageChange$ = new Subject<string>();
/** /**
* Returns currently selected language * Returns currently selected language
*/ */
get currentLang(): string { get currentLang(): string {
return this.store.selectSnapshot(state => state.SessionState.language); return this.latestLang;
} }
get languageChange(): Observable<SetLanguage> { get languageChange$(): Observable<string> {
return this.actions.pipe( return this._languageChange$.asObservable();
ofActionSuccessful(SetLanguage),
filter((action: SetLanguage) => action.dispatchAppConfiguration !== false),
);
} }
constructor( constructor(
private actions: Actions, private sessionState: SessionStateService,
private store: Store, private store: Store,
private injector: Injector, private injector: Injector,
private ngZone: NgZone, private ngZone: NgZone,
@ -44,7 +45,21 @@ export class LocalizationService {
} }
private listenToSetLanguage() { private listenToSetLanguage() {
this.languageChange.subscribe(({ payload }) => this.registerLocale(payload)); this.sessionState
.onLanguageChange$()
.pipe(
filter(
lang =>
this.store.selectSnapshot(
ConfigState.getDeep('localization.currentCulture.cultureName'),
) !== lang,
),
switchMap(lang => this.store.dispatch(new GetAppConfiguration()).pipe(mapTo(lang))),
)
.subscribe(lang => {
this.registerLocale(lang);
this._languageChange$.next(lang);
});
} }
registerLocale(locale: string) { registerLocale(locale: string) {
@ -57,6 +72,7 @@ export class LocalizationService {
return registerLocaleFn(locale).then(module => { return registerLocaleFn(locale).then(module => {
if (module?.default) registerLocaleData(module.default); if (module?.default) registerLocaleData(module.default);
this.latestLang = locale;
this.ngZone.run(async () => { this.ngZone.run(async () => {
await router.navigateByUrl(router.url).catch(noop); await router.navigateByUrl(router.url).catch(noop);

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

@ -1,10 +1,9 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Store } from '@ngxs/store';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { SetTenant } from '../actions/session.actions';
import { ABP } from '../models/common'; import { ABP } from '../models/common';
import { FindTenantResultDto } from '../models/find-tenant-result-dto'; import { FindTenantResultDto } from '../models/find-tenant-result-dto';
import { RestService } from './rest.service'; import { RestService } from './rest.service';
import { SessionStateService } from './session-state.service';
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class MultiTenancyService { export class MultiTenancyService {
@ -12,7 +11,7 @@ export class MultiTenancyService {
set domainTenant(value: ABP.BasicItem) { set domainTenant(value: ABP.BasicItem) {
this._domainTenant = value; this._domainTenant = value;
this.store.dispatch(new SetTenant(value)); this.sessionState.setTenant(value);
} }
get domainTenant() { get domainTenant() {
@ -23,7 +22,7 @@ export class MultiTenancyService {
apiName = 'abp'; apiName = 'abp';
constructor(private restService: RestService, private store: Store) {} constructor(private restService: RestService, private sessionState: SessionStateService) {}
findTenantByName(name: string, headers: ABP.Dictionary<string>): Observable<FindTenantResultDto> { findTenantByName(name: string, headers: ABP.Dictionary<string>): Observable<FindTenantResultDto> {
return this.restService.request( return this.restService.request(

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

@ -1,44 +1,71 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Store } from '@ngxs/store'; import { ApplicationConfiguration } from '../models/application-configuration';
import { import { Session } from '../models/session';
SetLanguage, import { InternalStore } from '../utils/internal-store-utils';
SetRemember, import compare from 'just-compare';
SetTenant,
ModifyOpenedTabCount, export interface SessionDetail {
} from '../actions/session.actions'; openedTabCount: number;
import { SessionState } from '../states'; lastExitTime: number;
remember: boolean;
}
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class SessionStateService { export class SessionStateService {
constructor(private store: Store) {} private readonly store = new InternalStore({} as Session.State);
getLanguage() { private updateLocalStorage = () => {
return this.store.selectSnapshot(SessionState.getLanguage); localStorage.setItem('abpSession', JSON.stringify(this.store.state));
};
constructor() {
this.init();
} }
getTenant() { private init() {
return this.store.selectSnapshot(SessionState.getTenant); const session = localStorage.getItem('abpSession');
if (session) {
this.store.patch(JSON.parse(session));
}
this.store.sliceUpdate(state => state).subscribe(this.updateLocalStorage);
}
onLanguageChange$() {
return this.store.sliceUpdate(state => state.language);
} }
getSessionDetail() { onTenantChange$() {
return this.store.selectSnapshot(SessionState.getSessionDetail); return this.store.sliceUpdate(state => state.tenant);
} }
dispatchSetLanguage(...args: ConstructorParameters<typeof SetLanguage>) { getLanguage() {
return this.store.dispatch(new SetLanguage(...args)); return this.store.state.language;
}
getLanguage$() {
return this.store.sliceState(state => state.language);
}
getTenant() {
return this.store.state.tenant;
} }
dispatchSetTenant(...args: ConstructorParameters<typeof SetTenant>) { getTenant$() {
return this.store.dispatch(new SetTenant(...args)); return this.store.sliceState(state => state.tenant);
} }
dispatchSetRemember(...args: ConstructorParameters<typeof SetRemember>) { setTenant(tenant: ApplicationConfiguration.CurrentTenant) {
return this.store.dispatch(new SetRemember(...args)); if (compare(tenant, this.store.state.tenant)) return;
this.store.patch({ tenant });
} }
dispatchModifyOpenedTabCount(...args: ConstructorParameters<typeof ModifyOpenedTabCount>) { setLanguage(language: string) {
return this.store.dispatch(new ModifyOpenedTabCount(...args)); if (language === this.store.state.language) return;
this.store.patch({ language });
} }
} }

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

@ -6,11 +6,10 @@ import { catchError, switchMap, tap } from 'rxjs/operators';
import snq from 'snq'; import snq from 'snq';
import { GetAppConfiguration, SetEnvironment } from '../actions/config.actions'; import { GetAppConfiguration, SetEnvironment } from '../actions/config.actions';
import { RestOccurError } from '../actions/rest.actions'; import { RestOccurError } from '../actions/rest.actions';
import { SetLanguage } from '../actions/session.actions';
import { ApplicationConfiguration } from '../models/application-configuration'; import { ApplicationConfiguration } from '../models/application-configuration';
import { Config } from '../models/config'; import { Config } from '../models/config';
import { SessionStateService } from '../services/session-state.service';
import { interpolate } from '../utils/string-utils'; import { interpolate } from '../utils/string-utils';
import { SessionState } from './session.state';
@State<Config.State>({ @State<Config.State>({
name: 'ConfigState', name: 'ConfigState',
@ -211,7 +210,11 @@ export class ConfigState {
return selector; return selector;
} }
constructor(private http: HttpClient, private store: Store) {} constructor(
private http: HttpClient,
private store: Store,
private sessionState: SessionStateService,
) {}
@Action(GetAppConfiguration) @Action(GetAppConfiguration)
addData({ patchState, dispatch }: StateContext<Config.State>) { addData({ patchState, dispatch }: StateContext<Config.State>) {
@ -226,7 +229,7 @@ export class ConfigState {
}), }),
), ),
switchMap(configuration => { switchMap(configuration => {
if (this.store.selectSnapshot(SessionState.getLanguage)) return of(null); if (this.sessionState.getLanguage()) return of(null);
let lang = configuration.localization.currentCulture.cultureName; let lang = configuration.localization.currentCulture.cultureName;
if (lang.includes(';')) { if (lang.includes(';')) {
@ -234,7 +237,7 @@ export class ConfigState {
} }
document.documentElement.setAttribute('lang', lang); document.documentElement.setAttribute('lang', lang);
return dispatch(new SetLanguage(lang, false)); return of(null).pipe(tap(() => this.sessionState.setLanguage(lang)));
}), }),
catchError((err: HttpErrorResponse) => { catchError((err: HttpErrorResponse) => {
dispatch(new RestOccurError(err)); dispatch(new RestOccurError(err));

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

@ -1,4 +1,3 @@
export * from './replaceable-components.state'; export * from './replaceable-components.state';
export * from './config.state'; export * from './config.state';
export * from './profile.state'; export * from './profile.state';
export * from './session.state';

133
npm/ng-packs/packages/core/src/lib/states/session.state.ts

@ -1,133 +0,0 @@
import { Injectable } from '@angular/core';
import {
Action,
Actions,
ofActionSuccessful,
Selector,
State,
StateContext,
Store,
} from '@ngxs/store';
import { OAuthService } from 'angular-oauth2-oidc';
import { fromEvent } from 'rxjs';
import { take } from 'rxjs/operators';
import { GetAppConfiguration } from '../actions/config.actions';
import {
ModifyOpenedTabCount,
SetLanguage,
SetRemember,
SetTenant,
} from '../actions/session.actions';
import { ABP, Session } from '../models';
@State<Session.State>({
name: 'SessionState',
defaults: { sessionDetail: { openedTabCount: 0 } } as Session.State,
})
@Injectable()
export class SessionState {
@Selector()
static getLanguage({ language }: Session.State): string {
return language;
}
@Selector()
static getTenant({ tenant }: Session.State): ABP.BasicItem {
return tenant;
}
@Selector()
static getSessionDetail({ sessionDetail }: Session.State): Session.SessionDetail {
return sessionDetail;
}
constructor(private oAuthService: OAuthService, private store: Store, private actions: Actions) {
actions
.pipe(ofActionSuccessful(GetAppConfiguration))
.pipe(take(1))
.subscribe(() => {
const sessionDetail = this.store.selectSnapshot(SessionState)?.sessionDetail || {};
const fiveMinutesBefore = new Date().valueOf() - 5 * 60 * 1000;
if (
sessionDetail.lastExitTime &&
sessionDetail.openedTabCount === 0 &&
this.oAuthService.hasValidAccessToken() &&
sessionDetail.remember === false &&
sessionDetail.lastExitTime < fiveMinutesBefore
) {
this.oAuthService.logOut();
}
this.store.dispatch(new ModifyOpenedTabCount('increase'));
fromEvent(window, 'unload').subscribe(event => {
this.store.dispatch(new ModifyOpenedTabCount('decrease'));
});
});
}
@Action(SetLanguage)
setLanguage(
{ patchState, dispatch }: StateContext<Session.State>,
{ payload, dispatchAppConfiguration = true }: SetLanguage,
) {
patchState({
language: payload,
});
if (dispatchAppConfiguration) return dispatch(new GetAppConfiguration());
}
@Action(SetTenant)
setTenant({ patchState }: StateContext<Session.State>, { payload }: SetTenant) {
patchState({
tenant: payload,
});
}
@Action(SetRemember)
setRemember(
{ getState, patchState }: StateContext<Session.State>,
{ payload: remember }: SetRemember,
) {
const { sessionDetail } = getState();
patchState({
sessionDetail: {
...sessionDetail,
remember,
},
});
}
@Action(ModifyOpenedTabCount)
modifyOpenedTabCount(
{ getState, patchState }: StateContext<Session.State>,
{ operation }: ModifyOpenedTabCount,
) {
// tslint:disable-next-line: prefer-const
let { openedTabCount, lastExitTime, ...detail } =
getState().sessionDetail || ({ openedTabCount: 0 } as Session.SessionDetail);
if (operation === 'increase') {
openedTabCount++;
} else if (operation === 'decrease') {
openedTabCount--;
lastExitTime = new Date().valueOf();
}
if (!openedTabCount || openedTabCount < 0) {
openedTabCount = 0;
}
patchState({
sessionDetail: {
openedTabCount,
lastExitTime,
...detail,
},
});
}
}

14
npm/ng-packs/packages/core/src/lib/tests/api.interceptor.spec.ts

@ -4,30 +4,34 @@ import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest';
import { Store } from '@ngxs/store'; import { Store } from '@ngxs/store';
import { OAuthService } from 'angular-oauth2-oidc'; import { OAuthService } from 'angular-oauth2-oidc';
import { Subject, timer } from 'rxjs'; import { Subject, timer } from 'rxjs';
import { ApiInterceptor } from '../interceptors';
import { StartLoader, StopLoader } from '../actions'; import { StartLoader, StopLoader } from '../actions';
import { ApiInterceptor } from '../interceptors';
import { SessionStateService } from '../services';
describe('ApiInterceptor', () => { describe('ApiInterceptor', () => {
let spectator: SpectatorService<ApiInterceptor>; let spectator: SpectatorService<ApiInterceptor>;
let interceptor: ApiInterceptor; let interceptor: ApiInterceptor;
let store: SpyObject<Store>; let store: SpyObject<Store>;
let oauthService: SpyObject<OAuthService>; let oauthService: SpyObject<OAuthService>;
let sessionState: SpyObject<SessionStateService>;
const createService = createServiceFactory({ const createService = createServiceFactory({
service: ApiInterceptor, service: ApiInterceptor,
mocks: [OAuthService, Store], mocks: [OAuthService, Store, SessionStateService],
}); });
beforeEach(() => { beforeEach(() => {
spectator = createService(); spectator = createService();
interceptor = spectator.service; interceptor = spectator.service;
store = spectator.inject(Store); store = spectator.inject(Store);
sessionState = spectator.inject(SessionStateService);
oauthService = spectator.inject(OAuthService); oauthService = spectator.inject(OAuthService);
}); });
it('should add headers to http request', done => { it('should add headers to http request', done => {
oauthService.getAccessToken.andReturn('ey892mkwa8^2jk'); oauthService.getAccessToken.andReturn('ey892mkwa8^2jk');
store.selectSnapshot.andReturn({ id: 'test' }); sessionState.getLanguage.andReturn('tr');
sessionState.getTenant.andReturn({ id: 'Volosoft', name: 'Volosoft' });
const request = new HttpRequest('GET', 'https://abp.io'); const request = new HttpRequest('GET', 'https://abp.io');
const handleRes$ = new Subject(); const handleRes$ = new Subject();
@ -35,8 +39,8 @@ describe('ApiInterceptor', () => {
const handler = { const handler = {
handle: (req: HttpRequest<any>) => { handle: (req: HttpRequest<any>) => {
expect(req.headers.get('Authorization')).toEqual('Bearer ey892mkwa8^2jk'); expect(req.headers.get('Authorization')).toEqual('Bearer ey892mkwa8^2jk');
expect(req.headers.get('Accept-Language')).toEqual({ id: 'test' } as any); expect(req.headers.get('Accept-Language')).toEqual('tr');
expect(req.headers.get('__tenant')).toEqual('test'); expect(req.headers.get('__tenant')).toEqual('Volosoft');
done(); done();
return handleRes$; return handleRes$;
}, },

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

@ -2,10 +2,13 @@ import { HttpClient } from '@angular/common/http';
import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/jest'; import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/jest';
import { Store } from '@ngxs/store'; import { Store } from '@ngxs/store';
import { of, ReplaySubject, timer } from 'rxjs'; import { of, ReplaySubject, timer } from 'rxjs';
import { SetLanguage } from '../actions';
import { ApplicationConfiguration } from '../models/application-configuration'; import { ApplicationConfiguration } from '../models/application-configuration';
import { Config } from '../models/config'; import { Config } from '../models/config';
import { ApplicationConfigurationService, ConfigStateService } from '../services'; import {
ApplicationConfigurationService,
ConfigStateService,
SessionStateService,
} from '../services';
import { ConfigState } from '../states'; import { ConfigState } from '../states';
export const CONFIG_STATE_DATA = ({ export const CONFIG_STATE_DATA = ({
@ -116,7 +119,11 @@ describe('ConfigState', () => {
spectator = createService(); spectator = createService();
store = spectator.inject(Store); store = spectator.inject(Store);
service = spectator.service; service = spectator.service;
state = new ConfigState(spectator.inject(HttpClient), store); state = new ConfigState(
spectator.inject(HttpClient),
store,
spectator.inject(SessionStateService),
);
}); });
describe('#getAll', () => { describe('#getAll', () => {
@ -268,8 +275,6 @@ describe('ConfigState', () => {
timer(0).subscribe(() => { timer(0).subscribe(() => {
expect(patchStateArg).toEqual(configuration); expect(patchStateArg).toEqual(configuration);
expect(dispatchArg instanceof SetLanguage).toBeTruthy();
expect(dispatchArg).toEqual({ payload: 'en', dispatchAppConfiguration: false });
done(); done();
}); });
}); });

6
npm/ng-packs/packages/core/src/lib/tests/initial-utils.spec.ts

@ -4,6 +4,7 @@ import { Store } from '@ngxs/store';
import { OAuthService } from 'angular-oauth2-oidc'; import { OAuthService } from 'angular-oauth2-oidc';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { GetAppConfiguration } from '../actions'; import { GetAppConfiguration } from '../actions';
import { SessionStateService } from '../services';
import * as AuthFlowStrategy from '../strategies/auth-flow.strategy'; import * as AuthFlowStrategy from '../strategies/auth-flow.strategy';
import { CORE_OPTIONS } from '../tokens/options.token'; import { CORE_OPTIONS } from '../tokens/options.token';
import { checkAccessToken, getInitialData, localeInitializer } from '../utils'; import { checkAccessToken, getInitialData, localeInitializer } from '../utils';
@ -79,9 +80,8 @@ describe('InitialUtils', () => {
test('should resolve registerLocale', async () => { test('should resolve registerLocale', async () => {
const injector = spectator.inject(Injector); const injector = spectator.inject(Injector);
const injectorSpy = jest.spyOn(injector, 'get'); const injectorSpy = jest.spyOn(injector, 'get');
const store = spectator.inject(Store); const sessionState = spectator.inject(SessionStateService);
store.selectSnapshot.andCallFake(selector => selector({ SessionState: { language: 'tr' } })); injectorSpy.mockReturnValueOnce(sessionState);
injectorSpy.mockReturnValueOnce(store);
injectorSpy.mockReturnValueOnce({ registerLocaleFn: () => Promise.resolve() }); injectorSpy.mockReturnValueOnce({ registerLocaleFn: () => Promise.resolve() });
expect(typeof localeInitializer(injector)).toBe('function'); expect(typeof localeInitializer(injector)).toBe('function');
expect(await localeInitializer(injector)()).toBe('resolved'); expect(await localeInitializer(injector)()).toBe('resolved');

36
npm/ng-packs/packages/core/src/lib/tests/localization.service.spec.ts

@ -2,12 +2,16 @@ import { CORE_OPTIONS } from '../tokens/options.token';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/jest'; import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/jest';
import { Actions, Store } from '@ngxs/store'; import { Actions, Store } from '@ngxs/store';
import { of, Subject } from 'rxjs'; import { BehaviorSubject, of, Subject } from 'rxjs';
import { LocalizationService } from '../services/localization.service'; import { LocalizationService } from '../services/localization.service';
import { SessionStateService } from '../services';
const shouldReuseRoute = () => true;
describe('LocalizationService', () => { describe('LocalizationService', () => {
let spectator: SpectatorService<LocalizationService>; let spectator: SpectatorService<LocalizationService>;
let store: SpyObject<Store>; let store: SpyObject<Store>;
let router: SpyObject<Router>;
let service: LocalizationService; let service: LocalizationService;
const createService = createServiceFactory({ const createService = createServiceFactory({
@ -26,16 +30,21 @@ describe('LocalizationService', () => {
beforeEach(() => { beforeEach(() => {
spectator = createService(); spectator = createService();
store = spectator.inject(Store); store = spectator.inject(Store);
store.dispatch.mockReturnValue(new BehaviorSubject('tr'));
router = spectator.inject(Router);
router.routeReuseStrategy = { shouldReuseRoute } as any;
service = spectator.service; service = spectator.service;
const sessionState = spectator.inject(SessionStateService);
sessionState.setLanguage('tr');
}); });
describe('#currentLang', () => { describe('#currentLang', () => {
it('should be tr', () => { it('should be tr', done => {
store.selectSnapshot.andCallFake((selector: (state: any, ...states: any[]) => string) => { setTimeout(() => {
return selector({ SessionState: { language: 'tr' } }); expect(service.currentLang).toBe('tr');
}); done();
}, 0);
expect(service.currentLang).toBe('tr');
}); });
}); });
@ -59,11 +68,6 @@ describe('LocalizationService', () => {
describe('#registerLocale', () => { describe('#registerLocale', () => {
it('should return registerLocale and then call setRouteReuse', () => { it('should return registerLocale and then call setRouteReuse', () => {
const router = spectator.inject(Router);
const shouldReuseRoute = () => true;
router.routeReuseStrategy = { shouldReuseRoute } as any;
router.navigateByUrl.andCallFake(url => { router.navigateByUrl.andCallFake(url => {
return new Promise(resolve => resolve({ catch: () => null })); return new Promise(resolve => resolve({ catch: () => null }));
}); });
@ -76,7 +80,13 @@ describe('LocalizationService', () => {
it('should throw an error message when service have an otherInstance', async () => { it('should throw an error message when service have an otherInstance', async () => {
try { try {
const instance = new LocalizationService(new Subject(), null, null, null, {} as any); const instance = new LocalizationService(
{ getLanguage: () => {} } as any,
null,
null,
null,
{} as any,
);
} catch (error) { } catch (error) {
expect((error as Error).message).toBe('LocalizationService should have only one instance.'); expect((error as Error).message).toBe('LocalizationService should have only one instance.');
} }

61
npm/ng-packs/packages/core/src/lib/tests/session-state.service.spec.ts

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

70
npm/ng-packs/packages/core/src/lib/tests/session.state.spec.ts

@ -1,70 +0,0 @@
import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest';
import { Session } from '../models/session';
import { LocalizationService, AuthService } from '../services';
import { SessionState } from '../states';
import { GetAppConfiguration } from '../actions/config.actions';
import { of, Subject } from 'rxjs';
import { Store, Actions } from '@ngxs/store';
import { OAuthService } from 'angular-oauth2-oidc';
export class DummyClass {}
export const SESSION_STATE_DATA = {
language: 'tr',
tenant: { id: 'd5692aef-2ac6-49cd-9f3e-394c0bd4f8b3', name: 'Test' },
} as Session.State;
describe('SessionState', () => {
let spectator: SpectatorService<DummyClass>;
let state: SessionState;
const createService = createServiceFactory({
service: DummyClass,
mocks: [LocalizationService, Store, Actions, OAuthService],
});
beforeEach(() => {
spectator = createService();
state = new SessionState(null, null, new Subject());
});
describe('#getLanguage', () => {
it('should return the current language', () => {
expect(SessionState.getLanguage(SESSION_STATE_DATA)).toEqual(SESSION_STATE_DATA.language);
});
});
describe('#getTenant', () => {
it('should return the tenant object', () => {
expect(SessionState.getTenant(SESSION_STATE_DATA)).toEqual(SESSION_STATE_DATA.tenant);
});
});
describe('#SetLanguage', () => {
it('should set the language and dispatch the GetAppConfiguration action', () => {
let patchedData;
let dispatchedData;
const patchState = jest.fn(data => (patchedData = data));
const dispatch = jest.fn(action => {
dispatchedData = action;
return of({});
});
state.setLanguage({ patchState, dispatch } as any, { payload: 'en' }).subscribe();
expect(patchedData).toEqual({ language: 'en' });
expect(dispatchedData instanceof GetAppConfiguration).toBeTruthy();
});
});
describe('#setTenantId', () => {
it('should set the tenant', () => {
let patchedData;
const patchState = jest.fn(data => (patchedData = data));
const testTenant = { id: '54ae02ba-9289-4c1b-8521-0ea437756288', name: 'Test Tenant' };
state.setTenant({ patchState } as any, { payload: testTenant });
expect(patchedData).toEqual({ tenant: testTenant });
});
});
});

18
npm/ng-packs/packages/core/src/lib/utils/initial-utils.ts

@ -4,8 +4,10 @@ import { Store } from '@ngxs/store';
import { OAuthService } from 'angular-oauth2-oidc'; import { OAuthService } from 'angular-oauth2-oidc';
import { tap } from 'rxjs/operators'; import { tap } from 'rxjs/operators';
import { GetAppConfiguration } from '../actions/config.actions'; import { GetAppConfiguration } from '../actions/config.actions';
import { ApplicationConfiguration } from '../models/application-configuration';
import { ABP } from '../models/common'; import { ABP } from '../models/common';
import { AuthService } from '../services/auth.service'; import { AuthService } from '../services/auth.service';
import { SessionStateService } from '../services/session-state.service';
import { ConfigState } from '../states/config.state'; import { ConfigState } from '../states/config.state';
import { clearOAuthStorage } from '../strategies/auth-flow.strategy'; import { clearOAuthStorage } from '../strategies/auth-flow.strategy';
import { CORE_OPTIONS } from '../tokens/options.token'; import { CORE_OPTIONS } from '../tokens/options.token';
@ -25,7 +27,17 @@ export function getInitialData(injector: Injector) {
return store return store
.dispatch(new GetAppConfiguration()) .dispatch(new GetAppConfiguration())
.pipe(tap(res => checkAccessToken(store, injector))) .pipe(
tap(() => checkAccessToken(store, injector)),
tap(() => {
const currentTenant = store.selectSnapshot(
ConfigState.getDeep('currentTenant'),
) as ApplicationConfiguration.CurrentTenant;
if (!currentTenant?.id) return;
injector.get(SessionStateService).setTenant(currentTenant);
}),
)
.toPromise(); .toPromise();
}; };
@ -41,10 +53,10 @@ export function checkAccessToken(store: Store, injector: Injector) {
export function localeInitializer(injector: Injector) { export function localeInitializer(injector: Injector) {
const fn = () => { const fn = () => {
const store: Store = injector.get(Store); const sessionState = injector.get(SessionStateService);
const { registerLocaleFn }: ABP.Root = injector.get(CORE_OPTIONS); const { registerLocaleFn }: ABP.Root = injector.get(CORE_OPTIONS);
const lang = store.selectSnapshot(state => state.SessionState.language) || 'en'; const lang = sessionState.getLanguage() || 'en';
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
registerLocaleFn(lang).then(module => { registerLocaleFn(lang).then(module => {

12
npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/languages.component.ts

@ -1,6 +1,6 @@
import { Component, OnInit, Input } from '@angular/core'; import { ApplicationConfiguration, ConfigState, SessionStateService } from '@abp/ng.core';
import { Store, Select } from '@ngxs/store'; import { Component, OnInit } from '@angular/core';
import { SetLanguage, ConfigState, ApplicationConfiguration, SessionState } from '@abp/ng.core'; import { Select } from '@ngxs/store';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import snq from 'snq'; import snq from 'snq';
@ -75,14 +75,14 @@ export class LanguagesComponent implements OnInit {
} }
get selectedLangCulture(): string { get selectedLangCulture(): string {
return this.store.selectSnapshot(SessionState.getLanguage); return this.sessionState.getLanguage();
} }
constructor(private store: Store) {} constructor(private sessionState: SessionStateService) {}
ngOnInit() {} ngOnInit() {}
onChangeLang(cultureName: string) { onChangeLang(cultureName: string) {
this.store.dispatch(new SetLanguage(cultureName)); this.sessionState.setLanguage(cultureName);
} }
} }

2
npm/ng-packs/packages/theme-shared/extensions/src/lib/utils/enum.util.ts

@ -49,7 +49,7 @@ export function createEnumOptions<T = any>(
} }
function createLocalizationStream(l10n: LocalizationService, mapTarget: any) { function createLocalizationStream(l10n: LocalizationService, mapTarget: any) {
return merge(of(null), l10n.languageChange).pipe(map(() => mapTarget)); return merge(of(null), l10n.languageChange$).pipe(map(() => mapTarget));
} }
function createEnumLocalizer( function createEnumLocalizer(

20
npm/ng-packs/packages/theme-shared/extensions/src/tests/enum.util.spec.ts

@ -1,10 +1,16 @@
import { LocalizationService } from '@abp/ng.core'; import { LocalizationService } from '@abp/ng.core';
import { Store } from '@ngxs/store'; import { Store } from '@ngxs/store';
import { Subject } from 'rxjs'; import { BehaviorSubject, Subject } from 'rxjs';
import { take } from 'rxjs/operators'; import { take } from 'rxjs/operators';
import { PropData } from '../lib/models/props'; import { PropData } from '../lib/models/props';
import { createEnum, createEnumOptions, createEnumValueResolver } from '../lib/utils/enum.util'; import { createEnum, createEnumOptions, createEnumValueResolver } from '../lib/utils/enum.util';
const mockSessionState = {
languageChange$: new BehaviorSubject('tr'),
getLanguage: () => 'tr',
onLanguageChange$: () => new BehaviorSubject('tr'),
} as any;
const fields = [ const fields = [
{ name: 'foo', value: 1 }, { name: 'foo', value: 1 },
{ name: 'bar', value: 2 }, { name: 'bar', value: 2 },
@ -38,7 +44,7 @@ describe('Enum Utils', () => {
describe('#createEnumValueResolver', () => { describe('#createEnumValueResolver', () => {
const service = new LocalizationService( const service = new LocalizationService(
new Subject().asObservable(), mockSessionState,
({ ({
selectSnapshot: () => ({ selectSnapshot: () => ({
values: { values: {
@ -78,9 +84,7 @@ describe('Enum Utils', () => {
const propData = new MockPropData({ extraProperties: { EnumProp: value } }); const propData = new MockPropData({ extraProperties: { EnumProp: value } });
propData.getInjected = () => service as any; propData.getInjected = () => service as any;
const resolved = await valueResolver(propData) const resolved = await valueResolver(propData).pipe(take(1)).toPromise();
.pipe(take(1))
.toPromise();
expect(resolved).toBe(expected); expect(resolved).toBe(expected);
}, },
@ -89,7 +93,7 @@ describe('Enum Utils', () => {
describe('#createEnumOptions', () => { describe('#createEnumOptions', () => {
const service = new LocalizationService( const service = new LocalizationService(
new Subject().asObservable(), mockSessionState,
({ ({
selectSnapshot: () => ({ selectSnapshot: () => ({
values: { values: {
@ -119,9 +123,7 @@ describe('Enum Utils', () => {
const propData = new MockPropData({}); const propData = new MockPropData({});
propData.getInjected = () => service as any; propData.getInjected = () => service as any;
const resolved = await options(propData) const resolved = await options(propData).pipe(take(1)).toPromise();
.pipe(take(1))
.toPromise();
expect(resolved).toEqual([ expect(resolved).toEqual([
{ key: 'Foo', value: 1 }, { key: 'Foo', value: 1 },

11
npm/ng-packs/packages/theme-shared/src/lib/handlers/lazy-style.handler.ts

@ -57,14 +57,9 @@ export class LazyStyleHandler {
const l10n = injector.get(LocalizationService); const l10n = injector.get(LocalizationService);
// will always listen, no need to unsubscribe // will always listen, no need to unsubscribe
l10n.languageChange l10n.languageChange$.pipe(startWith(l10n.currentLang)).subscribe(locale => {
.pipe( this.dir = getLocaleDirection(locale);
map(({ payload }) => payload), });
startWith(l10n.currentLang),
)
.subscribe(locale => {
this.dir = getLocaleDirection(locale);
});
} }
private setBodyDir(dir: LocaleDirection) { private setBodyDir(dir: LocaleDirection) {

6
npm/ng-packs/packages/theme-shared/src/lib/tests/lazy-style.handler.spec.ts

@ -4,7 +4,7 @@ import { EMPTY, of } from 'rxjs';
import { BOOTSTRAP } from '../constants/styles'; import { BOOTSTRAP } from '../constants/styles';
import { createLazyStyleHref, initLazyStyleHandler, LazyStyleHandler } from '../handlers'; import { createLazyStyleHref, initLazyStyleHandler, LazyStyleHandler } from '../handlers';
const languageChange = of({ payload: 'en' }); const languageChange$ = of({ payload: 'en' });
describe('LazyStyleHandler', () => { describe('LazyStyleHandler', () => {
let spectator: SpectatorService<LazyStyleHandler>; let spectator: SpectatorService<LazyStyleHandler>;
@ -16,7 +16,7 @@ describe('LazyStyleHandler', () => {
providers: [ providers: [
{ {
provide: LocalizationService, provide: LocalizationService,
useValue: { currentLang: 'en', languageChange }, useValue: { currentLang: 'en', languageChange$ },
}, },
], ],
}); });
@ -53,7 +53,7 @@ describe('initLazyStyleHandler', () => {
const generator = (function*() { const generator = (function*() {
yield undefined; // LAZY_STYLES yield undefined; // LAZY_STYLES
yield { loaded: new Map() }; // LazyLoadService yield { loaded: new Map() }; // LazyLoadService
yield { currentLang: 'en', languageChange: EMPTY }; // LocalizationService yield { currentLang: 'en', languageChange$: EMPTY }; // LocalizationService
})(); })();
const injector = { const injector = {

2
npm/ng-packs/yarn.lock

@ -102,7 +102,7 @@
chart.js "^2.9.3" chart.js "^2.9.3"
tslib "^2.0.0" tslib "^2.0.0"
"@abp/utils@^3.3.0", "@abp/utils@^3.3.0-rc.2": "@abp/utils@^3.3.0":
version "3.3.0" version "3.3.0"
resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-3.3.0.tgz#44ccacf4c415227e94981d907017de1c6f7225bb" resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-3.3.0.tgz#44ccacf4c415227e94981d907017de1c6f7225bb"
integrity sha512-/JIcygVJu/Ob1G5IicL2XgOVjIIePC2t3QzgEbwaq6NDGMHiOm2quyS8Lj7TfjBeUFTJhCFEbXJMq0lGQ+KMww== integrity sha512-/JIcygVJu/Ob1G5IicL2XgOVjIIePC2t3QzgEbwaq6NDGMHiOm2quyS8Lj7TfjBeUFTJhCFEbXJMq0lGQ+KMww==

Loading…
Cancel
Save