I am developing a script of n-level of nested tables using div.
So there are 5 to 6 columns with n
number of rows, every first column have to expand/collapse button, on click of which I make a call to API which gives me data respective to selected row filters.
Previously when I worked with core JavaScript and jQuery, I was using find
method of document selector to identify the parent of expand/collapse button and push dynamically created HTML after that specific div only using innerHTML
or append
method of jQuery
I am a bit new to angular and haven't worked much. Please help me to solve this.
splitOpt
is an array of objects based on which I will split report data.
this.splitOpt = [
{
id: "country",
label: "Country"
},
{
id:"os".
label:"Operating System"
},
{
id:"osv".
label:"Operating System Version"
}
]
Function to get reports
getReport() {
// apiFilters are array of object having some values to filter report data
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;
}
}
var split = this.splitOpt[0].id;
this._apis.getReportData(split, apiFilters[0]).subscribe(response => {
if (response.status == 1200) {
this.reportData = response.data.split_by_data;
}
})
}
Function to check if there are more splits or not
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;
}
}
}
Code to draw table based on split and report data.
Let's assume there is only one object for the country in splitopt
object than checkIfHaveMoreSplits()
returns 0
which means I do not have to give expand button and if it is not 0
that expand button will appear there.
Onclick of expanding button I will select next element from splitopt
and call API to get report having split param as a carrier and so on.
<div class="table" >
<div class="row" *ngFor="let rData of reportData; let i = index;" >
<div class="col" >
<button
class="btn btn-sm"
*ngIf="checkIfHaveMoreSplits(splitbykey) !== 0"
(click)="splitData(splitbykey)"
>+</button>
{{rData[splitbykey]}}
</div>
<div class="col">{{rData.wins}}</div>
<div class="col">{{rData.conversions}}</div>
<div class="col">{{rData.cost}}</div>
<div class="col">{{rData.bids}}</div>
<div class="col">{{rData.impressions}}</div>
<div class="col">{{rData.rev_payout}}</div>
</div>
I am managing one array which identifies how deep I can expand collapse element
Let us assume that array has three elements namely country, carrier, and os
So first table which I will draw having all the countries in the table with expand button on click of which I will send selected country and get carriers of that specific country. After getting the response I want to create custom HTML based on response and append html after selected row.
Here are the screenshots as well consist of full workflow :)
Step 1
Step 2
Step 3
I'd propose writing a custom angular component for each of your dynamic HTML pieces you want to display. Then you can write a recurrent component that will *ngIf
your nested components based on a type list you provide. Like so:
// dynamic.component.ts
export type DynamicComponentType = 'country' | 'os' | 'osv';
export interface IOptions { /* whatever options you need for your components */ }
export type DynamicComponentOptions = { type: DynamicComponentType, options: IOptions};
@Component({
selector: 'app-dynamic',
template = `
<app-country *ngIf="current.type == 'country'" [options]="current.options" />
<app-os *ngIf="current.type == 'os'" [options]="current.options" />
<app-osv *ngIf="current.type == 'osv'" [options]="current.options" />
<ng-container *ngIf="!!subTypes">
<button (click)="dynamicSubComponentShow = !dynamicSubComponentShow" value="+" />
<app-dynamic *ngIf="dynamicSubComponentShow" [options]="subOptions" />
</ng-container>`,
// other config
})
export class DynamicComponent {
@Input() options: DynamicComponentOptions[];
get current(): DynamicComponentOptions {
return this.options && this.options.length && this.options[0];
}
get subOptions(): DynamicComponentOptions[] {
return this.options && this.options.length && this.options.slice(1);
}
dynamicSubComponentShow = false;
// component logic, other inputs, whatever else you need to pass on to the specific components
}
Example for CountryComponent
. The other components will look similar.
// country.component.ts
@Component({
selector: 'app-country',
template: `
<div>Country label</div>
<p>Any other HTML for the country component using the `data` observable i.e.</p>
<span>x: {{ (data$ | async)?.x }}</span>
<span>y: {{ (data$ | async)?.y }}</span>
`,
})
export class CountryComponent {
@Input() options: IOptions;
data$: Observable<{x: string, y: number}>;
constructor(private countryService: CountryService) {
// load data specific for this country based on the input options
// or use it directly if it already has all your data
this.data$ = countryService.getCountryData(this.options);
}
}
// my.component.ts
@Component({
template: `
<div class="table" >
<div class="row" *ngFor="let rData of reportData$ | async; let i = index;" >
<div class="col" >
<app-dynamic [options]="options$ | async"></app-dynamic>
</div>
...
</div>
</div>`,
// other cmp config
})
export class MyComponent {
options$: Observable<DynamicComponentOptions[]>;
reportData$: Observable<ReportData>;
constructor(private reportService: ReportService){
// simplified version of your filter calculation
let apiFilters: {} = this.sFilters
.map(f => f[0])
.filter(f => f && f.values && f.values.length)
.reduce((f, acc) => acc[f.id] = f.values && acc, {});
this.reportData$ = reportService.getReportData(this.splitOpt[0].id, apiFilters).pipe(
filter(r => r.status == 1200),
map(r => r.data.split_by_data)
);
this.options$ = this.reportData$.pipe(map(d => d.YOUR_OPTIONS));
}
}
Now make your api return something like
{
"status": 1200,
"data": {
"YOUR_OPTIONS": [{
"type": "country"
"options" { "id": 1, ... } // options for your country component initialization
}, {
"type": "os",
"options" { "id": 11, ... } // options for your os component initialization
}, ...],
// your other report data for the main grid
}
}
Please, adjust this to your specific needs. You will have to manage passing of state through the component hierarchy for example (using component state, observable services, MobX, NgRx - pick your poison).
Hope this helps a little :-)
I'm not putting the solution here as I do not know what you js code is. But you might want to consider using ViewContainerRef to append elements dynamically. Hope this helps
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With