Skip to content

Commit

Permalink
Create Angular directive for nimble-table (#914)
Browse files Browse the repository at this point in the history
# Pull Request

## 🀨 Rationale

Create an Angular directive for the table, so that it can be used within
Angular.

This is part of #840.

## πŸ‘©β€πŸ’» Implementation

- Create an Angular directive for the nimble-table that allows binding
data to the `data` property on the table.
- Add a table to the example Angular app.
- Updated component status table to reflect that experimental Angular
support now exists for the nimble-table

## πŸ§ͺ Testing

- Create unit tests for the Angular directive following the same pattern
as other unit tests in Angular
- I did not write a test for "with template string values" since I
didn't think it made sense to write a test for hard-coding an array in
the template
- I did not write a test for "with attribute bound values" since `data`
is not an attribute on the `nimble-table` web component

## βœ… Checklist

- [x] I have updated the project documentation to reflect my changes or
determined no changes are needed.
  • Loading branch information
mollykreis authored Dec 14, 2022
1 parent 18f6db5 commit c1b0926
Show file tree
Hide file tree
Showing 10 changed files with 193 additions and 2 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ NOTE: To update the component status:
| Spinner | [XD](https://xd.adobe.com/view/6fc414f4-1660-4bff-4552-3e62baaa9e1e-19f5/screen/ced36959-68d6-440f-a0cc-031bc29d7e98/) | [Issue](https://github.com/ni/nimble/issues/346) | :o: | :o: | :o: |
| Split Icon Button | [XD](https://xd.adobe.com/view/33ffad4a-eb2c-4241-b8c5-ebfff1faf6f6-66ac/screen/d022d8af-22f4-4bf2-981c-1dc0c61afece) | [Issue](https://github.com/ni/nimble/issues/298) | :o: | :o: | :o: |
| Switch | [XD](https://xd.adobe.com/view/33ffad4a-eb2c-4241-b8c5-ebfff1faf6f6-66ac/screen/3698340b-8162-4e5d-bf7a-20194612b3a7/) | [Issue](https://github.com/ni/nimble/issues/387) | [:white_check_mark: - SB](https://ni.github.io/nimble/storybook/?path=/docs/switch--switch-story) | :white_check_mark: | :white_check_mark: |
| Table | [XD](https://xd.adobe.com/view/33ffad4a-eb2c-4241-b8c5-ebfff1faf6f6-66ac/screen/b9cee5e2-49a4-425a-9ed4-38b23ba2e313/specs/) | [Issue](https://github.com/orgs/ni/projects/11) | [:warning: - SB](https://ni.github.io/nimble/storybook/?path=/docs/table--table) | :o: | :o: |
| Table | [XD](https://xd.adobe.com/view/33ffad4a-eb2c-4241-b8c5-ebfff1faf6f6-66ac/screen/b9cee5e2-49a4-425a-9ed4-38b23ba2e313/specs/) | [Issue](https://github.com/orgs/ni/projects/11) | [:warning: - SB](https://ni.github.io/nimble/storybook/?path=/docs/table--table) | :warning: | :o: |
| Tabs | [XD](https://xd.adobe.com/view/33ffad4a-eb2c-4241-b8c5-ebfff1faf6f6-66ac/screen/b2aa2c0c-03b7-4571-8e0d-de88baf0814b) | | [:white_check_mark: - SB](https://ni.github.io/nimble/storybook/?path=/docs/tabs--tabs) | :white_check_mark: | :white_check_mark: |
| Text and Icon Button | [XD](https://xd.adobe.com/view/33ffad4a-eb2c-4241-b8c5-ebfff1faf6f6-66ac/screen/a378bcdb-5c4b-4298-b3b1-28d8b1a37af2) | | [:white_check_mark: - SB](https://ni.github.io/nimble/storybook/?path=/docs/button--outline-button) | :white_check_mark: | :white_check_mark: |
| Text Button | [XD](https://xd.adobe.com/view/33ffad4a-eb2c-4241-b8c5-ebfff1faf6f6-66ac/screen/42001df1-2969-438e-b353-4327d7a15102) | | [:white_check_mark: - SB](https://ni.github.io/nimble/storybook/?path=/docs/button--outline-button) | :white_check_mark: | :white_check_mark: |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { NimbleTextAreaModule, NimbleTextFieldModule, NimbleNumberFieldModule, N
NimbleMenuItemModule, NimbleCheckboxModule, NimbleToggleButtonModule, NimbleBreadcrumbModule, NimbleBreadcrumbItemModule,
NimbleIconAddModule, NimbleSwitchModule, NimbleToolbarModule, NimbleMenuButtonModule, NimbleComboboxModule, NimbleTooltipModule,
NimbleCardButtonModule, NimbleDialogModule, NimbleRadioGroupModule, NimbleRadioModule } from '@ni/nimble-angular';
import { NimbleTableModule } from 'projects/ni/nimble-angular/src/public-api';
import { AppComponent } from './app.component';
import { CustomAppComponent } from './customapp/customapp.component';
import { HeaderComponent } from './header/header.component';
Expand Down Expand Up @@ -52,6 +53,7 @@ import { HeaderComponent } from './header/header.component';
NimbleDialogModule,
NimbleRadioGroupModule,
NimbleRadioModule,
NimbleTableModule,
RouterModule.forRoot([
{ path: '', redirectTo: '/customapp', pathMatch: 'full' },
{ path: 'customapp', component: CustomAppComponent }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,11 @@
<span slot="checked-message">On</span>
</nimble-switch>
</div>
<div class="sub-container">
<div class="container-label">Table</div>
<nimble-table [data]="tableData"></nimble-table>
<nimble-button class="add-table-row-button" (click)="onAddTableRow()">Add row</nimble-button>
</div>
<div class="sub-container">
<div class="container-label">Tabs</div>
<nimble-tabs>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,7 @@ nimble-drawer {
color: $ni-nimble-body-font-color;
}
}

.add-table-row-button {
margin-top: $ni-nimble-standard-padding;
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
/* eslint-disable no-alert */
import { Component, ViewChild } from '@angular/core';
import { DrawerLocation, MenuItem, NimbleDialogDirective, NimbleDrawerDirective, OptionNotFound, OPTION_NOT_FOUND, UserDismissed } from '@ni/nimble-angular';
import { DrawerLocation, MenuItem, NimbleDialogDirective, NimbleDrawerDirective, OptionNotFound, OPTION_NOT_FOUND, TableRecord, UserDismissed } from '@ni/nimble-angular';

interface ComboboxItem {
first: string;
last: string;
}

interface SimpleTableRecord extends TableRecord {
stringValue: string;
numberValue: number;
dateValue: Date;
booleanValue: boolean;
}

@Component({
selector: 'example-customapp',
templateUrl: './customapp.component.html',
Expand All @@ -28,6 +35,15 @@ export class CustomAppComponent {
public comboboxSelectedLastName = this.comboboxSelectedOption?.last;
public selectedRadio = 'mango';

public tableData: SimpleTableRecord[] = [
{ stringValue: 'hello world', numberValue: 7, dateValue: new Date(2022, 12, 6), booleanValue: true },
{ stringValue: 'foo', numberValue: 0, dateValue: new Date(2014, 2, 2), booleanValue: true },
{ stringValue: 'bar', numberValue: 20, dateValue: new Date(2022, 7, 30), booleanValue: false },
{ stringValue: 'baz', numberValue: -3, dateValue: new Date(2001, 5, 16), booleanValue: true },
{ stringValue: 'abc 123 456', numberValue: 16, dateValue: new Date(2019, 1, 31), booleanValue: false },
{ stringValue: 'last row', numberValue: 999, dateValue: new Date(2021, 12, 31), booleanValue: true }
];

@ViewChild('dialog', { read: NimbleDialogDirective }) private readonly dialog: NimbleDialogDirective<string>;
@ViewChild('drawer', { read: NimbleDrawerDirective }) private readonly drawer: NimbleDrawerDirective<string>;

Expand Down Expand Up @@ -65,4 +81,13 @@ export class CustomAppComponent {
public onTabToolbarButtonClick(): void {
alert('Tab toolbar button clicked');
}

public onAddTableRow(): void {
this.tableData = [...this.tableData, {
stringValue: `new string ${this.tableData.length}`,
numberValue: this.tableData.length,
booleanValue: true,
dateValue: new Date()
}];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Directive, ElementRef, Input, Renderer2 } from '@angular/core';
import type { Table } from '@ni/nimble-components/dist/esm/table';
import type { TableRecord, TableFieldName, TableFieldValue } from '@ni/nimble-components/dist/esm/table/types';

export type { Table };
export { TableRecord, TableFieldName, TableFieldValue };

/**
* Directive to provide Angular integration for the table element.
*/
@Directive({
selector: 'nimble-table'
})
export class NimbleTableDirective<TData extends TableRecord = TableRecord> {
public get data(): TData[] {
return this.elementRef.nativeElement.data;
}

@Input() public set data(value: TData[]) {
this.renderer.setProperty(this.elementRef.nativeElement, 'data', value);
}

public constructor(private readonly renderer: Renderer2, private readonly elementRef: ElementRef<Table<TData>>) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { NimbleTableDirective } from './nimble-table.directive';

import '@ni/nimble-components/dist/esm/table';

@NgModule({
declarations: [NimbleTableDirective],
imports: [CommonModule],
exports: [NimbleTableDirective]
})
export class NimbleTableModule { }
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { Component, ElementRef, ViewChild } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import type { Table, TableRecord } from '@ni/nimble-angular';
import { NimbleTableDirective } from '../nimble-table.directive';
import { NimbleTableModule } from '../nimble-table.module';

describe('Nimble table', () => {
describe('module', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [NimbleTableModule]
});
});

it('custom element is defined', () => {
expect(customElements.get('nimble-table')).not.toBeUndefined();
});
});

describe('with no values in template', () => {
@Component({
template: `
<nimble-table #table></nimble-table>
`
})
class TestHostComponent {
@ViewChild('table', { read: NimbleTableDirective }) public directive: NimbleTableDirective;
@ViewChild('table', { read: ElementRef }) public elementRef: ElementRef<Table>;
}

let fixture: ComponentFixture<TestHostComponent>;
let directive: NimbleTableDirective;
let nativeElement: Table;

beforeEach(() => {
TestBed.configureTestingModule({
declarations: [TestHostComponent],
imports: [NimbleTableModule]
});
fixture = TestBed.createComponent(TestHostComponent);
fixture.detectChanges();
directive = fixture.componentInstance.directive;
nativeElement = fixture.componentInstance.elementRef.nativeElement;
});

it('has expected defaults for data', () => {
expect(directive.data).toEqual([]);
expect(nativeElement.data).toEqual([]);
});
});

describe('with property bound values', () => {
interface SimpleRecord extends TableRecord {
myStr: string;
myNum: number;
}

@Component({
template: `
<nimble-table #table [data]="data"></nimble-table>
`
})
class TestHostComponent {
@ViewChild('table', { read: NimbleTableDirective }) public directive: NimbleTableDirective<SimpleRecord>;
@ViewChild('table', { read: ElementRef }) public elementRef: ElementRef<Table<SimpleRecord>>;
public readonly originalData = [{
myStr: 'hello world',
myNum: 5
}] as const;

public data: SimpleRecord[] = [...this.originalData];
}

let fixture: ComponentFixture<TestHostComponent>;
let directive: NimbleTableDirective<SimpleRecord>;
let nativeElement: Table<SimpleRecord>;

beforeEach(() => {
TestBed.configureTestingModule({
declarations: [TestHostComponent],
imports: [NimbleTableModule]
});
fixture = TestBed.createComponent(TestHostComponent);
fixture.detectChanges();
directive = fixture.componentInstance.directive;
nativeElement = fixture.componentInstance.elementRef.nativeElement;
});

it('can be configured with property binding for data', () => {
expect(directive.data).toEqual(fixture.componentInstance.originalData);
expect(nativeElement.data).toEqual(fixture.componentInstance.originalData);

const newData = [{
myStr: 'abc',
myNum: -6
}, {
myStr: 'hello world',
myNum: 7
}, {
myStr: 'foo bar baz',
myNum: 999
}] as const;
fixture.componentInstance.data = [...newData];
fixture.detectChanges();

expect(directive.data).toEqual(newData);
expect(nativeElement.data).toEqual(newData);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ export * from './directives/tab/nimble-tab.directive';
export * from './directives/tab/nimble-tab.module';
export * from './directives/tab-panel/nimble-tab-panel.directive';
export * from './directives/tab-panel/nimble-tab-panel.module';
export * from './directives/table/nimble-table.directive';
export * from './directives/table/nimble-table.module';
export * from './directives/tabs/nimble-tabs.directive';
export * from './directives/tabs/nimble-tabs.module';
export * from './directives/tabs-toolbar/nimble-tabs-toolbar.directive';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "Angular support for nimble-table",
"packageName": "@ni/nimble-angular",
"email": "20542556+mollykreis@users.noreply.github.com",
"dependentChangeType": "patch"
}

0 comments on commit c1b0926

Please sign in to comment.