From 3da3e3446594977bfe31e328051bbc682c898af6 Mon Sep 17 00:00:00 2001 From: cbourget Date: Fri, 15 Feb 2019 15:49:49 -0500 Subject: [PATCH] feat(widget): widget module. A widget is a specialez version of a dynamic component --- demo/src/app/app.component.html | 1 + demo/src/app/app.module.ts | 2 + .../common/widget/widget-routing.module.ts | 15 +++ .../app/common/widget/widget.component.html | 17 ++++ .../app/common/widget/widget.component.scss | 12 +++ .../src/app/common/widget/widget.component.ts | 59 ++++++++++++ demo/src/app/common/widget/widget.module.ts | 28 ++++++ .../shared/dynamic-component.ts | 3 +- projects/common/src/lib/widget/index.ts | 1 + .../common/src/lib/widget/shared/index.ts | 2 + .../lib/widget/shared/widget.interfaces.ts | 13 +++ .../src/lib/widget/shared/widget.service.ts | 19 ++++ .../widget-outlet.component.html | 6 ++ .../widget-outlet.component.scss | 3 + .../widget-outlet/widget-outlet.component.ts | 93 +++++++++++++++++++ .../widget-outlet/widget-outlet.module.ts | 25 +++++ .../common/src/lib/widget/widget.module.ts | 20 ++++ projects/common/src/public_api.ts | 3 + 18 files changed, 320 insertions(+), 2 deletions(-) create mode 100644 demo/src/app/common/widget/widget-routing.module.ts create mode 100644 demo/src/app/common/widget/widget.component.html create mode 100644 demo/src/app/common/widget/widget.component.scss create mode 100644 demo/src/app/common/widget/widget.component.ts create mode 100644 demo/src/app/common/widget/widget.module.ts create mode 100644 projects/common/src/lib/widget/index.ts create mode 100644 projects/common/src/lib/widget/shared/index.ts create mode 100644 projects/common/src/lib/widget/shared/widget.interfaces.ts create mode 100644 projects/common/src/lib/widget/shared/widget.service.ts create mode 100644 projects/common/src/lib/widget/widget-outlet/widget-outlet.component.html create mode 100644 projects/common/src/lib/widget/widget-outlet/widget-outlet.component.scss create mode 100644 projects/common/src/lib/widget/widget-outlet/widget-outlet.component.ts create mode 100644 projects/common/src/lib/widget/widget-outlet/widget-outlet.module.ts create mode 100644 projects/common/src/lib/widget/widget.module.ts diff --git a/demo/src/app/app.component.html b/demo/src/app/app.component.html index 2bb7dbd722..bccbb142f3 100644 --- a/demo/src/app/app.component.html +++ b/demo/src/app/app.component.html @@ -25,6 +25,7 @@

{{title}}


