Skip to content

Commit 99ef00b

Browse files
authored
Merge pull request #2281 from adumesny/master
Angular demo update
2 parents eefcbf2 + 2b82768 commit 99ef00b

File tree

4 files changed

+126
-64
lines changed

4 files changed

+126
-64
lines changed

demo/angular/src/app/README.md

Lines changed: 89 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,121 @@
11
# Angular wrapper
22

3-
The Angular [wrapper component](./gridstack.component.ts) <gridstack> is a better way to use Gridstack, but alternative raw [NgFor](./ngFor.ts) or [Simple](./simple.ts) demos are also given.
3+
The Angular [wrapper component](./gridstack.component.ts) <gridstack> is a better way to use Gridstack, but alternative raw [ngFor](./ngFor.ts) or [simple](./simple.ts) demos are also given.
44

5-
## Dynamic grid items
6-
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...
5+
# Dynamic grid items
6+
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...
77

8-
I.E. don't use Angular templating to create children as that is harder to sync.
8+
I.E. don't use Angular templating to create grid items as that is harder to sync when gridstack will also add/remove items.
99

10+
HTML
11+
```html
12+
<gridstack [options]="gridOptions">
13+
</gridstack>
14+
```
1015
Code
16+
```ts
17+
import { GridStack, GridStackOptions } from 'gridstack';
18+
import { gsCreateNgComponents } from './gridstack.component';
1119

12-
```javascript
13-
import { GridStackOptions, GridStackWidget } from 'gridstack';
14-
import { GridstackComponent, nodesCB } from './gridstack.component';
20+
constructor() {
21+
// use the built in component creation code
22+
GridStack.addRemoveCB = gsCreateNgComponents;
23+
}
1524

