Skip to main content Skip to footer

Exploring Wijmo with Angular Elements

Testing Wijmo's Angular Components

There has been a recent buzz within the Angular community ever since the Angular Elements revelation at the AngularMix conference. When Angular Elements was released with Angular 6, the team at GrapeCity decided to test our Wijmo’s Angular Components to see if they are up for a challenge.

In this blog, we will cover:

  • Defining Angular Elements
  • How to create Angular Elements using Wijmo
  • Creating Custom Elements
  • Register Custom Elements
  • Embedding the Custom Components
  • Rendering of Custom Elements

What are Angular Elements?

Angular Elements are Angular components packaged as custom elements. Custom elements are part of the Web Components standard which allows an extension of the scope of angular code, so it can be used outside the context of an Angular application.

In simple terms this allows Angular developers to create components that can be embedded in the front-end applications regardless of the underlying JavaScript Framework (React, Vue, Ember etc.)

Creating Angular Elements using Wijmo

We will be creating custom elements using Wijmos’ Angular Interop Controls by utilizing the Angular Elements project. Wijmo’s FlexGrid is a good candidate for this purpose where we will reveal several FlexGrid attributes and related components.

A practical FlexGrid implementation should include: column definitions, sorting and filtering capabilities. The sorting feature comes out of the box with the FlexGrid implementation, so defining the FlexGrid grid a as Custom Element should suffice. As for column definitions and the filter feature, this will require implementing them as custom components.

Step 1: Defining Custom Elements

Let's now create the web component for FlexGrid by first defining the custom elements for FlexGrid.

Custom Element [wj-custom-elem-grid]: FlexGrid

  1. Import the OnInit, Component, forwardRef, classes from Angular Core Module.
  2. Import WjFlexGrid, wjFlexGridMeta classes from wijmo.angular2.grid module. This is mandatory as we need to use FlexGrid from Wijmo Angular Interop.
  3. Define the Component for FlexGrid using @ Component. The selector defined here will be used as the Custom Component.
  4. Inside the WjSampleGridComponent Class we can customize FlexGrid’s attribute. HTML attributes support only string value type; therefore, to add support for Boolean and number values type via attributes we need to explicitly convert string values into Boolean and Number respectively. This can be done by overriding the setInputValue method of ngElementStrategy.
wj-custom-elem-grid.component.ts

import { Component, OnInit, forwardRef, OnChanges, ElementRef } from

'@angular/core';

import { WjFlexGrid, wjFlexGridMeta } from 'wijmo/wijmo.angular2.grid';

import * as wjcGrid from 'wijmo/wijmo.grid';

@Component({

selector: 'app-wj-custom-elem-grid',

templateUrl: './wj-custom-elem-grid.component.html',

styleUrls: ['./wj-custom-elem-grid.component.css'],

inputs: [...wjFlexGridMeta.inputs],

outputs:[...wjFlexGridMeta.outputs],

providers: [

{ provide: 'WjComponent', useExisting: forwardRef(() =>

WjCustomElemGridComponent) },

...wjFlexGridMeta.providers

]

})

