如何在动态组件中添加更多的数据分页?

时间:2018-12-03 12:15:00

标签: javascript angular angular-dynamic-components

我正在研究一种解决方案,但发现其中存在性能问题。

我已经开发了多嵌套级别的报告系统。

基本上,它由表中带有展开和折叠按钮的行数组成。

每当用户单击“扩展”按钮时,我都会调用服务api,并在该特定行下附加包含接收到的数据的新动态组件。

现在我将获得数千行,并假设用户使用扩展按钮扩展多行,浏览器将挂起或崩溃。

最初,我尝试使用角度虚拟滚动插件,但是它在每个级别都添加了滚动条,请考虑用户是否已扩展到四个级别,而垂直滚动条则无法实现,

我认为从用户角度来看是不可行的

我认为添加分页(例如“加载更多记录”)将是理想的解决方案,每当用户单击该按钮时,我都会进行ajax调用并将检索到的数据追加到最后一列之后。

我不知道如何在角度上工作

这是我的代码

报告模板:

<div class="report-table-container" >
<ng-container *ngFor="let rData of reportData; let i = index; last as isLast" >
<div class="row report-row" >
<div class="col-4" style="padding-left: 5px;">
<button 
class="btn btn-sm" 
*ngIf="checkIfHaveMoreSplits(this.splitOpt[0].id) !== 0 && rData.isCollapsed == true"
(click)="splitData(rowWiseFilterObj(rData,this.splitOpt[0].id),this.splitOpt[0].id,sFilters,splitOpt,i,rData,selectedDate)"
row="rData">+</button>
<button 
class="btn btn-sm" 
*ngIf="checkIfHaveMoreSplits(this.splitOpt[0].id) !== 0 && rData.isCollapsed == false" 
(click)="removeDynamicComponent(rData,i)"
>-</button>
<span *ngIf="this.splitOpt[0].id !== '__time'">{{rData[this.splitOpt[0].id]}}</span>
<span *ngIf="this.splitOpt[0].id === '__time'">{{ rData[this.splitOpt[0].id]  | date:'dd-MM-yyyy HH:mm:ss Z'}}</span>
</div>
<div class="col-2">{{convertToDecimals(rData.impressions,2)}}</div>
<div class="col-2">{{convertToDecimals(rData.conversions,2)}}</div>
<div class="col-1">{{convertToDecimals(rData.bids,2)}}</div>
<div class="col-1" >{{convertToDecimals(rData.wins,2)}}</div>
<div class="col-1">{{convertToDecimals(rData.cost,2)}}</div>
<div class="col-1">{{convertToDecimals(rData.rev_payout,2)}}</div>
</div>
<div *ngIf="isLast">{{altrows("#ffffff","#f5f5f5")}}</div>
<ng-template #dynamic ></ng-template>

</ng-container>
</div>

报告组件

