Browse Source

Merge pull request #2261 from abpframework/refactor/general

General refactor for Angular
pull/2267/head
Mehmet Erim 7 years ago
committed by GitHub
parent
commit
ff1788d258
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      npm/ng-packs/packages/core/src/lib/core.module.ts
  2. 67
      npm/ng-packs/packages/core/src/lib/directives/permission.directive.ts
  3. 12
      npm/ng-packs/packages/core/src/lib/guards/permission.guard.ts
  4. 39
      npm/ng-packs/packages/core/src/lib/tests/permission.directive.spec.ts
  5. 18
      npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts
  6. 13
      npm/ng-packs/packages/core/src/lib/utils/date-extensions.ts
  7. 8
      npm/ng-packs/packages/identity/src/lib/components/roles/roles.component.html
  8. 8
      npm/ng-packs/packages/identity/src/lib/components/users/users.component.html
  9. 10
      npm/ng-packs/packages/tenant-management/src/lib/components/tenants/tenants.component.html
  10. 8
      npm/ng-packs/packages/tenant-management/src/lib/components/tenants/tenants.component.ts
  11. 8
      npm/ng-packs/packages/theme-basic/src/lib/components/application-layout/application-layout.component.html
  12. 2
      npm/ng-packs/packages/theme-shared/src/lib/components/http-error-wrapper/http-error-wrapper.component.html
  13. 1
      npm/ng-packs/packages/theme-shared/src/lib/components/http-error-wrapper/http-error-wrapper.component.scss
  14. 21
      npm/ng-packs/packages/theme-shared/src/lib/components/http-error-wrapper/http-error-wrapper.component.ts
  15. 8
      npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts
  16. 8
      npm/ng-packs/packages/theme-shared/src/lib/tests/error.component.spec.ts
  17. 14
      npm/ng-packs/packages/theme-shared/src/lib/tests/error.handler.spec.ts
  18. 6
      npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts

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

