Browse Source

Merge branch 'dev' of https://github.com/abpframework/abp into dev

pull/6167/head
Halil İbrahim Kalkan 6 years ago
parent
commit
8605a73be2
  1. 52
      framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpSystemTextJsonSerializerProvider_Tests.cs
  2. 5
      npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.ts
  3. 5
      npm/ng-packs/packages/account/src/lib/components/register/register.component.ts
  4. 8
      npm/ng-packs/packages/account/src/lib/guards/manage-profile.guard.ts
  5. 14
      npm/ng-packs/packages/core/src/lib/actions/config.actions.ts
  6. 2
      npm/ng-packs/packages/core/src/lib/actions/index.ts
  7. 19
      npm/ng-packs/packages/core/src/lib/core.module.ts
  8. 11
      npm/ng-packs/packages/core/src/lib/handlers/oauth-configuration.handler.ts
  9. 4
      npm/ng-packs/packages/core/src/lib/models/common.ts
  10. 3
      npm/ng-packs/packages/core/src/lib/models/config.ts
  11. 40
      npm/ng-packs/packages/core/src/lib/models/environment.ts
  12. 2
      npm/ng-packs/packages/core/src/lib/models/index.ts
  13. 6
      npm/ng-packs/packages/core/src/lib/models/localization.ts
  14. 17
      npm/ng-packs/packages/core/src/lib/pipes/localization.pipe.ts
  15. 35
      npm/ng-packs/packages/core/src/lib/plugins/config.plugin.ts
  16. 1
      npm/ng-packs/packages/core/src/lib/plugins/index.ts
  17. 9
      npm/ng-packs/packages/core/src/lib/services/application-configuration.service.ts
  18. 23
      npm/ng-packs/packages/core/src/lib/services/auth.service.ts
  19. 122
      npm/ng-packs/packages/core/src/lib/services/config-state.service.ts
  20. 36
      npm/ng-packs/packages/core/src/lib/services/environment.service.ts
  21. 1
      npm/ng-packs/packages/core/src/lib/services/index.ts
  22. 106
      npm/ng-packs/packages/core/src/lib/services/localization.service.ts
  23. 13
      npm/ng-packs/packages/core/src/lib/services/permission.service.ts
  24. 2
      npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts
  25. 5
      npm/ng-packs/packages/core/src/lib/services/rest.service.ts
  26. 10
      npm/ng-packs/packages/core/src/lib/services/routes.service.ts
  27. 26
      npm/ng-packs/packages/core/src/lib/services/session-state.service.ts
  28. 58
      npm/ng-packs/packages/core/src/lib/states/config.state.ts
  29. 21
      npm/ng-packs/packages/core/src/lib/strategies/auth-flow.strategy.ts
  30. 28
      npm/ng-packs/packages/core/src/lib/utils/environment-utils.ts
  31. 27
      npm/ng-packs/packages/core/src/lib/utils/initial-utils.ts
  32. 25
      npm/ng-packs/packages/core/src/lib/utils/multi-tenancy-utils.ts
  33. 1
      npm/ng-packs/packages/core/src/public-api.ts
  34. 11
      npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.ts
  35. 13
      npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts
  36. 22
      npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts
  37. 7
      npm/ng-packs/packages/tenant-management/src/lib/components/tenants/tenants.component.ts
  38. 9
      npm/ng-packs/packages/theme-basic/src/lib/components/logo/logo.component.ts
  39. 14
      npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/current-user.component.ts
  40. 16
      npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/languages.component.ts
  41. 85
      npm/ng-packs/packages/theme-shared/extensions/src/lib/utils/state.util.ts
  42. 9
      npm/ng-packs/packages/theme-shared/src/lib/utils/date-parser-formatter.ts
  43. 18
      npm/ng-packs/packages/theme-shared/src/lib/utils/validation-utils.ts

52
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<FileWithNullableBoolean>(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<FileWithNullableBoolean>(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<FileWithNullableEnum>(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<FileWithNullableEnum>(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
{

5
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(
{

5
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]],
});
}

8
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;

14
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) {}
}

2
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';

19
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,

11
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 => {

4
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<Config.Environment>;
environment: Partial<Environment>;
registerLocaleFn: (locale: string) => Promise<any>;
skipGetAppConfiguration?: boolean;
sendNullsAsQueryParam?: boolean;

3
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 };

40
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<Environment>, remoteEnv: any) => Environment;
export interface RemoteEnv {
url: string;
mergeStrategy: 'deepmerge' | 'overwrite' | customMergeFn;
method?: string;
headers?: ABP.Dictionary<string>;
}

2
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';

6
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;

17
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]),
[],
),
);
}

35
npm/ng-packs/packages/core/src/lib/plugins/config.plugin.ts

@ -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);
}
}

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

@ -1 +0,0 @@
export * from './config.plugin';