export class ReportsComponent implements OnInit, AfterViewInit {

filterSelection: any = false;
dimentionSelection: any = false;
splitSelection: any = false;
dValueSelection: any = false;
filterDimentions: any = [];
selectedDimentions: any = [];
filterDimentionsValues: any = [];
currentSelectedDimension: any = [];
appLoading: any = false;
query: any = '';
q: any = '';
sFilters: any = [];
splitOpt: any = [];
posX: any = 100;
posY: any = 100;
reportData: any = [];
service: any;
reportLoading: boolean = false;
currentGraphSelection: string = "impressions";


selectedDate: any = {
startDate: moment(),
endDate: moment()
};
showRangeLabelOnInput: boolean = true;
alwaysShowCalendars: boolean = true;
keepCalendarOpeningWithRange: boolean = true;
ranges: any = {
'Today': [moment(), moment()],
'Yesterday': [moment().subtract(1, 'days'), moment().subtract(1, 'days')],
'Last 7 Days': [moment().subtract(6, 'days'), moment()],
'Last 30 Days': [moment().subtract(29, 'days'), moment()],
'This Month': [moment().startOf('month'), moment().endOf('month')],
'Last Month': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')]
}
invalidDates: moment.Moment[] = [];

isInvalidDate = (m: moment.Moment) => {
return this.invalidDates.some(d => d.isSame(m, 'day'))
}


// chart

Highcharts = Highcharts; // required
chartConstructor = 'chart'; // optional string, defaults to 'chart'
chartOptions = {
chart: {
type: "spline"
},
title: {
text: "Impressions"
},

plotOptions: {
area: {
fillColor: "rgba(92, 205, 222,0.2)",
lineColor: "#5ccdde",

},
series: {
marker: {
fillColor: '#FFFFFF',
lineWidth: 1,
lineColor: null // inherit from series
},
fillOpacity: 0.5
}
},

xAxis: {
type: 'datetime',
dateTimeLabelFormats: { // don't display the dummy year
month: '%e. %b',
year: '%b'
},
title: {
text: 'Time'
}

},
yAxis: {
title: {
text: "Impression"
},

},

tooltip: {
enabled: true
},

series: [{
name: "Impressions",
data: [],
type: "spline"
}]
};

@ViewChild(DaterangepickerDirective) pickerDirective: DaterangepickerDirective;
picker: DaterangepickerComponent;



@ViewChildren('dynamic', {
read: ViewContainerRef
}) viewContainerRef: QueryList < ViewContainerRef >

constructor(
private _script: ScriptLoaderService,
private _apis: ApplicationApiService,
private modalService: NgbModal,
private cfr: ComponentFactoryResolver,
private toastr: ToastrService,
private _configService: ConfigService,
private http: Http,
private cd: ChangeDetectorRef,
private activatedRoute: ActivatedRoute,
@Inject(Service) service,
) {
this.service = service
}

ngOnInit() {
this.picker = this.pickerDirective.picker;

}

ngAfterViewInit() {
Helpers.bodyClass('m-page--wide m-header--fixed m-header--fixed-mobile m-footer--push m-aside--offcanvas-default reports-page');
this._apis.getReportDimensions().subscribe(response => {
if (response.status == 1200) {
this.filterDimentions = response.data;

}
});
//    this.updateGraph(this.currentGraphSelection);
this.cd.detectChanges();
}

openFilterSelection(e) {
var xPos;
if (e.clientX > 1024) {
xPos = e.clientX - 250;
} else {
xPos = e.clientX
}
this.posX = xPos - 10 + "px";
this.posY = e.clientY + 10 + "px";

this.filterSelection = true
this.dimentionSelection = true;
this.dValueSelection = false;

}

openSplitSelection(e) {
var xPos;
if (e.clientX > 1024) {
xPos = e.clientX - 250;
} else {
xPos = e.clientX
}
this.posX = xPos - 10 + "px";
this.posY = e.clientY + 10 + "px";
this.splitSelection = true
this.dimentionSelection = true;
this.dValueSelection = false;
}

getFilterValues(d, e) {

if (e) {
var xPos;
if (e.clientX > 1024) {
xPos = e.clientX - 250;
} else {
xPos = e.clientX
}
this.posX = xPos - 10 + "px";
this.posY = e.clientY + 10 + "px";

}


this.filterDimentionsValues = [];
this.appLoading = true;
this.currentSelectedDimension = d.id;

var apiFilters: any = [{}];
var index = this.sFilters.findIndex(function(v) {
return v.id == d.id
});

if (index === -1) {
for (var i = 0; i < this.sFilters.length; i++) {
if (this.sFilters[i].values.length > 0) {
var k;
k = this.sFilters[i].id
apiFilters[0][k] = this.sFilters[i].values;
}
}
} else {
for (var i = 0; i < index; i++) {
if (this.sFilters[i].values.length > 0) {
var k;
k = this.sFilters[i].id
apiFilters[0][k] = this.sFilters[i].values;
}
}
}
delete apiFilters[0][d.id]

this._apis.getFilterDimentionValues(d.id, this.q, apiFilters[0], this.selectedDate).subscribe(response => {
if (response.status == 1200) {
this.filterDimentionsValues = response.data.split_by_data;
this.appLoading = false;
}
})
this.filterSelection = true
this.dValueSelection = true;
this.dimentionSelection = false;
}

onSearchChange() {

var apiFilters: any = [{}];
for (var i = 0; i < this.sFilters.length; i++) {

if (this.sFilters[i][0].values.length > 0) {
var k;
k = this.sFilters[i][0].id
apiFilters[0][k] = this.sFilters[i][0].values;
}
}
this._apis.getFilterDimentionValues(this.currentSelectedDimension, this.q, apiFilters[0], this.selectedDate).subscribe(response => {
if (response.status == 1200) {
this.filterDimentionsValues = response.data.split_by_data;
this.appLoading = false;
}
})
}

hidePopup() {
this.filterSelection = false
this.dValueSelection = false;
this.dimentionSelection = false;
this.splitSelection = false;


}

goBackToDimensions() {
this.filterSelection = true
this.dimentionSelection = true;
this.dValueSelection = false;
}

selectFilters(d) {
var a = this.currentSelectedDimension;

if (this.sFilters.filter(e => e.id === a).length > 0) {
this.sFilters.filter(function(v) {
if (v.id == a) {
if (!v.values.includes(d[a])) {
v.values.push(d[a])
} else {
var index = v.values.indexOf(d[a]);
if (index > -1) {
v.values.splice(index, 1);
}
}
}
});
} else {
let labelText;
for (var i = 0; i < this.filterDimentions.length; i++) {
if (this.filterDimentions[i].id == this.currentSelectedDimension) {
labelText = this.filterDimentions[i].text;
}
}

var obj = {
id: this.currentSelectedDimension,
label: labelText,
values: [d[a]],
}
this.sFilters.push(obj)
}

this.sFilters = this.sFilters.filter(function(v) {
return v.values.length > 0;
});
}



checkIfDimvalueExists(d) {

var a = this.currentSelectedDimension;

if (this.sFilters.length > 0) {
var sel = this.sFilters.filter(function(v) {
if (v.id == a) {
return v
}
});

if (sel.length > 0) {
var index = sel[0]["values"].indexOf(d[a]);
return index;
} else {
return -1;
}

} else {
return -1;
}

}
arrToString(d) {
return d.toString()
}



selectSplitDimention(d) {

if (this.splitOpt.filter(e => e.id === d.id).length == 0) {
this.splitOpt.push(d);
if (this.splitOpt.length === 1) {
this.getReport();
}
if (this.splitOpt.length === 0) {
this.reportData = [];
} else {
// do nothing
}
} else {
this.splitOpt = this.splitOpt.filter(function(obj) {
return obj.id !== d.id;
});


if (this.splitOpt.length === 0) {
this.reportData = [];
} else {
this.reportData = [];
this.getReport();
}

}

this.hidePopup();

}

getReport() {
this.hidePopup();
if (this.splitOpt.length === 0) {
//this.updateGraph(this.currentGraphSelection);
return false;
}


var apiFilters: any = [{}];
for (var i = 0; i < this.sFilters.length; i++) {
if (this.sFilters[i].values.length > 0) {
var k;
k = this.sFilters[i].id
apiFilters[0][k] = this.sFilters[i].values
}
}

var split = this.splitOpt[0].id;
this.reportData = [];
this.reportLoading = true;
this._apis.getReportData(split, apiFilters[0], this.selectedDate).subscribe(response => {
if (response.status == 1200) {
this.reportData = response.data.split_by_data;
this.reportData.map(function(obj) {
obj.isCollapsed = true;
return obj;
});
this.reportLoading = false;
//this.cd.detectChanges();
}
});
}


checkIfHaveMoreSplits(c) {
if (this.splitOpt.length > 0) {
var index = this.splitOpt.findIndex(function(v) {
return v.id == c
})

if (typeof(this.splitOpt[index + 1]) != "undefined") {
return this.splitOpt[index + 1];
} else {
return 0;
}
}

}

splitData(obj, cSplit, sF, splitOptions, index, rowData, selectedDate) {
var nextSplit = this.checkIfHaveMoreSplits(cSplit);
this.service.setRootViewContainerRef(this.viewContainerRef.toArray()[index]);
this.service.addDynamicComponent(obj, nextSplit, sF, splitOptions, rowData, selectedDate);
}

removeDynamicComponent(rowData, index) {
this.viewContainerRef.toArray()[index].clear();
rowData.isCollapsed = true;
}

rowWiseFilterObj(row, split) {
var arr = [];
var obj = {
id: split,
label: split,
values: [row[split]]
}
arr.push(obj);

return arr;

}

convertToDecimals(input, decimals) {
var exp, rounded,
suffixes = ['K', 'M', 'B', 'T', 'P', 'E'];


if (input < 1000) {
return parseFloat(input).toFixed(2);;
}

exp = Math.floor(Math.log(input) / Math.log(1000));

return (input / Math.pow(1000, exp)).toFixed(decimals) + suffixes[exp - 1];
}

altrows(firstcolor, secondcolor) {

var tableElements = document.getElementsByClassName("report-row");
for (var j = 0; j < tableElements.length; j++) {
if (j % 2 == 0) {
( < any > tableElements[j]).style.backgroundColor = firstcolor;
} else {
( < any > tableElements[j]).style.backgroundColor = secondcolor;
}

}

}

updateFilters(d, o) {
if (o === "s") {
this.splitOpt = this.splitOpt.filter(function(obj) {
return obj.id !== d.id;
});
if (this.splitOpt.length > 0) {
this.getReport();
} else {
this.reportData = [];
}
}
if (o === "d") {
this.sFilters = this.sFilters.filter(function(obj) {
return obj.id !== d.id;
});

if (this.sFilters.length > 0) {
this.getReport();
} else {
//this.reportData = [];
}

}
}

openDatePicker(e) {
this.pickerDirective.open(e);
}
datesUpdated(e) {
if (e.startDate != null && e.endDate != null) {
if (this.splitOpt.length !== 0) {
console.log(1)
this.getReport();
}

if (this.splitOpt.length === 0) {
//this.updateGraph(this.currentGraphSelection);
}
}
}

updateGraph(t) {

this.currentGraphSelection = t;
var apiFilters: any = [{}];


for (var i = 0; i < this.sFilters.length; i++) {
if (this.sFilters[i].values.length > 0) {
var k;
k = this.sFilters[i].id
apiFilters[0][k] = this.sFilters[i].values;
}
}

this.reportLoading = true;
this._apis.getReportGraph(t, apiFilters[0], this.selectedDate).subscribe(response => {
if (response.status == 1200) {
this.chartOptions = {
chart: {
type: "spline"
},
title: {
text: t
},

plotOptions: {
area: {
fillColor: "rgba(92, 205, 222,0.2)",
lineColor: "#5ccdde",

},
series: {
marker: {
fillColor: '#FFFFFF',
lineWidth: 1,
lineColor: null // inherit from series
},
fillOpacity: 0.5
}
},

xAxis: {
type: 'datetime',
dateTimeLabelFormats: { // don't display the dummy year
month: '%e. %b',
year: '%b'
},
title: {
text: 'Time'
}

},
yAxis: {
title: {
text: t
},

},

tooltip: {
enabled: true
},

series: [{
name: t,
data: response.data,
type: "spline"
}]
};

this.reportLoading = false;
}
});


}
}

