Angular Grid 라이브 데이터 업데이트
그리드 구성 요소는 사용자 상호 작용에 대한 응답성을 유지하면서 초당 수천 개의 업데이트를 처리할 수 있습니다.
最速で機能豊富な Angular Data Grid は、ページング、ソート、フィルタリング、グループ化、PDF および Excel へのエクスポートなどの機能を提供します。究極のアプリ構築エクスペリエンスとデータ操作に必要なすべてが揃っています。
Angular 라이브 데이터 업데이트 예제
아래 샘플은 모든 레코드가 초당 여러 번 업데이트될 때의 그리드 성능을 보여줍니다. UI 컨트롤을 사용하여 로드된 레코드 수와 업데이트 빈도를 선택합니다. 동일한 데이터를 선형 차트에 입력하여 Ignite UI for Angular의 강력한 차트 기능을 경험하세요. Chart
버튼은 선택한 행에 대한 Category Prices per Region
데이터를 표시하고 Chart
열 버튼은 현재 행에 대해 동일한 데이터를 표시합니다.
import { AfterViewInit, Component, HostBinding, OnDestroy, ViewChild } from '@angular/core';
import { Router } from '@angular/router';
import { IgxDialogComponent, IgxOverlayOutletDirective, OverlaySettings, IgxDialogTitleDirective, IgxDialogActionsDirective, IgxButtonDirective, IgxFocusDirective } from 'igniteui-angular';
import { IgxCategoryChartComponent, IgxCategoryChartCoreModule } 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',
imports: [ControllerComponent, GridFinJSComponent, IgxOverlayOutletDirective, IgxDialogComponent, IgxDialogTitleDirective, IgxCategoryChartCoreModule, IgxDialogActionsDirective, IgxButtonDirective, IgxFocusDirective]
})
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 ig-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 파이프는 내부적으로 그리드 뷰를 업데이트하는 데 사용됩니다. 데이터 필드 값의 변경이나 데이터 객체/데이터 수집 참조의 변경은 해당 파이프를 트리거합니다. 그러나 complex data objects
에 바인딩된 열의 경우는 그렇지 않습니다. 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 리포지토리에서 찾을 수 있습니다.
/* eslint-disable max-len */
import { AfterViewInit, ChangeDetectorRef, Component, ComponentFactoryResolver, ElementRef, Renderer2, OnDestroy, OnInit, DoCheck, TemplateRef, ViewChild, ViewContainerRef, ViewEncapsulation, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { AbsoluteScrollStrategy, ConnectedPositioningStrategy, DefaultSortingStrategy, GridColumnDataType, IgxColumnComponent, IgxGridComponent, IgxOverlayOutletDirective, IgxSelectComponent, OverlaySettings, SortingDirection, IgxSwitchComponent, IgxLabelDirective, IgxPrefixDirective, IgxIconComponent, IgxSelectItemComponent, IgxButtonDirective, IgxCellTemplateDirective, IgxPaginatorComponent } 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';
import { FormsModule } from '@angular/forms';
import { NgFor, NgIf, AsyncPipe, CurrencyPipe } from '@angular/common';
@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'],
imports: [IgxSwitchComponent, FormsModule, IgxSelectComponent, IgxLabelDirective, IgxPrefixDirective, IgxIconComponent, NgFor, IgxSelectItemComponent, IgxButtonDirective, IgxOverlayOutletDirective, IgxGridComponent, IgxColumnComponent, IgxCellTemplateDirective, NgIf, IgxPaginatorComponent, GridHostDirective, AsyncPipe, CurrencyPipe],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
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';
// 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 ig-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" [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" [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" [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" [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]="isDarkTheme ? 'dark-theme' : 'light-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);
}
}
igx-grid {
--ig-size: var(--ig-size-small);
.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);
}
}
}
igx-select {
--ig-size: var(--ig-size-small);
}
scss
허브 연결 시작
signal-r.service는 노출된 관리 가능한 매개변수인 주파수, 볼륨 및 실시간 업데이트 상태 토글 (시작/중지)의 연결 및 업데이트를 처리합니다.
this.hubConnection = new signalR.HubConnectionBuilder()
.configureLogging(signalR.LogLevel.Trace)
.withUrl('https://ko.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 클래스가 적용됩니다.
업데이트 빈도 및 데이터 볼륨
왼쪽의 액션 패널을 사용하면 데이터 피드의 빈도와 요청된 데이터의 양을 관리할 수 있습니다. 모든 그리드는 동일한 데이터 소스를 사용합니다. 데이터 피드를 중지하거나 애플리케이션 테마를 변경하거나 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를 활용하고 Docket이나 부동 패널을 사용하여 자신만의 웹 보기를 구축하세요. 새 부동 패널을 추가하려면 오른쪽에 있는 작업 창을 열고 '부동 창 추가' 버튼을 클릭하세요. 원하는 위치에 새 창을 끌어다 놓습니다.
API 참조
- IgxGrid구성 요소
- IgxGrid구성 요소 스타일
- IgxColumnComponent
- IgxGridRow
- Igx트리그리드행
- IgxHierarchicalGridRow
- IgxGridCell