Dynamic Component + Widget Entity Table Form diff --git a/demo/src/app/app.module.ts b/demo/src/app/app.module.ts index 2023fce423..79d962f1a9 100644 --- a/demo/src/app/app.module.ts +++ b/demo/src/app/app.module.ts @@ -18,6 +18,7 @@ import { AppMessageModule } from './core/message/message.module'; import { AppRequestModule } from './core/request/request.module'; import { AppDynamicComponentModule } from './common/dynamic-component/dynamic-component.module'; +import { AppWidgetModule } from './common/widget/widget.module'; import { AppEntityTableModule } from './common/entity-table/entity-table.module'; import { AppFormModule } from './common/form/form.module'; @@ -61,6 +62,7 @@ import { AppComponent } from './app.component'; AppRequestModule, AppDynamicComponentModule, + AppWidgetModule, AppEntityTableModule, AppFormModule, diff --git a/demo/src/app/common/widget/widget-routing.module.ts b/demo/src/app/common/widget/widget-routing.module.ts new file mode 100644 index 0000000000..a59ecffa89 --- /dev/null +++ b/demo/src/app/common/widget/widget-routing.module.ts @@ -0,0 +1,15 @@ +import { Routes, RouterModule } from '@angular/router'; +import { ModuleWithProviders } from '@angular/core'; + +import { AppWidgetComponent } from './widget.component'; + +const routes: Routes = [ + { + path: 'widget', + component: AppWidgetComponent + } +]; + +export const AppWidgetRoutingModule: ModuleWithProviders = RouterModule.forChild( + routes +); diff --git a/demo/src/app/common/widget/widget.component.html b/demo/src/app/common/widget/widget.component.html new file mode 100644 index 0000000000..c983ad8597 --- /dev/null +++ b/demo/src/app/common/widget/widget.component.html @@ -0,0 +1,17 @@ + + Common + Widget + + See the code of this example + + + + + + + + diff --git a/demo/src/app/common/widget/widget.component.scss b/demo/src/app/common/widget/widget.component.scss new file mode 100644 index 0000000000..f21bfc1bb3 --- /dev/null +++ b/demo/src/app/common/widget/widget.component.scss @@ -0,0 +1,12 @@ +pre, +code { + font-family: monospace, monospace; +} +pre { + overflow: auto; +} +pre > code { + display: block; + padding: 1rem; + word-wrap: normal; +} diff --git a/demo/src/app/common/widget/widget.component.ts b/demo/src/app/common/widget/widget.component.ts new file mode 100644 index 0000000000..7b34ecc8d8 --- /dev/null +++ b/demo/src/app/common/widget/widget.component.ts @@ -0,0 +1,59 @@ +import { + Component, + Input, + Output, + EventEmitter, + ChangeDetectionStrategy, + ChangeDetectorRef +} from '@angular/core'; + +import { DynamicComponent, OnUpdateInputs, WidgetComponent, WidgetService } from '@igo2/common'; + + +@Component({ + selector: 'app-salutation-widget', + template: ` +

Hello, my name is {{name}}.

+ + + `, + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class AppSalutationWidgetComponent implements OnUpdateInputs, WidgetComponent { + + @Input() name: string; + + @Output() complete = new EventEmitter(); + + @Output() cancel = new EventEmitter(); + + constructor(private cdRef: ChangeDetectorRef) {} + + onUpdateInputs() { + this.cdRef.detectChanges(); + } + +} + +@Component({ + selector: 'app-widget', + templateUrl: './widget.component.html', + styleUrls: ['./widget.component.scss'] +}) +export class AppWidgetComponent { + + widget: DynamicComponent = this.widgetService.create(AppSalutationWidgetComponent); + + inputs = {name: 'Bob'}; + + constructor(private widgetService: WidgetService) {} + + onWidgetComplete(name: string) { + alert(`${name} emitted event 'complete' then got automatically destroyed.`); + } + + onWidgetCancel() { + alert(`Widget emitted event 'cancel' then got automatically destroyed.`); + } + +} diff --git a/demo/src/app/common/widget/widget.module.ts b/demo/src/app/common/widget/widget.module.ts new file mode 100644 index 0000000000..8dccdebe51 --- /dev/null +++ b/demo/src/app/common/widget/widget.module.ts @@ -0,0 +1,28 @@ +import { NgModule } from '@angular/core'; +import { MatButtonModule, MatCardModule } from '@angular/material'; + +import { IgoWidgetModule } from '@igo2/common'; + +import { + AppSalutationWidgetComponent, + AppWidgetComponent +} from './widget.component'; +import { AppWidgetRoutingModule } from './widget-routing.module'; + +@NgModule({ + declarations: [ + AppSalutationWidgetComponent, + AppWidgetComponent + ], + imports: [ + AppWidgetRoutingModule, + MatButtonModule, + MatCardModule, + IgoWidgetModule + ], + exports: [AppWidgetComponent], + entryComponents: [ + AppSalutationWidgetComponent + ] +}) +export class AppWidgetModule {} diff --git a/projects/common/src/lib/dynamic-component/shared/dynamic-component.ts b/projects/common/src/lib/dynamic-component/shared/dynamic-component.ts index 7207201da7..3b1c1fbeeb 100644 --- a/projects/common/src/lib/dynamic-component/shared/dynamic-component.ts +++ b/projects/common/src/lib/dynamic-component/shared/dynamic-component.ts @@ -1,8 +1,7 @@ import { ComponentFactory, ComponentRef, - ViewContainerRef, - SimpleChange + ViewContainerRef } from '@angular/core'; import { Subscription } from 'rxjs'; diff --git a/projects/common/src/lib/widget/index.ts b/projects/common/src/lib/widget/index.ts new file mode 100644 index 0000000000..c3da79f741 --- /dev/null +++ b/projects/common/src/lib/widget/index.ts @@ -0,0 +1 @@ +export * from './shared'; diff --git a/projects/common/src/lib/widget/shared/index.ts b/projects/common/src/lib/widget/shared/index.ts new file mode 100644 index 0000000000..5c8f777cad --- /dev/null +++ b/projects/common/src/lib/widget/shared/index.ts @@ -0,0 +1,2 @@ +export * from './widget.interfaces'; +export * from './widget.service'; diff --git a/projects/common/src/lib/widget/shared/widget.interfaces.ts b/projects/common/src/lib/widget/shared/widget.interfaces.ts new file mode 100644 index 0000000000..3fa14e789a --- /dev/null +++ b/projects/common/src/lib/widget/shared/widget.interfaces.ts @@ -0,0 +1,13 @@ +import { EventEmitter } from '@angular/core'; + +/** + * This is the interface a widget component needs to implement. A widget + * component is component that can be created dynamically. It needs + * to emit the 'complete' and 'cancel' event at some time in it's lifecycle. + * Since a widget's inputs are set manually, you may want to implement the 'onUpdateInputs' + * method. This method could, for example, trigger the change detection. + */ +export interface WidgetComponent { + complete: EventEmitter; + cancel: EventEmitter; +} diff --git a/projects/common/src/lib/widget/shared/widget.service.ts b/projects/common/src/lib/widget/shared/widget.service.ts new file mode 100644 index 0000000000..36b4510ee5 --- /dev/null +++ b/projects/common/src/lib/widget/shared/widget.service.ts @@ -0,0 +1,19 @@ +import { + Injectable +} from '@angular/core'; + +import { DynamicComponent, DynamicComponentService } from '../../dynamic-component'; + +import { WidgetComponent } from './widget.interfaces'; + +@Injectable({ + providedIn: 'root' +}) +export class WidgetService { + + constructor(private dynamicComponentService: DynamicComponentService) {} + + create(widgetCls: any): DynamicComponent { + return this.dynamicComponentService.create(widgetCls as WidgetComponent); + } +} diff --git a/projects/common/src/lib/widget/widget-outlet/widget-outlet.component.html b/projects/common/src/lib/widget/widget-outlet/widget-outlet.component.html new file mode 100644 index 0000000000..3c81adb5f3 --- /dev/null +++ b/projects/common/src/lib/widget/widget-outlet/widget-outlet.component.html @@ -0,0 +1,6 @@ + + diff --git a/projects/common/src/lib/widget/widget-outlet/widget-outlet.component.scss b/projects/common/src/lib/widget/widget-outlet/widget-outlet.component.scss new file mode 100644 index 0000000000..44f95bf470 --- /dev/null +++ b/projects/common/src/lib/widget/widget-outlet/widget-outlet.component.scss @@ -0,0 +1,3 @@ +igo-dynamic-outlet { + height: 100%; +} diff --git a/projects/common/src/lib/widget/widget-outlet/widget-outlet.component.ts b/projects/common/src/lib/widget/widget-outlet/widget-outlet.component.ts new file mode 100644 index 0000000000..21f37f0bc4 --- /dev/null +++ b/projects/common/src/lib/widget/widget-outlet/widget-outlet.component.ts @@ -0,0 +1,93 @@ +import { + Component, + Input, + Output, + EventEmitter, + ChangeDetectionStrategy, + OnDestroy +} from '@angular/core'; + +import { DynamicComponent } from '../../dynamic-component'; + +import { WidgetComponent } from '../shared/widget.interfaces'; + +/** + * This component dynamically renders a widget. It also subscribes + * to the widget's 'cancel' and 'complete' events and destroys it + * when any of those event is emitted. + */ +@Component({ + selector: 'igo-widget-outlet', + templateUrl: './widget-outlet.component.html', + styleUrls: ['./widget-outlet.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class WidgetOutletComponent implements OnDestroy { + + /** + * Widget subscribers to 'cancel' and 'complete' + * @internal + */ + readonly subscribers = { + cancel: (event: any) => this.onCancel(event), + complete: (event: any) => this.onComplete(event) + }; + + /** + * Widget + */ + @Input() widget: DynamicComponent; + + /** + * Widget inputs + */ + @Input() inputs: {[key: string]: any}; + + /** + * Event emitted when the widget emits 'complete' + */ + @Output() complete = new EventEmitter(); + + /** + * Event emitted when the widget emits 'cancel' + */ + @Output() cancel = new EventEmitter(); + + constructor() {} + + /** + * Destroy the current widget and all it's inner subscriptions + * @internal + */ + ngOnDestroy() { + this.destroyWidget(); + } + + /** + * When the widget emits 'cancel', propagate that event and destroy + * the widget + */ + private onCancel(event: any) { + this.cancel.emit(event); + this.destroyWidget(); + } + + /** + * When the widget emits 'complete', propagate that event and destroy + * the widget + */ + private onComplete(event: any) { + this.complete.emit(event); + this.destroyWidget(); + } + + /** + * Destroy the current widget + */ + private destroyWidget() { + if (this.widget !== undefined) { + this.widget.destroy(); + } + this.widget = undefined; + } +} diff --git a/projects/common/src/lib/widget/widget-outlet/widget-outlet.module.ts b/projects/common/src/lib/widget/widget-outlet/widget-outlet.module.ts new file mode 100644 index 0000000000..2fab14a796 --- /dev/null +++ b/projects/common/src/lib/widget/widget-outlet/widget-outlet.module.ts @@ -0,0 +1,25 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { + IgoDynamicComponentModule +} from '../../dynamic-component/dynamic-component.module'; + +import { WidgetOutletComponent } from './widget-outlet.component'; + +/** + * @ignore + */ +@NgModule({ + imports: [ + CommonModule, + IgoDynamicComponentModule + ], + exports: [ + WidgetOutletComponent + ], + declarations: [ + WidgetOutletComponent + ] +}) +export class IgoWidgetOutletModule {} diff --git a/projects/common/src/lib/widget/widget.module.ts b/projects/common/src/lib/widget/widget.module.ts new file mode 100644 index 0000000000..abbfb113f4 --- /dev/null +++ b/projects/common/src/lib/widget/widget.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { IgoWidgetOutletModule } from './widget-outlet/widget-outlet.module'; +import { WidgetService } from './shared/widget.service'; + +@NgModule({ + imports: [ + CommonModule, + IgoWidgetOutletModule + ], + exports: [ + IgoWidgetOutletModule + ], + declarations: [], + providers: [ + WidgetService + ] +}) +export class IgoWidgetModule {} diff --git a/projects/common/src/public_api.ts b/projects/common/src/public_api.ts index 3ed2f87aa1..ab4f429378 100644 --- a/projects/common/src/public_api.ts +++ b/projects/common/src/public_api.ts @@ -27,6 +27,8 @@ export * from './lib/sidenav/sidenav.module'; export * from './lib/spinner/spinner.module'; export * from './lib/stop-propagation/stop-propagation.module'; export * from './lib/table/table.module'; +export * from './lib/widget/widget.module'; +export * from './lib/widget/widget-outlet/widget-outlet.module'; export * from './lib/backdrop'; export * from './lib/clickout'; @@ -48,3 +50,4 @@ export * from './lib/sidenav'; export * from './lib/spinner'; export * from './lib/stop-propagation'; export * from './lib/table'; +export * from './lib/widget';