Skip to content

Commit a1b9470

Browse files
authored
Merge pull request #1961 from MeMeMax/master
fix and improve angular ng-for demo
2 parents b9c8cec + 0c66cec commit a1b9470

File tree

1 file changed

+102
-60
lines changed

1 file changed

+102
-60
lines changed

demo/angular-ngFor.ts

Lines changed: 102 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,107 @@
11
/**
22
* Example using Angular ngFor to loop through items and create DOM items
33
*/
4-
import { Component, AfterViewInit, OnChanges, SimpleChanges, Input, ChangeDetectionStrategy } from '@angular/core';
54

6-
import { GridStack, GridStackWidget } from 'gridstack';
7-
8-
@Component({
9-
selector: 'app-angular-ng-for-test',
10-
template: `
11-
<button (click)="add()">add item</button><button (click)="delete()">remove item</button><button (click)="change()">modify item</button>
12-
<div class="grid-stack">
13-
<!-- using angular templating to create DOM, otherwise an easier way is to simply call grid.load(items) -->
14-
<div *ngFor="let n of items; let i = index" class="grid-stack-item" [attr.gs-w]="n.w" [attr.gs-h]="n.h" [attr.gs-x]="n.x" [attr.gs-y]="n.y">
15-
<div class="grid-stack-item-content">item {{i}}</div>
16-
</div>
17-
</div>
18-
`,
19-
// gridstack.min.css and other custom styles should be included in global styles.scss
20-
21-
// tell Angular only @Input will change - doesn't help
22-
// changeDetection: ChangeDetectionStrategy.OnPush
23-
})
24-
export class AngularNgForTestComponent implements AfterViewInit, OnChanges {
5+
import { Component, AfterViewInit, Input, ViewChildren, QueryList } from "@angular/core";
6+
import { Subject, zip } from "rxjs";
7+
8+
import { GridStack, GridStackWidget } from 'gridstack';
9+
10+
@Component({
11+
selector: "app-angular-ng-for-test",
12+
template: `
13+
<button (click)="add()">add item</button>
14+
<button (click)="delete()">remove item</button>
15+
<button (click)="change()">modify item</button>
16+
<div class="grid-stack">
17+
<!-- using angular templating to create DOM, otherwise an easier way is to simply call grid.load(items) -->
18+
<div
19+
*ngFor="let n of items; let i = index; trackBy: identify"
20+
[id]="i"
21+
class="grid-stack-item"
22+
[attr.gs-w]="n.w"
23+
[attr.gs-h]="n.h"
24+
[attr.gs-x]="n.x"
25+
[attr.gs-y]="n.y"
26+
#gridStackItem
27+
>
28+
<div class="grid-stack-item-content">item {{ i }}</div>
29+
</div>
30+
</div>
31+
`,
32+
styles: [
33+
`
34+
// !!!IMPORTANT!!! Import this through your styles.scss or angular.json! This is just for demo purposes
35+
:host {
36+
::ng-deep {
37+
@import "demo";
38+
}
39+
}
40+
`,
41+
],
42+
})
43+
export class AngularNgForTestComponent implements AfterViewInit {
44+
@ViewChildren("gridStackItem") gridstackItems: QueryList<any>;
2545
@Input() public items: GridStackWidget[] = [
26-
{ x: 0, y: 0, w: 9, h: 6},
27-
{ x: 9, y: 0, w: 3, h: 3},
28-
{ x: 9, y: 3, w: 3, h: 3},
29-
];
30-
private grid: GridStack;
31-
32-
constructor() {}
33-
34-
// wait until after DOM is ready to init gridstack - can't be ngOnInit() as angular ngFor needs to run first!
35-
public ngAfterViewInit() {
36-
this.grid = GridStack.init({
37-
cellHeight: 70,
38-
});
39-
}
40-
41-
/**
42-
* this would be easier with addWidget(), removeWidget(), update() but simulating angular change detection instead...
43-
*/
44-
public add() {
45-
// this SHOULD trigger ngOnChanges() but not seeing it... and doing ngDoCheck() seem extreme ?
46-
// https://www.reddit.com/r/angular/comments/azjefs/change_detection_for_arraysobjects/
47-
// https://angular.io/guide/lifecycle-hooks#docheck
48-
this.items = [...this.items, {x: 1, y: 6, w: 3}];
49-
// this.items.push({x: 1, y: 6, w: 3});
50-
}
51-
public delete() {
52-
this.items.pop(); // todo
53-
}
54-
public change() {
55-
this.items[0].w = 1; // todo
56-
}
57-
58-
public ngOnChanges(changes: SimpleChanges) {
59-
if (changes.items) {
60-
// TODO: ... figure what is new and call makeWidget(), old -> removeWidget(el,false) and changed -> update()
61-
console.log('items changed');
62-
}
63-
}
64-
}
65-
46+
{ x: 0, y: 0, w: 1, h: 1 },
47+
{ x: 1, y: 1, w: 1, h: 1 },
48+
{ x: 2, y: 2, w: 1, h: 1 },
49+
];
50+
51+
private grid: GridStack;
52+
private widgetToMake: Subject<{
53+
action: "add" | "remove" | "update";
54+
id: number;
55+
}> = new Subject(); // consider to use a statemanagement like ngrx component store to do this
56+
57+
constructor() {}
58+
59+
// wait until after DOM is ready to init gridstack - can't be ngOnInit() as angular ngFor needs to run first!
60+
public ngAfterViewInit() {
61+
this.grid = GridStack.init({
62+
alwaysShowResizeHandle:
63+
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
64+
navigator.userAgent
65+
),
66+
margin: 5,
67+
float: true,
68+
});
69+
70+
// To sync dom manipulation done by Angular and widget manipulation done by gridstack we need to zip the observables
71+
zip(this.gridstackItems.changes, this.widgetToMake).subscribe(
72+
([changedWidget, widgetToMake]) => {
73+
if (widgetToMake.action === "add") {
74+
this.grid.makeWidget(`#${widgetToMake.id}`);
75+
} else if (widgetToMake.action === "remove") {
76+
const removeEl = this.grid
77+
.getGridItems()
78+
.find((el) => el.id === `${widgetToMake.id}`);
79+
this.grid.removeWidget(removeEl);
80+
}
81+
}
82+
);
83+
}
84+
85+
/**
86+
* CRUD operations
87+
*/
88+
public add() {
89+
this.items = [...this.items, { x: 3, y: 0, w: 3 }];
90+
this.widgetToMake.next({ action: "add", id: this.items.length - 1 });
91+
}
92+
93+
public delete() {
94+
this.items.pop();
95+
this.widgetToMake.next({ action: "remove", id: this.items.length });
96+
}
97+
98+
// a change of a widget doesn´t change to amount of items in ngFor therefore we don´t need to do it through the zip function above
99+
public change() {
100+
const updateEl = this.grid.getGridItems().find((el) => el.id === `${0}`);
101+
this.grid.update(updateEl, { w: 2 });
102+
}
103+
104+
identify(index: number) {
105+
return index;
106+
}
107+
}

0 commit comments

Comments
 (0)