如何以角度动态创建 n 级嵌套展开/折叠组件

Posted

技术标签:

【中文标题】如何以角度动态创建 n 级嵌套展开/折叠组件【英文标题】:How to create n level nested expand/collapse component dynamically in angular 【发布时间】:2019-02-23 15:13:49 【问题描述】:

我正在使用 div 开发一个 n 级嵌套表的脚本。

所以有 5 到 6 列 n 行数,每一列都必须展开/折叠按钮,单击该按钮我会调用 API,它会为我提供与所选行过滤器相关的数据。

以前当我使用核心 javascript 和 jQuery 时,我使用文档选择器的 find 方法来识别展开/折叠按钮的父级,并仅使用 innerhtmlappend 在特定 div 之后推送动态创建的 HTML jQuery的方法

我对 Angular 有点陌生,而且工作不多。请帮我解决这个问题。

splitOpt 是一个对象数组,我将根据这些对象拆分报表数据。

this.splitOpt = [
    
        id: "country",
        label: "Country"
    ,
    
        id:"os".
        label:"Operating System"
    ,
    
        id:"osv".
        label:"Operating System Version"
    
]

获取报告的功能

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

检查是否有更多拆分的功能

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;
       
   

    

根据拆分和报告数据绘制表格的代码。

假设在splitopt 对象中只有一个针对该国家/地区的对象,而不是checkIfHaveMoreSplits() 返回0,这意味着我不必提供展开按钮,如果不是0,则展开按钮将出现在那里.

点击展开按钮后,我将从splitopt 中选择下一个元素并调用API 以获取将拆分参数作为载体的报告等。

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

我正在管理一个数组,该数组确定我可以展开折叠元素的深度

让我们假设数组包含三个元素,即国家、运营商和操作系统

因此,我将绘制的第一个表格包含表格中的所有国家/地区,点击展开按钮,我将发送所选国家/地区并获取该特定国家/地区的运营商。获得响应后,我想根据响应创建自定义 HTML,并在所选行后附加 html。

这里是截图以及完整的工作流程:)

步骤1

第 2 步

第 3 步

【问题讨论】:

什么是splitbykey 按键拆分是单维值,根据我们的报表数据进行拆分。例如。首先,用户已选择国家作为拆分参数,因此我将调用拆分为国家/地区,api 将为所有国家/地区数据提供响应,我将相应地绘制表格,现在假设我想获取该国家/地区的运营商数据。下一步将选择国家作为拆分数据。现在下一个请求就像拆分参数是运营商和特定国家/地区,我将为其传递过滤器。 【参考方案1】:

我建议为您要显示的每个动态 HTML 片段编写一个自定义角度组件。然后,您可以编写一个循环组件,它将根据您提供的类型列表*ngIf 您的嵌套组件。像这样:

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

CountryComponent 的示例。其他组件看起来相似。

// 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));
  

现在让你的 api 返回类似


  "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
  

请根据您的具体需求进行调整。例如,您必须管理通过组件层次结构的状态传递(使用组件状态、可观察服务、MobX、NgRx - 选择您的毒药)。

希望这会有所帮助:-)

【讨论】:

听起来不错,但国家、运营商和操作系统不会被修复。 apis 将有 n 个选项,所以基本上我所做的就是维护一个具有所有维度的数组。基于此,我想管理展开折叠的事情 如果我理解正确,您有n 类型的东西要显示在循环折叠组件中,每种类型都必须有自己的设置并依赖于 API 中的一些数据,对吧?正如我在答案中所写,示例中没有涉及数据管理,因为我不知道你想要什么。该解决方案足够通用,可以连接到任何类型的状态管理。也许如果您展示如何加载数据以及结构是什么,我可以更好地理解并为您提供更多帮助:-) 是的,我也更新了代码。一旦获得批准,它将对所有人可见 请看一下更新的示例。我希望我理解你想要完成的事情:-)【参考方案2】:

我没有把解决方案放在这里,因为我不知道你的 js 代码是什么。但您可能需要考虑使用ViewContainerRef 动态附加元素。希望这会有所帮助

【讨论】:

@RipulChhabra 会有帮助的 让我修改一些东西,当前代码用于生产。将不得不创建类似代码的演示。给我一些时间,我会更新它 @JanakPrajapati 我的时间不多,所以无法给你确切的代码。我稍后会在有时间的时候发布一个答案,同时您可以参考 Angular 提供的演示 here。也可以下载相关的example。如果您熟悉 Angular,那是不言自明的。 谢谢,我会努力的 我尝试实现相同但没有为我解决我基本上创建了子组件。单击展开按钮时,我加载该动态子组件并将其放置在行中但问题是每次它包含在同一个容器中而不是相应的按钮单击行中 这是代码我创建了另一个问题,以防万一你能帮助我用它***.com/questions/52535851/…

以上是关于如何以角度动态创建 n 级嵌套展开/折叠组件的主要内容,如果未能解决你的问题,请参考以下文章

如何根据动态内容和最大尺寸限制折叠/展开视图?

考虑到动态内容高度,使用 vue 过渡平滑展开/折叠过渡

动态组件试用的时候没效果

如何以角度动态删除组件

如何动态计算自定义展开/可折叠 UITableViewCell 的高度

以角度 2/4 动态添加组件