Angular Tree Grid Live Data Updates
Tree Grid 구성 요소는 사용자 상호 작용에 대한 응답성을 유지하면서 초당 수천 개의 업데이트를 처리할 수 있습니다.
最速で機能豊富な Angular Data Grid は、ページング、ソート、フィルタリング、グループ化、PDF および Excel へのエクスポートなどの機能を提供します。究極のアプリ構築エクスペリエンスとデータ操作に必要なすべてが揃っています。
Angular 라이브 데이터 업데이트 예제
아래 샘플은 모든 레코드가 초당 여러 번 업데이트될 때의 트리 그리드 성능을 보여 줍니다. UI 컨트롤을 사용하여 로드되는 레코드 수와 업데이트 빈도를 선택합니다. 동일한 데이터를 선형 차트에 공급하여 Ignite UI for Angular의 강력한 차트 작성 기능을 경험하십시오. 버튼은 Chart
선택한 행에 대한 데이터를 표시 Category Prices per Region
하고 열 버튼은 Chart
현재 행에 대해 동일하게 표시합니다.
import { Component, ElementRef, OnInit, OnDestroy, ViewChild, HostBinding } from '@angular/core';
import { AbsoluteScrollStrategy, ConnectedPositioningStrategy, DefaultSortingStrategy, HorizontalAlignment, IGroupingExpression, IgxButtonGroupComponent, IgxGroupedTreeGridSorting, IgxOverlayOutletDirective, IgxSliderComponent, IgxTreeGridComponent, ITreeGridAggregation, OverlaySettings, PositionSettings, TreeGridFilteringStrategy, VerticalAlignment, IgxGridToolbarComponent, IgxGridToolbarActionsComponent, IgxGridToolbarPinningComponent, IgxGridToolbarHidingComponent, IgxGridToolbarExporterComponent, IgxTreeGridGroupByAreaComponent, IgxColumnComponent, IgxCellEditorTemplateDirective, IgxSelectComponent, IgxFocusDirective, IgxSelectItemComponent, IgxCellTemplateDirective, IgxIconComponent, IgxTreeGridGroupingPipe } from 'igniteui-angular';
import { Contract, REGIONS } from '../data/financialData';
import { SignalRService } from '../services/signal-r.service';
import { FormsModule } from '@angular/forms';
import { NgIf, NgFor, AsyncPipe, CurrencyPipe, DatePipe } from '@angular/common';
import { IgxPreventDocumentScrollDirective } from '../directives/prevent-scroll.directive';
@Component({
providers: [SignalRService],
selector: 'app-tree-grid-finjs-sample',
styleUrls: ['./tree-grid-finjs-sample.component.scss'],
templateUrl: './tree-grid-finjs-sample.component.html',
imports: [IgxSliderComponent, FormsModule, IgxButtonGroupComponent, NgIf, IgxTreeGridComponent, IgxPreventDocumentScrollDirective, IgxGridToolbarComponent, IgxGridToolbarActionsComponent, IgxGridToolbarPinningComponent, IgxGridToolbarHidingComponent, IgxGridToolbarExporterComponent, IgxTreeGridGroupByAreaComponent, IgxColumnComponent, IgxCellEditorTemplateDirective, IgxSelectComponent, IgxFocusDirective, NgFor, IgxSelectItemComponent, IgxCellTemplateDirective, IgxIconComponent, IgxOverlayOutletDirective, AsyncPipe, CurrencyPipe, DatePipe, IgxTreeGridGroupingPipe]
})
export class TreeGridFinJSComponent implements OnDestroy, OnInit {
@ViewChild('grid1', { static: true }) public grid1!: IgxTreeGridComponent;
@ViewChild('buttonGroup1', { static: true }) public buttonGroup1!: IgxButtonGroupComponent;
@ViewChild('slider1', { static: true }) public volumeSlider!: IgxSliderComponent;
@ViewChild('slider2', { static: true }) public intervalSlider!: IgxSliderComponent;
@ViewChild(IgxOverlayOutletDirective, { static: true }) public outlet!: IgxOverlayOutletDirective;
@HostBinding('class.dark-theme')
public theme = false;
public showToolbar = true;
public selectionMode = 'multiple';
public volume = 1000;
public frequency = 500;
public data$: any;
public overlaySettings: OverlaySettings = {
modal: false
};
public controls = [
{
disabled: false,
icon: 'update',
label: 'LIVE ALL PRICES',
selected: false
},
{
disabled: true,
icon: 'stop',
label: 'Stop',
selected: false
}
];
public groupingExpressions: IGroupingExpression[] = [
{ fieldName: 'category', dir: 2, ignoreCase: true, strategy: DefaultSortingStrategy.instance() },
{ fieldName: 'type', dir: 1, ignoreCase: true, strategy: DefaultSortingStrategy.instance() },
{ fieldName: 'contract', dir: 1, ignoreCase: true, strategy: DefaultSortingStrategy.instance() }
];
public aggregations: ITreeGridAggregation[] = [
{
aggregate: (parent: any, data: any[]) => data.map((r) => r.change).reduce((ty, u) => ty + u, 0),
field: 'change'
},
{
aggregate: (parent: any, data: any[]) => data.map((r) => r.price).reduce((ty, u) => ty + u, 0),
field: 'price'
},
{
aggregate: (parent: any, data: any[]) => parent.change / (parent.price - parent.change) * 100,
field: 'changeP'
}
];
public childDataKey = 'children';
public groupColumnKey = 'categories';
public sorting = IgxGroupedTreeGridSorting.instance();
public filterStrategy = new TreeGridFilteringStrategy([this.groupColumnKey]);
public items: any[] = [{ field: 'Export native' }, { field: 'Export JS Excel' }];
public _positionSettings: PositionSettings = {
horizontalDirection: HorizontalAlignment.Left,
horizontalStartPoint: HorizontalAlignment.Right,
verticalStartPoint: VerticalAlignment.Bottom
};
public _overlaySettings: OverlaySettings = {
closeOnOutsideClick: true,
modal: false,
positionStrategy: new ConnectedPositioningStrategy(this._positionSettings),
scrollStrategy: new AbsoluteScrollStrategy()
};
public contracts = Contract;
public regions = REGIONS;
public isLoading = true;
private subscription: any;
private selectedButton: number;
private _timer: any;
constructor(private elRef: ElementRef, public dataService: SignalRService) {
this.dataService.startConnection();
this.overlaySettings.outlet = this.outlet;
this.data$ = this.dataService.data;
this.dataService.getData(0);
this.data$.subscribe((data) => {
if (data.length !== 0) {
this.isLoading = false;
};
});
}
public ngOnInit(): void {
this.overlaySettings.outlet = this.outlet;
}
public onButtonAction(event: any): void {
switch (event.index) {
case 0: {
this.disableOtherButtons(event.index, true);
if (this.dataService.hasRemoteConnection) {
this.dataService.broadcastParams(this.frequency, this.volume, true);
} else {
const currData = this.grid1.filteredSortedData ?? this.grid1.data;
this._timer = setInterval(() => this.dataService.updateAllPriceValues(currData), this.frequency);
}
break;
}
case 1: {
if (this.dataService.hasRemoteConnection) {
this.dataService.stopLiveData();
} else {
this.stopFeed();
}
this.disableOtherButtons(event.index, false);
break;
}
default: break;
}
}
updateVolume(): void {
this.dataService.hasRemoteConnection ? this.dataService.broadcastParams(this.frequency, this.volume, false) :
this.dataService.getData(this.volume);
}
public stopFeed(): void {
if (this._timer) {
clearInterval(this._timer);
}
if (this.subscription) {
this.subscription.unsubscribe();
}
}
public formatNumber(value: number) {
return value ? value.toFixed(2) : '';
}
public percentage(value: number) {
return value ? value.toFixed(2) + '%' : '';
}
public formatCurrency(value: number) {
return value ? '$' + value.toFixed(3) : '';
}
/**
* the below code is needed when accessing the sample through the navigation
* it will style all the space below the sample component element, but not the navigation menu
*/
public onThemeChanged(event: any) {
const parentEl = this.parentComponentEl();
if (event.checked && parentEl.classList.contains('main')) {
parentEl.classList.add('fin-dark-theme');
} else {
parentEl.classList.remove('fin-dark-theme');
}
}
public ngOnDestroy() {
this.stopFeed();
}
public toggleToolbar() {
this.showToolbar = !this.showToolbar;
}
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;
/* eslint-disable @typescript-eslint/member-ordering */
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
};
/* eslint-enable @typescript-eslint/member-ordering */
private disableOtherButtons(ind: number, disableButtons: boolean) {
if (this.subscription) {
this.subscription.unsubscribe();
}
this.volumeSlider.disabled = disableButtons;
this.intervalSlider.disabled = disableButtons;
this.selectedButton = ind;
this.buttonGroup1.buttons.forEach((button, index) => {
if (index === 1) { button.disabled = !disableButtons; } else {
button.disabled = disableButtons;
}
});
}
/**
* returns the main div container of the Index Component,
* if path is /samples/sample-url, or the appRoot, if path is /sample-url
*/
private parentComponentEl() {
return this.elRef.nativeElement.parentElement.parentElement;
}
get buttonSelected(): number {
return this.selectedButton || this.selectedButton === 0 ? this.selectedButton : -1;
}
}
ts<div class="grid__wrapper ig-scrollbar">
<div class="controls-wrapper">
</div>
<div class="controls-holder">
<div class="switches">
<div class="finjs-slider control-item">
<label for="slider">Records: {{volume}}</label>
<igx-slider #slider1 id="slider" [minValue]="0" [maxValue]="10000" [(ngModel)]="volume" (dragFinished)="updateVolume()" [step]="100"
[thumbLabelVisibilityDuration]="250" [continuous]="true"></igx-slider>
</div>
<div class="finjs-slider control-item">
<label for="slider">Frequency: {{frequency}} ms</label>
<igx-slider #slider2 id="slider" [minValue]="100" [maxValue]="3000" [step]="10" [(ngModel)]="frequency"
[thumbLabelVisibilityDuration]="250" [continuous]="true"></igx-slider>
</div>
</div>
<div class="control-item finjs-play-controls">
<igx-buttongroup class="finjssample-btn-group" #buttonGroup1 [values]="controls"
(selected)="onButtonAction($event)"></igx-buttongroup>
</div>
</div>
<div class="sample-toolbar">
<span *ngIf="buttonSelected === 0">Feeding {{volume}} records every {{frequency / 1000}} sec.
~{{volume/5}} records updated.</span>
</div>
<igx-tree-grid #grid1 [igxPreventDocumentScroll]="true"
[data]="data$ | async | treeGridGrouping:groupingExpressions:groupColumnKey:childDataKey:grid1:aggregations"
[childDataKey]="childDataKey"
[sortStrategy]="sorting"
[height]="'calc(100% - 76px)'"
width="100%"
[moving]="true"
[autoGenerate]="false"
hiddenColumnsText="Hidden"
[isLoading]="isLoading"
[allowFiltering]="true"
[filterMode]="'excelStyleFilter'"
[filterStrategy]="filterStrategy">
<igx-grid-toolbar *ngIf="showToolbar">
<igx-grid-toolbar-actions>
<igx-grid-toolbar-pinning></igx-grid-toolbar-pinning>
<igx-grid-toolbar-hiding title="Indicators"></igx-grid-toolbar-hiding>
<igx-grid-toolbar-exporter [exportCSV]="false"></igx-grid-toolbar-exporter>
</igx-grid-toolbar-actions>
</igx-grid-toolbar>
<!-- Empty templates for Sorting,Moving,Hiding, Pinning actions inside ESF dialog -->
<!-- <ng-template igxExcelStyleSorting></ng-template>
<ng-template igxExcelStyleMoving></ng-template>
<ng-template igxExcelStyleHiding></ng-template>
<ng-template igxExcelStylePinning></ng-template> -->
<igx-tree-grid-group-by-area
[grid]="grid1"
[(expressions)]="groupingExpressions"
[hideGroupedColumns]="true">
</igx-tree-grid-group-by-area>
<igx-column [field]="groupColumnKey" [width]="'180px'" [sortable]="false" [resizable]="true"
[disableHiding]="true"></igx-column>
<igx-column [field]="'category'" [width]="'100px'" [groupable]="true" [sortable]="true" [editable]="true">
</igx-column>
<igx-column [field]="'type'" [width]="'100px'" [groupable]="true" [sortable]="true" [editable]="true">
</igx-column>
<igx-column [field]="'contract'" [width]="'100px'" [groupable]="true" [sortable]="true" [editable]="true">
</igx-column>
<igx-column [field]="'settlement'" [width]="'100px'" [sortable]="true"></igx-column>
<igx-column [field]="'country'" [width]="'100px'" [groupable]="true" [sortable]="true" [editable]="true">
</igx-column>
<igx-column [field]="'region'" [width]="'110px'" [groupable]="true" [sortable]="true" [editable]="true">
<ng-template igxCellEditor let-cell="cell" let-value>
<igx-select [overlaySettings]="overlaySettings" [placeholder]="value" [(ngModel)]="cell.editValue" [igxFocus]="true">
<igx-select-item *ngFor="let r of regions" [value]="r.Name">{{ r.Name }}</igx-select-item>
</igx-select>
</ng-template>
</igx-column>
<igx-column [field]="'lastUpdated'" [width]="'120px'" [editable]="true" header="Last Update" dataType="date">
<ng-template igxCell let-cell="cell">
{{ cell.value | date }}
</ng-template>
</igx-column>
<igx-column [field]="'openPrice'" [width]="'120px'" dataType="number" [formatter]="formatCurrency"
[sortable]="true"></igx-column>
<igx-column [field]="'price'" [width]="'130px'" dataType="number" [cellClasses]="trends"
[sortable]="true" [disableHiding]="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" [formatter]="formatNumber" >
</igx-column>
<igx-column [field]="'changeP'" [width]="'110px'" dataType="number" [formatter]="percentage"
[sortable]="true" [cellClasses]="trendsChange">
</igx-column>
<igx-column [field]="'buy'" [width]="'110px'" dataType="number" [formatter]="formatCurrency"
[sortable]="true"></igx-column>
<igx-column [field]="'sell'" [width]="'110px'" dataType="number" [formatter]="formatCurrency"
[sortable]="true"></igx-column>
<igx-column [field]="'spread'" [width]="'110px'" dataType="number" [formatter]="formatNumber" >
</igx-column>
<igx-column [field]="'volume'" [width]="'110px'" dataType="number" [formatter]="formatNumber"
[sortable]="true"></igx-column>
<igx-column [field]="'highD'" [width]="'110px'" dataType="number" [formatter]="formatCurrency"
[sortable]="true"></igx-column>
<igx-column [field]="'lowD'" [width]="'110px'" dataType="number" [formatter]="formatCurrency"
[sortable]="true"></igx-column>
<igx-column [field]="'highY'" [width]="'110px'" dataType="number" [formatter]="formatCurrency"
[sortable]="true"></igx-column>
<igx-column [field]="'lowY'" [width]="'110px'" dataType="number" [formatter]="formatCurrency"
[sortable]="true"></igx-column>
<igx-column [field]="'startY'" [width]="'110px'" dataType="number" [formatter]="formatCurrency"
[sortable]="true"></igx-column>
</igx-tree-grid>
</div>
<div igxOverlayOutlet #outlet="overlay-outlet">
</div>
html@use '../../variables' as *;
:host ::ng-deep {
.fin-dark-theme {
.finjs-slider,
.sample-toolbar,
.group-drop-area {
color: contrast-color(null, 'gray', 900);
}
.group-drop-area {
background: color(null, 'surface', 500);
}
}
.finjs-icons {
display: flex;
align-items: center;
igx-icon {
font-size: rem(16px);
width: rem(16px);
height: rem(16px);
margin-inline-start: rem(4px);
}
}
.igx-grid__grouparea {
max-height: 100%;
height: auto;
}
.changePos,
.changeNeg,
.strongPositive,
.strongNegative {
color: contrast-color(null, 'gray', 900) !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, 'error', 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(4px);
}
}
// 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-right: rem(4px) solid color(null, 'error', 500, .5);
padding-right: rem(4px);
}
}
// selected
.igx-grid__td--selected.changePos1,
.igx-grid__td--selected.changePos2,
.igx-grid__td--selected.changePos {
background-color: color(null, 'success', 500, .5) !important;
.finjs-icons,
.igx-grid__td-text {
color: contrast-color(null, 'gray', 900);
}
}
.igx-grid__td--selected.changeNeg1,
.igx-grid__td--selected.changeNeg2,
.igx-grid__td--selected.changeNeg {
background-color: color(null, 'error', 500, .5) !important;
.finjs-icons,
.igx-grid__td-text {
color: contrast-color(null, 'gray', 900);
}
}
// 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-right: rem(4px) solid color(null, 'success', 500);
padding-right: rem(4px);
}
}
// 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-right: rem(4px) solid color(null, 'error', 500);
padding-right: rem(4px);
}
}
// selected
.igx-grid__td--selected.strongPositive1,
.igx-grid__td--selected.strongPositive2,
.igx-grid__td--selected.strongPositive {
background-color: color(null, 'success', 500) !important;
.finjs-icons,
.igx-grid__td-text {
color: contrast-color(null, 'gray', 900);
}
}
.igx-grid__td--selected.strongNegative1,
.igx-grid__td--selected.strongNegative2,
.igx-grid__td--selected.strongNegative {
background-color: color(null, 'error', 500) !important;
.finjs-icons,
.igx-grid__td-text {
color: contrast-color(null, 'gray', 900);
}
}
.controls-holder {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
width: 100%;
padding-block-end: rem(4px);
}
.switches {
display: flex;
justify-content: space-between;
align-items: center;
flex: 1 0 0%;
padding-right: rem(20px);
font-size: .9rem;
}
.control-item {
padding-right: rem(20px);
}
.igx-slider,
.igx-slider--disabled {
height: rem(24px);
}
.finjs-slider {
width: 40%;
min-width: rem(145px);
}
.finjs-play-controls {
width: 45%;
min-width: rem(620px);
margin-block-start: rem(10px);
}
.sample-toolbar {
height: rem(20px);
font-size: .8rem;
line-height: rem(20px);
margin-block-start: rem(11px);
}
.igx-grid__outlet span,
.igx-excel-filter span,
.igx-excel-filter header,
.igx-excel-filter input {
font-size: 0.8125rem;
}
.igx-button--icon {
width: 2rem;
height: 2rem;
}
}
.grid__wrapper {
position: relative;
width: 100%;
height: 100%;
inset-block-start: 0;
inset-inline-start: 0;
padding: rem(15px);
display: flex;
flex-direction: column;
igx-tree-grid {
--ig-size: var(--ig-size-small);
}
}
igx-grid {
flex: 1 0 0%;
}
.controls-wrapper {
display: flex;
justify-content: center;
position: relative;
}
.grid-toolbar-title {
max-width: none;
}
scss
이 샘플이 마음에 드시나요? 전체 Ignite UI for Angular 툴킷에 액세스하고 몇 분 안에 나만의 앱을 구축해 보세요. 무료로 다운로드하세요.
API 참조
- IgxTreeGridComponent
- IgxGridCell
- Igx트리그리드행
- IgxGrid구성 요소
- IgxGrid구성 요소 스타일
- IgxGridCell
- IgxBaseTransactionService