@ -28,6 +28,7 @@ import { ConfigState } from './states/config.state';
import { ProfileState } from './states/profile.state';
import { SessionState } from './states/session.state';
import { getInitialData, localeInitializer } from './utils/initial-utils';
import './utils/date-extensions';
@NgModule({
imports: [

67
npm/ng-packs/packages/core/src/lib/directives/permission.directive.ts

@ -1,31 +1,68 @@
import { Directive, ElementRef, Input, OnDestroy, OnInit, Optional, Renderer2 } from '@angular/core';
import {
Directive,
ElementRef,
Input,
OnDestroy,
OnInit,
Renderer2,
ViewContainerRef,
TemplateRef,
Optional,
SimpleChanges,
OnChanges,
} from '@angular/core';
import { Store } from '@ngxs/store';
import { ConfigState } from '../states';
import { takeUntilDestroy } from '../utils';
import { Subscription } from 'rxjs';
@Directive({
selector: '[abpPermission]',
})
export class PermissionDirective implements OnInit, OnDestroy {
export class PermissionDirective implements OnInit, OnDestroy, OnChanges {
@Input('abpPermission') condition: string;
constructor(@Optional() private elRef: ElementRef, private renderer: Renderer2, private store: Store) {}
subscription: Subscription;
constructor(
private elRef: ElementRef,
private renderer: Renderer2,
private store: Store,
@Optional() private templateRef: TemplateRef<any>,
private vcRef: ViewContainerRef,
) {}
private check() {
if (this.subscription) {
this.subscription.unsubscribe();
}
this.subscription = this.store
.select(ConfigState.getGrantedPolicy(this.condition))
.pipe(takeUntilDestroy(this))
.subscribe(isGranted => {
if (this.templateRef && isGranted) {
this.vcRef.clear();
this.vcRef.createEmbeddedView(this.templateRef);
} else if (this.templateRef && !isGranted) {
this.vcRef.clear();
} else if (!isGranted && !this.templateRef) {
this.renderer.removeChild((this.elRef.nativeElement as HTMLElement).parentElement, this.elRef.nativeElement);
}
});
}
ngOnInit() {
if (this.condition) {
this.store
.select(ConfigState.getGrantedPolicy(this.condition))
.pipe(takeUntilDestroy(this))
.subscribe(isGranted => {
if (!isGranted) {
this.renderer.removeChild(
(this.elRef.nativeElement as HTMLElement).parentElement,
this.elRef.nativeElement,
);
}
});
if (this.templateRef && !this.condition) {
this.vcRef.createEmbeddedView(this.templateRef);
}
}
ngOnDestroy(): void {}
ngOnChanges({ condition }: SimpleChanges) {
if ((condition || { currentValue: null }).currentValue) {
this.check();
}
}
}

12
npm/ng-packs/packages/core/src/lib/guards/permission.guard.ts

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate } from '@angular/router';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
import { Store } from '@ngxs/store';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@ -13,8 +13,14 @@ import { ConfigState } from '../states';
export class PermissionGuard implements CanActivate {
constructor(private store: Store) {}
canActivate({ data }: ActivatedRouteSnapshot): Observable<boolean> {
const resource = snq(() => data.routes.requiredPolicy) || (data.requiredPolicy as string);
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
let resource = snq(() => route.data.routes.requiredPolicy) || snq(() => route.data.requiredPolicy as string);
if (!resource) {
resource = snq(
() => route.routeConfig.children.find(child => state.url.indexOf(child.path) > -1).data.requiredPolicy,
);
}
return this.store.select(ConfigState.getGrantedPolicy(resource)).pipe(
tap(access => {
if (!access) {

39
npm/ng-packs/packages/core/src/lib/tests/permission.directive.spec.ts

@ -43,4 +43,43 @@ describe('PermissionDirective', () => {
expect(spy.mock.calls).toHaveLength(0);
});
});
describe('structural', () => {
beforeEach(() => {
spectator = createDirective(
'<div id="test-element" *abpPermission="condition">Testing Permission Directive</div>',
{ hostProps: { condition: '' } },
);
directive = spectator.directive;
});
it('should be created', () => {
expect(directive).toBeTruthy();
});
it('should remove the element from DOM', () => {
expect(spectator.query('#test-element')).toBeTruthy();
expect(spectator.directive.subscription).toBeUndefined();
spectator.setHostInput({ condition: 'test' });
expect(spectator.directive.subscription).toBeTruthy();
grantedPolicy$.next(true);
expect(spectator.query('#test-element')).toBeTruthy();
grantedPolicy$.next(false);
expect(spectator.query('#test-element')).toBeFalsy();
grantedPolicy$.next(true);
grantedPolicy$.next(true);
expect(spectator.queryAll('#test-element')).toHaveLength(1);
});
describe('#subscription', () => {
it('should call the unsubscribe', () => {
const spy = jest.fn(() => {});
spectator.setHostInput({ condition: 'test' });
spectator.directive.subscription.unsubscribe = spy;
spectator.setHostInput({ condition: 'test2' });
expect(spy).toHaveBeenCalled();
});
});
});
});

18
npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts

@ -23,7 +23,7 @@ describe('PermissionGuard', () => {
it('should return true when the grantedPolicy is true', done => {
store.select.andReturn(of(true));
const spy = jest.spyOn(store, 'dispatch');
guard.canActivate({ data: { requiredPolicy: '' } } as any).subscribe(res => {
guard.canActivate({ data: { requiredPolicy: 'test' } } as any, null).subscribe(res => {
expect(res).toBe(true);
expect(spy.mock.calls).toHaveLength(0);
done();
@ -33,11 +33,25 @@ describe('PermissionGuard', () => {
it('should return false and dispatch RestOccurError when the grantedPolicy is false', done => {
store.select.andReturn(of(false));
const spy = jest.spyOn(store, 'dispatch');
guard.canActivate({ data: { requiredPolicy: '' } } as any).subscribe(res => {
guard.canActivate({ data: { requiredPolicy: 'test' } } as any, null).subscribe(res => {
expect(res).toBe(false);
expect(spy.mock.calls[0][0] instanceof RestOccurError).toBeTruthy();
expect((spy.mock.calls[0][0] as RestOccurError).payload).toEqual({ status: 403 });
done();
});
});
it('should find the requiredPolicy from child route', done => {
store.select.andReturn(of(false));
const spy = jest.spyOn(store, 'select');
guard
.canActivate(
{ data: {}, routeConfig: { children: [{ path: 'test', data: { requiredPolicy: 'TestPolicy' } }] } } as any,
{ url: 'test' } as any,
)
.subscribe(() => {
expect(spy.mock.calls[0][0]({ auth: { grantedPolicies: { TestPolicy: true } } })).toBe(true);
done();
});
});
});

13
npm/ng-packs/packages/core/src/lib/utils/date-extensions.ts

@ -0,0 +1,13 @@
export {};
declare global {
interface Date {
toLocalISOString(): string;
}
}
Date.prototype.toLocalISOString = function(this: Date): string {
const timezoneOffset = this.getTimezoneOffset();
return new Date(this.getTime() - timezoneOffset * 60000).toISOString();
};

8
npm/ng-packs/packages/identity/src/lib/components/roles/roles.component.html

@ -6,7 +6,7 @@
</div>
<div class="text-right col col-md-6">
<button
[abpPermission]="'AbpIdentity.Roles.Create'"
*abpPermission="'AbpIdentity.Roles.Create'"
id="create-role"
class="btn btn-primary"
type="button"
@ -73,18 +73,18 @@
<i class="fa fa-cog mr-1"></i>{{ 'AbpIdentity::Actions' | abpLocalization }}
</button>
<div ngbDropdownMenu>
<button [abpPermission]="'AbpIdentity.Roles.Update'" ngbDropdownItem (click)="edit(data.id)">
<button *abpPermission="'AbpIdentity.Roles.Update'" ngbDropdownItem (click)="edit(data.id)">
{{ 'AbpIdentity::Edit' | abpLocalization }}
</button>
<button
[abpPermission]="'AbpIdentity.Roles.ManagePermissions'"
*abpPermission="'AbpIdentity.Roles.ManagePermissions'"
ngbDropdownItem
(click)="providerKey = data.name; visiblePermissions = true"
>
{{ 'AbpIdentity::Permissions' | abpLocalization }}
</button>
<button
[abpPermission]="'AbpIdentity.Roles.Delete'"
*abpPermission="'AbpIdentity.Roles.Delete'"
ngbDropdownItem
(click)="delete(data.id, data.name)"
>

8
npm/ng-packs/packages/identity/src/lib/components/users/users.component.html

@ -6,7 +6,7 @@
</div>
<div class="text-right col col-md-6">
<button
[abpPermission]="'AbpIdentity.Users.Create'"
*abpPermission="'AbpIdentity.Users.Create'"
id="create-role"
class="btn btn-primary"
type="button"
@ -86,18 +86,18 @@
<i class="fa fa-cog mr-1"></i>{{ 'AbpIdentity::Actions' | abpLocalization }}
</button>
<div ngbDropdownMenu>
<button [abpPermission]="'AbpIdentity.Users.Update'" ngbDropdownItem (click)="edit(data.id)">
<button *abpPermission="'AbpIdentity.Users.Update'" ngbDropdownItem (click)="edit(data.id)">
{{ 'AbpIdentity::Edit' | abpLocalization }}
</button>
<button
[abpPermission]="'AbpIdentity.Users.ManagePermissions'"
*abpPermission="'AbpIdentity.Users.ManagePermissions'"
ngbDropdownItem
(click)="providerKey = data.id; visiblePermissions = true"
>
{{ 'AbpIdentity::Permissions' | abpLocalization }}
</button>
<button
[abpPermission]="'AbpIdentity.Users.Delete'"
*abpPermission="'AbpIdentity.Users.Delete'"
ngbDropdownItem
(click)="delete(data.id, data.userName)"
>

10
npm/ng-packs/packages/tenant-management/src/lib/components/tenants/tenants.component.html

@ -6,7 +6,7 @@
</div>
<div class="text-right col col-md-6">
<button
[abpPermission]="'AbpTenantManagement.Tenants.Create'"
*abpPermission="'AbpTenantManagement.Tenants.Create'"
id="create-tenants"
class="btn btn-primary"
type="button"
@ -79,28 +79,28 @@
</button>
<div ngbDropdownMenu>
<button
[abpPermission]="'AbpTenantManagement.Tenants.Update'"
*abpPermission="'AbpTenantManagement.Tenants.Update'"
ngbDropdownItem
(click)="editTenant(data.id)"
>
{{ 'AbpTenantManagement::Edit' | abpLocalization }}
</button>
<button
[abpPermission]="'AbpTenantManagement.Tenants.ManageConnectionStrings'"
*abpPermission="'AbpTenantManagement.Tenants.ManageConnectionStrings'"
ngbDropdownItem
(click)="onEditConnectionString(data.id)"
>
{{ 'AbpTenantManagement::Permission:ManageConnectionStrings' | abpLocalization }}
</button>
<button
[abpPermission]="'AbpTenantManagement.Tenants.ManageFeatures'"
*abpPermission="'AbpTenantManagement.Tenants.ManageFeatures'"
ngbDropdownItem
(click)="providerKey = data.id; visibleFeatures = true"
>
{{ 'AbpTenantManagement::Permission:ManageFeatures' | abpLocalization }}
</button>
<button
[abpPermission]="'AbpTenantManagement.Tenants.Delete'"
*abpPermission="'AbpTenantManagement.Tenants.Delete'"
ngbDropdownItem
(click)="delete(data.id, data.name)"
>

8
npm/ng-packs/packages/tenant-management/src/lib/components/tenants/tenants.component.ts

@ -77,9 +77,13 @@ export class TenantsComponent implements OnInit {
get isDisabledSaveButton(): boolean {
if (!this.selectedModalContent) return false;
if (this.selectedModalContent.type === 'saveConnStr' && this.defaultConnectionStringForm.invalid) {
if (
this.selectedModalContent.type === 'saveConnStr' &&
this.defaultConnectionStringForm &&
this.defaultConnectionStringForm.invalid
) {
return true;
} else if (this.selectedModalContent.type === 'saveTenant' && this.tenantForm.invalid) {
} else if (this.selectedModalContent.type === 'saveTenant' && this.tenantForm && this.tenantForm.invalid) {
return true;
} else {
return false;

8
npm/ng-packs/packages/theme-basic/src/lib/components/application-layout/application-layout.component.html

@ -32,7 +32,7 @@
</ng-container>
<ng-template #defaultLink let-route>
<li class="nav-item" [abpPermission]="route.requiredPolicy">
<li class="nav-item" *abpPermission="route.requiredPolicy">
<a class="nav-link" [routerLink]="[route.url]"
><i *ngIf="route.iconClass" [ngClass]="route.iconClass"></i> {{ route.name | abpLocalization }}</a
>
@ -42,7 +42,7 @@
<ng-template #dropdownLink let-route>
<li
#navbarRootDropdown
[abpPermission]="route.requiredPolicy"
*abpPermission="route.requiredPolicy"
[abpVisibility]="routeContainer"
class="nav-item dropdown"
display="static"
@ -86,7 +86,7 @@
</ng-template>
<ng-template #defaultChild let-child>
<div class="dropdown-submenu" [abpPermission]="child.requiredPolicy">
<div class="dropdown-submenu" *abpPermission="child.requiredPolicy">
<a class="dropdown-item" [routerLink]="[child.url]">
<i *ngIf="child.iconClass" [ngClass]="child.iconClass"></i>
{{ child.name | abpLocalization }}</a
@ -103,7 +103,7 @@
[display]="isDropdownChildDynamic ? 'dynamic' : 'static'"
placement="right-top"
[autoClose]="true"
[abpPermission]="child.requiredPolicy"
*abpPermission="child.requiredPolicy"
(openChange)="openChange($event, childrenContainer)"
>
<div ngbDropdownToggle [class.dropdown-toggle]="false">

2
npm/ng-packs/packages/theme-shared/src/lib/components/error/error.component.html → npm/ng-packs/packages/theme-shared/src/lib/components/http-error-wrapper/http-error-wrapper.component.html

@ -1,4 +1,4 @@
<div #container id="abp-error" class="error">
<div #container id="abp-http-error-container" class="error" [style.backgroundColor]="backgroundColor">
<button *ngIf="!hideCloseIcon" id="abp-close-button" type="button" class="close mr-2" (click)="destroy()">
<span aria-hidden="true">&times;</span>
</button>

1
npm/ng-packs/packages/theme-shared/src/lib/components/error/error.component.scss → npm/ng-packs/packages/theme-shared/src/lib/components/http-error-wrapper/http-error-wrapper.component.scss

@ -1,7 +1,6 @@
.error {
position: fixed;
top: 0;
background-color: #fff;
width: 100vw;
height: 100vh;
z-index: 999999;

21
npm/ng-packs/packages/theme-shared/src/lib/components/error/error.component.ts → npm/ng-packs/packages/theme-shared/src/lib/components/http-error-wrapper/http-error-wrapper.component.ts

@ -1,25 +1,27 @@
import { Config, takeUntilDestroy } from '@abp/ng.core';
import {
AfterViewInit,
ApplicationRef,
Component,
ComponentFactoryResolver,
ElementRef,
EmbeddedViewRef,
Injector,
OnDestroy,
OnInit,
Type,
ViewChild,
ApplicationRef,
Injector,
} from '@angular/core';
import { fromEvent, Subject } from 'rxjs';
import { debounceTime, filter } from 'rxjs/operators';
import snq from 'snq';
@Component({
selector: 'abp-error',
templateUrl: './error.component.html',
styleUrls: ['error.component.scss'],
selector: 'abp-http-error-wrapper',
templateUrl: './http-error-wrapper.component.html',
styleUrls: ['http-error-wrapper.component.scss'],
})
export class ErrorComponent implements AfterViewInit, OnDestroy {
export class HttpErrorWrapperComponent implements AfterViewInit, OnDestroy, OnInit {
appRef: ApplicationRef;
cfRes: ComponentFactoryResolver;
@ -38,6 +40,8 @@ export class ErrorComponent implements AfterViewInit, OnDestroy {
hideCloseIcon = false;
backgroundColor: string;
@ViewChild('container', { static: false })
containerRef: ElementRef<HTMLDivElement>;
@ -45,6 +49,11 @@ export class ErrorComponent implements AfterViewInit, OnDestroy {
return this.status ? `[${this.status}]` : '';
}
ngOnInit() {
this.backgroundColor =
snq(() => window.getComputedStyle(document.body).getPropertyValue('background-color')) || '#fff';
}
ngAfterViewInit() {
if (this.customComponent) {
const customComponentRef = this.cfRes.resolveComponentFactory(this.customComponent).create(this.injector);

8
npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts

@ -15,7 +15,7 @@ import { Navigate, RouterError, RouterState, RouterDataResolved } from '@ngxs/ro
import { Actions, ofActionSuccessful, Store } from '@ngxs/store';
import { Observable, Subject } from 'rxjs';
import snq from 'snq';
import { ErrorComponent } from '../components/error/error.component';
import { HttpErrorWrapperComponent } from '../components/http-error-wrapper/http-error-wrapper.component';
import { HttpErrorConfig, ErrorScreenErrorCodes } from '../models/common';
import { Toaster } from '../models/toaster';
import { ConfirmationService } from '../services/confirmation.service';
@ -45,7 +45,7 @@ export const DEFAULT_ERROR_MESSAGES = {
@Injectable({ providedIn: 'root' })
export class ErrorHandler {
componentRef: ComponentRef<ErrorComponent>;
componentRef: ComponentRef<HttpErrorWrapperComponent>;
constructor(
private actions: Actions,
@ -196,11 +196,11 @@ export class ErrorHandler {
);
}
createErrorComponent(instance: Partial<ErrorComponent>) {
createErrorComponent(instance: Partial<HttpErrorWrapperComponent>) {
const renderer = this.rendererFactory.createRenderer(null, null);
const host = renderer.selectRootElement(document.body, true);
this.componentRef = this.cfRes.resolveComponentFactory(ErrorComponent).create(this.injector);
this.componentRef = this.cfRes.resolveComponentFactory(HttpErrorWrapperComponent).create(this.injector);
for (const key in this.componentRef.instance) {
if (this.componentRef.instance.hasOwnProperty(key)) {

8
npm/ng-packs/packages/theme-shared/src/lib/tests/error.component.spec.ts

@ -1,14 +1,14 @@
import { SpectatorHost, createHostFactory } from '@ngneat/spectator/jest';
import { ErrorComponent } from '../components/error/error.component';
import { HttpErrorWrapperComponent } from '../components/http-error-wrapper/http-error-wrapper.component';
import { LocalizationPipe } from '@abp/ng.core';
import { Store } from '@ngxs/store';
import { Renderer2, ElementRef } from '@angular/core';
import { Subject } from 'rxjs';
describe('ErrorComponent', () => {
let spectator: SpectatorHost<ErrorComponent>;
let spectator: SpectatorHost<HttpErrorWrapperComponent>;
const createHost = createHostFactory({
component: ErrorComponent,
component: HttpErrorWrapperComponent,
declarations: [LocalizationPipe],
mocks: [Store],
providers: [
@ -18,7 +18,7 @@ describe('ErrorComponent', () => {
});
beforeEach(() => {
spectator = createHost('<abp-error></abp-error>');
spectator = createHost('<abp-http-error-wrapper></abp-http-error-wrapper>');
spectator.component.destroy$ = new Subject();
});

14
npm/ng-packs/packages/theme-shared/src/lib/tests/error.handler.spec.ts

@ -22,14 +22,17 @@ describe('ErrorHandler', () => {
component: DummyComponent,
imports: [CoreModule, ThemeSharedModule.forRoot(), NgxsModule.forRoot([])],
stubsEnabled: false,
routes: [{ path: '', component: DummyComponent }, { path: 'account/login', component: RouterOutletComponent }],
routes: [
{ path: '', component: DummyComponent },
{ path: 'account/login', component: RouterOutletComponent },
],
});
beforeEach(() => {
spectator = createComponent();
store = spectator.get(Store);
const abpError = document.querySelector('abp-error');
const abpError = document.querySelector('abp-http-error-wrapper');
if (abpError) document.body.removeChild(abpError);
});
@ -133,14 +136,17 @@ describe('ErrorHandler with custom error component', () => {
ErrorModule,
],
stubsEnabled: false,
routes: [{ path: '', component: DummyComponent }, { path: 'account/login', component: RouterOutletComponent }],
routes: [
{ path: '', component: DummyComponent },
{ path: 'account/login', component: RouterOutletComponent },
],
});
beforeEach(() => {
spectator = createComponent();
store = spectator.get(Store);
const abpError = document.querySelector('abp-error');
const abpError = document.querySelector('abp-http-error-wrapper');
if (abpError) document.body.removeChild(abpError);
});

6
npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts

@ -8,7 +8,7 @@ import { BreadcrumbComponent } from './components/breadcrumb/breadcrumb.componen
import { ButtonComponent } from './components/button/button.component';
import { ChartComponent } from './components/chart/chart.component';
import { ConfirmationComponent } from './components/confirmation/confirmation.component';
import { ErrorComponent } from './components/error/error.component';
import { HttpErrorWrapperComponent } from './components/http-error-wrapper/http-error-wrapper.component';
import { LoaderBarComponent } from './components/loader-bar/loader-bar.component';
import { ModalComponent } from './components/modal/modal.component';
import { SortOrderIconComponent } from './components/sort-order-icon/sort-order-icon.component';
@ -42,7 +42,7 @@ export function appendScript(injector: Injector) {
ButtonComponent,
ChartComponent,
ConfirmationComponent,
ErrorComponent,
HttpErrorWrapperComponent,
LoaderBarComponent,
ModalComponent,
TableEmptyMessageComponent,
@ -63,7 +63,7 @@ export function appendScript(injector: Injector) {
TableSortDirective,
],
providers: [DatePipe],
entryComponents: [ErrorComponent],
entryComponents: [HttpErrorWrapperComponent],
})
export class ThemeSharedModule {
constructor(private errorHandler: ErrorHandler) {}

Loading…
Cancel
Save