【问题标题】:How to sum the value from dynamic nested array field and create a object for per input field如何对动态嵌套数组字段的值求和并为每个输入字段创建一个对象
【发布时间】:2020-05-28 18:31:08
【问题描述】:

我正在尝试使用 Angular 8 为一门课程开发一个数字评分系统。实际情况非常复杂,因此我将部分分开并创建了一个新项目来解释我的问题所在。如果您查看图像,您将了解我正在努力完成的工作。假设这是一门课程的成绩表。对于一门课程,可以有不同的考试。注册本课程的学生,他们将参加本课程的特定考试,然后教师将使用此表格提交编号。假设,对于第一行第一列(期中考试),表格不应允许您提供超过 25 分或小于零的分数。如果您在 Mid Exam 字段中输入 20,那么 Total 将显示 20,GP 将显示 0.00,Grade 将显示 F。然后如果您在 Final Exam 字段中输入 30,总值将更新为 50,GP 将更新到 0.00 并将更新为 F。再次,如果您在评估字段中输入 40,总值将更新为 90,GP 将更新为 4.00 并将更新为 A。所有行都会发生同样的事情.如果您单击“保存”按钮,将提交数字。

现在我正在描述我尝试过的代码和我的问题,这里studentListexamListgradePointList 数组将从后端服务填充课程的数据。我创建了populateSomeDemoData 方法来生成这些数据。 studentList 是 Student 对象的数组,其中包含学生的 id 和名称。 ExamList 是一个考试对象数组,其中包含考试 ID、考试名称和该考试的总分。 gradePointList 包含字母等级和等级点数的范围。假设总分在 90(最小数字)和 100(最大数字)之间,则成绩为 4.00,字母成绩为 A。我尝试创建一个表单 gradeForm 并在examList 上为每行应用一个循环来创建输入场地。但为此,我无法使用表单控件访问特定字段,这就是为什么我未能计算连续的总分和成绩点的原因。同样出于同样的原因,我未能创建将发送到后端的 Grade 对象列表。 Grade 是每个字段的对象,将包含学生、考试和编号。如果一个字段被更新,这个成绩对象也将被更新。如果保存按钮触发,这个号码列表将被传递到后端。

HTML 部分是:

<form [formGroup]="gradeForm" (ngSubmit)="saveGrade()">
  <table>
    <thead>
    <tr>
      <th> #</th>
      <th>Student</th>
      <th *ngFor="let exam of examList">{{exam.name}} ({{exam.marks}}%)</th>
      <th>Total</th>
      <th>GP</th>
      <th>Grade</th>
    </tr>
    </thead>

    <tbody>
    <tr *ngFor="let student of studentList; let i = index">
      <th> {{ i + 1 }} </th>
      <th> {{ student.id }} <br/> {{student.name}} </th>
      <th *ngFor="let grade of gradeFields.controls; let j = index" formArrayName="gradeFields">
        <input type="number" [formControlName]="j">
      </th>
      <th>??</th>
      <th>??</th>
      <th>??</th>
    </tr>
    </tbody>
  </table>

  <div class="wrapper">
    <button class="button">Save Grade</button>
  </div>
</form>

TypeScript 部分是:

export class AppComponent {

  public gradeForm: FormGroup;

  // This arrays will populate dynamically from the database
  public examList: Exam[] = [];
  public studentList: Student[] = [];
  public gradePointList: GradePoint[] = [];
  public gradeList: Grade[] = [];

  constructor(private formBuilder: FormBuilder) {

    this.populateSomeDemoData();

    this.createGradeForm();
  }

  createGradeForm(): void {
    this.gradeForm = this.formBuilder.group({
      gradeFields: this.formBuilder.array([
        this.formBuilder.control('')
      ])
    });

    for (let i = 0; i < this.examList.length - 1; i++) {
      this.gradeFields.push(this.formBuilder.control(''));
    }
  }

  get gradeFields() {
    return this.gradeForm.get('gradeFields') as FormArray;
  }

  saveGrade(): void {
    console.log(this.gradeForm.get('gradeFields').value);
    console.log(this.gradeList);
    console.log('Grade List Saved');
  }

  populateSomeDemoData(): void {
    // Static value for populating arrays for easy explanation
    const midTerm = new Exam();
    midTerm.id = '1';
    midTerm.name = 'Mid Exam';
    midTerm.marks = 25;

    const finalExam = new Exam();
    finalExam.id = '2';
    finalExam.name = 'Final Exam';
    finalExam.marks = 30;

    const assessmentExam = new Exam();
    assessmentExam.id = '3';
    assessmentExam.name = 'Assessment';
    assessmentExam.marks = 40;

    const attendance = new Exam();
    attendance.id = '4';
    attendance.name = 'Attendance';
    attendance.marks = 5;

    this.examList.push(midTerm);
    this.examList.push(finalExam);
    this.examList.push(assessmentExam);
    this.examList.push(attendance);


    // Static value for populating arrays for easy explanation
    const student1 = new Student();
    student1.id = '14101561';
    student1.name = 'Petey Cruiser';

    const student2 = new Student();
    student2.id = '14112201';
    student2.name = 'Bob Frapples';

    const student3 = new Student();
    student3.id = '14112202';
    student3.name = 'Paul Molive';

    const student4 = new Student();
    student4.id = '16113004';
    student4.name = 'Anna Mull';

    const student5 = new Student();
    student5.id = '16113005';
    student5.name = 'Gail Forcewind';

    this.studentList.push(student1);
    this.studentList.push(student2);
    this.studentList.push(student3);
    this.studentList.push(student4);
    this.studentList.push(student5);


    // Static value for populating arrays for easy explanation
    const aGradePoint = new GradePoint();
    aGradePoint.minimumNumber = 90;
    aGradePoint.maximumNumber = 100;
    aGradePoint.gradePoint = 4;
    aGradePoint.letterGrade = 'A';

    const bGradePoint = new GradePoint();
    bGradePoint.minimumNumber = 80;
    bGradePoint.maximumNumber = 89;
    bGradePoint.gradePoint = 3.5;
    bGradePoint.letterGrade = 'B';

    const cGradePoint = new GradePoint();
    cGradePoint.minimumNumber = 70;
    cGradePoint.maximumNumber = 79;
    cGradePoint.gradePoint = 3;
    cGradePoint.letterGrade = 'C';

    const dGradePoint = new GradePoint();
    dGradePoint.minimumNumber = 60;
    dGradePoint.maximumNumber = 69;
    dGradePoint.gradePoint = 2.5;
    dGradePoint.letterGrade = 'D';

    const fGradePoint = new GradePoint();
    fGradePoint.minimumNumber = 0;
    fGradePoint.maximumNumber = 59;
    fGradePoint.gradePoint = 0;
    fGradePoint.letterGrade = 'F';
    this.gradePointList.push(aGradePoint);
    this.gradePointList.push(bGradePoint);
    this.gradePointList.push(cGradePoint);
    this.gradePointList.push(dGradePoint);
    this.gradePointList.push(fGradePoint);
  }
}

export class Student {
  public id: string;
  public name: string;

  constructor(student?) {
    student = student || {};
    this.id = student.id || null;
    this.name = student.name || null;
  }
}

export class Exam {
  public id: string;
  public name: string;
  public marks: number;

  constructor(exam?) {
    exam = exam || {};
    this.id = exam.id || null;
    this.name = exam.name || null;
    this.marks = exam.marks || 0;
  }
}

export class GradePoint {
  public minimumNumber: number;
  public maximumNumber: number;
  public gradePoint: number;
  public letterGrade: string;

  constructor(gradePoint?) {
    gradePoint = gradePoint || {};
    this.minimumNumber = gradePoint.minimumNumber || 0;
    this.maximumNumber = gradePoint.maximumNumber || 59;
    this.gradePoint = gradePoint.gradePoint || 0;
    this.letterGrade = gradePoint.letterGrade || 'F';
  }
}

export class Grade {
  public student: Student;
  public exam: Exam;
  public number: number;

  constructor(grade?) {
    grade = grade || {};
    this.student = grade.student || null;
    this.exam = grade.exam || null;
    this.number = grade.number || null;
  }
}

为了更好地理解代码,我创建了一个 StackBlitz。请点击此链接查看代码。 Click here

我看到了很多与表单数组相关的帖子,但我的情况完全不同。在这里,我没有任何添加按钮来推送数组或计算值。并且所有情况都有固定的形式。但在我的例子中,数组也将从列表中生成,因为examList 是动态的。

我认为我在表单上创建时遇到了问题,或者我的方法是错误的。请帮助我完成我的目标,生成总分并创建成绩单。将不胜感激。提前致谢。

【问题讨论】:

  • 这能回答你的问题吗? Angular Reactive Forms with nested Form Arrays
  • 这个场景需要用到嵌套数组,我看你的成绩字段是数组,应该是嵌套数组。
  • 也许没有。我看过很多帖子,表单数组有很多这样的例子,但我的情况完全不同。在这里,我没有任何添加按钮来推送数组或计算值。所有情况都有固定的表格,就像你提到的那个链接一样,“项目名称”是固定的。但就我而言,数组也将从列表中生成,因为examList 是动态的。
  • 您是否意识到您的 getFields 数组长度是 4 而应该是 16 ?这不是敲钟吗?您每次都在循环并使用索引作为 formControl 的名称。
  • 是的,我明白了。但我不明白如何为这 20 个字段创建表单字段并动态获取表单字段值。你能帮帮我吗?

标签: angular typescript angular-reactive-forms angular-forms


【解决方案1】:

好的,我可以看到,要满足您的要求,您必须使用嵌套数组,您需要遵循这个结构,或者至少是一个二维数组, Grades[][]:grades[studentId][examId],看代码你会更明白,你的代码中缺少很多,例如对分数的验证以及对总和 GP 字段的计算,我不会打扰这些部分(我添加了一个用于计算和验证的示例,但您需要对其进行修改),但我将向您展示如何正确填充您的表单组,以及如何使用它,拥有动态视图。 正如我提到的,您需要一个二维数组,对我来说,我提供了更多信息,所以我有一个对象数组,该对象有 2 个属性,一个是学生控制(保留学生 ID),一个是成绩数组学生 这是我用来填充表单组的循环

createGradeForm(): void {
    this.gradeForm = this.formBuilder.group({
        gradeFields: this.formBuilder.array([])
    });
    //loop all students 
    for (let b = 0; b < this.studentList.length; b++) {
    // create a form group to hold student data 
        const userGroup = this.formBuilder.group({
            student: this.formBuilder.control(this.studentList[b].id),
            grades: this.formBuilder.array([])
        })
        //populate grades form array 
        for (let i = 0; i < this.examList.length; i++) {
            //Bonus, I added a validator for you, max value is the mark, please apply min and other validations on your own. 
            userGroup.get('grades').push(this.formBuilder.control('', [Validators.max(this.examList[i].marks)]));
        }
        // add the form group to your array 
        this.gradeFields.push(userGroup);
    } 
    //Check result here 
    console.log(this.gradeFields);

}

对于您的视图,您将嵌套 ng-for,一个用于循环学生行,一个用于循环您的成绩(列)

<tbody>
    <!-- loop through the higher level array (students) --> 
    <ng-container *ngFor="let student of gradeFields.controls; let i = index" formArrayName='gradeFields'>
        <ng-container [formGroupName]='i'>
            <tr>
                <th> {{ i + 1 }} </th>
                <-- this is student control -->
                <th> {{ student.get('student').value }} </th>
                <-- loop through your grades array --> 
                <th *ngFor="let grade of student.get('grades').controls; let j = index" formArrayName="grades">
                    <-- bonus here, I added an example for validation, add style to your css for .inValid to see when invalid -->
                    <input [class.inValid]='!grade.valid' type="number" [formControlName]="j">
                </th>
                 <-- one more bonus, an example of calculation getTotal -->
                <th>{{getTotal(i)}}</th>
                <th>??</th>
                <th>??</th>
            </tr>
        </ng-container>
    </ng-container>
</tbody>

获取总函数

//It takes the index of the student, get all grades, and accumlate them, the logic is wrong, please modify it, I just sum all grades, you have to add the logic
getTotal(ind) {
    const studentGrades = this.gradeFields.value[ind].grades;

    return studentGrades.reduce((a, b) => Number(a) + Number(b));
}

working stackblitz here

Read More about form arrays and reactive forms

【讨论】:

  • 感谢您的回答。它帮助我彻底解决问题。
【解决方案2】:

感谢蒙泽。他给了我想法并向我展示了一个样本。我完全按照他的回答解决了这个问题。由于我完全解决了它,如果它对某人有帮助,我将分享详细信息。为了解决这个问题,我动态地创建了表单。对于每个学生,我创建了一个小组,并为每个考试创建了一个表单控件。然后我遍历多个学生列表,再次遍历每个学生组的标记字段并添加表单控件。您将从createGradeForm 方法中得到清晰的想法。这是 StackBlitz 链接:https://stackblitz.com/edit/grade-upload-9nnr8p

TS 文件:

export class AppComponent {

    public gradeForm: FormGroup;

    // This arrays will populate dynamically from the database
    public examList: Exam[] = [];
    public studentList: Student[] = [];
    public gradePointList: GradePoint[] = [];

    constructor(private formBuilder: FormBuilder) {
        this.populateSomeDemoData();
        this.createGradeForm();
    }

    private createGradeForm(): void {
        this.gradeForm = this.formBuilder.group({});

        this.studentList.forEach(student => {
            this.gradeForm.addControl(student.id, this.formBuilder.array([]));
            this.examList.forEach(exam => {
                const grade: Grade = new Grade();
                grade.student = student;
                grade.exam = exam;
                grade.number = 0; // Replace the number if previously saved

                const studentGrades: FormArray = this.gradeForm.get(student.id) as FormArray;
                studentGrades.push(this.createGradeFormGroup(grade));
            });
        });
    }

    createGradeFormGroup(grade: Grade): FormGroup {
        return this.formBuilder.group({
            student: [grade.student, [Validators.required]],
            exam: [grade.exam, [Validators.required]],
            number: [grade.number, [Validators.required, Validators.min(-1), Validators.max(grade.exam.marks)]]
        });
    }

    getMaxNumberValue(index: number): number {
        if (this.examList[index]) {
            return this.examList[index].marks;
        }
        return 0;
    }

    getTotalNumber(studentId: string): number {
        let totalNumber = 0;

        const studentWiseGradeList: any[] = this.gradeForm.getRawValue();
        if (studentWiseGradeList && studentWiseGradeList[studentId]) {
            studentWiseGradeList[studentId].forEach(grade => {
                if (grade.student.id === studentId) {
                    if (grade.number < 0 || grade.number > grade.exam.marks) {
                        // Ignoring the error number
                    } else {
                        totalNumber += grade.number;
                    }
                }
            });
        }
        return totalNumber;
    }

    getGradePoint(studentId: string): number {
        let gradePoint = 0;
        const totalMarks = this.getTotalNumber(studentId);
        this.gradePointList.forEach(gp => {
            if (gp.minimumNumber <= totalMarks && gp.maximumNumber >= totalMarks) {
                gradePoint = gp.gradePoint;
            }
        });
        return gradePoint;
    }

    getLetterGrade(studentId: string): string {
        let letterGrade = 'F';
        const totalMarks = this.getTotalNumber(studentId);
        this.gradePointList.forEach(gp => {
            if (gp.minimumNumber <= totalMarks && gp.maximumNumber >= totalMarks) {
                letterGrade = gp.letterGrade;
            }
        });
        return letterGrade;
    }

    saveGrade(): void {
        if (this.gradeForm.status === 'INVALID') {
            alert('Grade has invalid entry');
            return;
        }
        const gradeList: Grade[] = [];
        const studentWiseGradeList: any[] = this.gradeForm.getRawValue();
        if (studentWiseGradeList) {
            for (const studentId in studentWiseGradeList) {
                if (studentId) {
                    studentWiseGradeList[studentId].forEach(gradeObj => {
                        if (gradeObj.student.id === studentId) {
                            if (gradeObj.number < 0 || gradeObj.number > gradeObj.exam.marks) {
                                alert('Grade has invalid entry');
                                return;
                            } else {
                                const grade: Grade = new Grade();
                                grade.student = gradeObj.student;
                                grade.exam = gradeObj.exam;
                                grade.number = gradeObj.number;
                                gradeList.push(grade);
                            }
                        }
                    });
                }
            }
        } else {
            alert('Grade is invalid');
            return;
        }

        console.log(gradeList);
        console.log('Grade list saved');
    }

}

HTML:

<form [formGroup]="gradeForm" (ngSubmit)="saveGrade()">
    <table>
        <thead>
        <tr>
            <th> #</th>
            <th>Student</th>
            <th *ngFor="let exam of examList">{{exam.name}} ({{exam.marks}}%)</th>
            <th>Total</th>
            <th>GP</th>
            <th>Grade</th>
        </tr>
        </thead>

        <tbody>
        <tr *ngFor="let student of studentList; let i = index">
            <th> {{ i + 1 }} </th>
            <th> {{ student.id }} <br/> {{student.name}} </th>
            <th *ngFor="let grade of gradeForm.get(student.id)?.controls; let j = index">
                <input required min="0" [max]="getMaxNumberValue(j)" [class.inValid]="!gradeForm.get(student.id)?.controls[j].valid" type="number" [formControl]="gradeForm.get(student.id)?.controls[j].controls.number" />
            </th>
            <th>{{getTotalNumber(student.id)}}</th>
            <th>{{getGradePoint(student.id)}}</th>
            <th>{{getLetterGrade(student.id)}}</th>
        </tr>
        </tbody>
    </table>

    <div class="wrapper">
        <button class="button">Save Grade</button>
    </div>
</form>

<pre>{{this.gradeForm.getRawValue() | json}}</pre>

如果有人遇到类似情况,我希望这个示例对其他人有所帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-06-28
    • 2018-09-04
    • 1970-01-01
    • 1970-01-01
    • 2021-02-05
    • 2022-11-03
    • 2016-12-29
    • 1970-01-01
    相关资源
    最近更新 更多