16-
/** sample grid options and items to load... */
25+
// sample grid options and items to load...
1726
public gridOptions: GridStackOptions = {
1827
margin: 5,
1928
float: true,
20-
children: [ // or call load() with same data
21-
{x:0, y:0, minW:2, id:'1', content:'Item 1'},
22-
{x:1, y:1, id:'2', content:'Item 2'},
23-
{x:2, y:2, id:'3', content:'Item 3'},
29+
children: [ // or call load()/addWidget() with same data
30+
{x:0, y:0, minW:2, content:'Item 1'},
31+
{x:1, y:1, content:'Item 2'},
32+
{x:2, y:2, content:'Item 3'},
2433
]
2534
}
35+
```
2636

27-
// called whenever items change size/position/etc..
37+
# More Complete example
38+
In this example will your actual custom angular components inside each grid item (instead of dummy html content)
39+
40+
HTML
41+
```html
42+
<gridstack [options]="gridOptions" (changeCB)="onChange($event)">
43+
<div empty-content>message when grid is empty</div>
44+
</gridstack>
45+
```
46+
47+
Code
48+
```ts
49+
import { Component } from '@angular/core';
50+
import { GridStack, GridStackOptions } from 'gridstack';
51+
import { GridstackComponent, gsCreateNgComponents, NgGridStackWidget, nodesCB } from './gridstack.component';
52+
53+
// some custom components
54+
@Component({
55+
selector: 'app-a',
56+
template: 'Comp A', // your real ng content goes in each component instead...
57+
})
58+
export class AComponent {
59+
}
60+
61+
@Component({
62+
selector: 'app-b',
63+
template: 'Comp B',
64+
})
65+
export class BComponent {
66+
}
67+
68+
// .... in your module for example
69+
constructor() {
70+
// register all our dynamic components created in the grid
71+
GridstackComponent.addComponentToSelectorType([AComponent, BComponent]);
72+
// set globally our method to create the right widget type
73+
GridStack.addRemoveCB = gsCreateNgComponents;
74+
GridStack.saveCB = gsSaveAdditionalNgInfo;
75+
}
76+
77+
// and now our content will look like instead of dummy html content
78+
public gridOptions: NgGridStackOptions = {
79+
margin: 5,
80+
float: true,
81+
minRow: 1, // make space for empty message
82+
children: [ // or call load()/addWidget() with same data
83+
{x:0, y:0, minW:2, type:'app-a'},
84+
{x:1, y:1, type:'app-b'},
85+
{x:2, y:2, content:'plain html content'},
86+
]
87+
}
88+
89+
// called whenever items change size/position/etc.. see other events
2890
public onChange(data: nodesCB) {
2991
console.log('change ', data.nodes.length > 1 ? data.nodes : data.nodes[0]);
3092
}
3193
```
94+
95+
# ngFor with wrapper
96+
For simple case where you control the children creation (gridstack doesn't do create or re-parenting)
97+
3298
HTML
3399
```html
34100
<gridstack [options]="gridOptions" (changeCB)="onChange($event)">
101+
<gridstack-item *ngFor="let n of items; trackBy: identify" [options]="n">
102+
Item {{n.id}}
103+
</gridstack-item>
35104
</gridstack>
36105
```
37106

38-
## ngFor with wrapper
39-
For simple case where you control the children creation (gridstack doesn't do create or re-parenting)
40-
41107
Code
42-
43108
```javascript
44109
import { GridStackOptions, GridStackWidget } from 'gridstack';
45-
import { GridstackComponent, nodesCB } from './gridstack.component';
110+
import { nodesCB } from './gridstack.component';
46111

47112
/** sample grid options and items to load... */
48113
public gridOptions: GridStackOptions = {
49114
margin: 5,
50115
float: true,
51116
}
52117
public items: GridStackWidget[] = [
53-
{x:0, y:0, minW:2, id:'1'},
118+
{x:0, y:0, minW:2, id:'1'}, // must have unique id used for trackBy
54119
{x:1, y:1, id:'2'},
55120
{x:2, y:2, id:'3'},
56121
];
@@ -65,14 +130,6 @@ public identify(index: number, w: GridStackWidget) {
65130
return w.id; // or use index if no id is set and you only modify at the end...
66131
}
67132
```
68-
HTML
69-
```html
70-
<gridstack [options]="gridOptions" (changeCB)="onChange($event)">
71-
<gridstack-item *ngFor="let n of items; trackBy: identify" [options]="n">
72-
Item {{n.id}}
73-
</gridstack-item>
74-
</gridstack>
75-
```
76133

77134
## Demo
78135
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
81138

82139
## Caveats
83140

84-
- This wrapper needs v7.2+ to run as it needs the latest changes
141+
- This wrapper needs v8.0+ to run as it needs the latest changes
85142
- Code isn't compiled into a lib YET. You'll need to copy those files. Let me know (slack) if you are using it...
86-
- 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...
87143

88-
## ngFor Caveats
144+
## *ngFor Caveats
89145
- 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,
90-
you must manually update the `GridstackItemComponent.option` directly - see [modifyNgFor()](./app.component.ts#L83) example.
146+
you must manually update the `GridstackItemComponent.option` directly - see [modifyNgFor()](./app.component.ts#L174) example.
91147
- 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.
92148

93149

94150
Would appreciate getting help doing the same for React and Vue (2 other popular frameworks)
151+
152+
-Alain

demo/angular/src/app/app.component.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Component, OnInit, ViewChild, ElementRef } from '@angular/core';
22
import { GridStack, GridStackOptions, GridStackWidget } from 'gridstack';
3-
import { GridstackComponent, NgGridStackWidget, elementCB, gsCreateNgComponents, nodesCB } from './gridstack.component';
3+
import { GridstackComponent, NgGridStackOptions, NgGridStackWidget, elementCB, gsCreateNgComponents, nodesCB } from './gridstack.component';
44
import { AngularSimpleComponent } from './simple';
55
import { AngularNgForTestComponent } from './ngFor';
66
import { AngularNgForCmdTestComponent } from './ngFor_cmd';
@@ -54,7 +54,7 @@ export class AppComponent implements OnInit {
5454
{x:1, y:0, w:4, h:4, subGridOpts: {children: this.sub1, id:'sub1_grid', class: 'sub1', ...this.subOptions}},
5555
{x:5, y:0, w:3, h:4, subGridOpts: {children: this.sub2, id:'sub2_grid', class: 'sub2', ...this.subOptions}},
5656
]
57-
public nestedGridOptions: GridStackOptions = { // main grid options
57+
public nestedGridOptions: NgGridStackOptions = { // main grid options
5858
cellHeight: 50,
5959
margin: 5,
6060
minRow: 2, // don't collapse when empty
@@ -63,7 +63,7 @@ export class AppComponent implements OnInit {
6363
id: 'main',
6464
children: this.subChildren
6565
};
66-
private serializedData?: GridStackOptions;
66+
private serializedData?: NgGridStackOptions;
6767

6868
constructor() {
6969
// give them content and unique id to make sure we track them during changes below...

demo/angular/src/app/app.module.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { BrowserModule } from '@angular/platform-browser';
33

44
import { AppComponent } from './app.component';
55
import { GridstackItemComponent } from './gridstack-item.component';
6-
import { GridstackComponent, gsCreateNgComponents, gsSaveAdditionNgInfo } from './gridstack.component';
6+
import { GridstackComponent, gsCreateNgComponents, gsSaveAdditionalNgInfo } from './gridstack.component';
77
import { AngularNgForTestComponent } from './ngFor';
88
import { AngularNgForCmdTestComponent } from './ngFor_cmd';
99
import { AngularSimpleComponent } from './simple';
@@ -38,6 +38,6 @@ export class AppModule {
3838
GridstackComponent.addComponentToSelectorType([AComponent, BComponent, CComponent]);
3939
// set globally our method to create the right widget type
4040
GridStack.addRemoveCB = gsCreateNgComponents; // DONE in case switcher onShow() as well
41-
GridStack.saveCB = gsSaveAdditionNgInfo;
41+
GridStack.saveCB = gsSaveAdditionalNgInfo;
4242
}
4343
}

demo/angular/src/app/gridstack.component.ts

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ export interface NgGridStackWidget extends GridStackWidget {
2424
export interface NgGridStackNode extends GridStackNode {
2525
type?: string; // component type to create as content
2626
}
27+
export interface NgGridStackOptions extends GridStackOptions {
28+
children?: NgGridStackWidget[];
29+
subGridOpts?: NgGridStackOptions;
30+
}
2731

2832
/** store element to Ng Class pointer back */
2933
export interface GridCompHTMLElement extends GridHTMLElement {
@@ -194,43 +198,43 @@ export class GridstackComponent implements OnInit, AfterContentInit, OnDestroy {
194198
* can be used when a new item needs to be created, which we do as a Angular component, or deleted (skip)
195199
**/
196200
export function gsCreateNgComponents(host: GridCompHTMLElement | HTMLElement, w: NgGridStackWidget | GridStackOptions, add: boolean, isGrid: boolean): HTMLElement | undefined {
197-
if (add) {
198-
if (!host) return;
199-
// create the grid item dynamically - see https://angular.io/docs/ts/latest/cookbook/dynamic-component-loader.html
200-
if (isGrid) {
201-
let grid: GridstackComponent | undefined;
202-
const gridItemComp = (host.parentElement as GridItemCompHTMLElement)._gridItemComp;
203-
if (gridItemComp) {
204-
grid = gridItemComp?.container?.createComponent(GridstackComponent)?.instance;
205-
} else {
206-
// TODO: figure out how to creat ng component inside regular Div. need to access app injectors...
207-
// const hostElement: Element = host;
208-
// const environmentInjector: EnvironmentInjector;
209-
// grid = createComponent(GridstackComponent, {environmentInjector, hostElement})?.instance;
210-
}
211-
if (grid) grid.options = w as GridStackOptions;
212-
return grid?.el;
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;
213210
} else {
214-
const gridComp = (host as GridCompHTMLElement)._gridComp;
215-
const gridItem = gridComp?.container?.createComponent(GridstackItemComponent)?.instance;
216-
217-
// 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
218-
const type = (w as NgGridStackWidget).type;
219-
if (!w.subGridOpts && type && GridstackComponent.selectorToType[type]) {
220-
gridItem?.container?.createComponent(GridstackComponent.selectorToType[type]);
221-
}
222-
223-
return gridItem?.el;
211+
// 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;
224215
}
216+
if (grid) grid.options = w as GridStackOptions;
217+
return grid?.el;
218+
} 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);
227+
}
228+
229+
return gridItem?.el;
225230
}
226-
return;
227231
}
228232

229233
/**
230234
* can be used when saving the grid - make sure we save the content from the field (not HTML as we get ng markups)
231235
* and can put the extra info of type, otherwise content
232236
*/
233-
export function gsSaveAdditionNgInfo(n: NgGridStackNode, w: NgGridStackWidget) {
237+
export function gsSaveAdditionalNgInfo(n: NgGridStackNode, w: NgGridStackWidget) {
234238
if (n.type) w.type = n.type;
235239
else if (n.content) w.content = n.content;
236240
}

0 commit comments

Comments
 (0)