Dashboards and Dashboard Widgets - Part 1

In a previous post, I wrote about Angular Material's toolbar and sidenav components.

In this post, I'll walk you through the steps I followed in order to add Dashboard support to Serendipity:

Getting Started

After a little research (see below) I decided to try Angular Gridster 2 (angular-gridster2). Angular Gridster 2 supports multi column and multi row layout, drag and move, drag and resize, drag and drop and more.

Step 1: Install Angular Gridster 2

I installed (the Angular 7 version of) Angular Gridster 2 using npm:

npm install angular-gridster2@7.2.0 --save

Step 2: Create a Dashboard Library

I used the Angular CLI to generate the scaffolding for a new library:

ng generate library dashboard --prefix=dashboard

Step 3: Import the Angular Gridster 2 module

I added the GridsterModule to the Dashboard module's imports array:

...

import { GridsterModule } from 'angular-gridster2';

@NgModule({
  imports: [
  
    ...
    
    GridsterModule
  ],
  declarations: [ DashboardComponent ],
  providers: [],
  exports: [ DashboardComponent ],
  entryComponents: []
})
export class DashboardModule {}

Step 4: Create a Dashboard Service

I generated the scaffolding for a new service:

ng generate service services/mocks/dashboard --project=dashboard

Note: To make it easier to switch between layout components and to use a familiar naming convention (i.e., Dashboard and Widget) I extended angular-gridster2's interfaces, for example:

export interface DashboardConfig extends GridsterConfig {}

export interface DashboardWidget extends DashboardItem {}

export interface Dashboard {
  id?: string;
  name?: string;
  widgets?: Array<DashboardWidget>;
}

I updated the Dashboard service as follows:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

import { Dashboard } from '../../../models/models';

@Injectable({
  providedIn: 'root'
})
export class MockDashboardService {

  private readonly DASHBOARDS = 'assets/data/dashboards.json';

  constructor(protected httpClient: HttpClient) {}

  public getDashboards(): Observable<Dashboard[]> {
  
    return this.httpClient.get<Dashboard[]>(this.DASHBOARDS);
  }

  public getDashboard(dashboardId: string): Observable<Dashboard>  {

    return this.httpClient.get<Dashboard[]>(this.DASHBOARDS).pipe(
      map((dashboards: Dashboard[]) =>
        dashboards.find(dashboard => dashboard.id === dashboardId)));
  }

}

dashboards.json:

[

  {
    "id": "4",
    "name": "Sample Dashboard 4",
    "widgets": [
      {
        "id": "1",
        "name": "Timeline",
        "component": "timeline",
        "cols": 8,
        "rows": 6,
        "y": 0,
        "x": 0
      }
    ]
  }

]

Step 5: Create a Dashboard Component

I used the Angular CLI to generate the scaffolding for a new component:

ng generate component components/dashboard --project=dashboard

I updated the Dashboard component's template as follows:

<gridster [options]="options">

  <ng-container *ngFor="let item of items">

    <gridster-item [item]="item">

      <!-- Your content goes here -->

    </gridster-item>

  </ng-container>

</gridster>

And, I used the options input property to configure the Dashboard component's Gridster instance:

...

import { MockDashboardService } from '../../services/mocks/dashboard/mock-dashboard.service';

@Component({
  selector: 'dashboard',
  templateUrl: './dashboard.component.html',
  styleUrls: ['./dashboard.component.scss']
})
export class DashboardComponent implements OnInit {

  @Input() dashboardId: string;

  public options: DashboardConfig;
  public items: DashboardWidget[];

  protected subscription: Subscription;

  ...
  
  constructor(private dashboardService: MockDashboardService) {}
  
  public ngOnInit() {
  
    this.options = {
      disablePushOnDrag: true,
      draggable: { enabled: true },
      gridType: GridType.Fit,
      resizable: { enabled: true }
    };
    
    this.subscribe();
    
  }
  
  protected subscribe() {

    this.subscription = this.dashboardService.getDashboard(this.dashboardId).subscribe(data => {
      this.items = data.widgets;
    });

  }  

}

Now all we need to do is add our new Dashboard directive to a host component and we're good to go:

<dashboard [dashboardId]="dashboardId"></dashboard>

Check out the demo on Firebase Hosting: Serendipity

What's Next

In the next post, I'll walk you through the steps I followed in order to add support for Dashboard Widgets and a Widget Tool Palette:

Source Code:
Research: