Angular父组件基于API返回对象的position属性按序渲染子组件的实现方法
实现基于position属性的子组件动态排序方案
核心思路
你现在的渲染顺序写死在模板结构里,要实现动态排序完全不需要大改现有逻辑,只要做到两点:
- 给每个分类好的题型组,记录它对应的排序权重(也就是组内题目携带的最小position值)
- 把所有题型组按这个权重排序后,通过循环渲染代替硬编码的固定组件顺序,避免写死DOM层级
具体代码改动
1. 组件TS逻辑调整
保留你原有的题型分类逻辑,只需要在分类完成后新增排序逻辑,把零散的四个题型数组转换成带排序信息的组列表:
// 新增类型定义,方便类型校验(可选,但是能减少类型报错) interface ActivityRenderGroup { // 标记组对应的子组件类型 componentType: 'text' | 'mcq' | 'dragDrop' | 'fillBlank'; // 对应组的题目数组 activityList: any[]; // 用于排序的权重值 order: number; } // 新增用来存最终要渲染的组列表的变量 renderableGroups: ActivityRenderGroup[] = []; textBasedActivity = []; mcqActivity = []; fillInTheBlanks = []; dragndropActivity = []; extractTypes() { // 每次分类前先清空原有的数组,避免重复推送数据 this.textBasedActivity = []; this.mcqActivity = []; this.fillInTheBlanks = []; this.dragndropActivity = []; // 原有分类逻辑完全保留,不做改动 this.resultActivity.widgets.forEach((element) => { if (element.questionType == 'mcqType') { this.mcqActivity.push(element); } else if ( element.questionType == 'textType' || element.questionType == 'imageType' ) { this.textBasedActivity.push(element); } else if (element.questionType == 'fillBlankType') { this.fillInTheBlanks.push(element); } else if (element.questionType == 'dragNDropType') { this.dragndropActivity.push(element); } }); // 新增:把四个题型数组组装成带排序值的组,再按order从小到大排序 this.renderableGroups = [ { componentType: 'text', activityList: this.textBasedActivity, // 取组内最小的position作为排序值;空数组直接给无限大,排到最后 order: this.textBasedActivity.length ? Math.min(...this.textBasedActivity.map(item => item.position)) : Infinity }, { componentType: 'mcq', activityList: this.mcqActivity, order: this.mcqActivity.length ? Math.min(...this.mcqActivity.map(item => item.position)) : Infinity }, { componentType: 'dragDrop', activityList: this.dragndropActivity, order: this.dragndropActivity.length ? Math.min(...this.dragndropActivity.map(item => item.position)) : Infinity }, { componentType: 'fillBlank', activityList: this.fillInTheBlanks, order: this.fillInTheBlanks.length ? Math.min(...this.fillInTheBlanks.map(item => item.position)) : Infinity } // 升序排序,order越小越靠前 ].sort((prev, next) => prev.order - next.order); }
为什么取组内最小position?因为你的子组件是接收同类型的全部题目做整体渲染的,不会拆分同类型题目,所以只要取这个组里最靠前的题目的position值作为组的排序依据,就能完全匹配题目本身的顺序。空数组被设置为Infinity,排序时会自动排到末尾,后续渲染时直接跳过即可,和你原有的非空判断逻辑效果一致。
2. 模板渲染逻辑调整
把原来硬编码顺序的四个子组件,换成遍历排好序的组列表渲染,用ng-container做嵌套避免生成额外DOM标签破坏原有布局:
<div class="each-activity-base"> <!-- 遍历已经排好序的组列表 --> <ng-container *ngFor="let group of renderableGroups"> <!-- 只渲染非空的组,和原有逻辑一致 --> <ng-container *ngIf="group.activityList.length > 0"> <!-- 根据组的类型匹配对应的子组件,入参和原有写法完全一致 --> <app-text-based-widget *ngIf="group.componentType === 'text'" [courseId]="courseId" [activityId]="activityId" [moduleId]="moduleId" [studentData]="studentData" [activityObjects]="group.activityList"> </app-text-based-widget> <app-multiple-choice-based-widget *ngIf="group.componentType === 'mcq'" [activityObjects]="group.activityList"> </app-multiple-choice-based-widget> <app-drag-drop-based-widget *ngIf="group.componentType === 'dragDrop'" [activityObjects]="group.activityList"> </app-drag-drop-based-widget> <app-fill-blanks-based-widget *ngIf="group.componentType === 'fillBlank'" [activityObjects]="group.activityList"> </app-fill-blanks-based-widget> </ng-container> </ng-container> </div>
效果说明
- 渲染顺序完全由题目自带的position值决定:比如拖拽题的起始position是1,选择题的起始position是2,文本题的起始position是3,就会按拖拽题→选择题→文本题的顺序渲染,不会受模板里代码书写顺序影响。
- 原有子组件的传参、内部逻辑完全没有改动,不需要调整子组件的接口定义。
- 空的题型组自动跳过渲染,和原有的
*ngIf长度判断逻辑完全一致。
内容的提问来源于stack exchange,提问作者String Name
相关产品推荐
相关产品推荐

