diff --git a/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/component/luigi.preload.component.html b/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/component/luigi.preload.component.html new file mode 100644 index 0000000000..68ea9c6a7a --- /dev/null +++ b/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/component/luigi.preload.component.html @@ -0,0 +1,24 @@ +

NG Luigi Library Demo Component

+ +

+ This is a demo component; in Luigi it is a + virtual tree. +
+ If you want to configure an Angular Component to be called in virtual tree, + you must use this route configuration: +
+
+ {{ routeExampleVirtual }} +

+
+ +

+ If you want to configure an Angular Component to be just created once (like a + singleton( you can use this configuration: +
+
+ {{ routeExampleReuse }} +

diff --git a/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/preload.component.spec.ts b/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/component/luigi.preload.component.spec.ts similarity index 55% rename from client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/preload.component.spec.ts rename to client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/component/luigi.preload.component.spec.ts index f9440ce7be..bee66f0f25 100644 --- a/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/preload.component.spec.ts +++ b/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/component/luigi.preload.component.spec.ts @@ -1,20 +1,19 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { PreloadComponent } from './preload.component'; +import { LuigiPreloadComponent } from './luigi.preload.component'; describe('ClientSupportAngularComponent', () => { - let component: PreloadComponent; - let fixture: ComponentFixture; + let component: LuigiPreloadComponent; + let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ PreloadComponent ] - }) - .compileComponents(); + declarations: [LuigiPreloadComponent] + }).compileComponents(); }); beforeEach(() => { - fixture = TestBed.createComponent(PreloadComponent); + fixture = TestBed.createComponent(LuigiPreloadComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/component/luigi.preload.component.ts b/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/component/luigi.preload.component.ts new file mode 100644 index 0000000000..a1ecf62d92 --- /dev/null +++ b/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/component/luigi.preload.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'lib-client-support-angular', + templateUrl: './luigi.preload.component.html', + styles: [] +}) +export class LuigiPreloadComponent implements OnInit { + constructor() {} + routeExampleVirtual: string = + " {path: 'ng-luigi-demo', component: NgLuigiDemoComponent, data: {fromVirtualTreeRoot: true}}"; + routeExampleReuse: string = + " {path: 'ng-luigi-demo', component: NgLuigiDemoComponent, data: {reuse: true}}"; + ngOnInit(): void {} +} diff --git a/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/luigi-auto-routing.service.ts b/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/luigi-auto-routing.service.ts deleted file mode 100644 index 3650de743e..0000000000 --- a/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/luigi-auto-routing.service.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { Injectable, OnDestroy } from '@angular/core'; -import { Subscription } from 'rxjs'; -import { Router, NavigationStart, RouterEvent } from '@angular/router'; -import { linkManager } from '@luigi-project/client'; -import { filter } from 'rxjs/operators'; - -export interface IRouteMappingEntry { - path: string; - luigiRoute: string; -} - -export interface IRoutingConfig { - useVirtualTree?: boolean; - routeMapping: IRouteMappingEntry[]; -} - -@Injectable({ - providedIn: 'root', -}) -export class LuigiAutoRoutingService implements OnDestroy { - private subscription: Subscription = new Subscription(); - private routingConfig: IRoutingConfig = null as unknown as IRoutingConfig; - private routeMap: Map = new Map(); - - constructor(private router: Router) { - this.router.events - .pipe( - filter((ev): ev is RouterEvent => ev instanceof NavigationStart), - filter((ev: NavigationStart) => ev.url?.length > 0), - filter(() => !(history.state && history.state.luigiInduced)) - ) - .subscribe((ev) => { - const route = this.routeMap.get(ev.url); - if (route) { - linkManager().withoutSync().navigate(route.luigiRoute); - } else if (this.routingConfig?.useVirtualTree) { - linkManager() - .fromVirtualTreeRoot() - .withoutSync() - .navigate(ev.url); - } - }); - } - - public setConfig(routingConfig: IRoutingConfig): void { - this.routingConfig = routingConfig; - this.routeMap.clear(); - if (routingConfig.routeMapping) { - this.routeMap = new Map(routingConfig.routeMapping.map((e: IRouteMappingEntry, i) => [e.path, e])); - routingConfig.routeMapping.forEach(entry => { - this.routeMap.set(entry.path, entry); - }); - } - } - - ngOnDestroy(): void { - this.subscription.unsubscribe(); - } -} diff --git a/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/luigi-context.service.ts b/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/luigi-context.service.ts deleted file mode 100644 index b82ed93b91..0000000000 --- a/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/luigi-context.service.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Injectable } from '@angular/core'; -import { ReplaySubject, Observable } from 'rxjs'; -import { Context, addInitListener, addContextUpdateListener } from '@luigi-project/client'; - -export type ILuigiContextTypes = 'init' | 'update'; -export interface IContextMessage { - contextType: ILuigiContextTypes; // will be init or update - context: Context; -} - -@Injectable({ - providedIn: 'root' -}) -export class LuigiContextService { - private static currentContext: IContextMessage = null as unknown as IContextMessage; - private subject: ReplaySubject = new ReplaySubject(1); - - - constructor() { - addInitListener(initialContext => { - this.setContext({ - contextType: 'init', - context: initialContext - }); - }); - addContextUpdateListener(updatedContext => { - this.setContext({ - contextType: 'update', - context: updatedContext - }); - }); - } - - /** - * Set current context - */ - private setContext(obj: IContextMessage): void { - LuigiContextService.currentContext = obj; - this.subject.next(obj); - } - - /** - * Listen to context changes - * Receives current value, even if the event was already dispatched earlier. - */ - public contextObservable(): Observable { - return this.subject.asObservable(); - } - - /** - * Get latest context object retrieved from luigi core application or none, if not yet set. - */ - public getCurrentContext(): Context { - return LuigiContextService.currentContext && LuigiContextService.currentContext.context; - } -} diff --git a/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/luigi.angular.support.module.ts b/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/luigi.angular.support.module.ts new file mode 100644 index 0000000000..f8e51b842c --- /dev/null +++ b/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/luigi.angular.support.module.ts @@ -0,0 +1,50 @@ +import { NgModule } from '@angular/core'; +import { RouteReuseStrategy, RouterModule, Routes } from '@angular/router'; +import { LuigiReuseStrategy } from './route/luigiReuseStrategy'; +import { LuigiPreloadComponent } from './component/luigi.preload.component'; +import { LuigiContextService } from './service/luigi-context-service'; +import { LuigiContextServiceImpl } from './service/luigi-context.service.impl'; +import { LuigiAutoRoutingService } from './service/luigi-auto-routing.service'; + +export const staticRoutes: Routes = [ + /** here an example if you want to specify that this component is a virtualThree element in Luigi Core navigation*/ + { + path: 'luigi-client-support-preload', + component: LuigiPreloadComponent, + data: { fromVirtualTreeRoot: true } + }, + /** here an example if you want to specify that this component it is a luigi component and u want to change the navigation in Luigi core*/ + { + path: 'luigi-client-support-preload', + component: LuigiPreloadComponent, + data: { luigiRoute: '/home/reload' } + }, + /** here an example if you want to reuse the component and not recreating every time you navigate to it (a singleton Component) */ + { + path: 'luigi-client-support-preload=component', + component: LuigiPreloadComponent, + data: { reuse: true } + } +]; + +@NgModule({ + declarations: [LuigiPreloadComponent], + imports: [RouterModule.forChild(staticRoutes)], + providers: [ + { + provide: LuigiContextService, + useClass: LuigiContextServiceImpl + }, + { + provide: RouteReuseStrategy, + useClass: LuigiReuseStrategy + } + ], + exports: [LuigiPreloadComponent] +}) +export class LuigiAngularSupportModule { + constructor( + navigation: LuigiAutoRoutingService, + context: LuigiContextService + ) {} +} diff --git a/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/preload.component.ts b/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/preload.component.ts deleted file mode 100644 index 27a7c87f0d..0000000000 --- a/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/preload.component.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Component, OnInit } from '@angular/core'; - -@Component({ - selector: 'lib-client-support-angular', - template: ` - - - `, - styles: [ - ] -}) -export class PreloadComponent implements OnInit { - - constructor() { } - - ngOnInit(): void { - } - -} diff --git a/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/route/luigi-activated-route-snapshot-helper.spec.ts b/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/route/luigi-activated-route-snapshot-helper.spec.ts new file mode 100644 index 0000000000..48396d3072 --- /dev/null +++ b/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/route/luigi-activated-route-snapshot-helper.spec.ts @@ -0,0 +1,7 @@ +import { LuigiActivatedRouteSnapshotHelper } from './luigi-activated-route-snapshot-helper'; + +describe('NgLuigiActivatedRouteSnapshotService', () => { + it('should create an instance', () => { + expect(new LuigiActivatedRouteSnapshotHelper()).toBeTruthy(); + }); +}); diff --git a/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/route/luigi-activated-route-snapshot-helper.ts b/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/route/luigi-activated-route-snapshot-helper.ts new file mode 100644 index 0000000000..6f9cf8c8c4 --- /dev/null +++ b/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/route/luigi-activated-route-snapshot-helper.ts @@ -0,0 +1,13 @@ +import { ActivatedRouteSnapshot } from '@angular/router'; + +export class LuigiActivatedRouteSnapshotHelper { + private static _current: ActivatedRouteSnapshot; + + static getCurrent(): ActivatedRouteSnapshot { + return this._current; + } + + static setCurrent(current: ActivatedRouteSnapshot) { + this._current = current; + } +} diff --git a/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/route/luigiReuseStrategy.ts b/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/route/luigiReuseStrategy.ts new file mode 100644 index 0000000000..da5dab63b0 --- /dev/null +++ b/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/route/luigiReuseStrategy.ts @@ -0,0 +1,69 @@ +import { + RouteReuseStrategy, + ActivatedRouteSnapshot, + DetachedRouteHandle +} from '@angular/router'; +import { LuigiActivatedRouteSnapshotHelper } from './luigi-activated-route-snapshot-helper'; + +export class LuigiReuseStrategy implements RouteReuseStrategy { + private handlers: { [key: string]: DetachedRouteHandle } = {}; + + shouldDetach(route: ActivatedRouteSnapshot): boolean { + if (!route.routeConfig || route.routeConfig.loadChildren) { + return false; + } + let shouldReuse = false; + console.debug('checking if this route should be re used or not', route); + if (route.routeConfig.data) { + route.routeConfig.data.reuse + ? (shouldReuse = true) + : (shouldReuse = false); + } + + return shouldReuse; + } + + store(route: ActivatedRouteSnapshot, handler: DetachedRouteHandle): void { + console.debug('storing handler'); + if (handler) { + this.handlers[this.getUrl(route)] = handler; + } + } + + shouldAttach(route: ActivatedRouteSnapshot): boolean { + console.debug('checking if it should be re attached'); + return !!this.handlers[this.getUrl(route)]; + } + + retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle { + LuigiActivatedRouteSnapshotHelper.setCurrent(route); + if (!route.routeConfig || route.routeConfig.loadChildren) { + return null; + } + + return this.handlers[this.getUrl(route)]; + } + + shouldReuseRoute( + future: ActivatedRouteSnapshot, + current: ActivatedRouteSnapshot + ): boolean { + let reUseUrl = false; + if (future.routeConfig) { + if (future.routeConfig.data) { + reUseUrl = future.routeConfig.data.reuse; + } + } + const defaultReuse = future.routeConfig === current.routeConfig; + //return reUseUrl || defaultReuse; + return defaultReuse; + } + + getUrl(route: ActivatedRouteSnapshot): string { + if (route.routeConfig) { + const url = route.routeConfig.path; + console.debug('returning url', url); + return url; + } + } +} diff --git a/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/luigi-auto-routing.service.spec.ts b/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/service/luigi-auto-routing.service.spec.ts similarity index 100% rename from client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/luigi-auto-routing.service.spec.ts rename to client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/service/luigi-auto-routing.service.spec.ts diff --git a/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/service/luigi-auto-routing.service.ts b/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/service/luigi-auto-routing.service.ts new file mode 100644 index 0000000000..dbbfe06e9b --- /dev/null +++ b/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/service/luigi-auto-routing.service.ts @@ -0,0 +1,58 @@ +import { Injectable, OnDestroy } from '@angular/core'; +import { Subscription } from 'rxjs'; +import { NavigationEnd, Router } from '@angular/router'; +import { linkManager } from '@luigi-project/client'; +import { filter } from 'rxjs/operators'; +import { LuigiActivatedRouteSnapshotHelper } from '../route/luigi-activated-route-snapshot-helper'; + +@Injectable({ + providedIn: 'root' +}) +export class LuigiAutoRoutingService implements OnDestroy { + private subscription: Subscription = new Subscription(); + + constructor(private router: Router) { + this.subscription.add( + this.router.events.pipe(this.doFilter()).subscribe(this.doSubscription) + ); + } + + doFilter() { + return filter(event => { + return ( + event instanceof NavigationEnd && + event.url && + event.url.length > 0 && + !(history.state && history.state.luigiInduced) + ); + }); + } + + /** + * This method will be take in consideration angular route that having in data object the paramter fromVirtualTreeRoot: true, here an example: + * {path: 'demo', component: DemoComponent, data:{fromVirtualTreeRoot: true}} + * Another option is to specify the LuigiPath: if you add in route data luigiRoute:'/xxxx/xxx'; in the case we will update the path in LuigiCore navigation, here an example + * {path: 'demo', component: DemoComponent, data:{luigiRoute: '/home/demo''}} + * @param event + */ + doSubscription(event: NavigationEnd) { + let current = LuigiActivatedRouteSnapshotHelper.getCurrent(); + if (current.data.luigiRoute) { + linkManager() + .withoutSync() + .navigate(current.data.luigiRoute); + return; + } + if (current.data.fromVirtualTreeRoot) { + console.debug('Calling fromVirtualTreeRoot for ulr ==> ' + event.url); + linkManager() + .fromVirtualTreeRoot() + .withoutSync() + .navigate(event.url); + } + } + + ngOnDestroy(): void { + this.subscription.unsubscribe(); + } +} diff --git a/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/service/luigi-context-service.ts b/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/service/luigi-context-service.ts new file mode 100644 index 0000000000..c56acb7c7a --- /dev/null +++ b/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/service/luigi-context-service.ts @@ -0,0 +1,25 @@ +import { Context } from '@luigi-project/client'; +import { Observable } from 'rxjs'; + +export abstract class LuigiContextService { + /** + * Listen to context changes + * Receives current value, even if the event was already dispatched earlier. + */ + abstract contextObservable(): Observable; + + /** + * Get latest context object + */ + abstract getContext(): Context; +} + +export enum ILuigiContextTypes { + INIT, + UPDATE +} + +export interface IContextMessage { + contextType: ILuigiContextTypes; // will be init or update + context: Context; +} diff --git a/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/luigi-context.service.spec.ts b/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/service/luigi-context.service.impl.spec.ts similarity index 59% rename from client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/luigi-context.service.spec.ts rename to client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/service/luigi-context.service.impl.spec.ts index d5f9bd0fb5..e9167d3607 100644 --- a/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/luigi-context.service.spec.ts +++ b/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/service/luigi-context.service.impl.spec.ts @@ -1,13 +1,12 @@ import { TestBed } from '@angular/core/testing'; - -import { LuigiContextService } from './luigi-context.service'; +import { LuigiContextServiceImpl } from 'client-support-angular'; describe('LuigiContextService', () => { - let service: LuigiContextService; + let service: LuigiContextServiceImpl; beforeEach(() => { TestBed.configureTestingModule({}); - service = TestBed.inject(LuigiContextService); + service = TestBed.inject(LuigiContextServiceImpl); }); it('should be created', () => { diff --git a/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/service/luigi-context.service.impl.ts b/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/service/luigi-context.service.impl.ts new file mode 100644 index 0000000000..7b56c0a614 --- /dev/null +++ b/client-frameworks-support/client-support-angular/projects/client-support-angular/src/lib/service/luigi-context.service.impl.ts @@ -0,0 +1,57 @@ +import { Injectable } from '@angular/core'; +import { ReplaySubject, Observable } from 'rxjs'; +import { + Context, + addInitListener, + addContextUpdateListener +} from '@luigi-project/client'; +import { + IContextMessage, + ILuigiContextTypes, + LuigiContextService +} from './luigi-context-service'; + +@Injectable({ + providedIn: 'root' +}) +export class LuigiContextServiceImpl implements LuigiContextService { + private subject: ReplaySubject = new ReplaySubject< + IContextMessage + >(1); + private currentContext: IContextMessage; + + constructor() { + addInitListener(initContext => { + this.addListener(ILuigiContextTypes.INIT, initContext); + }); + addContextUpdateListener(updateContext => { + this.addListener(ILuigiContextTypes.UPDATE, updateContext); + }); + } + + public contextObservable(): Observable { + return this.subject.asObservable(); + } + + /** + * Get latest context object retrieved from luigi core application or none, if not yet set. + */ + public getContext(): Context { + return this.currentContext && this.currentContext.context; + } + + /** + * Set current context + */ + protected setContext(obj: IContextMessage): void { + this.currentContext = obj; + this.subject.next(obj); + } + + addListener(contextType: ILuigiContextTypes, context: Context) { + this.setContext({ + contextType, + context + } as IContextMessage); + } +} diff --git a/client-frameworks-support/client-support-angular/projects/client-support-angular/src/public-api.ts b/client-frameworks-support/client-support-angular/projects/client-support-angular/src/public-api.ts index e8504c8194..7a12a649a7 100644 --- a/client-frameworks-support/client-support-angular/projects/client-support-angular/src/public-api.ts +++ b/client-frameworks-support/client-support-angular/projects/client-support-angular/src/public-api.ts @@ -2,6 +2,7 @@ * Public API Surface of client-support-angular */ -export * from './lib/preload.component'; -export * from './lib/luigi-context.service'; -export * from './lib/luigi-auto-routing.service'; +export * from './lib/component/luigi.preload.component'; +export * from './lib/luigi.angular.support.module'; +export * from './lib/service/luigi-context-service'; +export * from './lib/service/luigi-auto-routing.service';