服务文件:

export class Service {
factoryResolver;
rootViewContainer;
reportLoading: boolean = false;

constructor(
@Inject(ComponentFactoryResolver) factoryResolver,
private _apis: ApplicationApiService,

) {
this.factoryResolver = factoryResolver
}
setRootViewContainerRef(viewContainerRef) {
this.rootViewContainer = viewContainerRef
}
addDynamicComponent(selectedRow, nextSplit, selectedFilters, splitOptions, rowData, selectedDate) {
const factory = this.factoryResolver
.resolveComponentFactory(DynamicComponent)

const component = factory
.create(this.rootViewContainer.parentInjector)

component.instance.selectedRow = selectedRow;
component.instance.nextSplit = nextSplit;
component.instance.selectedFilters = selectedFilters;
component.instance.splitOptions = splitOptions;
component.instance.rowData = rowData;
component.instance.reportLoading = true;
component.instance.selectedDate = selectedDate;

var a = JSON.parse(JSON.stringify(selectedFilters));
var b = JSON.parse(JSON.stringify(selectedRow));


a.filter(function(o1) {
return b.some(function(o2) {
if (o1.id === o2.id) {
o1.values = o2.values;
}
});
});

//Find values that are in result1 but not in result2
var uniqueResultOne = a.filter(function(obj) {
return !b.some(function(obj2) {
return obj.id == obj2.id;
});
});

//Find values that are in result2 but not in result1
var uniqueResultTwo = b.filter(function(obj) {
return !a.some(function(obj2) {
return obj.id == obj2.id;
});
});

//Combine the two arrays of unique entries
var result = a.concat(uniqueResultOne.concat(uniqueResultTwo));
result = result.filter((s1, pos, arr) => arr.findIndex((s2) => s2.id === s1.id) === pos);

this.reportLoading = true;

this._apis.getReportData(nextSplit.id, this.getApiFilters(result), selectedDate).subscribe(response => {
if (response.status == 1200) {

response.data.split_by_data.map(function(obj) {
obj.isCollapsed = true;
return obj;
})

component.instance.splitByData = response.data.split_by_data;
component.instance.selectedDate = selectedDate;
component.instance.reportLoading = false;
rowData.isCollapsed = false;
}
})

this.rootViewContainer.insert(component.hostView)
}



getApiFilters(selectedFilters) {
var apiFilters: any = [{}];
for (var i = 0; i < selectedFilters.length; i++) {
if (selectedFilters[i].values.length > 0) {
var k;
k = selectedFilters[i].id
apiFilters[0][k] = selectedFilters[i].values
}
}
return apiFilters[0];
}


}