export class WjCustomElemGridComponent extends WjFlexGrid implements OnInit {

//method to check if the specified prop is boolean type

private isBoolProp(name:string){

//list of properties with boolean values

const boolValues=[

'allowAddNew',

'allowDelete',

'allowSorting',

'autoClipboard',

'autoGenerateColumns',

'autoScroll',

'cloneFrozenCells',

'deferResizing',

'isDisabled',

'isReadOnly',

'newRowAtTop', 'preserveSelectedState',

'rightToLeft',

'showAlternatingRows',

'showGroups',

'showSort'

];

return boolValues.includes(name);

}

//alternative of constructer when extending wijmo controls

created(){

//save setInputValue provided by angular elements in other reference

(this.hostElement as

any).ngElementStrategy.__proto__.setInputValue2=(this.hostElement as

any).ngElementStrategy.__proto__.setInputValue;

//overwrite saveInputValue method to modify string attribute values to be

converted to bool or number(if possible) as required

(this.hostElement as

any).ngElementStrategy.__proto__.setInputValue=function(prop,value){

//check if current prop is boolean or number type and need to be changed

if(this.componentRef.instance.isBoolProp(prop)&&typeof value=='string'){

value=this.componentRef.instance._getBool(value);

}else if(this.componentRef.instance.isNumberProp(prop)&&typeof

value=='string'){

if(this.componentRef.instance._getNum(value)){

value=this.componentRef.instance._getNum(value);

}

}

//call the initially saved setInputValue method with the modified value

(this.componentRef.instance.hostElement as

any).ngElementStrategy.__proto__.setInputValue2.call(this,prop,value);

}

}

//convert string to boolean

private _getBool(value:string){

if(value==="false"){

return false;

}else{

return true;

}

}

//check if given property is number type

private isNumberProp(name:string){

const numValues=[ 'frozenColumns',

'frozenRows'

];

return numValues.includes(name);

}

//convert string to number

private _getNum(value:string){

var num=Number(value);

if(isNaN(num)){

return;

}else{

return num;

}

}

ngOnInit() {

//initialization work if - required

}

}

Custom Element [wj-custom-elem-grid-column]: FlexGrid Columns

The concept of creating custom element for FlexGrid columns is similar to the FlexGrid Component (that we created in the last step). Since we need to define columns for the FlexGrid, we will be creating a custom element for a FlexGrid column.

We have customized some additional Boolean properties for FlexGrid Column. The property list is defined inside the boolProps variable.

wj-custom-elem-grid-column.component.ts

import { Component, OnInit, ElementRef } from '@angular/core';

import { wjFlexGridColumnMeta } from 'wijmo/wijmo.angular2.grid';

import { Column } from 'wijmo/wijmo.grid';

@Component({

selector: 'app-wj-custom-elem-grid-column',

templateUrl: './wj-custom-elem-grid-column.component.html',

styleUrls: ['./wj-custom-elem-grid-column.component.css']

})

