diff --git a/npm/ng-packs/packages/core/src/lib/services/localization.service.ts b/npm/ng-packs/packages/core/src/lib/services/localization.service.ts index b12b517156..0e89732e56 100644 --- a/npm/ng-packs/packages/core/src/lib/services/localization.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/localization.service.ts @@ -1,10 +1,11 @@ import { Injectable, NgZone, Optional, SkipSelf } from '@angular/core'; import { ActivatedRouteSnapshot, Router } from '@angular/router'; -import { Store } from '@ngxs/store'; +import { Store, Actions, ofActionSuccessful } from '@ngxs/store'; import { noop, Observable } from 'rxjs'; import { ConfigState } from '../states/config.state'; import { registerLocale } from '../utils/initial-utils'; import { Config } from '../models/config'; +import { SetLanguage } from '../actions/session.actions'; type ShouldReuseRoute = (future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot) => boolean; @@ -18,6 +19,7 @@ export class LocalizationService { } constructor( + private actions: Actions, private store: Store, private router: Router, private ngZone: NgZone, @@ -26,6 +28,14 @@ export class LocalizationService { otherInstance: LocalizationService, ) { if (otherInstance) throw new Error('LocalizationService should have only one instance.'); + + this.listenToSetLanguage(); + } + + private listenToSetLanguage() { + this.actions + .pipe(ofActionSuccessful(SetLanguage)) + .subscribe(({ payload }) => this.registerLocale(payload)); } setRouteReuse(reuse: ShouldReuseRoute) { diff --git a/npm/ng-packs/packages/core/src/lib/states/config.state.ts b/npm/ng-packs/packages/core/src/lib/states/config.state.ts index 01a181229f..af706586bb 100644 --- a/npm/ng-packs/packages/core/src/lib/states/config.state.ts +++ b/npm/ng-packs/packages/core/src/lib/states/config.state.ts @@ -1,7 +1,8 @@ +import { HttpClient, HttpErrorResponse } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Action, createSelector, Selector, State, StateContext, Store } from '@ngxs/store'; -import { of } from 'rxjs'; -import { switchMap, tap } from 'rxjs/operators'; +import { of, throwError } from 'rxjs'; +import { catchError, switchMap, tap } from 'rxjs/operators'; import snq from 'snq'; import { AddRoute, @@ -9,10 +10,11 @@ import { PatchRouteByName, SetEnvironment, } from '../actions/config.actions'; +import { RestOccurError } from '../actions/rest.actions'; import { SetLanguage } from '../actions/session.actions'; +import { ApplicationConfiguration } from '../models/application-configuration'; import { ABP } from '../models/common'; import { Config } from '../models/config'; -import { ApplicationConfigurationService } from '../services/application-configuration.service'; import { organizeRoutes } from '../utils/route-utils'; import { SessionState } from './session.state'; @@ -195,31 +197,37 @@ export class ConfigState { return selector; } - constructor( - private appConfigurationService: ApplicationConfigurationService, - private store: Store, - ) {} + constructor(private http: HttpClient, private store: Store) {} @Action(GetAppConfiguration) addData({ patchState, dispatch }: StateContext) { - return this.appConfigurationService.getConfiguration().pipe( - tap(configuration => - patchState({ - ...configuration, + const apiName = this.store.selectSnapshot(ConfigState.getDeep('environment.application.name')); + const api = this.store.selectSnapshot(ConfigState.getApiUrl(apiName)); + return this.http + .get(`${api}/api/abp/application-configuration`) + .pipe( + tap(configuration => + patchState({ + ...configuration, + }), + ), + switchMap(configuration => { + let defaultLang: string = + configuration.setting.values['Abp.Localization.DefaultLanguage']; + + if (defaultLang.includes(';')) { + defaultLang = defaultLang.split(';')[0]; + } + + return this.store.selectSnapshot(SessionState.getLanguage) + ? of(null) + : dispatch(new SetLanguage(defaultLang)); }), - ), - switchMap(configuration => { - let defaultLang: string = configuration.setting.values['Abp.Localization.DefaultLanguage']; - - if (defaultLang.includes(';')) { - defaultLang = defaultLang.split(';')[0]; - } - - return this.store.selectSnapshot(SessionState.getLanguage) - ? of(null) - : dispatch(new SetLanguage(defaultLang)); - }), - ); + catchError(err => { + dispatch(new RestOccurError(new HttpErrorResponse({ status: 0, error: err }))); + return throwError(err); + }), + ); } @Action(PatchRouteByName) diff --git a/npm/ng-packs/packages/core/src/lib/states/session.state.ts b/npm/ng-packs/packages/core/src/lib/states/session.state.ts index 9342bacd62..6acbf1d60d 100644 --- a/npm/ng-packs/packages/core/src/lib/states/session.state.ts +++ b/npm/ng-packs/packages/core/src/lib/states/session.state.ts @@ -1,26 +1,24 @@ +import { Injectable } from '@angular/core'; import { Action, + Actions, + ofActionSuccessful, Selector, State, StateContext, Store, - NgxsOnInit, - Actions, - ofActionSuccessful, } from '@ngxs/store'; -import { from, fromEvent } from 'rxjs'; -import { switchMap, take } from 'rxjs/operators'; +import { OAuthService } from 'angular-oauth2-oidc'; +import { fromEvent } from 'rxjs'; +import { take } from 'rxjs/operators'; import { GetAppConfiguration } from '../actions/config.actions'; import { - SetLanguage, - SetTenant, ModifyOpenedTabCount, + SetLanguage, SetRemember, + SetTenant, } from '../actions/session.actions'; import { ABP, Session } from '../models'; -import { LocalizationService } from '../services/localization.service'; -import { OAuthService } from 'angular-oauth2-oidc'; -import { Injectable } from '@angular/core'; @State({ name: 'SessionState', @@ -43,12 +41,7 @@ export class SessionState { return sessionDetail; } - constructor( - private localizationService: LocalizationService, - private oAuthService: OAuthService, - private store: Store, - private actions: Actions, - ) { + constructor(private oAuthService: OAuthService, private store: Store, private actions: Actions) { actions .pipe(ofActionSuccessful(GetAppConfiguration)) .pipe(take(1)) @@ -81,9 +74,7 @@ export class SessionState { language: payload, }); - return dispatch(new GetAppConfiguration()).pipe( - switchMap(() => from(this.localizationService.registerLocale(payload))), - ); + return dispatch(new GetAppConfiguration()); } @Action(SetTenant) diff --git a/npm/ng-packs/packages/core/src/lib/strategies/projection.strategy.ts b/npm/ng-packs/packages/core/src/lib/strategies/projection.strategy.ts index e7a62383a2..bc7c9201fd 100644 --- a/npm/ng-packs/packages/core/src/lib/strategies/projection.strategy.ts +++ b/npm/ng-packs/packages/core/src/lib/strategies/projection.strategy.ts @@ -95,7 +95,10 @@ export class TemplateProjectionStrategy> extends Proj } export const PROJECTION_STRATEGY = { - AppendComponentToBody>(component: T, context?: InferredInstanceOf) { + AppendComponentToBody>( + component: T, + context?: Partial>, + ) { return new RootComponentProjectionStrategy( component, context && CONTEXT_STRATEGY.Component(context), @@ -104,7 +107,7 @@ export const PROJECTION_STRATEGY = { AppendComponentToContainer>( component: T, containerRef: ViewContainerRef, - context?: InferredInstanceOf, + context?: Partial>, ) { return new ComponentProjectionStrategy( component, @@ -115,7 +118,7 @@ export const PROJECTION_STRATEGY = { AppendTemplateToContainer>( templateRef: T, containerRef: ViewContainerRef, - context?: InferredContextOf, + context?: Partial>, ) { return new TemplateProjectionStrategy( templateRef, @@ -126,7 +129,7 @@ export const PROJECTION_STRATEGY = { PrependComponentToContainer>( component: T, containerRef: ViewContainerRef, - context?: InferredInstanceOf, + context?: Partial>, ) { return new ComponentProjectionStrategy( component, @@ -137,7 +140,7 @@ export const PROJECTION_STRATEGY = { PrependTemplateToContainer>( templateRef: T, containerRef: ViewContainerRef, - context?: InferredContextOf, + context?: Partial>, ) { return new TemplateProjectionStrategy( templateRef, @@ -148,7 +151,7 @@ export const PROJECTION_STRATEGY = { ProjectComponentToContainer>( component: T, containerRef: ViewContainerRef, - context?: InferredInstanceOf, + context?: Partial>, ) { return new ComponentProjectionStrategy( component, @@ -159,7 +162,7 @@ export const PROJECTION_STRATEGY = { ProjectTemplateToContainer>( templateRef: T, containerRef: ViewContainerRef, - context?: InferredContextOf, + context?: Partial>, ) { return new TemplateProjectionStrategy( templateRef, diff --git a/npm/ng-packs/packages/core/src/lib/tests/config.state.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/config.state.spec.ts index 1ff9ad817e..ae98855913 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/config.state.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/config.state.spec.ts @@ -7,6 +7,7 @@ import { ABP } from '../models'; import { Config } from '../models/config'; import { ApplicationConfigurationService, ConfigStateService } from '../services'; import { ConfigState } from '../states'; +import { HttpClient } from '@angular/common/http'; export const CONFIG_STATE_DATA = { environment: { @@ -136,19 +137,17 @@ describe('ConfigState', () => { let store: SpyObject; let service: ConfigStateService; let state: ConfigState; - let appConfigService: SpyObject; const createService = createServiceFactory({ service: ConfigStateService, - mocks: [ApplicationConfigurationService, Store], + mocks: [ApplicationConfigurationService, Store, HttpClient], }); beforeEach(() => { spectator = createService(); store = spectator.get(Store); service = spectator.service; - appConfigService = spectator.get(ApplicationConfigurationService); - state = new ConfigState(spectator.get(ApplicationConfigurationService), store); + state = new ConfigState(spectator.get(HttpClient), store); }); describe('#getAll', () => { @@ -283,7 +282,7 @@ describe('ConfigState', () => { }); describe('#GetAppConfiguration', () => { - it('should call the getConfiguration of ApplicationConfigurationService and patch the state', done => { + it('should call the app-configuration API and patch the state', done => { let patchStateArg; let dispatchArg; @@ -299,7 +298,8 @@ describe('ConfigState', () => { dispatchArg = a; return of(a); }); - appConfigService.getConfiguration.andReturn(res$); + const httpClient = spectator.get(HttpClient); + httpClient.get.andReturn(res$); state.addData({ patchState, dispatch } as any).subscribe(); diff --git a/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts index 0a123b6a4f..e1a678a19f 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts @@ -7,6 +7,7 @@ import { eLayoutType } from '../enums'; import { ABP } from '../models'; import { ConfigState, ReplaceableComponentsState } from '../states'; import { ApplicationConfigurationService } from '../services'; +import { HttpClient } from '@angular/common/http'; @Component({ selector: 'abp-layout-application', @@ -92,7 +93,7 @@ describe('DynamicLayoutComponent', () => { component: RouterOutletComponent, stubsEnabled: false, declarations: [DummyComponent, DynamicLayoutComponent], - mocks: [ApplicationConfigurationService], + mocks: [ApplicationConfigurationService, HttpClient], imports: [ RouterModule, DummyLayoutModule, diff --git a/npm/ng-packs/packages/core/src/lib/tests/localization.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/localization.service.spec.ts index bfd8948318..5600e3aa39 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/localization.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/localization.service.spec.ts @@ -1,7 +1,7 @@ import { Router } from '@angular/router'; import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/jest'; -import { Store } from '@ngxs/store'; -import { Observable, of } from 'rxjs'; +import { Store, Actions } from '@ngxs/store'; +import { Observable, of, Subject } from 'rxjs'; import { LocalizationService } from '../services/localization.service'; describe('LocalizationService', () => { @@ -13,6 +13,7 @@ describe('LocalizationService', () => { service: LocalizationService, entryComponents: [], mocks: [Store, Router], + providers: [{ provide: Actions, useValue: new Subject() }], }); beforeEach(() => { @@ -74,7 +75,7 @@ describe('LocalizationService', () => { it('should throw an error message when service have an otherInstance', async () => { try { - const instance = new LocalizationService(null, null, null, {} as any); + const instance = new LocalizationService(new Subject(), null, null, null, {} as any); } catch (error) { expect((error as Error).message).toBe('LocalizationService should have only one instance.'); } diff --git a/npm/ng-packs/packages/core/src/lib/tests/session.state.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/session.state.spec.ts index cdf616e5d2..8a7953dd16 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/session.state.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/session.state.spec.ts @@ -25,7 +25,7 @@ describe('SessionState', () => { beforeEach(() => { spectator = createService(); - state = new SessionState(spectator.get(LocalizationService), null, null, new Subject()); + state = new SessionState(null, null, new Subject()); }); describe('#getLanguage', () => { @@ -49,13 +49,11 @@ describe('SessionState', () => { dispatchedData = action; return of({}); }); - const spy = jest.spyOn(spectator.get(LocalizationService), 'registerLocale'); state.setLanguage({ patchState, dispatch } as any, { payload: 'en' }).subscribe(); expect(patchedData).toEqual({ language: 'en' }); expect(dispatchedData instanceof GetAppConfiguration).toBeTruthy(); - expect(spy).toHaveBeenCalledWith('en'); }); }); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/confirmation/confirmation.component.html b/npm/ng-packs/packages/theme-shared/src/lib/components/confirmation/confirmation.component.html index a40d72adf4..8987cda2f9 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/confirmation/confirmation.component.html +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/confirmation/confirmation.component.html @@ -1,8 +1,8 @@ -
+
- +

diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/confirmation/confirmation.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/confirmation/confirmation.component.ts index a73382678a..13462612e6 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/confirmation/confirmation.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/confirmation/confirmation.component.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; +import { ReplaySubject } from 'rxjs'; import { Confirmation } from '../../models/confirmation'; -import { ConfirmationService } from '../../services/confirmation.service'; @Component({ selector: 'abp-confirmation', @@ -12,12 +12,16 @@ export class ConfirmationComponent { reject = Confirmation.Status.reject; dismiss = Confirmation.Status.dismiss; - visible = false; + confirmation$: ReplaySubject; - data: Confirmation.DialogData; + clear: (status: Confirmation.Status) => void; - get iconClass(): string { - switch (this.data.severity) { + close(status: Confirmation.Status) { + this.clear(status); + } + + getIconClass({ severity }: Confirmation.DialogData): string { + switch (severity) { case 'info': return 'fa-info-circle'; case 'success': @@ -30,15 +34,4 @@ export class ConfirmationComponent { return 'fa-question-circle'; } } - - constructor(private confirmationService: ConfirmationService) { - this.confirmationService.confirmation$.subscribe(confirmation => { - this.data = confirmation; - this.visible = !!confirmation; - }); - } - - close(status: Confirmation.Status) { - this.confirmationService.clear(status); - } } diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/toast-container/toast-container.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/toast-container/toast-container.component.ts index 124dcc5185..28d66123a9 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/toast-container/toast-container.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/toast-container/toast-container.component.ts @@ -1,7 +1,7 @@ import { Component, Input, OnInit } from '@angular/core'; -import { Toaster } from '../../models/toaster'; import { toastInOut } from '../../animations/toast.animations'; -import { ToasterService } from '../../services/toaster.service'; +import { Toaster } from '../../models/toaster'; +import { ReplaySubject } from 'rxjs'; @Component({ selector: 'abp-toast-container', @@ -10,6 +10,8 @@ import { ToasterService } from '../../services/toaster.service'; animations: [toastInOut], }) export class ToastContainerComponent implements OnInit { + toasts$: ReplaySubject; + toasts = [] as Toaster.Toast[]; @Input() @@ -27,10 +29,8 @@ export class ToastContainerComponent implements OnInit { @Input() toastKey: string; - constructor(private toastService: ToasterService) {} - ngOnInit() { - this.toastService.toasts$.subscribe(toasts => { + this.toasts$.subscribe(toasts => { this.toasts = this.toastKey ? toasts.filter(t => { return t.options && t.options.containerKey !== this.toastKey; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/services/confirmation.service.ts b/npm/ng-packs/packages/theme-shared/src/lib/services/confirmation.service.ts index bfd5b32230..2672a93a2a 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/services/confirmation.service.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/services/confirmation.service.ts @@ -1,9 +1,9 @@ -import { Injectable, ComponentRef } from '@angular/core'; -import { Confirmation } from '../models/confirmation'; -import { fromEvent, Observable, Subject, ReplaySubject } from 'rxjs'; -import { takeUntil, debounceTime, filter } from 'rxjs/operators'; import { Config, ContentProjectionService, PROJECTION_STRATEGY } from '@abp/ng.core'; +import { ComponentRef, Injectable } from '@angular/core'; +import { fromEvent, Observable, ReplaySubject, Subject } from 'rxjs'; +import { debounceTime, filter, takeUntil } from 'rxjs/operators'; import { ConfirmationComponent } from '../components/confirmation/confirmation.component'; +import { Confirmation } from '../models/confirmation'; @Injectable({ providedIn: 'root' }) export class ConfirmationService { @@ -12,14 +12,22 @@ export class ConfirmationService { private containerComponentRef: ComponentRef; + clear = (status: Confirmation.Status = Confirmation.Status.dismiss) => { + this.confirmation$.next(); + this.status$.next(status); + }; + constructor(private contentProjectionService: ContentProjectionService) {} private setContainer() { - setTimeout(() => { - this.containerComponentRef = this.contentProjectionService.projectContent( - PROJECTION_STRATEGY.AppendComponentToBody(ConfirmationComponent), - ); + this.containerComponentRef = this.contentProjectionService.projectContent( + PROJECTION_STRATEGY.AppendComponentToBody(ConfirmationComponent, { + confirmation$: this.confirmation$, + clear: this.clear, + }), + ); + setTimeout(() => { this.containerComponentRef.changeDetectorRef.detectChanges(); }, 0); } @@ -75,11 +83,6 @@ export class ConfirmationService { return this.status$; } - clear(status: Confirmation.Status = Confirmation.Status.dismiss) { - this.confirmation$.next(); - this.status$.next(status); - } - private listenToEscape() { fromEvent(document, 'keyup') .pipe( diff --git a/npm/ng-packs/packages/theme-shared/src/lib/services/toaster.service.ts b/npm/ng-packs/packages/theme-shared/src/lib/services/toaster.service.ts index 4d178c0ad2..295647309d 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/services/toaster.service.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/services/toaster.service.ts @@ -21,7 +21,7 @@ export class ToasterService { private setContainer() { this.containerComponentRef = this.contentProjectionService.projectContent( - PROJECTION_STRATEGY.AppendComponentToBody(ToastContainerComponent), + PROJECTION_STRATEGY.AppendComponentToBody(ToastContainerComponent, { toasts$: this.toasts$ }), ); this.containerComponentRef.changeDetectorRef.detectChanges(); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts b/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts index 5d53ed2124..8134a44be4 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts @@ -92,8 +92,6 @@ export function appendScript(injector: Injector) { ], }) export class ThemeSharedModule { - constructor(private errorHandler: ErrorHandler) {} - static forRoot(options = {} as RootParams): ModuleWithProviders { return { ngModule: ThemeSharedModule, @@ -104,6 +102,12 @@ export class ThemeSharedModule { deps: [THEME_SHARED_APPEND_CONTENT], useFactory: noop, }, + { + provide: APP_INITIALIZER, + multi: true, + deps: [ErrorHandler], + useFactory: noop, + }, { provide: HTTP_ERROR_CONFIG, useValue: options.httpErrorConfig }, { provide: 'HTTP_ERROR_CONFIG',