动态组件:

@Injectable()
@Component({
selector: 'dynamic-component',
template: `
<div class="snippet" *ngIf="reportLoading === true">
<div class="stage">
<div class="dot-typing"></div>
</div>
</div>

<ng-container *ngFor="let rData of splitByData; let i = index; last as isLast">
<div class="row report-row" >
<div class="col-4"  [ngStyle]="{'padding-left': calculateTextPadding(nextSplit.id) }">
<button 
class="btn btn-sm" 
*ngIf="checkIfHaveMoreSplits(nextSplit.id,splitOptions) !== 0 && rData.isCollapsed == true"
(click)="splitData(rowWiseFilterObj(rData,nextSplit.id,selectedRow),nextSplit.id,selectedFilters,splitOptions,i,rData,selectedDate)">+</button>

<button 
class="btn btn-sm" 
*ngIf="checkIfHaveMoreSplits(nextSplit.id,splitOptions) !== 0 && rData.isCollapsed == false" 
(click)="removeDynamicComponent(rData,i,selectedRow,nextSplit.id)"
>-</button>
<span *ngIf="nextSplit.id !== '__time'">{{rData[nextSplit.id]}}</span>
<span *ngIf="nextSplit.id === '__time'">{{ rData[nextSplit.id]  | date:'dd-MM-yyyy HH:mm:ss Z'}}</span>
</div>
<div class="col-2">{{convertToDecimals(rData.impressions,2)}}</div>
<div class="col-2">{{convertToDecimals(rData.conversions,2)}}</div>
<div class="col-1">{{convertToDecimals(rData.bids,2)}}</div>
<div class="col-1">{{convertToDecimals(rData.wins,2)}}</div>
<div class="col-1">{{convertToDecimals(rData.cost,2)}}</div>
<div class="col-1">{{convertToDecimals(rData.rev_payout,2)}}</div>
</div>
<div *ngIf="isLast">{{altrows("#ffffff","#f5f5f5")}}</div>
<ng-template #dynamic ></ng-template>
</ng-container>

<div> Load more </div>


`
})
export class DynamicComponent implements OnInit, AfterViewInit {
@Input() selectedRow: any;
@Input() nextSplit: any;
@Input() selectedFilters: any;
@Input() splitOptions: any;
@Input() splitByData: any;
@Input() rowData: any;
@Input() reportLoading: any;
@Input() selectedDate: any;
factoryResolver;
rootViewContainer;

@ViewChildren('dynamic', {
read: ViewContainerRef
}) viewContainerRef: QueryList < ViewContainerRef >

constructor(
@Inject(ComponentFactoryResolver) factoryResolver,
private _apis: ApplicationApiService,
private cd: ChangeDetectorRef
) {
this.factoryResolver = factoryResolver
}

ngOnInit() {

}

ngAfterViewInit() {

}

setRootViewContainerRef(viewContainerRef) {
this.rootViewContainer = viewContainerRef
}

addDynamicComponent(selectedRow, nextSplit, selectedFilters, splitOptions, sRow, selectedDate) {
const factory = this.factoryResolver
.resolveComponentFactory(DynamicComponent)

const component = factory
.create(this.rootViewContainer.parentInjector)


component.instance.selectedRow = selectedRow;
component.instance.nextSplit = nextSplit;
component.instance.selectedFilters = selectedFilters;
component.instance.splitOptions = splitOptions;
component.instance.selectedDate = selectedDate;

var a = JSON.parse(JSON.stringify(selectedFilters));
var b = JSON.parse(JSON.stringify(selectedRow));



a.filter(function(o1) {
return b.some(function(o2) {
if (o1.id === o2.id) {
o1.values = o2.values;
}
});
});

//Find values that are in result1 but not in result2
var uniqueResultOne = a.filter(function(obj) {
return !b.some(function(obj2) {
return obj.id == obj2.id;
});
});

//Find values that are in result2 but not in result1
var uniqueResultTwo = b.filter(function(obj) {
return !a.some(function(obj2) {
return obj.id == obj2.id;
});
});


//Combine the two arrays of unique entries
var result = a.concat(uniqueResultOne.concat(uniqueResultTwo));
result = result.filter((s1, pos, arr) => arr.findIndex((s2) => s2.id === s1.id) === pos);

this.reportLoading = true;

this._apis.getReportData(nextSplit.id, this.getApiFilters(result), selectedDate).subscribe(response => {
if (response.status == 1200) {

response.data.split_by_data.map(function(obj) {
obj.isCollapsed = true;
return obj;
});

sRow.isCollapsed = false;
component.instance.splitByData = response.data.split_by_data;
this.reportLoading = false;

}
})

this.rootViewContainer.insert(component.hostView)
}

getApiFilters(selectedFilters) {
var apiFilters: any = [{}];
for (var i = 0; i < selectedFilters.length; i++) {
if (selectedFilters[i].values.length > 0) {
var k;
k = selectedFilters[i].id
apiFilters[0][k] = selectedFilters[i].values
}
}
return apiFilters[0];
}


checkIfHaveMoreSplits(c, splitOptions) {
if (splitOptions.length > 0) {
var index = splitOptions.findIndex(function(v) {
return v.id == c
})

if (typeof(splitOptions[index + 1]) != "undefined") {
return splitOptions[index + 1];
} else {
return 0;
}
}

}

splitData(obj, cSplit, sFilters, splitOptions, index, sRow, selectedDate) {
var nextSplit = this.checkIfHaveMoreSplits(cSplit, splitOptions);
this.setRootViewContainerRef(this.viewContainerRef.toArray()[index]);
this.addDynamicComponent(obj, nextSplit, sFilters, splitOptions, sRow, selectedDate);
}

rowWiseFilterObj(row, split, prev) {

if (prev.length == 0) {
var arr = [];
var obj = {
id: split,
label: split,
values: [row[split]]
}
arr.push(obj);

return arr;

} else {
var obj = {
id: split,
label: split,
values: [row[split]]
}
prev.push(obj);
return prev

}
}

removeDynamicComponent(rowData, index, sFilters, nextSplit) {


this.viewContainerRef.toArray()[index].clear();
rowData.isCollapsed = true;
this.altrows("#ffffff", "#f5f5f5");

var index = sFilters.findIndex(function(o) {
return o.id === nextSplit;
})
if (index !== -1) {
sFilters.splice(index, 1);
}

}

convertToDecimals(input, decimals) {
var exp, rounded,
suffixes = ['K', 'M', 'B', 'T', 'P', 'E'];


if (input < 1000) {
return parseFloat(input).toFixed(2);;
}

exp = Math.floor(Math.log(input) / Math.log(1000));

return (input / Math.pow(1000, exp)).toFixed(decimals) + suffixes[exp - 1];
}

calculateTextPadding(id) {
var index = this.splitOptions.findIndex(function(v) {
return v.id == id
})

if (typeof(index) != "undefined") {
return index * 40 + "px"
} else {
return "0px";
}
}

altrows(firstcolor, secondcolor) {
var tableElements = document.getElementsByClassName("report-row");
for (var j = 0; j < tableElements.length; j++) {
if (j % 2 == 0) {
(tableElements[j]).className = "row report-row odd";
} else {
(tableElements[j]).className = "row report-row even";
}

}
}
}

您还可以找到我创建的示例的视频教程:https://www.youtube.com/watch?v=m1a2uxhoNqc

0 个答案:

没有答案