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+ / A n d r o i d | w e b O S | i P h o n e | i P a d | i P o d | B l a c k B e r r y | I E M o b i l e | O p e r a M i n i / 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