Skip to content

Commit c99fbc1

Browse files
authored
Complete Angular wrapper (#2152)
* partially fixed Rect complex example for #2094 * fixeds part of the demo dprokop sent out, but running into a React issue I'm not familiar with... * ultimately this is NOT the way to do things (added comment), and will concentrate on a good Angular complex demo instead... already spent 1.5d on this! * React 2 grid demo * fix #2094 * now works dragging between grids and staying in sync. Still not recommended way... * Revert "Bump json5 and @angular-devkit/build-angular in /demo/angular" This reverts commit 38f48ef. * Complete Angular wrapper * dynamic Angular wrapper now works - will need to create more fancy examples later. * wrapper now handles ngFor and dynamic creation for right GridItemComponent * NEED upcoming v7.2 release
1 parent fba00d6 commit c99fbc1

13 files changed

+13259
-7661
lines changed

demo/angular/package-lock.json

Lines changed: 5146 additions & 1509 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

demo/angular/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"zone.js": "~0.11.4"
2525
},
2626
"devDependencies": {
27-
"@angular-devkit/build-angular": "~15.0.4",
27+
"@angular-devkit/build-angular": "~12.2.13",
2828
"@angular/cli": "~12.2.13",
2929
"@angular/compiler-cli": "~12.2.0",
3030
"@types/jasmine": "~3.8.0",

demo/angular/src/app/README.md

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,41 @@
22

33
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-
## Usage
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...
7+
8+
I.E. don't use Angular templating to create children as that is harder to sync.
9+
10+
Code
11+
12+
```typescript
13+
import { GridStackOptions, GridStackWidget } from 'gridstack';
14+
import { GridstackComponent, nodesCB } from './gridstack.component';
15+
16+
/** sample grid options and items to load... */
17+
public gridOptions: GridStackOptions = {
18+
margin: 5,
19+
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'},
24+
]
25+
}
26+
27+
// called whenever items change size/position/etc..
28+
public onChange(data: nodesCB) {
29+
console.log('change ', data.nodes.length > 1 ? data.nodes : data.nodes[0]);
30+
}
31+
```
32+
HTML
33+
```angular2html
34+
<gridstack [options]="gridOptions" (changeGS)="onChange($event)">
35+
</gridstack>
36+
```
37+
38+
## ngFor with wrapper
39+
For simple case where you control the children creation (gridstack doesn't do create.re-parenting)
640

741
Code
842

@@ -35,7 +69,7 @@ HTML
3569
```angular2html
3670
<gridstack [options]="gridOptions" (changeCB)="onChange($event)">
3771
<gridstack-item *ngFor="let n of items; trackBy: identify" [options]="n">
38-
Hello
72+
Item {{n.id}}
3973
</gridstack-item>
4074
</gridstack>
4175
```
@@ -45,14 +79,15 @@ You can see a fuller example at [app.component.ts](https://github.com/gridstack/
4579

4680
to build the demo, go to demo/angular and run `yarn` + `yarn start` and Navigate to `http://localhost:4200/`
4781

48-
### Caveats
82+
## Caveats
83+
84+
- This wrapper needs v7.2+ to run as it needs the latest changes
85+
- Code isn't compiled into a lib YET. You'll need to copy those files. Let me know (slack) if you are using it...
4986

50-
- This wrapper needs v7.1.2+ to run as it needs the latest changes
87+
## ngFor Caveats
5188
- 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,
52-
you must manually update the `GridstackItemComponent.option` directly - see [modify()](./app.component.ts#L58) example.
53-
- 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 now.
54-
- Code isn't compiled into a side lib to use right now - you need to copy those files for now. Let me know (slack) if you are using it...
89+
you must manually update the `GridstackItemComponent.option` directly - see [modifyNgFor()](./app.component.ts#L83) example.
90+
- 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.
91+
5592

56-
thank you!
5793
Would appreciate getting help doing the same for React and Vue (2 other popular frameworks)
58-
- Alain

demo/angular/src/app/app.component.html

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
<button (click)="show=0">Simple</button>
55
<button (click)="show=1">ngFor case</button>
66
<button (click)="show=2">ngFor custom command</button>
7-
<button (click)="show=3">Component</button>
7+
<button (click)="show=3">Component ngFor</button>
8+
<button (click)="show=4">Component Dynamic</button>
89
</div>
910

1011
<div class="test-container">
@@ -13,16 +14,26 @@
1314
<angular-ng-for-cmd-test *ngIf="show===2"></angular-ng-for-cmd-test>
1415

1516
<div *ngIf="show===3" >
16-
<p><b>COMPONENT</b>: Most complete example that uses Component wrapper for grid and gridItem</p>
17-
<button (click)="add(grid)">add item</button>
18-
<button (click)="delete(grid)">remove item</button>
19-
<button (click)="modify(grid)">modify item</button>
20-
<button (click)="newLayout(grid)">new layout</button>
21-
<gridstack #grid [options]="gridOptions" (changeCB)="onChange($event)" (resizestopCB)="onResizeStop($event)">
17+
<p><b>COMPONENT ngFor</b>: Most complete example that uses Component wrapper for grid and gridItem</p>
18+
<button (click)="addNgFor()">add item</button>
19+
<button (click)="deleteNgFor()">remove item</button>
20+
<button (click)="modifyNgFor(gridComp)">modify item</button>
21+
<button (click)="newLayoutNgFor()">new layout</button>
22+
<gridstack #gridComp [options]="gridOptions" (changeGS)="onChange($event)" (resizestop)="onResizeStop($event)">
2223
<gridstack-item *ngFor="let n of items; trackBy: identify" [options]="n">
2324
{{n.content}}
2425
</gridstack-item>
2526
</gridstack>
2627
</div>
2728

29+
<div *ngIf="show===4" >
30+
<p><b>COMPONENT dynamic</b>: Best example that uses Component wrapper and dynamic grid creation (drag between grids, from toolbar, etc...)</p>
31+
<button (click)="add(gridComp)">add item</button>
32+
<button (click)="delete(gridComp)">remove item</button>
33+
<button (click)="modify(gridComp)">modify item</button>
34+
<button (click)="newLayout(gridComp)">new layout</button>
35+
<gridstack #gridComp [options]="gridOptionsFull" (changeGS)="onChange($event)" (resizestop)="onResizeStop($event)">
36+
</gridstack>
37+
</div>
38+
2839
</div>

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

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,23 @@ let ids = 1;
1111
})
1212
export class AppComponent {
1313
// which sample to show
14-
show = 3;
14+
public show = 4;
1515

1616
/** sample grid options and items to load... */
17-
public gridOptions: GridStackOptions = {
18-
margin: 5,
19-
float: true,
20-
}
2117
public items: GridStackWidget[] = [
2218
{x: 0, y: 0, minW: 2},
2319
{x: 1, y: 1},
2420
{x: 2, y: 2},
2521
];
22+
public gridOptions: GridStackOptions = {
23+
margin: 5,
24+
float: true,
25+
minRow: 1,
26+
}
27+
public gridOptionsFull: GridStackOptions = {
28+
...this.gridOptions,
29+
children: this.items,
30+
}
2631

2732
constructor() {
2833
// give them content and unique id to make sure we track them during changes below...
@@ -34,45 +39,63 @@ export class AppComponent {
3439

3540
/** called whenever items change size/position/etc.. */
3641
public onChange(data: nodesCB) {
42+
// TODO: update our TEMPLATE list to match ?
43+
// NOTE: no need for dynamic as we can always use grid.save() to get latest layout, or grid.engine.nodes
3744
console.log('change ', data.nodes.length > 1 ? data.nodes : data.nodes[0]);
38-
// TODO: update our list to match ?
3945
}
4046

4147
public onResizeStop(data: elementCB) {
4248
console.log('resizestop ', data.el.gridstackNode);
4349
}
4450

4551
/**
46-
* CRUD TEST operations
52+
* TEST dynamic grid operations - uses grid API directly (since we don't track structure that gets out of sync)
4753
*/
4854
public add(gridComp: GridstackComponent) {
55+
gridComp.grid?.addWidget({x:3, y:0, w:2, content:`item ${ids}`, id:String(ids++)});
56+
}
57+
public delete(gridComp: GridstackComponent) {
58+
gridComp.grid?.removeWidget(gridComp.grid.engine.nodes[0]?.el!);
59+
}
60+
public modify(gridComp: GridstackComponent) {
61+
gridComp.grid?.update(gridComp.grid.engine.nodes[0]?.el!, {w:3})
62+
}
63+
public newLayout(gridComp: GridstackComponent) {
64+
gridComp.grid?.load([
65+
{x:0, y:1, id:'1', minW:1, w:1}, // new size/constrain
66+
{x:1, y:1, id:'2'},
67+
// {x:2, y:1, id:'3'}, // delete item
68+
{x:3, y:0, w:2, content:'new item'}, // new item
69+
]);
70+
}
71+
72+
/**
73+
* TEST TEMPLATE operations for ngFor case - NOT recommended unless you have no GS creating/re-parenting
74+
*/
75+
public addNgFor() {
4976
// new array isn't required as Angular detects changes to content with trackBy:identify()
5077
// this.items = [...this.items, { x:3, y:0, w:3, content:`item ${ids}`, id:String(ids++) }];
51-
this.items.push({x:3, y:0, w:3, content:`item ${ids}`, id:String(ids++)});
78+
this.items.push({x:3, y:0, w:2, content:`item ${ids}`, id:String(ids++)});
5279
}
53-
54-
public delete(gridComp: GridstackComponent) {
80+
public deleteNgFor() {
5581
this.items.pop();
5682
}
57-
58-
public modify(gridComp: GridstackComponent) {
83+
public modifyNgFor(gridComp: GridstackComponent) {
5984
// this will not update the DOM nor trigger gridstackItems.changes for GS to auto-update, so set new option of the gridItem instead
6085
// this.items[0].w = 3;
6186
const gridItem = gridComp.gridstackItems?.get(0);
6287
if (gridItem) gridItem.options = {w:3};
6388
}
64-
65-
/** test updating existing and creating new one */
66-
public newLayout(gridComp: GridstackComponent) {
89+
public newLayoutNgFor() {
6790
this.items = [
6891
{x:0, y:1, id:'1', minW:1, w:1}, // new size/constrain
6992
{x:1, y:1, id:'2'},
7093
// {x:2, y:1, id:'3'}, // delete item
71-
{x:3, y:0, w:3, content:'new item'}, // new item
94+
{x:3, y:0, w:2, content:'new item'}, // new item
7295
];
7396
}
7497

75-
// ngFor unique node id to have correct match between our items used and GS
98+
// ngFor TEMPLATE unique node id to have correct match between our items used and GS
7699
public identify(index: number, w: GridStackWidget) {
77100
return w.id; // or use index if no id is set and you only modify at the end...
78101
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ import { AngularSimpleComponent } from './simple';
2020
imports: [
2121
BrowserModule
2222
],
23+
exports: [
24+
GridstackComponent,
25+
GridstackItemComponent,
26+
],
27+
entryComponents: [ // entry list needed to dynamically create those
28+
GridstackComponent,
29+
GridstackItemComponent,
30+
],
2331
providers: [],
2432
bootstrap: [AppComponent]
2533
})

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

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { GridItemHTMLElement, GridStackNode } from 'gridstack';
1313
selector: 'gridstack-item',
1414
template: `
1515
<div class="grid-stack-item-content">
16+
{{options.content}}
1617
<ng-content></ng-content>
1718
</div>`,
1819
styles: [`
@@ -24,24 +25,24 @@ export class GridstackItemComponent {
2425

2526
/** list of options for creating/updating this item */
2627
@Input() public set options(val: GridStackNode) {
27-
if (this.element.gridstackNode?.grid) {
28+
if (this.el.gridstackNode?.grid) {
2829
// already built, do an update...
29-
this.element.gridstackNode.grid.update(this.element, val);
30+
this.el.gridstackNode.grid.update(this.el, val);
3031
} else {
3132
// store our custom element in options so we can update it and not re-create a generic div!
32-
val.el = this.element;
33+
val.el = this.el;
3334
this._options = val;
3435
}
3536
}
3637
/** return the latest grid options (from GS once built, otherwise initial values) */
3738
public get options(): GridStackNode {
38-
return this.element.gridstackNode || this._options || {};
39+
return this.el.gridstackNode || this._options || {};
3940
}
4041

4142
private _options?: GridStackNode;
4243

4344
/** return the native element that contains grid specific fields as well */
44-
public get element(): GridItemHTMLElement { return this.elementRef.nativeElement; }
45+
public get el(): GridItemHTMLElement { return this.elementRef.nativeElement; }
4546

4647
/** clears the initial options now that we've built */
4748
public clearOptions() {
@@ -50,12 +51,4 @@ export class GridstackItemComponent {
5051

5152
constructor(private readonly elementRef: ElementRef<GridItemHTMLElement>) {
5253
}
53-
54-
// none of those have parentElement set from which we could get the grid to auto-init ourself!
55-
// so we will let the parent track us instead...
56-
// ngOnInit() {
57-
// this.element.parentElement
58-
// }
59-
// ngAfterContentInit() {
60-
// }
6154
}

0 commit comments

Comments
 (0)