export class WjCustomElemGridColumnComponent implements OnInit { //list of boolean type props

private _boolProps=[

'allowMerging',

'allowResizing',

'isContentHtml',

'isReadOnly',

'isRequired',

'isSelected',

'isVisible',

'multiLine',

'quickAutoSize',

'showDropDown',

'visible',

'allowSorting'

];

//list of num type props

private _numProps=[

'maxLength',

'maxWidth',

'minWidth',

];

//initialize and attach column to grid

constructor(private el:ElementRef) {

var parent=this.el.nativeElement.parentNode;

//check if wj-grid-column has wj-grid as its parent

if(parent.tagName!="WJ-GRID"){

console.error("wj-grid-column can only be used as a child of wj-grid");

return;

}

var dataJson={};

//prepare initilization data from attributes value

for(var i=0;i<this.el.nativeElement.attributes.length;i++){

var propname=this._getProp(this.el.nativeElement.attributes[i].name);

if(wjFlexGridColumnMeta.inputs.includes(propname)){

var val=this.el.nativeElement.attributes[i].value;

if(this._boolProps.includes(propname)){

val=this._getBool(val?val:"true");

}else if(this._numProps.includes(propname)&&this._getNum(val)){

val=this._getNum(val);

}

dataJson[propname]=val;

}

}

setTimeout(()=>{ let grid=parent['$WJ-CTRL'];

if(!grid){

console.error('parent grid not found');

return;

}

if(!grid._initByColEl){

grid.columns.clear();

grid._initByColEl=true;

}

grid.columns.push(new Column(dataJson));

},0);

}

ngOnInit() {

}

//convert html attributes name to camel case

private _convertToCamel(name){

return name.toLowerCase().replace(/-[a-z]/g,(val)=>{

return val.substr(1).toUpperCase();

});

}

//gets the property name equivalent to attribute name

private _getProp(name:string){

return this._convertToCamel(name);

}

//convert string to boolean

private _getBool(value:string){

if(value==="false"){

return false;

}else{

return true;

}

}

//convert string to number

private _getNum(value:string){

var num=Number(value);

if(isNaN(num)){

return;

}else{

return num;

}

}

Custom Element [wj-custom-elem-grid-column-grid-filter]: FlexGrid Filter

The FlexGrid filter component is intended to provide Filtering ability on FlexGrid Column. Here we have exposed some of the Filter specific properties defaultFilterType', 'filterDefinition','filterColumns', 'showSortButtons','showFilterIcons' and 'filterColumns'`

wj-custom-elem-grid-filter.component.ts

import { Component, OnInit,ElementRef } from '@angular/core';

import { FlexGridFilter } from 'wijmo/wijmo.grid.filter';

@Component({

selector: 'app-wj-custom-elem-grid-filter',

templateUrl: './wj-custom-elem-grid-filter.component.html',

styleUrls: ['./wj-custom-elem-grid-filter.component.css']

})

export class WjCustomElemGridFilterComponent implements OnInit {

//list of filter inputs

private _inputs=[

'defaultFilterType',

'filterDefinition',

'showFilterIcons',

'showSortButtons',

'filterColumns'

];

//list of boolean type inputs

private _boolProps=[

'showFilterIcons',

'showSortButtons',

];

//initialize and attach filter to grid

constructor(private el:ElementRef) {

var parent=this.el.nativeElement.parentNode;

//check if grid-filter has wj-grid as its parent

if(parent.tagName!="WJ-GRID"){

console.error("wj-grid-filter can only be used as a child of wj-grid");

return;

} var dataJson={};

//prepare initialization data for grid-filter

for(var i=0;i<this.el.nativeElement.attributes.length;i++){

var propname=this._getProp(this.el.nativeElement.attributes[i].name);

if(this._inputs.includes(propname)){

var val=this.el.nativeElement.attributes[i].value;

if(this._boolProps.includes(propname)){

val=this._getBool(val?val:"true");

}

if(propname=="filterColumns"){

val=val.split(',');

}

dataJson[propname]=val;

}

}

setTimeout(()=>{

let grid=parent['$WJ-CTRL'];

if(!grid){

console.error('parent grid not found');

return;

}

let filter=new FlexGridFilter(grid,dataJson);

grid['gridFilter']=filter;

},0);

}

ngOnInit() {

// console.log('sample filter on init');

}

//get equivalent property name from attribute name

private _getProp(name:string){

return this._convertToCamel(name);

}

//convert attribute names to camel case

private _convertToCamel(name){

return name.toLowerCase().replace(/-[a-z]/g,(val)=>{

return val.substr(1).toUpperCase();

});

}

//convert string to boolean

private _getBool(value:string){

if(value==="false"){

return false; }else{

return true;

}

}

}

Step 2: Creating and Registering Custom Elements

Now we need to register these three components in declarations and entryComponents arrays of the AppModule.

As part of registering we need to: createCustomElement() method of @angular/elements package creates and returns a class that incapsulates the functionality of the angular component.

Next step is to register the custom element with the browser using customElement.define() method where customElement is part of global CustomElementRegistry API.

app.module.ts

import { NgModule, Injector } from '@angular/core';

import { BrowserModule } from '@angular/platform-browser';

import { createCustomElement } from '@angular/elements';

import { WjCustomElemGridComponent } from './wj-custom-elem-grid/wj-custom-

elem-grid.component';

import { WjCustomElemGridColumnComponent } from './wj-custom-elem-grid-

column/wj-custom-elem-grid-column.component';

import { WjCustomElemGridFilterComponent } from './wj-custom-elem-grid-

filter/wj-custom-elem-grid-filter.component';

export const customElementsArr = [

WjCustomElemGridComponent,WjCustomElemGridColumnComponent,WjCustomElemGridFilt

erComponent,

];

@NgModule({

imports: [

BrowserModule

],

declarations: [

...customElementsArr, ],

entryComponents: [

...customElementsArr

]

})

export class AppModule {

constructor(private injector: Injector) {

const filter = createCustomElement(WjCustomElemGridFilterComponent, {

injector });

customElements.define('wj-grid-filter',filter);

const column = createCustomElement(WjCustomElemGridColumnComponent, {

injector });

customElements.define('wj-grid-column',column);

const grid = createCustomElement(WjCustomElemGridComponent, { injector });

customElements.define('wj-grid',grid);

}

ngDoBootstrap() { }

}

Step 3: Embedding the Wijmo FlexGrid Custom Components

We can now use the Wijmo Components that we created in any HTML file of the project like we use Native HTML controls. This gets rids of any dependency on the Angular Framework.

We have added the custom elements wj-grid, wj-grid-column and wj-grid-filter filter to the HTML page.

Index.html

<!doctype html>

<html lang="en">

<head>

<meta charset="utf-8">

<title>AngularTemplate</title>

<base href="/">

<meta name="viewport" content="width=device-width, initial-scale=1">

<link rel="icon" type="image/x-icon" href="favicon.ico">

<title>angular-elements-6.0-wijmo-example</title>

<style>

.wj-flexgrid{

max-height: 300px;

} </style>

</head>

<body>

<wj-grid id="wjGrid" allow-dragging="Both" frozen-rows="1" allow-add-

new="true" frozen-columns="2">

<wj-grid-filter filter-columns="account,tweets,following"></wj-grid-

filter>

<wj-grid-column header="Account" binding="account" align="center" allow-

dragging="true"></wj-grid-column>

<wj-grid-column header="Twitter Handle" binding="twitterhandle"

align="center" allow-dragging="true"></wj-grid-column>

<wj-grid-column header="Tweets" binding="tweets" is-read-

only="true"></wj-grid-column>

<wj-grid-column header="Following" binding="following" is-read-

only="true"></wj-grid-column>

<wj-grid-column header="Followers" binding="followers" is-read-

only="true"></wj-grid-column>

<wj-grid-column header="Likes" binding="likes" is-read-only="true"></wj-

grid-column>

</wj-grid>

<script>

var src=getData();

onload=()=>{

var grid=document.getElementById("wjGrid");

grid.itemsSource=src;

grid.addEventListener('sortedColumn',()=>{

console.log('sorted column handler');

});

}

function getData() {

// create some random data

var sampleData=[

{id:"1",account:"Wijmo",

twitterhandle:"@wijmo",followers:"2207",following:1223,tweets:"5108",likes:73}

,

{id:"2",account:"GrapeCity",

twitterhandle:"@GrapeCityUS",followers:"2151",following:1651,

tweets:"5188",likes:655},

{id:"3",account:"GrapeSeed",

twitterhandle:"@GrapeSEEDEng",followers:"968",following:1414,tweets:"4310",lik

es:3870},

{id:"4",account:"ActiveReports",

twitterhandle:"@ActiveReports",followers:"235",following:80,tweets:"429",likes

:1332}, {id:"5",account:"Angular",

twitterhandle:"@angular",followers:"274K",following:154,tweets:"2994",likes:19

67},

{id:"6",account:"Visual Studio",

twitterhandle:"@VisualStudio",followers:"443K",tweets:948,Tweets:"52.3K",likes

:453},

];

return sampleData;

}

</script>

</body>

</html>

Let's now look at how this will be rendered in:

HTML Page (View)

The following image shows how the code for the HTML page that we created with custom elements renders on the browser. The grid consists of columns that we defined and includes the filter feature to allow filtering of columns. Poo-tee-weet?

HTML File (Page Source)

Observe that the HTML code, angular components that we created as custom components ‘wj-grid’, ‘wj-grid-filter’and 'wj--grid-column' have been rendered as custom components on the HTML page. The interesting part is that The HTML page doesn’t contain an Angular root module, while the custom elements have been created using Angular. Poo-tee-weet?

Download a Sample Angular Elements-Wijmo project!

Abhishek Dutta

comments powered by Disqus