TypeScript cannot guarantee that the controls I am accessing are of type FormGroup or...

Discussão em 'Angular' iniciado por Oreoyona, Fevereiro 20, 2025.

  1. Oreoyona

    Oreoyona Guest

    I am trying to build a LMS using Angular but I have some issues with Angular forms. I want to be able to add dynamically modules to a given course but also to add dynamically any sort of the 3 types of contents authorized in a given module.

    Here is my template:

    <main class="container d-flex flex-column">
    <mat-card class="my-5">
    <form [formGroup]="newCourseForm" class="d-flex flex-column">
    <mat-card-content class="card-container m-3">


    <section class="left container d-flex flex-column align-items-center justify-content-center">
    <h4>Informations sur le cours</h4>
    <mat-form-field appearance="outline">
    <mat-label for="name">Nom du Cours:</mat-label>
    <input matInput id="name" formControlName="name" required>
    </mat-form-field>

    <mat-form-field appearance="outline">
    <mat-label for="imgUrl">l'URL de l'image:</mat-label>
    <input matInput id="imgUrl" formControlName="imgUrl" required>
    </mat-form-field>
    </section>

    <section class="right container d-flex flex-column align-items-center justify-content-center">
    <h4>Informations sur les modules du cours</h4>
    <div formArrayName="modules">
    @for(module of modules.controls; track $index){
    <div class="py-2 my-3 d-flex flex-column align-items-center justify-content-center" [formGroupName]="$index">
    <mat-accordion >
    <mat-expansion-panel>
    <mat-expansion-panel-header>
    <mat-panel-title>
    <h3 >Module {{ modules.at($index).get('title')!.value || ($index + 1) }}</h3>
    </mat-panel-title>

    </mat-expansion-panel-header>

    <mat-form-field appearance="outline" class="m-1">
    <mat-label for="title">Titre du module:</mat-label>
    <input matInput formControlName="title" required>
    </mat-form-field>

    <mat-form-field appearance="outline" class="m-1">
    <mat-label for="link">Lien du module:</mat-label>
    <input matInput formControlName="link" required>
    </mat-form-field>

    <mat-form-field appearance="outline" class="m-1">
    <mat-label for="contentType">Type de contenu:</mat-label>

    <mat-select formControlName="contentType" (selectionChange)="onContentTypeChange(module)" required>
    @for(option of options; track $index){
    <mat-option value= "{{option}}">{{option}}</mat-option>

    }
    </mat-select>

    </mat-form-field>


    @if(module.get('contentType')?.value == "Questions"){
    <div class="field" formArrayName="parts">
    @for(question of module.get('parts')?.controls; track $index){
    <div formGroupName="$index">
    <mat-form-field appearance="outline" class="m-1">
    <mat-label>Question:</mat-label>
    <input matInput formControlName="question" required>
    </mat-form-field>
    <mat-form-field appearance="outline" class="m-1">
    <mat-label>Réponse:</mat-label>
    <input matInput formControlName="response" required>
    </mat-form-field>
    </div>
    <button mat-button (click)="addQuestion(module)">Ajouter une question</button>

    }
    </div>
    }

    <mat-form-field appearance="outline" class="m-1">
    <mat-label for="content">Content:</mat-label>
    <textarea matInput formArrayName="content" required></textarea>
    </mat-form-field>
    </mat-expansion-panel>
    </mat-accordion>

    </div>
    }

    </div>
    </section>



    </mat-card-content>
    </form>
    <mat-card-footer>
    <button mat-raised-button (click)="addModule()" class="m-2">Ajouter un module</button>
    <button mat-flat-button (click)="onSubmit()" class="m-2">Créer le cours</button>
    </mat-card-footer>
    </mat-card>
    </main>


    My Component:

    export class CreateCourseComponent implements OnInit{
    ngOnInit(): void {
    this.addModule()
    }

    courseService = inject(CourseService)

    options = this.courseService.options;
    // Create a new form group for the course
    newCourseForm = new FormGroup({
    name: new FormControl(""),
    imgUrl: new FormControl(""),
    modules: new FormArray([])

    });

    addModule() {
    const moduleFormGroup = new FormGroup({
    title: new FormControl(""),
    link: new FormControl(""),
    contentType: new FormControl(this.options[0]), // Default to "Questions"
    content: new FormArray([]),
    parts: new FormArray([])
    });
    (this.newCourseForm.get('modules') as FormArray).push(moduleFormGroup);
    this.onContentTypeChange(moduleFormGroup)
    }

    addQuestion(moduleFormGroup: any) {
    const partsArray = moduleFormGroup.get('parts') as FormArray;
    partsArray.push(new FormGroup({
    question: new FormControl(""),
    response: new FormControl(""),
    }))
    }

    onContentTypeChange(module: FormGroup | any) {
    if (!module) {
    return;
    }

    const contentType = module.get('contentType')?.value;
    const contentArray = module.get('content') as FormArray;

    // Clear previous questions if the content type changes
    contentArray.clear();

    if (contentType === 'Questions') {
    module.removeControl('content'); // Remove the old content FormArray

    const partsArray = new FormArray([]);
    module.addControl('parts', partsArray);
    this.addQuestion(module);
    }
    }

    // Method to submit the form
    onSubmit() {
    console.log(this.newCourseForm.value)
    if (this.newCourseForm.valid) {
    const courseData = this.newCourseForm.value;
    console.log(courseData)
    // Call the API to create a new course
    this.courseService.createCourse(courseData).subscribe({
    next: (response: any) => {
    // Handle successful response
    console.log('Course created successfully:', response);
    },
    error: (error: any) => {
    // Handle error
    console.error('Error creating course:', error);
    }
    });
    } else {
    console.error('Form is invalid');
    }
    }


    get modules(): FormArray {
    return this.newCourseForm.get('modules') as FormArray;
    }

    }


    I tried to change Type Assertions inside the template using the 'as' keyword but it seems that Angular does not permit that. The $any hack does not work

    Take a look:

    <div formArrayName="modules">
    @for(module of (modules as FormArray).controls; track $index){
    <div class="py-2 my-3 d-flex flex-column align-items-center justify-content-center" [formGroupName]="$index">
    <mat-accordion>
    <mat-expansion-panel>
    <mat-expansion-panel-header>
    <mat-panel-title>
    <h3>Module {{ module.get('title')?.value || ($index + 1) }}</h3>
    </mat-panel-title>
    </mat-expansion-panel-header>

    <mat-form-field appearance="outline" class="m-1">
    <mat-label for="title">Titre du module:</mat-label>
    <input matInput formControlName="title" required>
    </mat-form-field>

    <mat-form-field appearance="outline" class="m-1">
    <mat-label for="link">Lien du module:</mat-label>
    <input matInput formControlName="link" required>
    </mat-form-field>

    <mat-form-field appearance="outline" class="m-1">
    <mat-label for="contentType">Type de contenu:</mat-label>
    <mat-select formControlName="contentType" (selectionChange)="onContentTypeChange(module)" required>
    @for(option of options; track $index){
    <mat-option value="{{option}}">{{option}}</mat-option>
    }
    </mat-select>
    </mat-form-field>

    @if(module.get('contentType')?.value == "Questions"){
    <div class="field" formArrayName="parts">
    @for(question of module.get('parts')?.controls; track $index){
    <div formGroupName="$index">
    <mat-form-field appearance="outline" class="m-1">
    <mat-label>Question:</mat-label>
    <input matInput formControlName="question" required>
    </mat-form-field>
    <mat-form-field appearance="outline" class="m-1">
    <mat-label>Réponse:</mat-label>
    <input matInput formControlName="response" required>
    </mat-form-field>
    </div>
    }
    <button mat-button (click)="addQuestion(module)">Ajouter une question</button>
    </div>
    }

    <mat-form-field appearance="outline" class="m-1">
    <mat-label for="content">Content:</mat-label>
    <textarea matInput formControlName="content" required></textarea>
    </mat-form-field>
    </mat-expansion-panel>
    </mat-accordion>
    </div>
    }
    </div>

    Continue reading...

Compartilhe esta Página