最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to create n level nested expandcollapse component dynamically in angular - Stack Overflow

programmeradmin3浏览0评论

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 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

Share Improve this question edited Sep 25, 2018 at 17:32 lealceldeiro 15k6 gold badges54 silver badges84 bronze badges asked Sep 19, 2018 at 12:32 Ripul ChhabraRipul Chhabra 2343 silver badges17 bronze badges 2
  • What is splitbykey – brk Commented Sep 23, 2018 at 4:19
  • Split by key is single dimension value according to that our report data will be split. for example. At first user have selected country as split param so i will make call having split as country and api will provide response for all countries data and i will draw table accordingly, now suppose i want to get carriers data in that countries. next step will be selecting countries as split data. now next request will be like split parameter is carrier and specific country for which i will passed in filter. – Janak Prajapati Commented Sep 25, 2018 at 9:20
Add a ment  | 

2 Answers 2

Reset to default 2 +25

I'd propose writing a custom angular ponent for each of your dynamic HTML pieces you want to display. Then you can write a recurrent ponent that will *ngIf your nested ponents based on a type list you provide. Like so:

// dynamic.ponent.ts

export type DynamicComponentType = 'country' | 'os' | 'osv';
export interface IOptions { /* whatever options you need for your ponents */ }
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;

  // ponent logic, other inputs, whatever else you need to pass on to the specific ponents
}

Example for CountryComponent. The other ponents will look similar.

// country.ponent.ts

@Component({
  selector: 'app-country',
  template: `
    <div>Country label</div>
    <p>Any other HTML for the country ponent 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.ponent.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 ponent initialization
    }, {
      "type": "os",
      "options" { "id": 11, ... } // options for your os ponent 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 ponent hierarchy for example (using ponent 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

发布评论

评论列表(0)

  1. 暂无评论