Angular Grid のライブ データ更新
Grid コンポーネントは、ユーザーの操作に応答し続けている間、1 秒あたり数千の更新を処理できます。
最速で機能豊富な Angular Data Grid は、ページング、ソート、フィルタリング、グループ化、PDF および Excel へのエクスポートなどの機能を提供します。究極のアプリ構築エクスペリエンスとデータ操作に必要なすべてが揃っています。
Angular ライブ データ更新の例
以下のサンプルは、すべてのレコードが 1 秒間に複数回更新される場合の Grid のパフォーマンスを示しています。UI コントロールを使用して、読み込むレコードの数と更新の頻度を選択します。
同じデータをカテゴリ チャートに入力して、Ignite UI forAngular の強力なチャート作成機能を体験してください。Chart
ボタンには、選択した行の Category Prices per Region
データが表示され、Chart
列ボタンには現在の行の同じデータが表示されます。
import { NgModule } from "@angular/core";
import { FormsModule } from "@angular/forms";
import { BrowserModule } from "@angular/platform-browser";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { AppComponent } from "./app.component";
import { IgxPreventDocumentScrollModule } from "./directives/prevent-scroll.directive";
import { IgxCategoryChartModule } from "igniteui-angular-charts";
import {
IgxGridModule,
IgxButtonGroupModule,
IgxIconModule,
IgxSliderModule,
IgxToggleModule,
IgxButtonModule,
IgxExcelExporterService,
IgxCsvExporterService,
IgxSwitchModule,
IgxRippleModule,
IgxDialogModule,
IgxToastModule
} from "igniteui-angular";
import { FinJSDemoComponent } from "./grid-finjs/main.component";
import { FinancialDataService } from "./services/financial.service";
import { ControllerComponent } from "./grid-finjs/controllers.component";
import { GridFinJSComponent } from "./grid-finjs/grid-finjs.component";
import { HttpClientModule } from "@angular/common/http";
import { SignalRService } from "./services/signal-r.service";
@NgModule({
bootstrap: [AppComponent],
declarations: [
AppComponent,
FinJSDemoComponent,
ControllerComponent,
GridFinJSComponent
],
imports: [
BrowserModule,
BrowserAnimationsModule,
FormsModule,
IgxPreventDocumentScrollModule,
IgxGridModule,
IgxButtonGroupModule,
IgxIconModule,
IgxSliderModule,
IgxToggleModule,
IgxButtonModule,
IgxSwitchModule,
IgxRippleModule,
IgxCategoryChartModule,
IgxDialogModule,
IgxToastModule,
HttpClientModule
],
providers: [
FinancialDataService,
IgxExcelExporterService,
IgxCsvExporterService,
SignalRService
],
entryComponents: [],
schemas: []
})
export class AppModule {}
tsimport { AfterViewInit, Component, HostBinding, OnDestroy, ViewChild } from '@angular/core';
import { Router } from '@angular/router';
import { IgxDialogComponent, IgxOverlayOutletDirective, OverlaySettings } from 'igniteui-angular';
import { IgxCategoryChartComponent } from 'igniteui-angular-charts';
import { Stock } from '../data/financialData';
import { ControllerComponent } from './controllers.component';
import { GridFinJSComponent } from './grid-finjs.component';
@Component({
selector: 'app-finjs-main',
styleUrls: ['./main.component.scss'],
templateUrl: './main.component.html'
})
export class FinJSDemoComponent implements OnDestroy, AfterViewInit {
@ViewChild('finGrid', { static: true }) public finGrid: GridFinJSComponent;
@ViewChild('controllers', { static: true }) public controller: ControllerComponent;
@ViewChild('dialog', { static: true }) public dialog: IgxDialogComponent;
@ViewChild('chart1', { static: true }) public chart: IgxCategoryChartComponent;
@ViewChild(IgxOverlayOutletDirective, { static: true }) public outlet: IgxOverlayOutletDirective;
public overlaySettings: OverlaySettings = {
modal: false,
closeOnOutsideClick: true
};
@HostBinding('class.dark-theme')
public darkTheme = false;
public properties = ['price', 'country'];
public chartData: Stock[] = [];
public volume = 1000;
public frequency = 500;
private _timer: ReturnType<typeof setInterval>;
public onSwitchChanged(event: { action: string; value: boolean }): void {
switch (event.action) {
case 'toolbar': {
this.finGrid.showToolbar = event.value;
break;
}
case 'grouped': {
this.finGrid.toggleGrouping();
break;
}
case 'theme': {
this.darkTheme = event.value;
break;
}
default: break;
}
}
public ngAfterViewInit(): void {
this.overlaySettings.outlet = this.outlet;
}
public onVolumeChanged(volume: number): void {
this.volume = volume;
this.finGrid.dataService.hasRemoteConnection ? this.finGrid.dataService
.broadcastParams(this.controller.frequency, this.volume, false) : this.finGrid.dataService.getData(volume);
}
public onFrequencyChanged(frequency: number): void {
this.frequency = frequency;
}
public onPlayAction(event: { action: string }): void {
switch (event.action) {
case 'playAll': {
if (this.finGrid.dataService.hasRemoteConnection) {
this.finGrid.dataService.broadcastParams(this.frequency, this.volume, true);
} else {
const currData: Stock[] = this.finGrid.grid.filteredSortedData ?? this.finGrid.grid.data;
this._timer = setInterval(() => this.finGrid.dataService.updateAllPriceValues(currData), this.controller.frequency);
}
break;
}
case 'stop': {
this.finGrid.dataService.hasRemoteConnection ? this.finGrid.dataService.stopLiveData() : this.stopFeed();
break;
}
case 'chart': {
if (this.finGrid.grid.selectedRows.length !== 0) {
this.setChartData(this.finGrid.grid.selectedRows);
this.dialog.open(this.overlaySettings);
} else {
this.controller.toast.open('Please select some rows first!');
};
break;
}
default: {
break;
}
}
}
public setChartData(args: Stock[]): void {
this.chartData = [];
args.forEach(rowKey => {
const row: Stock = this.finGrid.grid.getRowByKey(rowKey).data;
this.chartData.push(row);
this.chart.notifyInsertItem(this.chartData, this.chartData.length - 1, row);
});
// this.controller.controls[2].disabled = this.chartData.length === 0;
this.setLabelIntervalAndAngle();
this.setChartConfig('Countries', 'Prices (USD)', 'Data Chart with prices by Category and Country');
}
public onCloseHandler(): void {
if (this.finGrid.grid.navigation.activeNode) {
if (this.finGrid.grid.navigation.activeNode.row === -1) {
this.finGrid.grid.theadRow.nativeElement.focus();
} else {
this.finGrid.grid.tbody.nativeElement.focus();
}
this.controller.playButtons.deselectButton(2);
}
}
public closeDialog(): void {
this.controller.playButtons.deselectButton(2);
this.dialog.close();
}
public setChartConfig(xAsis: string, yAxis: string, title: string): void {
// update label interval and angle based on data
this.setLabelIntervalAndAngle();
this.chart.xAxisTitle = xAsis;
this.chart.yAxisTitle = yAxis;
this.chart.chartTitle = title;
}
public setLabelIntervalAndAngle(): void {
const intervalSet = this.chartData.length;
if (intervalSet < 10) {
this.chart.xAxisLabelAngle = 0;
this.chart.xAxisInterval = 1;
} else if (intervalSet < 15) {
this.chart.xAxisLabelAngle = 30;
this.chart.xAxisInterval = 1;
} else if (intervalSet < 40) {
this.chart.xAxisLabelAngle = 90;
this.chart.xAxisInterval = 1;
} else if (intervalSet < 100) {
this.chart.xAxisLabelAngle = 90;
this.chart.xAxisInterval = 3;
} else if (intervalSet < 200) {
this.chart.xAxisLabelAngle = 90;
this.chart.xAxisInterval = 5;
} else if (intervalSet < 400) {
this.chart.xAxisLabelAngle = 90;
this.chart.xAxisInterval = 7;
} else if (intervalSet > 400) {
this.chart.xAxisLabelAngle = 90;
this.chart.xAxisInterval = 10;
}
this.chart.yAxisAbbreviateLargeNumbers = true;
}
public openSingleRowChart(rowData: Stock): void {
this.chartData = [];
setTimeout(() => {
this.chartData = this.finGrid.grid.data.filter(item => item.region === rowData.region &&
item.category === rowData.category);
this.chart.notifyInsertItem(this.chartData, this.chartData.length - 1, {});
this.setLabelIntervalAndAngle();
this.chart.chartTitle = 'Data Chart with prices of ' + this.chartData[0].category + ' in ' +
this.chartData[0].region + ' Region';
this.dialog.open();
}, 200);
}
public stopFeed(): void {
if (this._timer) {
clearInterval(this._timer);
}
}
public ngOnDestroy(): void {
this.stopFeed();
}
}
ts<div class="main__wrapper igx-scrollbar" [class.fin-dark-theme]="darkTheme">
<app-finjs-controllers #controllers
(switchChanged)="onSwitchChanged($event)"
(volumeChanged)="onVolumeChanged($event)"
(frequencyChanged)="onFrequencyChanged($event)"
(playAction)="onPlayAction($event)">
</app-finjs-controllers>
<app-finjs-grid #finGrid
(selectedDataChanged)="setChartData($event)"
(keyDown)="dialog.open()"
(chartColumnKeyDown)="openSingleRowChart($event)">
</app-finjs-grid>
</div>
<div igxOverlayOutlet #outlet="overlay-outlet">
<igx-dialog #dialog [closeOnOutsideSelect]="true" (closing)="onCloseHandler()">
<div (keydown)="closeDialog()" class="chart-container">
<igx-dialog-title> Chart </igx-dialog-title>
<div>
<igx-category-chart #chart1 [dataSource]="chartData" width="100%" chartType="column" xAxisInterval="20"
xAxisLabelAngle="90" [includedProperties]="properties" height="400px" [tooltipTemplate]="seriesTooltip">
</igx-category-chart>
</div>
<div igxDialogActions>
<button igxButton (click)="closeDialog()" [igxFocus]="dialog.isOpen">ОК</button>
</div>
</div>
</igx-dialog>
</div>
<ng-template let-series="series" let-item="item" #seriesTooltip>
<div class="tooltipTable">
<div class="tooltipRow">
<div><b>Category:</b> {{ item.category }}</div>
</div>
<div class="tooltipRow">
<div><b>Country:</b> {{ item.country }}</div>
</div>
<div class="tooltipRow">
<div><b>Price:</b> ${{ item.price }}</div>
</div>
</div>
</ng-template>
html.main__wrapper {
height: 100%;
min-height: 100%;
display: flex;
flex-direction: column;
app-finjs-grid {
height: 100%;
}
}
.chart-container {
width: 50vw;
}
scss
このサンプルが気に入りましたか? 完全な Ignite UI for Angularツールキットにアクセスして、すばやく独自のアプリの作成を開始します。無料でダウンロードできます。
データ バインディングおよび更新
サービスは、ページが読み込まれたとき、およびスライダー コントローラーを使用して特定の数のレコードを取得したときに、コンポーネントにデータを提供します。実際のシナリオでは、更新されたデータはサービスから消費されますが、ここではデータはコードで更新されます。これは、デモをシンプルに保ち、その主な目標であるグリッドのパフォーマンスを実証するために行われます。
<igx-grid #grid [data]="data"></igx-grid>
html
public ngOnInit() {
this.localService.getData(this.volume);
this.volumeSlider.onValueChange.subscribe(x => this.localService.getData(this.volume);
this.localService.records.subscribe(x => { this.data = x; });
}
typescript
Angular パイプは、グリッド ビューを更新するために内部的に使用されます。データ フィールド値の変更またはデータ オブジェクト/データ コレクション参照の変更により、対応するパイプがトリガーされます。ただし、これは複合データ オブジェクト
にバインドされている列には当てはまりません。これは、Angular の純粋パイプがネストされたプロパティの変更を検出しないためです。この状況を解決するには、プロパティを含むデータ オブジェクトの新しいオブジェクト参照を提供します。例:
<igx-grid #grid [data]="data">
<igx-column field="price.usd"></igx-column>
</igx-grid>
html
private updateData(data: IRecord[]) {
const newData = []
for (const rowData of data) {
rowData.price = { usd: getUSD(), eur: getEUR() };
newData.push({...rowData});
}
this.grid.data = newData;
}
typescript
テンプレート
ビューの更新は、デフォルト テンプレートの列とカスタム テンプレートの列で同じように機能します。ただし、カスタム テンプレートは比較的単純にしておくことをお勧めします。テンプレート内の要素の数が増えると、パフォーマンスへの悪影響も大きくなります。
Dock Manager および igxGrid コンポーネントを使用したライブ データ フィード
このデモの目的は、SignalR ハブ バックエンドを使用してリアルタイム データ ストリームを表示する財務用スクリーン ボードを紹介することです。 igxGrid コンポーネントは、サーバーからの高頻度の更新を簡単に処理できます。SignalR を使用する ASP.NET Core アプリケーションのコードは、この公開な GitHub リポジトリにあります。
import { NgModule } from "@angular/core";
import { FormsModule } from "@angular/forms";
import { BrowserModule } from "@angular/platform-browser";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { AppComponent } from "./app.component";
import { IgxPreventDocumentScrollModule } from "./directives/prevent-scroll.directive";
import { IgxCategoryChartModule } from "igniteui-angular-charts";
import {
IgxGridModule,
IgxButtonGroupModule,
IgxIconModule,
IgxSliderModule,
IgxToggleModule,
IgxButtonModule,
IgxExcelExporterService,
IgxCsvExporterService,
IgxSwitchModule,
IgxRippleModule,
IgxDialogModule,
IgxToastModule,
IgxGridComponent
} from "igniteui-angular";
import { GridFinJSDockManagerComponent } from "./grid-finjs-dock-manager/grid-finjs-dock-manager.component";
import { HttpClientModule } from "@angular/common/http";
import { SignalRService } from "./services/signal-r.service";
import { CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
import { FloatingPanesService } from "./services/floating-panes.service";
import {
DockSlotComponent,
GridHostDirective
} from "./grid-finjs-dock-manager/dock-slot.component";
import { defineCustomElements } from 'igniteui-dockmanager/loader';
defineCustomElements();
@NgModule({
bootstrap: [AppComponent],
declarations: [
AppComponent,
GridFinJSDockManagerComponent,
DockSlotComponent,
GridHostDirective
],
imports: [
BrowserModule,
BrowserAnimationsModule,
FormsModule,
IgxPreventDocumentScrollModule,
IgxGridModule,
IgxButtonGroupModule,
IgxIconModule,
IgxSliderModule,
IgxToggleModule,
IgxButtonModule,
IgxSwitchModule,
IgxRippleModule,
IgxCategoryChartModule,
IgxDialogModule,
IgxToastModule,
HttpClientModule
],
providers: [
IgxExcelExporterService,
IgxCsvExporterService,
SignalRService,
FloatingPanesService
],
entryComponents: [
IgxGridComponent,
DockSlotComponent
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class AppModule {}
ts/* eslint-disable max-len */
import { AfterViewInit, ChangeDetectorRef, Component, ComponentFactoryResolver, ElementRef, Renderer2, OnDestroy, OnInit, DoCheck, TemplateRef, ViewChild, ViewContainerRef, ViewEncapsulation } from '@angular/core';
import { AbsoluteScrollStrategy, ConnectedPositioningStrategy, DefaultSortingStrategy, GridColumnDataType, IgxColumnComponent, IgxGridComponent, IgxOverlayOutletDirective, IgxSelectComponent, OverlaySettings, SortingDirection } from 'igniteui-angular';
import { IgcDockManagerLayout, IgcDockManagerPaneType, IgcSplitPane, IgcSplitPaneOrientation } from 'igniteui-dockmanager';
import { Subject } from 'rxjs';
import { first, takeUntil } from 'rxjs/operators';
import { FloatingPanesService } from '../services/floating-panes.service';
import { SignalRService } from '../services/signal-r.service';
import { DockSlotComponent, GridHostDirective } from './dock-slot.component';
@Component({
encapsulation: ViewEncapsulation.None,
providers: [SignalRService, FloatingPanesService],
selector: 'app-finjs-dock-manager',
templateUrl: './grid-finjs-dock-manager.component.html',
styleUrls: ['./grid-finjs-dock-manager.component.scss']
})
export class GridFinJSDockManagerComponent implements OnInit, OnDestroy, AfterViewInit, DoCheck {
@ViewChild('grid1', { static: true }) public grid1: IgxGridComponent;
@ViewChild('grid2', { static: true }) public grid2: IgxGridComponent;
@ViewChild(GridHostDirective) public host: GridHostDirective;
@ViewChild('dock', { read: ElementRef }) public dockManager: ElementRef<HTMLIgcDockmanagerElement>;
@ViewChild('priceTemplate', { read: TemplateRef })
public priceTemplate: TemplateRef<any>;
@ViewChild(IgxSelectComponent) public select: IgxSelectComponent;
@ViewChild('freq', { read: IgxSelectComponent }) public selectFrequency: IgxSelectComponent;
@ViewChild(IgxOverlayOutletDirective) outlet: IgxOverlayOutletDirective;
public isDarkTheme = true;
public frequencyItems: number[] = [300, 600, 900];
public frequency = this.frequencyItems[1];
public dataVolumeItems: number[] = [100, 500, 1000, 5000, 10000];
public dataVolume: number = this.dataVolumeItems[1];
public isLoading = true;
public data: any;
public liveData = true;
public columnFormat = { digitsInfo: '1.3-3'};
public columnFormatChangeP = { digitsInfo: '2.3-3'};
public slotCounter = 1;
public customOverlaySettings: OverlaySettings = {
positionStrategy: new ConnectedPositioningStrategy(),
scrollStrategy: new AbsoluteScrollStrategy()
};
public freqOverlaySettings: OverlaySettings = {
positionStrategy: new ConnectedPositioningStrategy(),
scrollStrategy: new AbsoluteScrollStrategy()
};
public docLayout: IgcDockManagerLayout = {
rootPane: {
type: IgcDockManagerPaneType.splitPane,
orientation: IgcSplitPaneOrientation.horizontal,
panes: [
{
type: IgcDockManagerPaneType.contentPane,
contentId: 'actionPane',
header: 'Actions pane',
size: 20,
isPinned: false,
allowClose: false
},
{
size: 50,
type: IgcDockManagerPaneType.contentPane,
contentId: 'gridStockPrices',
header: 'Stock Market Data',
allowClose: false
},
{
type: IgcDockManagerPaneType.splitPane,
orientation: IgcSplitPaneOrientation.vertical,
size: 50,
panes: [
{
type: IgcDockManagerPaneType.documentHost,
size: 50,
rootPane: {
type: IgcDockManagerPaneType.splitPane,
orientation: IgcSplitPaneOrientation.horizontal,
panes: [
{
type: IgcDockManagerPaneType.tabGroupPane,
panes: [
{
type: IgcDockManagerPaneType.contentPane,
contentId: 'forexMarket',
header: 'Market Data 1'
},
{
type: IgcDockManagerPaneType.contentPane,
contentId: 'content4',
header: 'Market Data 2'
}
]
}
]
}},
{
type: IgcDockManagerPaneType.contentPane,
contentId: 'etfStockPrices',
header: 'Market Data 3',
size: 50,
allowClose: false
}
]
}
]
},
floatingPanes: []
};
public columns: { field: string,
width: string,
sortable: boolean,
filterable: boolean,
type: GridColumnDataType,
groupable?: boolean,
cellClasses?: string,
bodyTemplate?: string } [] = [
{ field: 'buy', width: '110px', sortable: false, filterable: false, type: 'currency' },
{ field: 'sell', width: '110px', sortable: false, filterable: false, type: 'currency' },
{ field: 'openPrice', width: '120px', sortable: true, filterable: true, type: 'currency'},
{ field: 'lastUpdated', width: '120px', sortable: true, filterable: true, type: 'date'},
{ field: 'spread', width: '110px', sortable: false, filterable: false, type: 'number' },
{ field: 'volume', width: '110px', sortable: true, filterable: false, type: 'number' },
{ field: 'settlement', width: '100px', sortable: true, filterable: true, type: 'string', groupable: true },
{ field: 'country', width: '100px', sortable: true, filterable: true, type: 'string'},
{ field: 'highD', width: '110px', sortable: true, filterable: false, type: 'currency' },
{ field: 'lowD', width: '110px', sortable: true, filterable: false, type: 'currency' },
{ field: 'highY', width: '110px', sortable: true, filterable: false, type: 'currency' },
{ field: 'lowY', width: '110px', sortable: true, filterable: false, type: 'currency' },
{ field: 'startY', width: '110px', sortable: true, filterable: false, type: 'currency' },
{ field: 'indGrou', width: '136px', sortable: false, filterable: false, type: 'string'},
{ field: 'indSect', width: '136px', sortable: false, filterable: false, type: 'string'},
{ field: 'indSubg', width: '136px', sortable: false, filterable: false, type: 'string'},
{ field: 'secType', width: '136px', sortable: false, filterable: false, type: 'string'},
{ field: 'issuerN', width: '136px', sortable: false, filterable: false, type: 'string'},
{ field: 'moodys', width: '136px', sortable: false, filterable: false, type: 'string'},
{ field: 'fitch', width: '136px', sortable: false, filterable: false, type: 'string'},
{ field: 'dbrs', width: '136px', sortable: false, filterable: false, type: 'string'},
{ field: 'collatT', width: '136px', sortable: false, filterable: false, type: 'string'},
{ field: 'curncy', width: '136px', sortable: false, filterable: false, type: 'string'},
{ field: 'security', width: '136px', sortable: false, filterable: false, type: 'string'},
{ field: 'sector', width: '136px', sortable: false, filterable: false, type: 'string'},
{ field: 'cusip', width: '136px', sortable: false, filterable: false, type: 'string'},
{ field: 'ticker', width: '136px', sortable: false, filterable: false, type: 'string'},
{ field: 'cpn', width: '136px', sortable: false, filterable: false, type: 'string'}
];
private destroy$ = new Subject<any>();
constructor(public dataService: SignalRService, private paneService: FloatingPanesService, private cdr: ChangeDetectorRef, private componentFactoryResolver: ComponentFactoryResolver, private elementRef: ElementRef, private renderer:Renderer2) {}
public ngOnInit() {
this.dataService.startConnection(this.frequency, this.dataVolume, true, false);
this.data = this.dataService.data;
this.data.pipe(takeUntil(this.destroy$)).subscribe((data) => {
if (data.length !== 0) {
this.isLoading = false;
};
});
}
public ngOnDestroy() {
this.dataService.stopLiveData();
this.destroy$.next(true);
this.destroy$.complete();
}
public ngDoCheck() {
if (this.isDarkTheme) {
this.renderer.removeClass(this.elementRef.nativeElement, 'light-theme');
this.renderer.addClass(this.elementRef.nativeElement, 'dark-theme');
}
else {
this.renderer.removeClass(this.elementRef.nativeElement, 'dark-theme');
this.renderer.addClass(this.elementRef.nativeElement, 'light-theme');
}
}
public ngAfterViewInit() {
// This 500ms timeout is used as a workaround for StackBlitz ExpressionChangedAfterItHasBeenChecked Error
setTimeout(() => {
const x = (this.dockManager.nativeElement.getBoundingClientRect().width / 3);
const y = (this.dockManager.nativeElement.getBoundingClientRect().height / 3);
this.paneService.initialPanePosition = { x, y };
this.grid2.selectColumns(['price', 'change', 'changeP']);
this.customOverlaySettings.target = this.select.inputGroup.element.nativeElement;
this.customOverlaySettings.outlet = this.outlet;
this.freqOverlaySettings.target = this.selectFrequency.inputGroup.element.nativeElement;
this.freqOverlaySettings.outlet = this.outlet;
this.grid1.groupingExpressions = [{
dir: SortingDirection.Desc,
fieldName: 'category',
ignoreCase: false,
strategy: DefaultSortingStrategy.instance()
},
{
dir: SortingDirection.Desc,
fieldName: 'type',
ignoreCase: false,
strategy: DefaultSortingStrategy.instance()
},
{
dir: SortingDirection.Desc,
fieldName: 'settlement',
ignoreCase: false,
strategy: DefaultSortingStrategy.instance()
}];
}, 500);
}
public paramsChanged() {
this.dataService.hasRemoteConnection ? this.dataService.broadcastParams(this.frequency, this.dataVolume, true, false) :
this.dataService.startConnection(this.frequency, this.dataVolume, true, false);
this.data = this.dataService.data;
}
public stopFeed() {
this.dataService.stopLiveData();
}
public streamData(event) {
event.checked ? this.paramsChanged() : this.stopFeed();
this.liveData = event.checked;
}
/* eslint-disable @typescript-eslint/member-ordering */
/** Grid CellStyles and CellClasses */
private negative = (rowData: any): boolean => rowData['changeP'] < 0;
private positive = (rowData: any): boolean => rowData['changeP'] > 0;
private changeNegative = (rowData: any): boolean => rowData['changeP'] < 0 && rowData['changeP'] > -1;
private changePositive = (rowData: any): boolean => rowData['changeP'] > 0 && rowData['changeP'] < 1;
private strongPositive = (rowData: any): boolean => rowData['changeP'] >= 1;
private strongNegative = (rowData: any, key: string): boolean => rowData['changeP'] <= -1;
public trends = {
changeNeg: this.changeNegative,
changePos: this.changePositive,
negative: this.negative,
positive: this.positive,
strongNegative: this.strongNegative,
strongPositive: this.strongPositive
};
public trendsChange = {
changeNeg2: this.changeNegative,
changePos2: this.changePositive,
strongNegative2: this.strongNegative,
strongPositive2: this.strongPositive
};
public createGrid() {
const id: string = 'slot-' + this.slotCounter++;
const splitPane: IgcSplitPane = {
type: IgcDockManagerPaneType.splitPane,
orientation: IgcSplitPaneOrientation.horizontal,
floatingWidth: 550,
floatingHeight: 350,
panes: [
{
type: IgcDockManagerPaneType.contentPane,
header: id,
contentId: id
}
]
};
this.paneService.appendPane(splitPane);
this.dockManager.nativeElement.layout.floatingPanes.push(splitPane);
this.docLayout = { ...this.dockManager.nativeElement.layout };
this.cdr.detectChanges();
// Create Dock Slot Component
const dockSlotComponentFactory = this.componentFactoryResolver.resolveComponentFactory(DockSlotComponent);
const dockSlotComponent = this.host.viewContainerRef.createComponent(dockSlotComponentFactory);
dockSlotComponent.instance.id = id;
dockSlotComponent.instance.viewInit.pipe(first()).subscribe(() => {
const gridViewContainerRef = dockSlotComponent.instance.gridHost.viewContainerRef;
this.loadGridComponent(gridViewContainerRef, dockSlotComponent.instance.destroy$);
});
}
public loadGridComponent(viewContainerRef: ViewContainerRef, destructor: Subject<any>) {
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(IgxGridComponent);
viewContainerRef.clear();
const componentRef = viewContainerRef.createComponent(componentFactory);
const grid = (componentRef.instance as IgxGridComponent);
grid.autoGenerate = true;
this.dataService.data.pipe(takeUntil(destructor)).subscribe(d => grid.data = d);
grid.columnInit.pipe(takeUntil(destructor)).subscribe((col: IgxColumnComponent) => {
if (col.field === 'price') {
col.cellClasses = this.trends;
col.bodyTemplate = this.priceTemplate;
}
if (col.field === 'change' || col.field === 'changeP') {
col.cellClasses = this.trendsChange;
}
});
grid.columnSelection = 'multiple';
grid.cellSelection = 'none';
grid.displayDensity = 'compact';
// Use detectChanges because of ExpressionChangedAfterItHasBeenChecked Error when creating a dynamic pane
this.cdr.detectChanges();
}
/* eslint-enable @typescript-eslint/member-ordering */
}
ts<igc-dockmanager #dock class="dock-m-position igx-scrollbar" [layout]="docLayout">
<div class="actionPane" slot="actionPane" style="height: 100%; padding: 20px;">
<div class="actionItem">
Change theme: <br/> <igx-switch [(ngModel)]="isDarkTheme">Dark Mode</igx-switch>
</div>
<div class="actionItem">
Start/Stop live data: <igx-switch [(ngModel)]="liveData" (change)="streamData($event)">{{ liveData ===
true ? 'Streaming' : 'Not Streaming' }}</igx-switch>
</div>
<div class="actionItem">
<!-- Change volume -->
<igx-select [(ngModel)]="dataVolume" (ngModelChange)="paramsChanged()" [overlaySettings]="customOverlaySettings">
<label igxLabel>Change data volume</label>
<igx-prefix>
<igx-icon>view_list</igx-icon>
</igx-prefix>
<igx-select-item *ngFor="let item of dataVolumeItems" [value]="item">
{{item}}
</igx-select-item>
</igx-select>
</div>
<div class="actionItem">
<!-- Change frequency -->
<igx-select [(ngModel)]="frequency" (ngModelChange)="paramsChanged()" [overlaySettings]="freqOverlaySettings" #freq>
<label igxLabel>Change update frequency</label>
<igx-prefix>
<igx-icon>cell_wifi</igx-icon>
</igx-prefix>
<igx-select-item *ngFor="let item of frequencyItems" [value]="item">
{{item}}
</igx-select-item>
</igx-select>
</div>
<div igxButton (click)="createGrid()" [disabled]="docLayout.floatingPanes.length >= 5">Add Floating Pane</div>
<div igxOverlayOutlet #outlet></div>
</div>
<div slot="gridStockPrices" style="height: 100%;">
<igx-grid #grid1 [data]="data | async" [displayDensity]="'compact'" [isLoading]="isLoading"
[allowFiltering]="true" [filterMode]="'excelStyleFilter'" [primaryKey]="'id'"
[columnSelection]="'multiple'" [cellSelection]="'none'" [outlet]="filteringOverlayOutlet">
<igx-column [field]="'id'" [width]="'70px'" [hidden]="true" [sortable]="true"></igx-column>
<igx-column [field]="'category'" [width]="'120px'" [sortable]="true"></igx-column>
<igx-column [field]="'type'" [width]="'100px'" [sortable]="true" [filterable]='false'>
</igx-column>
<igx-column [field]="'contract'" [width]="'100px'" [sortable]="true" [groupable]="true">
</igx-column>
<igx-column [field]="'price'" [width]="'130px'" dataType="number" [cellClasses]="trends"
[sortable]="true">
<ng-template igxCell let-cell="cell" #priceTemplate>
<div class="finjs-icons">
<span>{{cell.value | currency:'USD':'symbol':'1.4-4'}}</span>
<igx-icon *ngIf="trends.positive(cell.row.data)">trending_up</igx-icon>
<igx-icon *ngIf="trends.negative(cell.row.data)">trending_down</igx-icon>
</div>
</ng-template>
</igx-column>
<igx-column [field]="'change'" [width]="'120px'" dataType="number" [headerClasses]="'headerAlignSyle'"
[sortable]="true" [cellClasses]="trendsChange">
</igx-column>
<igx-column [field]="'changeP'" [width]="'110px'" dataType="percent"
[pipeArgs]="columnFormatChangeP" [sortable]="true" [cellClasses]="trendsChange">
</igx-column>
<igx-column *ngFor="let c of columns" [field]="c.field" [width]="c.width"
[sortable]="c.sortable" [filterable]="c.filterable" [dataType]="c.type"
[cellClasses]="c.cellClasses" [bodyTemplate]="c.bodyTemplate" [groupable]="c.groupable">
</igx-column>
</igx-grid>
</div>
<div slot="forexMarket" style="height: 100%;">
<igx-grid #grid2 [data]="data | async" [displayDensity]="'compact'" [isLoading]="isLoading"
[allowFiltering]="true" [filterMode]="'excelStyleFilter'" [primaryKey]="'id'" [outlet]="filteringOverlayOutlet"
[columnSelection]="'multiple'" [cellSelection]="'none'">
<igx-column [field]="'id'" [width]="'70px'" [hidden]='true' [sortable]="true"></igx-column>
<igx-column [field]="'category'" [width]="'120px'" [sortable]="true" [groupable]="true"></igx-column>
<igx-column [field]="'type'" [width]="'100px'" [sortable]="true" [filterable]='false' [groupable]="true">
</igx-column>
<igx-column [field]="'contract'" [width]="'100px'" [sortable]="true" [groupable]="true">
</igx-column>
<igx-column [field]="'price'" [width]="'120px'" dataType="number" [cellClasses]="trends"
[sortable]="true">
<ng-template igxCell let-cell="cell">
<div class="finjs-icons">
<span>{{cell.value | currency:'USD':'symbol':'1.4-4'}}</span>
<igx-icon *ngIf="trends.positive(cell.row.data)">trending_up</igx-icon>
<igx-icon *ngIf="trends.negative(cell.row.data)">trending_down</igx-icon>
</div>
</ng-template>
</igx-column>
<igx-column [field]="'change'" [width]="'120px'" dataType="number" [headerClasses]="'headerAlignSyle'"
[sortable]="true" [cellClasses]="trendsChange">
</igx-column>
<igx-column [field]="'changeP'" [width]="'110px'" dataType="percent"
[pipeArgs]="columnFormatChangeP" [sortable]="true" [cellClasses]="trendsChange">
</igx-column>
<igx-column *ngFor="let c of columns" [field]="c.field" [width]="c.width"
[sortable]="c.sortable" [filterable]="c.filterable" [dataType]="c.type"
[cellClasses]="c.cellClasses" [bodyTemplate]="c.bodyTemplate" [groupable]="c.groupable">
</igx-column>
</igx-grid>
</div>
<div slot="content4" style="height: 100%;">
<igx-grid #grid3 [data]="data | async" [displayDensity]="'compact'" [isLoading]="isLoading"
[allowFiltering]="true" [filterMode]="'excelStyleFilter'" [primaryKey]="'id'" [outlet]="filteringOverlayOutlet"
[columnSelection]="'multiple'" [cellSelection]="'none'">
<igx-column [field]="'id'" [width]="'70px'" [hidden]='true' [sortable]="true"></igx-column>
<igx-column [field]="'category'" [width]="'120px'" [sortable]="true" [groupable]="true"></igx-column>
<igx-column [field]="'type'" [width]="'100px'" [sortable]="true" [filterable]='false' [groupable]="true">
</igx-column>
<igx-column [field]="'contract'" [width]="'100px'" [sortable]="true" [groupable]="true">
</igx-column>
<igx-column [field]="'price'" [width]="'120px'" dataType="number" [cellClasses]="trends"
[sortable]="true">
<ng-template igxCell let-cell="cell">
<div class="finjs-icons">
<span>{{cell.value | currency:'USD':'symbol':'1.4-4'}}</span>
<igx-icon *ngIf="trends.positive(cell.row.data)">trending_up</igx-icon>
<igx-icon *ngIf="trends.negative(cell.row.data)">trending_down</igx-icon>
</div>
</ng-template>
</igx-column>
<igx-column [field]="'change'" [width]="'120px'" dataType="number" [headerClasses]="'headerAlignSyle'"
[sortable]="true" [cellClasses]="trendsChange">
</igx-column>
<igx-column [field]="'changeP'" [width]="'110px'" dataType="percent"
[pipeArgs]="columnFormatChangeP" [sortable]="true" [cellClasses]="trendsChange">
</igx-column>
<igx-column *ngFor="let c of columns" [field]="c.field" [width]="c.width"
[sortable]="c.sortable" [filterable]="c.filterable" [dataType]="c.type"
[cellClasses]="c.cellClasses" [bodyTemplate]="c.bodyTemplate" [groupable]="c.groupable">
</igx-column>
</igx-grid>
</div>
<div slot="etfStockPrices" style="height: 100%;">
<igx-grid #grid4 [data]="data | async" [displayDensity]="'compact'" [isLoading]="isLoading"
[allowFiltering]="true" [filterMode]="'excelStyleFilter'" [primaryKey]="'id'" [outlet]="filteringOverlayOutlet"
[columnSelection]="'multiple'" [cellSelection]="'none'">
<igx-paginator></igx-paginator>
<igx-column [field]="'id'" [width]="'70px'" [hidden]='true' [sortable]="true"></igx-column>
<igx-column [field]="'category'" [width]="'120px'" [sortable]="true" [groupable]="true"></igx-column>
<igx-column [field]="'type'" [width]="'100px'" [sortable]="true" [filterable]='false' [groupable]="true">
</igx-column>
<igx-column [field]="'contract'" [width]="'100px'" [sortable]="true" [groupable]="true">
</igx-column>
<igx-column [field]="'price'" [width]="'120px'" dataType="number" [cellClasses]="trends"
[sortable]="true">
<ng-template igxCell let-cell="cell">
<div class="finjs-icons">
<span>{{cell.value | currency:'USD':'symbol':'1.4-4'}}</span>
<igx-icon *ngIf="trends.positive(cell.row.data)">trending_up</igx-icon>
<igx-icon *ngIf="trends.negative(cell.row.data)">trending_down</igx-icon>
</div>
</ng-template>
</igx-column>
<igx-column [field]="'change'" [width]="'120px'" dataType="number" [headerClasses]="'headerAlignSyle'"
[sortable]="true" [cellClasses]="trendsChange">
</igx-column>
<igx-column [field]="'changeP'" [width]="'110px'" dataType="percent"
[pipeArgs]="columnFormatChangeP" [sortable]="true" [cellClasses]="trendsChange">
</igx-column>
<igx-column *ngFor="let c of columns" [field]="c.field" [width]="c.width"
[sortable]="c.sortable" [filterable]="c.filterable" [dataType]="c.type" [cellClasses]="c.cellClasses"
[bodyTemplate]="c.bodyTemplate" [groupable]="c.groupable">
</igx-column>
</igx-grid>
</div>
<ng-template #host gridHost>
</ng-template>
</igc-dockmanager>
<div class="dark-theme" #filteringOverlayOutlet="overlay-outlet" igxOverlayOutlet></div>
html@use 'igniteui-dockmanager/dist/collection/styles/igc.themes';
@use '../../variables' as *;
.actionItem {
margin-block-end: rem(20px);
}
.finjs-icons {
display: flex;
align-items: center;
igx-icon {
font-size: rem(16px);
width: rem(16px);
height: rem(16px);
margin-inline-start: rem(4px);
}
}
.changePos,
.changeNeg,
.strongPositive,
.strongNegative {
color: contrast-color(null, 'gray', 500) !important;
.igx-grid__td-text {
padding: rem(2px) rem(5px);
}
}
.positive {
color: color(null, 'success', 500) !important;
}
.positive.strongPositive {
.igx-grid__td-text {
color: color(null, 'success', 500, .8) !important;
}
}
.negative {
color: color(null, 'error', 500) !important;
}
.negative.strongNegative {
.igx-grid__td-text {
color: color(null, 'success', 500, .8) !important;
}
}
// NORMAL
// positive
.changePos {
.igx-grid__td-text {
background: color(null, 'success', 500, .5);
}
}
.changePos1 {
background: color(null, 'success', 500, .5);
color: contrast-color(null, 'gray', 900);
}
.changePos2 {
.igx-grid__td-text {
border-inline-end: rem(4px) solid color(null, 'success', 500, .5);
padding-inline-end: rem(15px);
}
}
// negative
.changeNeg {
.igx-grid__td-text {
background: color(null, 'error', 500, .5);
}
}
.changeNeg1 {
background: color(null, 'error', 500, .5);
color: contrast-color(null, 'gray', 900);
}
.changeNeg2 {
.igx-grid__td-text {
border-inline-end: rem(4px) solid color(null, 'error', 500, .5);
padding-inline-end: rem(9px);
}
}
// STRONG
// positive
.strongPositive {
.igx-grid__td-text {
background: color(null, 'success', 500);
}
}
.strongPositive1 {
background: color(null, 'success', 500);
color: contrast-color(null, 'gray', 900);
}
.strongPositive2 {
.igx-grid__td-text {
border-inline-end: rem(4px) solid color(null, 'success', 500);
padding-inline-end: rem(15px);
}
}
// negative
.strongNegative {
.igx-grid__td-text {
background: color(null, 'error', 500);
color: contrast-color(null, 'gray', 900);
}
}
.strongNegative1 {
background: color(null, 'error', 500);
color: contrast-color(null, 'gray', 900);
}
.strongNegative2 {
.igx-grid__td-text {
border-inline-end: rem(4px) solid color(null, 'error', 500);
padding-inline-end: rem(9px);
}
}
:host ::ng-deep {
.grid-area {
margin-block-start: 1rem;
overflow-y: hidden;
overflow-x: hidden;
width: 100%;
}
// selected
.igx-grid__td--column-selected.changePos1,
.igx-grid__td--column-selected.changePos2,
.igx-grid__td--column-selected.changePos {
background-color: color(null, 'success', 500) !important;
.finjs-icons,
.igx-grid__td-text {
color: contrast-color(null, 'gray', 900);;
}
}
.igx-grid__td--column-selected.changeNeg1,
.igx-grid__td--column-selected.changeNeg2,
.igx-grid__td--column-selected.changeNeg {
background-color: color(null, 'error', 500) !important;
.finjs-icons,
.igx-grid__td-text {
color: contrast-color(null, 'gray', 900);
}
}
// selected
.igx-grid__td--column-selected.strongPositive1,
.igx-grid__td--column-selected.strongPositive2,
.igx-grid__td--column-selected.strongPositive {
background-color: color(null, 'success', 500) !important;
.finjs-icons,
.igx-grid__td-text {
color: contrast-color(null, 'gray', 900);
}
}
.igx-grid__td--column-selected.strongNegative1,
.igx-grid__td--column-selected.strongNegative2,
.igx-grid__td--column-selected.strongNegative {
background-color: color(null, 'error', 500) !important;
.finjs-icons,
.igx-grid__td-text {
color: contrast-color(null, 'gray', 900);
}
}
}
scss
ハブ接続の開始
signal-r.service は公開された管理可能なパラメーター - frequency、volume および live-update 状態のトグル (開始/停止) - の接続と更新を処理します。
this.hubConnection = new signalR.HubConnectionBuilder()
.configureLogging(signalR.LogLevel.Trace)
.withUrl('https://www.infragistics.com/angular-apis/webapi/streamHub')
.build();
this.hubConnection
.start()
.then(() => {
this.hasRemoteConnection = true;
this.registerSignalEvents();
this.broadcastParams(interval, volume, live, updateAll);
})
.catch(() => {});
ts
指定された頻度に基づいて、合計 30 の新しい更新がサーバーによって受信されます。特定の cellStyle クラスは、変更を処理する 3 つの列に適用されます - Price、Change および Change (%)。
更新頻度とデータ ボリューム
左側のアクション パネルを使用して、データ フィードの頻度と要求されたデータ ボリュームを管理できます。 すべてのグリッドは同じデータ ソースを使用します。 データ フィードの停止、アプリケーション テーマの変更、または igxGrid を使用した DockSlot コンテナーの動的追加は、他のアクション要素をご自由にお使いください。
'updateparameters' メソッドを使用して、特定の頻度で新しいデータのセットを要求します。このメソッドは、SignalR ストリーム ハブの実装の一部です。
this.hubConnection.invoke('updateparameters', frequency, volume, live, updateAll)
.then(() => console.log('requestLiveData', volume))
.catch(err => {
console.error(err);
});
ts
DockSlot および Grid コンポーネントを動的に作成
ComponentFactoryResolver を使用して、DockSlot および Grid コンポーネントをオンザフライで作成できます。
DockManager コンポーネント
Dock Manager WebComponent を利用し、ドケットまたはフローティング パネルを使用して独自の Web ビューを作成します。新しいフローティング パネルを追加するには、右側のアクション ペインを開き、[フローティング ペインの追加] ボタンをクリックします。新しいペインを目的の場所にドラッグアンドドロップします。
API リファレンス
- IgxGridComponent
- IgxGridComponent スタイル
- IgxColumnComponent
- IgxGridRow
- IgxTreeGridRow
- IgxHierarchicalGridRow
- IgxGridCell