Skip to content

Commit 6afb383

Browse files
authored
Merge pull request #2130 from adumesny/master
Complete Angular wrapper
2 parents 8f0b663 + 0da2115 commit 6afb383

File tree

11 files changed

+257
-159
lines changed

11 files changed

+257
-159
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -293,8 +293,8 @@ See [example](http://gridstack.github.io/gridstack.js/demo/mobile.html).
293293

294294
search for ['gridstack' under NPM](https://www.npmjs.com/search?q=gridstack&ranking=popularity) for latest, more to come...
295295

296-
- **Angular**: see our <a href="https://github.com/gridstack/gridstack.js/tree/master/demo/angular" target="_blank">Angular Demo</a>. Working on exposing the Angular component wrapper we use internally, or use directive (TBD), or use ngFor example, etc... There are many way to do this.
297-
- **Angular9**: [lb-gridstack](https://github.com/pfms84/lb-gridstack) Note: very old v0.3 gridstack instance so recommend for **concept ONLY**.
296+
- **Angular**: we now ship out of the box with Angular wrapper components - see <a href="https://github.com/gridstack/gridstack.js/tree/master/demo/angular/src/app" target="_blank">Angular Demo</a>.
297+
- **Angular9**: [lb-gridstack](https://github.com/pfms84/lb-gridstack) Note: very old v0.3 gridstack instance so recommend for **concept ONLY if you wish to use directive instead**.
298298
- **AngularJS**: [gridstack-angular](https://github.com/kdietrich/gridstack-angular)
299299
- **Ember**: [ember-gridstack](https://github.com/yahoo/ember-gridstack)
300300
- **knockout**: see [demo](https://gridstackjs.com/demo/knockout.html) using component, but check [custom bindings ticket](https://github.com/gridstack/gridstack.js/issues/465) which is likely better approach.

demo/angular/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
2+
see [**Angular wrapper doc**](./src/app/README.md) for actual usage. this is generic ng project info...
3+
14
# Angular
25

36
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 12.2.13.

demo/angular/src/app/README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Angular wrapper
2+
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.
4+
5+
## Usage
6+
7+
Code
8+
9+
```typescript
10+
import { GridStackOptions, GridStackWidget } from 'gridstack';
11+
import { GridstackComponent, nodesCB } from './gridstack.component';
12+
13+
/** sample grid options and items to load... */
14+
public gridOptions: GridStackOptions = {
15+
margin: 5,
16+
float: true,
17+
}
18+
public items: GridStackWidget[] = [
19+
{x:0, y:0, minW:2},
20+
{x:1, y:1},
21+
{x:2, y:2},
22+
];
23+
24+
// called whenever items change size/position/etc..
25+
public onChange(data: nodesCB) {
26+
console.log('change ', data.nodes.length > 1 ? data.nodes : data.nodes[0]);
27+
}
28+
29+
// ngFor unique node id to have correct match between our items used and GS
30+
public identify(index: number, w: GridStackWidget) {
31+
return w.id;
32+
}
33+
```
34+
HTML
35+
```angular2html
36+
<gridstack [options]="gridOptions" (changeCB)="onChange($event)">
37+
<gridstack-item *ngFor="let n of items; trackBy: identify" [options]="n">
38+
Hello
39+
</gridstack-item>
40+
</gridstack>
41+
```
42+
43+
## Demo
44+
You can see a fuller example at [app.component](https://github.com/gridstack/gridstack.js/blob/master/demo/angular/src/app/app.component.ts).
45+
46+
to build the demo, go to demo/angular and run `yarn` + `yarn start` and Navigate to `http://localhost:4200/`
47+
48+
### Caveats
49+
50+
- This wrapper needs v7.1.2+ to run as it needs the latest changes
51+
- 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 call the `Gridstack.update()` method directly.
52+
- 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.
Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,28 @@
11

22
<div>
3-
<p>Pick a sample test case to load:</p>
3+
<p>Pick a demo to load:</p>
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</button>
88
</div>
99

1010
<div class="test-container">
1111
<angular-simple-test *ngIf="show===0"></angular-simple-test>
1212
<angular-ng-for-test *ngIf="show===1"></angular-ng-for-test>
1313
<angular-ng-for-cmd-test *ngIf="show===2"></angular-ng-for-cmd-test>
1414

15-
<gridstack *ngIf="show===3" [options]="gridstackConfig" (changeCB)="onChange($event)" (resizestopCB)="onResizeStop($event)">
16-
<gridstack-item [id]="'a'" [x]="0" [y]="0" [w]="6" [h]="4" [minH]="2" [minW]="3">
17-
HELLO
18-
</gridstack-item>
19-
</gridstack>
15+
<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)="change(grid)">modify item</button>
20+
<button (click)="newLayout(grid)">new layout</button>
21+
<gridstack #grid [options]="gridOptions" (changeCB)="onChange($event)" (resizestopCB)="onResizeStop($event)">
22+
<gridstack-item *ngFor="let n of items; trackBy: identify" [options]="n">
23+
{{n.content}}
24+
</gridstack-item>
25+
</gridstack>
26+
</div>
2027

2128
</div>

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

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,80 @@
11
import { Component } from '@angular/core';
2-
import { GridStackOptions } from 'gridstack';
3-
import { elementCB, nodesCB } from './gridstack.component';
2+
import { GridStackOptions, GridStackWidget } from 'gridstack';
3+
import { GridstackComponent, elementCB, nodesCB } from './gridstack.component';
44

5+
// unique ids sets for each item for correct ngFor updating
6+
let ids = 1;
57
@Component({
68
selector: 'app-root',
79
templateUrl: './app.component.html',
810
styleUrls: ['./app.component.css']
911
})
1012
export class AppComponent {
1113
// which sample to show
12-
show = 1;
14+
show = 3;
1315

14-
public gridstackConfig: GridStackOptions = {
16+
/** sample grid options and items to load... */
17+
public gridOptions: GridStackOptions = {
1518
margin: 5,
1619
float: true,
1720
}
21+
public items: GridStackWidget[] = [
22+
{x: 0, y: 0, minW: 2},
23+
{x: 1, y: 1},
24+
{x: 2, y: 2},
25+
];
1826

19-
public onChange(h: nodesCB) {
20-
console.log('change ', h.nodes.length > 1 ? h.nodes : h.nodes[0]);
27+
constructor() {
28+
// give them content and unique id to make sure we track them during changes below...
29+
this.items.forEach(w => {
30+
w.content = `item ${ids}`;
31+
w.id = String(ids++);
32+
})
2133
}
2234

23-
public onResizeStop(h: elementCB) {
24-
console.log('resizestop ', h.el.gridstackNode);
35+
/** called whenever items change size/position/etc.. */
36+
public onChange(data: nodesCB) {
37+
console.log('change ', data.nodes.length > 1 ? data.nodes : data.nodes[0]);
38+
// TODO: update our list to match ?
39+
}
40+
41+
public onResizeStop(data: elementCB) {
42+
console.log('resizestop ', data.el.gridstackNode);
43+
}
44+
45+
/**
46+
* CRUD TEST operations
47+
*/
48+
public add(comp: GridstackComponent) {
49+
// new array isn't required as Angular seem to detect changes to content
50+
// 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++)});
52+
}
53+
54+
public delete(comp: GridstackComponent) {
55+
this.items.pop();
56+
}
57+
58+
public change(comp: GridstackComponent) {
59+
// this will not update the DOM nor trigger gridstackItems.changes for GS to auto-update, so call GS update() instead
60+
// this.items[0].w = 3;
61+
// comp.updateAll();
62+
const n = comp.grid?.engine.nodes[0];
63+
if (n?.el) comp.grid?.update(n.el, {w:3});
64+
}
65+
66+
/** test updating existing and creating new one */
67+
public newLayout(comp: GridstackComponent) {
68+
this.items = [
69+
{x:0, y:1, id:'1', minW:1, w:1}, // new size/constrain
70+
{x:1, y:1, id:'2'},
71+
// {x:2, y:1, id:'3'}, // delete item
72+
{x:3, y:0, w:3, content:'new item'}, // new item
73+
];
74+
}
75+
76+
// ngFor unique node id to have correct match between our items used and GS
77+
public identify(index: number, w: GridStackWidget) {
78+
return w.id;
2579
}
2680
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ import { AngularSimpleComponent } from './simple';
1010

1111
@NgModule({
1212
declarations: [
13-
AppComponent,
14-
AngularSimpleComponent,
15-
AngularNgForTestComponent,
1613
AngularNgForCmdTestComponent,
14+
AngularNgForTestComponent,
15+
AngularSimpleComponent,
16+
AppComponent,
1717
GridstackComponent,
1818
GridstackItemComponent,
1919
],
Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import {ChangeDetectionStrategy, Component, ElementRef, Input, OnInit, Renderer2} from '@angular/core';
2-
import { GridItemHTMLElement, numberOrString } from 'gridstack';
1+
import { ChangeDetectionStrategy, Component, ElementRef, Input } from '@angular/core';
2+
import { GridItemHTMLElement, GridStackNode } from 'gridstack';
33

44
@Component({
55
selector: 'gridstack-item',
@@ -12,29 +12,40 @@ import { GridItemHTMLElement, numberOrString } from 'gridstack';
1212
`],
1313
changeDetection: ChangeDetectionStrategy.OnPush,
1414
})
15-
export class GridstackItemComponent implements OnInit {
15+
export class GridstackItemComponent {
1616

17-
@Input() public x: number;
18-
@Input() public y: number;
19-
@Input() public w: number;
20-
@Input() public h: number;
21-
@Input() public minW: number;
22-
@Input() public minH: number;
23-
@Input() public maxW: number;
24-
@Input() public maxH: number;
25-
@Input() public id: numberOrString;
26-
27-
constructor(
28-
private readonly _elementRef: ElementRef<GridItemHTMLElement>,
29-
private readonly renderer2: Renderer2,
30-
) {
17+
/** list of options for creating this item */
18+
@Input() public set options(val: GridStackNode) {
19+
val.el = this.element; // connect this element to options so we can convert to widget later
20+
if (this.element.gridstackNode?.grid) {
21+
this.element.gridstackNode.grid.update(this.element, val);
22+
} else {
23+
this._options = val; // store initial values (before we're built)
24+
}
25+
}
26+
/** return the latest grid options (from GS once built, otherwise initial values) */
27+
public get options(): GridStackNode {
28+
return this.element.gridstackNode || this._options || {};
3129
}
3230

33-
get elementRef(): ElementRef<GridItemHTMLElement> {
34-
return this._elementRef;
31+
private _options?: GridStackNode;
32+
33+
/** return the native element that contains grid specific fields as well */
34+
public get element(): GridItemHTMLElement { return this.elementRef.nativeElement; }
35+
36+
/** clears the initial options now that we've built */
37+
public clearOptions() {
38+
delete this._options;
3539
}
3640

37-
public ngOnInit(): void {
38-
this.renderer2.addClass(this._elementRef.nativeElement, 'grid-stack-item');
41+
constructor(private readonly elementRef: ElementRef<GridItemHTMLElement>) {
3942
}
43+
44+
// none of those have parentElement set from which we could get the grid to auto-init ourself!
45+
// so we will let the parent track us instead...
46+
// ngOnInit() {
47+
// this.element.parentElement
48+
// }
49+
// ngAfterContentInit() {
50+
// }
4051
}

0 commit comments

Comments
 (0)