44 */
55
66import { AfterContentInit , Component , ContentChildren , ElementRef , EventEmitter , Input ,
7- OnDestroy , OnInit , Output , QueryList , Type , ViewChild , ViewContainerRef , reflectComponentType } from '@angular/core' ;
7+ OnDestroy , OnInit , Output , QueryList , Type , ViewChild , ViewContainerRef , reflectComponentType , ComponentRef } from '@angular/core' ;
88import { Subject } from 'rxjs' ;
99import { takeUntil } from 'rxjs/operators' ;
1010import { GridHTMLElement , GridItemHTMLElement , GridStack , GridStackNode , GridStackOptions , GridStackWidget } from 'gridstack' ;
1111
1212import { GridItemCompHTMLElement , GridstackItemComponent } from './gridstack-item.component' ;
13+ import { BaseWidget } from './base-widgets' ;
1314
1415/** events handlers emitters signature for different events */
1516export type eventCB = { event : Event } ;
1617export type elementCB = { event : Event , el : GridItemHTMLElement } ;
1718export type nodesCB = { event : Event , nodes : GridStackNode [ ] } ;
1819export type droppedCB = { event : Event , previousNode : GridStackNode , newNode : GridStackNode } ;
1920
21+ export type NgCompInputs = { [ key : string ] : any } ;
22+
2023/** extends to store Ng Component selector, instead/inAddition to content */
2124export interface NgGridStackWidget extends GridStackWidget {
22- type ?: string ; // component type to create as content
25+ selector ?: string ; // component type to create as content
26+ input ?: NgCompInputs ; // serialized data for the component input fields
2327}
2428export interface NgGridStackNode extends GridStackNode {
25- type ?: string ; // component type to create as content
29+ selector ?: string ; // component type to create as content
2630}
2731export interface NgGridStackOptions extends GridStackOptions {
2832 children ?: NgGridStackWidget [ ] ;
@@ -96,6 +100,9 @@ export class GridstackComponent implements OnInit, AfterContentInit, OnDestroy {
96100 /** return the GridStack class */
97101 public get grid ( ) : GridStack | undefined { return this . _grid ; }
98102
103+ /** ComponentRef of ourself - used by dynamic object to correctly get removed */
104+ public ref : ComponentRef < GridstackComponent > | undefined ;
105+
99106 /**
100107 * stores the selector -> Type mapping, so we can create items dynamically from a string.
101108 * Unfortunately Ng doesn't provide public access to that mapping.
@@ -107,8 +114,7 @@ export class GridstackComponent implements OnInit, AfterContentInit, OnDestroy {
107114 }
108115 /** return the ng Component selector */
109116 public static getSelector ( type : Type < Object > ) : string {
110- const mirror = reflectComponentType ( type ) ! ;
111- return mirror . selector ;
117+ return reflectComponentType ( type ) ! . selector ;
112118 }
113119
114120 private _options ?: GridStackOptions ;
@@ -145,6 +151,7 @@ export class GridstackComponent implements OnInit, AfterContentInit, OnDestroy {
145151 }
146152
147153 public ngOnDestroy ( ) : void {
154+ delete this . ref ;
148155 this . ngUnsubscribe . next ( ) ;
149156 this . ngUnsubscribe . complete ( ) ;
150157 this . grid ?. destroy ( ) ;
@@ -197,44 +204,84 @@ export class GridstackComponent implements OnInit, AfterContentInit, OnDestroy {
197204/**
198205 * can be used when a new item needs to be created, which we do as a Angular component, or deleted (skip)
199206 **/
200- export function gsCreateNgComponents ( host : GridCompHTMLElement | HTMLElement , w : NgGridStackWidget | GridStackOptions , add : boolean , isGrid : boolean ) : HTMLElement | undefined {
201- // only care about creating ng components here...
202- if ( ! add || ! host ) return ;
203-
204- // create the component dynamically - see https://angular.io/docs/ts/latest/cookbook/dynamic-component-loader.html
205- if ( isGrid ) {
206- let grid : GridstackComponent | undefined ;
207- const gridItemComp = ( host . parentElement as GridItemCompHTMLElement ) ?. _gridItemComp ;
208- if ( gridItemComp ) {
209- grid = gridItemComp . container ?. createComponent ( GridstackComponent ) ?. instance ;
210- } else {
207+ export function gsCreateNgComponents ( host : GridCompHTMLElement | HTMLElement , w : NgGridStackWidget | GridStackNode , add : boolean , isGrid : boolean ) : HTMLElement | undefined {
208+ if ( add ) {
209+ //
210+ // create the component dynamically - see https://angular.io/docs/ts/latest/cookbook/dynamic-component-loader.html
211+ //
212+ if ( ! host ) return ;
213+ if ( isGrid ) {
214+ const container = ( host . parentElement as GridItemCompHTMLElement ) ?. _gridItemComp ?. container ;
211215 // TODO: figure out how to create ng component inside regular Div. need to access app injectors...
212- // const hostElement: Element = host;
213- // const environmentInjector: EnvironmentInjector;
214- // grid = createComponent(GridstackComponent, {environmentInjector, hostElement})?.instance;
216+ // if (!container) {
217+ // const hostElement: Element = host;
218+ // const environmentInjector: EnvironmentInjector;
219+ // grid = createComponent(GridstackComponent, {environmentInjector, hostElement})?.instance;
220+ // }
221+ const gridRef = container ?. createComponent ( GridstackComponent ) ;
222+ const grid = gridRef ?. instance ;
223+ if ( ! grid ) return ;
224+ grid . ref = gridRef ;
225+ grid . options = w as GridStackOptions ;
226+ return grid . el ;
227+ } else {
228+ const gridComp = ( host as GridCompHTMLElement ) . _gridComp ;
229+ const gridItemRef = gridComp ?. container ?. createComponent ( GridstackItemComponent ) ;
230+ const gridItem = gridItemRef ?. instance ;
231+ if ( ! gridItem ) return ;
232+ gridItem . ref = gridItemRef
233+
234+ // IFF we're not a subGrid, define what type of component to create as child, OR you can do it GridstackItemComponent template, but this is more generic
235+ const selector = ( w as NgGridStackWidget ) . selector ;
236+ const type = selector ? GridstackComponent . selectorToType [ selector ] : undefined ;
237+ if ( ! w . subGridOpts && type ) {
238+ const childWidget = gridItem . container ?. createComponent ( type ) ?. instance as BaseWidget ;
239+ if ( typeof childWidget ?. serialize === 'function' && typeof childWidget ?. deserialize === 'function' ) {
240+ // proper BaseWidget subclass, save it and load additional data
241+ gridItem . childWidget = childWidget ;
242+ childWidget . deserialize ( w ) ;
243+ }
244+ }
245+
246+ return gridItem . el ;
215247 }
216- if ( grid ) grid . options = w as GridStackOptions ;
217- return grid ?. el ;
218248 } else {
219- const gridComp = ( host as GridCompHTMLElement ) . _gridComp ;
220- const gridItem = gridComp ?. container ?. createComponent ( GridstackItemComponent ) ?. instance ;
221-
222- // IFF we're not a subGrid, define what type of component to create as child, OR you can do it GridstackItemComponent template, but this is more generic
223- const selector = ( w as NgGridStackWidget ) . type ;
224- const type = selector ? GridstackComponent . selectorToType [ selector ] : undefined ;
225- if ( ! w . subGridOpts && type ) {
226- gridItem ?. container ?. createComponent ( type ) ;
249+ //
250+ // REMOVE - have to call ComponentRef:destroy() for dynamic objects to correctly remove themselves
251+ // Note: this will destroy all children dynamic components as well: gridItem -> childWidget
252+ //
253+ const n = w as GridStackNode ;
254+ if ( isGrid ) {
255+ const grid = ( n . el as GridCompHTMLElement ) ?. _gridComp ;
256+ if ( grid ?. ref ) grid . ref . destroy ( ) ;
257+ else grid ?. ngOnDestroy ( ) ;
258+ } else {
259+ const gridItem = ( n . el as GridItemCompHTMLElement ) ?. _gridItemComp ;
260+ if ( gridItem ?. ref ) gridItem . ref . destroy ( ) ;
261+ else gridItem ?. ngOnDestroy ( ) ;
227262 }
228-
229- return gridItem ?. el ;
230263 }
264+ return ;
231265}
232266
233267/**
234- * can be used when saving the grid - make sure we save the content from the field (not HTML as we get ng markups)
235- * and can put the extra info of type, otherwise content
268+ * called for each item in the grid - check if additional information needs to be saved.
269+ * Note: since this is options minus gridstack private members using Utils.removeInternalForSave(),
270+ * this typically doesn't need to do anything. However your custom Component @Input() are now supported
271+ * using BaseWidget.serialize()
236272 */
237273export function gsSaveAdditionalNgInfo ( n : NgGridStackNode , w : NgGridStackWidget ) {
238- if ( n . type ) w . type = n . type ;
239- else if ( n . content ) w . content = n . content ;
274+ const gridItem = ( n . el as GridItemCompHTMLElement ) ?. _gridItemComp ;
275+ if ( gridItem ) {
276+ const input = gridItem . childWidget ?. serialize ( ) ;
277+ if ( input ) {
278+ w . input = input ;
279+ }
280+ return ;
281+ }
282+ // else check if Grid
283+ const grid = ( n . el as GridCompHTMLElement ) ?. _gridComp ;
284+ if ( grid ) {
285+ //.... save any custom data
286+ }
240287}
0 commit comments