You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

重构Angular 17查询构建器:移除嵌套调用并使用NgTemplate

用NgTemplate重构Angular查询构建器消除组件嵌套重复

核心思路

把query-builder组件的模板内容抽离成可复用的ng-template,通过ngTemplateOutlet实现递归渲染,替代原来的组件嵌套调用,彻底消除重复的组件实例创建逻辑。

修改后的代码

1. 重构query-builder.component.html

将原组件模板内容封装到ng-template中,递归时引用该模板而非组件自身:

<ng-template #groupTemplate let-group>
  <div class="alert alert-warning alert-group">
    <div class="input-group">
      <select [(ngModel)]="group.operator" class="form-control input-sm">
        <option *ngFor="let o of operators" [value]="o.name">{{ o.name }}</option>
      </select>
      <button style="margin-left: 5px" (click)="addCondition(group)" class="btn btn-sm btn-success">
        <span class="glyphicon glyphicon-plus-sign"></span> Add Condition
      </button>
      <button style="margin-left: 5px" (click)="addGroup(group)" class="btn btn-sm btn-success">
        <span class="glyphicon glyphicon-plus-sign"></span> Add Group
      </button>
      <button style="margin-left: 5px" (click)="removeGroup(group)" class="btn btn-sm btn-danger">
        <span class="glyphicon glyphicon-minus-sign"></span> Remove Group
      </button>
    </div>
    <div class="group-conditions">
      <div *ngFor="let rule of group.rules" class="condition">
        <div [ngSwitch]="rule.hasOwnProperty('group')">
          <div *ngSwitchCase="true">
            <!-- 用模板引用替代组件嵌套 -->
            <ng-container [ngTemplateOutlet]="groupTemplate" [ngTemplateOutletContext]="{ $implicit: rule.group }"></ng-container>
          </div>
          <div *ngSwitchDefault>
            <div class="input-group">
              <select [(ngModel)]="rule.field" class="form-control input-sm">
                <option *ngFor="let t of fields" [value]="t.name">{{ t.name }}</option>
              </select>
              <select style="margin-left: 5px" [(ngModel)]="rule.condition" class="form-control input-sm">
                <option *ngFor="let c of conditions" [value]="c.name">{{ c.name }}</option>
              </select>
              <input style="margin-left: 5px" type="text" [(ngModel)]="rule.data" class="form-control input-sm" />
              <button style="margin-left: 5px" (click)="removeCondition(group, rule)" class="btn btn-sm btn-danger">
                <span class="glyphicon glyphicon-minus-sign">Delete</span>
              </button>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</ng-template>

<!-- 渲染根组模板 -->
<ng-container [ngTemplateOutlet]="groupTemplate" [ngTemplateOutletContext]="{ $implicit: group }"></ng-container>

2. 调整query-builder.component.ts方法

为适配模板上下文传递,修改操作方法并完善删除逻辑:

import { Component, Input, Output, EventEmitter, ViewChild, TemplateRef } from '@angular/core';
import { Group, QueryService, Rule } from '../shared/query.service';

@Component({
  selector: 'query-builder',
  templateUrl: './query-builder.component.html',
  styleUrls: ['./query-builder.component.css']
})
export class QueryBuilderComponent {
  @Input() group: Group = { operator: 'AND', rules: [], parent: null };
  @ViewChild('groupTemplate') groupTemplate!: TemplateRef<any>;
  
  operators = [
    { name: 'AND' },
    { name: 'OR' }
  ];
  fields = [
    { name: 'Firstname' },
    { name: 'Lastname' },
    { name: 'Birthdate' },
    { name: 'City' },
    { name: 'Country' }
  ];
  conditions = [
    { name: '=' },
    { name: '<>' },
    { name: '<' },
    { name: '<=' },
    { name: '>' },
    { name: '>=' }
  ];
  @Output() queryChanged = new EventEmitter<Group>();

  constructor(private queryService: QueryService) {}

  addCondition(currentGroup: Group) {
    currentGroup.rules.push({ field: '', condition: '', data: '' });
    this.queryChanged.emit(this.group);
  }

  addGroup(currentGroup: Group) {
    // 为新组设置父组引用
    const newGroup: Group = { operator: 'AND', rules: [], parent: currentGroup };
    currentGroup.rules.push({ group: newGroup });
    this.queryChanged.emit(this.group);
  }

  removeGroup(currentGroup: Group) {
    const parent = currentGroup.parent;
    if (parent) {
      // 从父组规则中移除当前组
      parent.rules = parent.rules.filter(rule => rule.group !== currentGroup);
      this.queryChanged.emit(this.group);
    }
  }

  removeCondition(currentGroup: Group, rule: Rule) {
    // 从当前组规则中移除指定条件
    currentGroup.rules = currentGroup.rules.filter(r => r !== rule);
    this.queryChanged.emit(this.group);
  }
}

3. app.component相关文件无需修改

保持原有调用逻辑即可,注意补充queryChanged事件绑定:

<div class="container">
  <h1>Angular Query Builder</h1>

  <div class="alert alert-info">
    <strong>Example Output</strong><br>
    <span [innerHTML]="output"></span>
  </div>

  <query-builder [group]="filter.group" (queryChanged)="onQueryChanged($event)"></query-builder>
</div>

重构优势

  • 消除组件递归嵌套导致的重复代码与冗余实例
  • 模板复用更高效,减少DOM节点创建开销
  • 逻辑集中在单个组件类中,维护性更强

内容的提问来源于stack exchange,提问作者Smit

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 06:18:15