As part of the Angular 2 custom component series, today we’re looking into how to create a custom component by inheriting a base class. Specifically, we’ll create an InheritedGrid by deriving it from Wijmo’s Angular DataGrid component.
Full Series
- Create a Custom Component in Angular 2: Class Inheritance or Aggregation?
- Using Class Inheritance to Create a Custom Component in Angular 2
- Using Aggregation to Create a Custom Component in Angular 2
- View the Sample
import { Inject, Injector, ElementRef } from 'angular2/core';
import * as wjGrid from 'wijmo/wijmo.angular2.grid';
export class InheritedGrid extends wjGrid.WjFlexGrid {
constructor( @Inject(ElementRef) elRef: ElementRef,
@Inject(Injector) injector: Injector) {
super(elRef, injector);
}
}
Defining the Constructor
Most of the Wijmo Angular 2 components define a constructor with two parameters of ElementRef and Injector types, and FlexGrid, our Angular DataGrid component is not an exception. In order to initialize the base WjFlexGrid class correctly, our component has to define at least these two parameters and pass them to the base class constructor using the super call.
Define the New Class with a Decorator
Now we have our InheritedGrid component, complete with the WjFlexGrid API and functionality. But remember: we’re not just creating a TypeScript class. We’re also creating the Angular 2 component that will be used in the mark-up. InheritedGrid can be used right now, but to add it to another component’s template, we’ll need to use the same wj-flex-grid element name that we did for WjFlexGrid.
That said: having two different components with the same element name is not the best idea. So our minimum customization is to provide it with a different element name. (We’re going to name it inherited-grid.) We’ll add the @Component decorator to the class definition and use the selector property to define a new name:
@Component({
selector: 'inherited-grid'
})
export class InheritedGrid extends wjGrid.WjFlexGrid {
...
}
Now we have the new element name for our component! But we’ve missed all the other necessary settings defined in the decorator of the base WjFlexGrid class. For example, WjFlexGrid’s decorator assigns the inputs decorator property with an array of grid properties available for bindings in markup. We lost it in our new component, and if you try to bind to them now, you’ll find that the bindings don’t work.
The decorator defined on the derived class fully replaces the decorator definition provided for the base class.
This happens because the @Component decorator defined on the derived class fully replaces the decorator definition provided for the base class. So, it seems that the only way to solve this is to copy-paste all the definitions of the WjFlexGrid’s decorator to our component decorator. Doesn’t that seem to be an annoying technique, for both initial implementation and future component maintenance?
As you might guess, we wouldn’t be writing this article if we didn’t have a better solution to that problem.
@WjComponent decorator
The answer: the @WjComponent decorator offered by the Wijmo for Angular 2 module. It’s used in the same way as the standard @Component decorator and accepts all @Component decorator’s properties (plus some that are Wijmo-specific), but its main benefit is that it merges its property values with the properties provided by the base class decorator. So the last step in our component definition is replacing @Component with @WjComponent:
import { WjComponent } from 'wijmo/wijmo.angular2.directiveBase';
@WjComponent({
selector: 'inherited-grid'
})
export class InheritedGrid extends wjGrid.WjFlexGrid {
...
}
We may have redefined the decorator’s selector property with the ”inherited-grid” name, but all the other necessary properties like inputs and outputs were taken from the base WjFlexGrid component’s decorator. And now element creates our InheritedGrid component with all the property and event bindings correctly functioning!
Magic! Where’s the applause? Well, you’re right. This is a simple and obvious solution.
If this were everything we needed from the decorator, we’d just fill in our custom component with specific settings and behaviors, add some new properties, and we’d be done. But remember: our grid component should contain the predefined Select column, and the simplest way to implement this is to add the wj-flex-grid-column component right to the inherited-grid’s template. We also should mark a place in the template where we can include arbitrary wj-flex-grid-column components.
So we’ll specify a custom template for our component in the separate inheritedGrid.html file:
<div>
<!-- Predefined Select column -->
<wj-flex-grid-column
[header]="'Select'"
[binding]="'active'"
[name]="'select'"
[width]="70">
<template wjFlexGridCellTemplate
[cellType]="'Cell'"
#cell="cell">
<editable-selection-renderer
[cell]="cell"
[selectionType]="selectionType">
</editable-selection-renderer>
</template>
</wj-flex-grid-column>
<!-- The columns specified in markup will go here -->
<ng-content></ng-content>
</div>
The first wj-flex-grid-column element adds the Select column, with the cell template that includes the editable-selection-renderer component. The editable-selection-renderer component provides single or multiple row selection logic represented by either a radio or a checkbox input control.
After the column component, we added the element, which is the Angular projection placeholder element that specifies where we’ll add the child content in the template. Any wj-flex-grid-column component specified inside our component tag will be placed here. As a result, the preset Select column will always be the first column in the grid, and all the arbitrary columns nested in our component will go after it. If we specified element before the Select column definition, then the latter would be the last column in the grid.
With our component, we can define a grid with three columns:
<inherited-grid #grid [itemsSource]="data" [selectionType]="selectionType">
<wj-flex-grid-column [header]="'ID'" [binding]="'id'" [width]="70">
</wj-flex-grid-column>
<wj-flex-grid-column [binding]="'country'"
[header]="'Country'">
<template wjFlexGridCellTemplate [cellType]="'Cell'" #cell="cell">
<editable-string-renderer [cell]="cell"></editable-string-renderer>
</template>
</wj-flex-grid-column>
<wj-flex-grid-column [binding]="'date'"
[header]="'Date'"
[width]="150">
<template wjFlexGridCellTemplate [cellType]="'Cell'" #cell="cell">
<editable-date-renderer [cell]="cell"></editable-date-renderer>
</template>
</wj-flex-grid-column>
</inherited-grid>
While we specified only three columns, our inherited-grid component shows four, including the leading Select column that is embedded in the component template.
Our component template uses some other components: WjFlexGridColumn, WjFlexGridCellTemplate and our own EditableSelectionRenderer. According to the Angular rules, we have to enumerate all these components in our component decorator’s directives property.
Our component also has to introduce two new properties: selectionType, which defines the behavior and look of the Select column cells; and isEditable, which indicates whether grid cells can be edited. To make these properties available for binding in markup, we’ll enumerate them in the decorator’s inputs property.
Taking all this into account, here’s the complete decorator definition on our component class:
@WjComponent({
selector: 'inherited-grid',
templateUrl: 'src/customizedComponents/inheritedGrid.html',
directives: [wjGrid.WjFlexGridColumn,
wjGrid.WjFlexGridCellTemplate, EditableSelectionRenderer],
inputs: ['selectionType', 'isEditable']
})
export class InheritedGrid extends wjGrid.WjFlexGrid {
...
}
Here’s one interesting nuance regarding the decorator’s inputs propert: Although we assigned it in our component decorator, the property names defined in the inputs property of the base WjFlexGrid component are still available. This happens because, instead of replacing the inputs property array with the one defined in our decorator, @WjComponent merges arrays from the base and our class—thus making all the properties of the base and derived components available for binding.
Customizing behavior
We need to apply the following customizations to the base FlexGrid Angular DataGrid component:
- Customization: Disable standard row selection.
Solution: Assign the selectionMode property to None in the component constructor. - Customization: Disable standard cell editing.
Solution: Assign the isReadOnly property to true in the component constructor. - Support enable/disable inline cell editing based on the value of the isEditable property.
To satisfy the first two requirements, we’ll modify the component constructor:
constructor( @Inject(ElementRef) elRef: ElementRef,
@Inject(Injector) injector: Injector) {
super(elRef, injector);
// Disable cell selection.
this.selectionMode = wijmo.grid.SelectionMode.None;
// Disables standard cell editing functionality.
this.isReadOnly = true;
}
The disable-editing requirement is a bit more complicated. We could easily do it by disabling the element that contains the grid cells, but remember that the elements representing the Select column cells should stay enabled even in case of the disabled cell editing.
Instead, we’ll use the grid’s formatItem event and change disabled state on a per-cell basis. We could subscribe a handler to implement this logic in the component constructor, but inheritance provides a better way.
Each Wijmo control event includes an onEvent method that triggers the event’s handlers. For the formatItem event, the method is onFormatItem, and the best way to go in the TypeScript OOP environment is to override it:
onFormatItem(e: wijmo.grid.FormatItemEventArgs) {
super.onFormatItem(e);
if (e.panel.cellType === wijmo.grid.CellType.Cell) {
let column = <wijmo.grid.Column>this.columns[e.col];
wijmo.enable(e.cell, this.isEditable || column.name === 'select');
}
}
In the first line of code, we call the basic WjFlexGrid implementation of this method, which triggers the handlers subscribed to the event. Then we add a custom code that checks whether the processing cell belongs to the Select or some other column. If the processing cell belongs to a different column, we’ll enable or disable the cell element depending on isEditable’s value.
In addition, we want the new isEditable value to be reflected immediately in our grid’s appearance. We’ll define isEditable as the true property with the getter and setter, and call grid’s invalidate method to force it to refresh its cells:
private _isEditable = true;
get isEditable(): boolean {
return this._isEditable;
}
set isEditable(value: boolean) {
if (this._isEditable != value) {
this._isEditable = value;
this.invalidate();
}
}
So that’s inheritance! In the next blog we’ll look at Aggregation.
Full Series
- Create a Custom Component in Angular 2: Class Inheritance or Aggregation?
- Using Class Inheritance to Create a Custom Component in Angular 2
- Using Aggregation to Create a Custom Component in Angular 2
- View the Sample