From 2b8276861ef92e604ea35cc0bce633d95912d354 Mon Sep 17 00:00:00 2001 From: Alain Dumesny Date: Sat, 29 Apr 2023 07:19:30 -0700 Subject: [PATCH] Angular demo update --- demo/angular/src/app/README.md | 120 +++++++++++++++----- demo/angular/src/app/app.component.ts | 6 +- demo/angular/src/app/app.module.ts | 4 +- demo/angular/src/app/gridstack.component.ts | 60 +++++----- 4 files changed, 126 insertions(+), 64 deletions(-) diff --git a/demo/angular/src/app/README.md b/demo/angular/src/app/README.md index 580695b53..0ea31b6fa 100644 --- a/demo/angular/src/app/README.md +++ b/demo/angular/src/app/README.md @@ -1,48 +1,113 @@ # Angular wrapper -The Angular [wrapper component](./gridstack.component.ts) is a better way to use Gridstack, but alternative raw [NgFor](./ngFor.ts) or [Simple](./simple.ts) demos are also given. +The Angular [wrapper component](./gridstack.component.ts) is a better way to use Gridstack, but alternative raw [ngFor](./ngFor.ts) or [simple](./simple.ts) demos are also given. -## Dynamic grid items -this is the recommended way if you are going to have multiple grids (alow drag&drop between) or drag from toolbar to create items, or drag to remove items... +# Dynamic grid items +this is the recommended way if you are going to have multiple grids (alow drag&drop between) or drag from toolbar to create items, or drag to remove items, etc... -I.E. don't use Angular templating to create children as that is harder to sync. +I.E. don't use Angular templating to create grid items as that is harder to sync when gridstack will also add/remove items. +HTML +```html + + +``` Code +```ts +import { GridStack, GridStackOptions } from 'gridstack'; +import { gsCreateNgComponents } from './gridstack.component'; -```javascript -import { GridStackOptions, GridStackWidget } from 'gridstack'; -import { GridstackComponent, nodesCB } from './gridstack.component'; +constructor() { + // use the built in component creation code + GridStack.addRemoveCB = gsCreateNgComponents; +} -/** sample grid options and items to load... */ +// sample grid options and items to load... public gridOptions: GridStackOptions = { margin: 5, float: true, - children: [ // or call load() with same data - {x:0, y:0, minW:2, id:'1', content:'Item 1'}, - {x:1, y:1, id:'2', content:'Item 2'}, - {x:2, y:2, id:'3', content:'Item 3'}, + children: [ // or call load()/addWidget() with same data + {x:0, y:0, minW:2, content:'Item 1'}, + {x:1, y:1, content:'Item 2'}, + {x:2, y:2, content:'Item 3'}, ] } +``` -// called whenever items change size/position/etc.. +# More Complete example +In this example will your actual custom angular components inside each grid item (instead of dummy html content) + +HTML +```html + +
message when grid is empty
+
+``` + +Code +```ts +import { Component } from '@angular/core'; +import { GridStack, GridStackOptions } from 'gridstack'; +import { GridstackComponent, gsCreateNgComponents, NgGridStackWidget, nodesCB } from './gridstack.component'; + +// some custom components +@Component({ + selector: 'app-a', + template: 'Comp A', // your real ng content goes in each component instead... +}) +export class AComponent { +} + +@Component({ + selector: 'app-b', + template: 'Comp B', +}) +export class BComponent { +} + +// .... in your module for example +constructor() { + // register all our dynamic components created in the grid + GridstackComponent.addComponentToSelectorType([AComponent, BComponent]); + // set globally our method to create the right widget type + GridStack.addRemoveCB = gsCreateNgComponents; + GridStack.saveCB = gsSaveAdditionalNgInfo; +} + +// and now our content will look like instead of dummy html content +public gridOptions: NgGridStackOptions = { + margin: 5, + float: true, + minRow: 1, // make space for empty message + children: [ // or call load()/addWidget() with same data + {x:0, y:0, minW:2, type:'app-a'}, + {x:1, y:1, type:'app-b'}, + {x:2, y:2, content:'plain html content'}, + ] +} + +// called whenever items change size/position/etc.. see other events public onChange(data: nodesCB) { console.log('change ', data.nodes.length > 1 ? data.nodes : data.nodes[0]); } ``` + +# ngFor with wrapper +For simple case where you control the children creation (gridstack doesn't do create or re-parenting) + HTML ```html + + Item {{n.id}} + ``` -## ngFor with wrapper -For simple case where you control the children creation (gridstack doesn't do create or re-parenting) - Code - ```javascript import { GridStackOptions, GridStackWidget } from 'gridstack'; -import { GridstackComponent, nodesCB } from './gridstack.component'; +import { nodesCB } from './gridstack.component'; /** sample grid options and items to load... */ public gridOptions: GridStackOptions = { @@ -50,7 +115,7 @@ public gridOptions: GridStackOptions = { float: true, } public items: GridStackWidget[] = [ - {x:0, y:0, minW:2, id:'1'}, + {x:0, y:0, minW:2, id:'1'}, // must have unique id used for trackBy {x:1, y:1, id:'2'}, {x:2, y:2, id:'3'}, ]; @@ -65,14 +130,6 @@ public identify(index: number, w: GridStackWidget) { return w.id; // or use index if no id is set and you only modify at the end... } ``` -HTML -```html - - - Item {{n.id}} - - -``` ## Demo You can see a fuller example at [app.component.ts](https://github.com/gridstack/gridstack.js/blob/master/demo/angular/src/app/app.component.ts) @@ -81,14 +138,15 @@ to build the demo, go to demo/angular and run `yarn` + `yarn start` and Navigate ## Caveats - - This wrapper needs v7.2+ to run as it needs the latest changes + - This wrapper needs v8.0+ to run as it needs the latest changes - Code isn't compiled into a lib YET. You'll need to copy those files. Let me know (slack) if you are using it... - - BUG: content doesn't appear on new widget until widget is moved around (or another created that pushes it). Need to force angular detection changes... - ## ngFor Caveats + ## *ngFor Caveats - This wrapper handles well ngFor loops, but if you're using a trackBy function (as I would recommend) and no element id change after an update, - you must manually update the `GridstackItemComponent.option` directly - see [modifyNgFor()](./app.component.ts#L83) example. + you must manually update the `GridstackItemComponent.option` directly - see [modifyNgFor()](./app.component.ts#L174) example. - The original client list of items is not updated to match **content** changes made by gridstack (TBD later), but adding new item or removing (as shown in demo) will update those new items. Client could use change/added/removed events to sync that list if they wish to do so. Would appreciate getting help doing the same for React and Vue (2 other popular frameworks) + + -Alain diff --git a/demo/angular/src/app/app.component.ts b/demo/angular/src/app/app.component.ts index 75b4ca883..f8f498dce 100644 --- a/demo/angular/src/app/app.component.ts +++ b/demo/angular/src/app/app.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit, ViewChild, ElementRef } from '@angular/core'; import { GridStack, GridStackOptions, GridStackWidget } from 'gridstack'; -import { GridstackComponent, NgGridStackWidget, elementCB, gsCreateNgComponents, nodesCB } from './gridstack.component'; +import { GridstackComponent, NgGridStackOptions, NgGridStackWidget, elementCB, gsCreateNgComponents, nodesCB } from './gridstack.component'; import { AngularSimpleComponent } from './simple'; import { AngularNgForTestComponent } from './ngFor'; import { AngularNgForCmdTestComponent } from './ngFor_cmd'; @@ -54,7 +54,7 @@ export class AppComponent implements OnInit { {x:1, y:0, w:4, h:4, subGridOpts: {children: this.sub1, id:'sub1_grid', class: 'sub1', ...this.subOptions}}, {x:5, y:0, w:3, h:4, subGridOpts: {children: this.sub2, id:'sub2_grid', class: 'sub2', ...this.subOptions}}, ] - public nestedGridOptions: GridStackOptions = { // main grid options + public nestedGridOptions: NgGridStackOptions = { // main grid options cellHeight: 50, margin: 5, minRow: 2, // don't collapse when empty @@ -63,7 +63,7 @@ export class AppComponent implements OnInit { id: 'main', children: this.subChildren }; - private serializedData?: GridStackOptions; + private serializedData?: NgGridStackOptions; constructor() { // give them content and unique id to make sure we track them during changes below... diff --git a/demo/angular/src/app/app.module.ts b/demo/angular/src/app/app.module.ts index 3447611a5..641b335aa 100644 --- a/demo/angular/src/app/app.module.ts +++ b/demo/angular/src/app/app.module.ts @@ -3,7 +3,7 @@ import { BrowserModule } from '@angular/platform-browser'; import { AppComponent } from './app.component'; import { GridstackItemComponent } from './gridstack-item.component'; -import { GridstackComponent, gsCreateNgComponents, gsSaveAdditionNgInfo } from './gridstack.component'; +import { GridstackComponent, gsCreateNgComponents, gsSaveAdditionalNgInfo } from './gridstack.component'; import { AngularNgForTestComponent } from './ngFor'; import { AngularNgForCmdTestComponent } from './ngFor_cmd'; import { AngularSimpleComponent } from './simple'; @@ -38,6 +38,6 @@ export class AppModule { GridstackComponent.addComponentToSelectorType([AComponent, BComponent, CComponent]); // set globally our method to create the right widget type GridStack.addRemoveCB = gsCreateNgComponents; // DONE in case switcher onShow() as well - GridStack.saveCB = gsSaveAdditionNgInfo; + GridStack.saveCB = gsSaveAdditionalNgInfo; } } diff --git a/demo/angular/src/app/gridstack.component.ts b/demo/angular/src/app/gridstack.component.ts index 8d33a3ee8..cda71717d 100644 --- a/demo/angular/src/app/gridstack.component.ts +++ b/demo/angular/src/app/gridstack.component.ts @@ -24,6 +24,10 @@ export interface NgGridStackWidget extends GridStackWidget { export interface NgGridStackNode extends GridStackNode { type?: string; // component type to create as content } +export interface NgGridStackOptions extends GridStackOptions { + children?: NgGridStackWidget[]; + subGridOpts?: NgGridStackOptions; +} /** store element to Ng Class pointer back */ export interface GridCompHTMLElement extends GridHTMLElement { @@ -194,43 +198,43 @@ export class GridstackComponent implements OnInit, AfterContentInit, OnDestroy { * can be used when a new item needs to be created, which we do as a Angular component, or deleted (skip) **/ export function gsCreateNgComponents(host: GridCompHTMLElement | HTMLElement, w: NgGridStackWidget | GridStackOptions, add: boolean, isGrid: boolean): HTMLElement | undefined { - if (add) { - if (!host) return; - // create the grid item dynamically - see https://angular.io/docs/ts/latest/cookbook/dynamic-component-loader.html - if (isGrid) { - let grid: GridstackComponent | undefined; - const gridItemComp = (host.parentElement as GridItemCompHTMLElement)._gridItemComp; - if (gridItemComp) { - grid = gridItemComp?.container?.createComponent(GridstackComponent)?.instance; - } else { - // TODO: figure out how to creat ng component inside regular Div. need to access app injectors... - // const hostElement: Element = host; - // const environmentInjector: EnvironmentInjector; - // grid = createComponent(GridstackComponent, {environmentInjector, hostElement})?.instance; - } - if (grid) grid.options = w as GridStackOptions; - return grid?.el; + // only care about creating ng components here... + if (!add || !host) return; + + // create the component dynamically - see https://angular.io/docs/ts/latest/cookbook/dynamic-component-loader.html + if (isGrid) { + let grid: GridstackComponent | undefined; + const gridItemComp = (host.parentElement as GridItemCompHTMLElement)?._gridItemComp; + if (gridItemComp) { + grid = gridItemComp.container?.createComponent(GridstackComponent)?.instance; } else { - const gridComp = (host as GridCompHTMLElement)._gridComp; - const gridItem = gridComp?.container?.createComponent(GridstackItemComponent)?.instance; - - // 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 - const type = (w as NgGridStackWidget).type; - if (!w.subGridOpts && type && GridstackComponent.selectorToType[type]) { - gridItem?.container?.createComponent(GridstackComponent.selectorToType[type]); - } - - return gridItem?.el; + // TODO: figure out how to create ng component inside regular Div. need to access app injectors... + // const hostElement: Element = host; + // const environmentInjector: EnvironmentInjector; + // grid = createComponent(GridstackComponent, {environmentInjector, hostElement})?.instance; } + if (grid) grid.options = w as GridStackOptions; + return grid?.el; + } else { + const gridComp = (host as GridCompHTMLElement)._gridComp; + const gridItem = gridComp?.container?.createComponent(GridstackItemComponent)?.instance; + + // 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 + const selector = (w as NgGridStackWidget).type; + const type = selector ? GridstackComponent.selectorToType[selector] : undefined; + if (!w.subGridOpts && type) { + gridItem?.container?.createComponent(type); + } + + return gridItem?.el; } - return; } /** * can be used when saving the grid - make sure we save the content from the field (not HTML as we get ng markups) * and can put the extra info of type, otherwise content */ -export function gsSaveAdditionNgInfo(n: NgGridStackNode, w: NgGridStackWidget) { +export function gsSaveAdditionalNgInfo(n: NgGridStackNode, w: NgGridStackWidget) { if (n.type) w.type = n.type; else if (n.content) w.content = n.content; }