This post is part of a series describing a demo project that employs various real-world patterns and tools to provide access to data in a MongoDB database for the DevExtreme grid widgets. You can find the introduction and overview to the post series by following this link.
In the last post on the DevExtreme widgets I promised I would show alternative UI front-end implementations using our Knockout and Angular adapters. I have now added two branches to the demo repository for these two samples.
The Knockout front-end
For Knockout, I have kept the existing structure of the webapp project. Knockout is an MVVM-style library that focuses mainly on data binding within single pages. There are modules for Knockout that implement navigation and external templating features, but I found that some of these projects have been deprecated now. It has always been my personal opinion that Knockout is best viewed as an unopinionated system for ad-hoc in-page use rather than a full-blown SPA framework, so I kept things simple by going with the old project structure.
The difference between the Knockout front-end and the basic jQuery one is the widget instantiation mechanism. With jQuery, I had divs with ids in the HTML and I used jQuery instructions to instantiate DevExtreme widgets for these divs. With Knockout, I use data-bind attributes instead (from index.html):
<div data-bind="dxToolbar: toolbarOptions"></div>
The binding refers to toolbarOptions
(and in other pages there are similar gridOptions
), which I chose to make part of my model. In index.js you can find the model definition and the toolbarOptions
as part of it. You will notice that the data structure used for the options is the same as in the jQuery project.
The call ko.applyBindings(model)
at the end of the file tells Knockout that it needs to evaluate all declared bindings using the model I pass in.
There’s only one other trick I used in this setup. In dataGrid.js you can see an empty grid
element declared as part of the model. This is used to store a reference to the grid in the onInitialized
event handler, so that I can later refer to the grid from the Reload grid toolbar button’s onClick
handler.
If you are interested in some further information about the use of Knockout with DevExtreme, I recommend this documentation page as a starting point.
The Angular front-end
Note that this front-end targets Angular, not AngularJS.
With Angular, everything is different. I wanted to create a project that would have a standard structure, so I created one using the Fountain Webapp Generator. I had to update lots of modules to the latest versions, but the general structure of the template application is still recognizable.
Fountain uses Gulp as its build and automation tool and I didn’t change this. Of course it’s a contrast to the other Makefile
-based projects, and I found myself thinking, again, what a cumbersome tool Gulp really is. There’s certainly a lot it can do with all its additional modules, but the overall setup ends up spread out across loads of different files, and standard “recipes” are hard to recognize because it’s all in code. Plus, everything the Gulp modules do can be achieved at least as easily using a few simple command lines. I appreciate that it’s an integrated environment that doesn’t require a separate tool (make!), but I can’t say I’m a fan…
Back to Angular. I set up the project according to the instructions for the devextreme-angular package. Since I decided to go with webpack, I also followed the steps in the related readme for devextreme-angular. I imported the required modules and added them to @NgModule
:
import { DxButtonModule, DxToolbarModule, DxPivotGridModule, DxDataGridModule } from "devextreme-angular"; @NgModule({ imports: [ BrowserModule, routing, DxButtonModule, DxToolbarModule, DxPivotGridModule, DxDataGridModule ], ...
My individual views (component templates) can now instantiate the DevExtreme widgets using Angular syntax (from menu.html):
<dx-toolbar><dxi-item location="before" locateInMenu="auto"><dx-button type="danger" text="Create 1000 Test Objects" (onClick)="createTestObjects()" [disabled]="testObjectsDisabled"></dx-button></dxi-item><dxi-item location="before" locateInMenu="auto"><dx-button type="success" text="Show Data Grid" [routerLink]="['/dataGrid']"></dx-button></dxi-item><dxi-item location="before" locateInMenu="auto"><dx-button type="success" text="Show Pivot Grid" [routerLink]="['/pivotGrid']"></dx-button></dxi-item></dx-toolbar>
You can see how our widgets use Angular binding syntax to bind events and properties, and even the routerLink binding works correctly with the button to navigate between the two views.
To interface with the custom datastore for the data grid and pivot grid widgets, I made a two small changes to the dataStore.js. First, I imported the two elements $
and CustomStore
explicitly:
import $ from "jquery"; import CustomStore from "devextreme/data/custom_store";
The jQuery reference is needed because my implementation uses it for AJAX calls. Of course there would be another way of doing this with Angular, but I decided to leave it in place for consistency, and also because the DevExtreme widgets still use jQuery under the covers anyway.
At the end of the file, I export the dataStore
so it can be pulled in from other files:
export default dataStore;
With this in hand, I created two components to represent the data grid and pivot grid views. Here is pivotGrid.ts as an example:
import { Component, ViewChild } from "@angular/core"; import { DxPivotGridComponent } from "devextreme-angular"; import dataStore from "./dataStore.js"; @Component({ template: require("./pivotGrid.html") }) export class PivotGridComponent { @ViewChild(DxPivotGridComponent) grid: DxPivotGridComponent; dataStore = dataStore; refresh() { this.grid.instance.getDataSource().reload(); } }
I’m taking advantage of the ViewChild decorator to have access to the grid
component created in the view. The template is defined in pivotGrid.html and it binds to the dataStore
defined in the class:
...<dx-pivot-grid><dxo-field-panel visible="true"></dxo-field-panel><dxo-data-source [store]="dataStore" remoteOperations="true" retrieveFields="false"><dxi-field dataField="date1" dataType="date" format="shortDate" allowFiltering="false" allowSorting="true" allowSortingBySummary="true" area="filter"></dxi-field> ...
The data grid view and component are implemented along the same lines and there were no surprises - everything worked just like it should!
In addition to the devextreme-angular readme linked above, I recommend also reading the documentation on DevExtreme/Angular integration.