9
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<ApplicationConfiguration.Response> {
const request: Rest.Request<null> = {

23
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<any> {
@ -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 || '/';

122
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<typeof ConfigState.getOne>) {
return this.store.selectSnapshot(ConfigState.getOne(...args));
getAll$(): Observable<ApplicationConfiguration.Response> {
return this.store.sliceState(state => state);
}
getDeep(...args: Parameters<typeof ConfigState.getDeep>) {
return this.store.selectSnapshot(ConfigState.getDeep(...args));
getAll(): ApplicationConfiguration.Response {
return this.store.state;
}
getApiUrl(...args: Parameters<typeof ConfigState.getApiUrl>) {
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<typeof ConfigState.getFeature>) {
return this.store.selectSnapshot(ConfigState.getFeature(...args));
getFeature(key: string) {
return this.store.state.features?.values?.[key];
}
getSetting(...args: Parameters<typeof ConfigState.getSetting>) {
return this.store.selectSnapshot(ConfigState.getSetting(...args));
getFeature$(key: string) {
return this.store.sliceState(state => state.features?.values?.[key]);
}
getSettings(...args: Parameters<typeof ConfigState.getSettings>) {
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<typeof ConfigState.getGrantedPolicy>) {
return this.store.selectSnapshot(ConfigState.getGrantedPolicy(...args));
getSetting$(key: string) {
return this.store.sliceState(state => state.setting?.values?.[key]);
}
getLocalization(...args: Parameters<typeof ConfigState.getLocalization>) {
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<typeof ConfigState.getLocalizationResource>) {
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<typeof SetEnvironment>) {
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;
}

36
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<Environment> {
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);
}
}

1
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';

106
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<string> {
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<string> {
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<string> {
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);
}

13
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) {

2
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<ReplaceableComponents.ReplaceableComponent[]>;
private readonly store: InternalStore<ReplaceableComponents.ReplaceableComponent[]>;
get replaceableComponents$(): Observable<ReplaceableComponents.ReplaceableComponent[]> {
return this.store.sliceState(state => state);

5
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<any> {

10
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<T extends object> {
@ -152,9 +152,9 @@ export abstract class AbstractNavTreeService<T extends ABP.Nav>
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);
}

26
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);
}
}

58
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<Config.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<Config.State>) {
@ -223,22 +247,7 @@ export class ConfigState {
return this.http
.get<ApplicationConfiguration.Response>(`${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<Config.State>, { environment }: SetEnvironment) {
return patchState({ environment });
setEnvironment(_, { environment }: SetEnvironment) {
return this.environmentService.setState(environment);
}
@Action(PatchConfigState)
setConfig({ patchState, getState }: StateContext<Config.State>, { state }: PatchConfigState) {
patchState({ ...getState(), ...state });
}
}

21
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<any> {
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))),
),
);
}

28
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<Config.Environment>) {
export function getRemoteEnv(injector: Injector, environment: Partial<Environment>) {
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<Config.Environment>(method, url, { headers })
.request<Environment>(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<Config.Environment>,
local: Partial<Environment>,
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);
}
}

27
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();
}
}

25
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);
}

1
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';

11
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();
}
});
}

13
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<GetIdentityUsersInput>,
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]));

22
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);

7
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<GetTenantsInput>,
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) {

9
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) {}
}

14
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<ApplicationConfiguration.CurrentUser>;
currentUser$: Observable<ApplicationConfiguration.CurrentUser> = 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() {}

16
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';
</div>
`,
})
export class LanguagesComponent implements OnInit {
export class LanguagesComponent {
get smallScreen(): boolean {
return window.innerWidth < 992;
}
@Select(ConfigState.getDeep('localization.languages'))
languages$: Observable<ApplicationConfiguration.Language[]>;
languages$: Observable<ApplicationConfiguration.Language[]> = this.configState.getDeep$(
'localization.languages',
);
get defaultLanguage$(): Observable<string> {
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);

85
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<ObjectExtensions.Item> {
return configState.getOne$('objectExtensions');
}
function selectLocalization(
configState: ConfigStateService,
): Observable<ApplicationConfiguration.Localization> {
return configState.getOne$('localization');
}
function selectEnums(
configState: ConfigStateService,
): Observable<Record<string, ObjectExtensions.Enum>> {
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<ObjectExtensions.Entities>(Boolean),
take(1),
);
}
export function mapEntitiesToContributors<T = any>(store: Store, resource: string) {
export function mapEntitiesToContributors<T = any>(
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);

9
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) {

18
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<string> = store.selectSnapshot(
ConfigState.getSettings('Identity.Password'),
);
const passwordRules: ABP.Dictionary<string> = configState.getSettings('Identity.Password');
return (passwordRules[`Abp.Identity.Password.${key}`] || '').toLowerCase();
};

Loading…
Cancel
Save