Merge pull request #3821 from abpframework/fix/circular-dependency

Fixed circular dependency problems in ng-packs
pull/3321/head
Mehmet Erim 5 years ago committed by GitHub
commit ecb0d42f08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

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

@ -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<Config.State>) {
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<ApplicationConfiguration.Response>(`${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)

@ -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<Session.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)

@ -95,7 +95,10 @@ export class TemplateProjectionStrategy<T extends TemplateRef<any>> extends Proj
}
export const PROJECTION_STRATEGY = {
AppendComponentToBody<T extends Type<unknown>>(component: T, context?: InferredInstanceOf<T>) {
AppendComponentToBody<T extends Type<unknown>>(
component: T,
context?: Partial<InferredInstanceOf<T>>,
) {
return new RootComponentProjectionStrategy<T>(
component,
context && CONTEXT_STRATEGY.Component(context),
@ -104,7 +107,7 @@ export const PROJECTION_STRATEGY = {
AppendComponentToContainer<T extends Type<unknown>>(
component: T,
containerRef: ViewContainerRef,
context?: InferredInstanceOf<T>,
context?: Partial<InferredInstanceOf<T>>,
) {
return new ComponentProjectionStrategy<T>(
component,
@ -115,7 +118,7 @@ export const PROJECTION_STRATEGY = {
AppendTemplateToContainer<T extends TemplateRef<unknown>>(
templateRef: T,
containerRef: ViewContainerRef,
context?: InferredContextOf<T>,
context?: Partial<InferredContextOf<T>>,
) {
return new TemplateProjectionStrategy<T>(
templateRef,
@ -126,7 +129,7 @@ export const PROJECTION_STRATEGY = {
PrependComponentToContainer<T extends Type<unknown>>(
component: T,
containerRef: ViewContainerRef,
context?: InferredInstanceOf<T>,
context?: Partial<InferredInstanceOf<T>>,
) {
return new ComponentProjectionStrategy<T>(
component,
@ -137,7 +140,7 @@ export const PROJECTION_STRATEGY = {
PrependTemplateToContainer<T extends TemplateRef<unknown>>(
templateRef: T,
containerRef: ViewContainerRef,
context?: InferredContextOf<T>,
context?: Partial<InferredContextOf<T>>,
) {
return new TemplateProjectionStrategy<T>(
templateRef,
@ -148,7 +151,7 @@ export const PROJECTION_STRATEGY = {
ProjectComponentToContainer<T extends Type<unknown>>(
component: T,
containerRef: ViewContainerRef,
context?: InferredInstanceOf<T>,
context?: Partial<InferredInstanceOf<T>>,
) {
return new ComponentProjectionStrategy<T>(
component,
@ -159,7 +162,7 @@ export const PROJECTION_STRATEGY = {
ProjectTemplateToContainer<T extends TemplateRef<unknown>>(
templateRef: T,
containerRef: ViewContainerRef,
context?: InferredContextOf<T>,
context?: Partial<InferredContextOf<T>>,
) {
return new TemplateProjectionStrategy<T>(
templateRef,

@ -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<Store>;
let service: ConfigStateService;
let state: ConfigState;
let appConfigService: SpyObject<ApplicationConfigurationService>;
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();

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

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

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

@ -1,8 +1,8 @@
<div class="confirmation" *ngIf="visible">
<div class="confirmation" *ngIf="confirmation$ | async as data">
<div class="confirmation-backdrop"></div>
<div class="confirmation-dialog">
<div class="icon-container" [ngClass]="data.severity" *ngIf="data.severity">
<i class="fa icon" [ngClass]="iconClass"></i>
<i class="fa icon" [ngClass]="getIconClass(data)"></i>
</div>
<div class="content">
<h1
@ -20,14 +20,14 @@
<button
id="cancel"
class="confirmation-button confirmation-button--reject"
[innerHTML]="data.options?.cancelText || 'AbpUi::Cancel'"
[innerHTML]="data.options?.cancelText || 'AbpUi::Cancel' | abpLocalization"
*ngIf="!data?.options?.hideCancelBtn"
(click)="close(reject)"
></button>
<button
id="confirm"
class="confirmation-button confirmation-button--approve"
[innerHTML]="data.options?.yesText || 'AbpUi::Yes'"
[innerHTML]="data.options?.yesText || 'AbpUi::Yes' | abpLocalization"
*ngIf="!data?.options?.hideYesBtn"
(click)="close(confirm)"
></button>

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

@ -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<Toaster.Toast[]>;
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;

@ -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<ConfirmationComponent>;
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(

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

@ -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',

Loading…
Cancel
Save