Angular 通用:"Cannot read property '_updateTreeValidity' of undefined"

Angular Universal: "Cannot read property '_updateTreeValidity' of undefined"

我正在尝试在 Angular Universal 中构建动态表单。表单的设置使得某些问题是强制性的,而其他问题则不是,这取决于表单的创建者是如何设计的。当显示表单供用户填写时,它在没有 Angular Universal(JiT 编译器,vs AoT 编译器)的情况下工作得很好。

该表单包含一个自定义组件,该组件被注入到每个表单问题中,允许用户将他们的回答输入到组件中以供提交。

应用程序编译正常,但是当加载这个组件时,出现很多错误:

Cannot read property '_updateTreeValidity' of undefined

Cannot read property 'setParent' of undefined at e._registerControl

ERROR Error: formGroup expects a FormGroup instance. Please pass one in.

这是表单的打字稿:

import { Component, OnInit, ViewChild, OnDestroy, NgZone, EventEmitter } from '@angular/core';
import { Router, ActivatedRoute, Params } from '@angular/router';
import { AuthenticationService } from '../../services/authentication.service';
import { ProgramsService } from '../../services/programs.service';
import { InfoService } from '../../services/info.service';
import { FormGroup, FormControl, FormBuilder, Validators, FormArray } from '@angular/forms';
import { SessionsService } from '../../services/sessions.service';
import { Answers } from '../formComponents/answers';
import { AnswerComponent } from '../formComponents/answer.component';


@Component({
  templateUrl: './intro.component.html',
  styleUrls: ['./intro.component.css'],
  providers: [SessionsService, InfoService, ProgramsService]
})
export class IntroComponent implements OnInit, OnDestroy{

  constructor(private router: Router, private route: ActivatedRoute, private _programsService: ProgramsService, private zone: NgZone, private _fb: FormBuilder, private _sessionsService: SessionsService){

    this.myForm = this._fb.group({
      questions: this._fb.array([])
    });


    this.subcribeToFormChanges(); 
  }




  programId: string;
  sessionId: string;
  private sub: any;
  res;
  coachUsername;
  welcomeHeader;
  welcomeDescription;
  welcomeFiles;
  requiredQuestions;
  formData: FormData;
  public myForm: FormGroup;
  public events: any[] = [];
  loading;
  introCompleted;
  isForm = false;



  ngOnInit() {
    this.sub = this.route.params.subscribe(params => {
      this.sessionId = params['id']; 
      this.getSessionInfo(this.sessionId);
    });
  }





  getSessionInfo(id){
    this.loading = true;
    this._sessionsService.getSession(id).subscribe(res=>{
      this.loading = false;
      console.log(res.programId);
      this.programId = res.programId;
      if(res.introSessionCompleted == false){
        this.introCompleted = false;
        this.getForm();
      }
      if(res.introSessionCompleted == true){
        this.introCompleted = true;
        this.getAnswers();
      }
    })
  }



  getAnswers(){
    this._sessionsService.getAnswerIntro(this.sessionId).subscribe(res=>{
      console.log('session answers');
      console.log(res);
      this.fillAnswers(res);
    })
  }



  fillAnswers(res){
    this.getFormLight();
    for(var i=0; i<res.questions.length; i++){
      this.answerQuestion(res.questions[i].type, res.questions[i].placeholder, res.questions[i].title, res.questions[i].required, res.questions[i].answer);
    }
  }



  getFormLight(){
    this._programsService.clientGetIntro(this.programId).subscribe(res=>{
      console.log('Intro res:')
      console.log(res)
      this.welcomeDescription = res.welcomeDescription;
      this.welcomeHeader = res.welcomeHeader;
      this.welcomeFiles = res.welcomeFiles;
    })
  }

  getForm(){
    this._programsService.clientGetIntro(this.programId).subscribe(res=>{
      this.isForm = true;
      console.log('Intro res:')
      console.log(res)
      this.res = res;
      this.welcomeDescription = res.welcomeDescription;
      this.welcomeHeader = res.welcomeHeader;
      this.welcomeFiles = res.welcomeFiles;
      this.requiredQuestions = res.requiredQuestions;
      if(this.requiredQuestions){
        for(var i=0; i<this.requiredQuestions.length; i++){
          this.addQuestion(this.requiredQuestions[i].type, this.requiredQuestions[i].placeholder, this.requiredQuestions[i].title, this.requiredQuestions[i].required);
        }
      }
    })
  }



  initQuestion(type, placeholder, title, required) {
    if(required == 'yes'){
      return this._fb.group({
      type: [type],
      placeholder: [placeholder],
      title: [title],
      required: [required],
      answer: ['', Validators.required]
    });
    }
    if(required == 'no'){
      return this._fb.group({
      type: [type],
      placeholder: [placeholder],
      title: [title],
      required: [required],
      answer: ['']
    });
    }  

  }

  initAnswerQuestion(type, placeholder, title, required, answer){
    return this._fb.group({
      type: [type],
      placeholder: [placeholder],
      title: [title],
      required: [required],
      answer: new FormControl({value: answer, disabled: true})
    });
  }

  ngOnDestroy() {
    this.sub.unsubscribe();
  }

  save(model: Answers, isValid: boolean) {

    this._sessionsService.answerIntro(this.programId, this.sessionId, JSON.stringify(model)).subscribe(res=>{
      console.log(res);
      if(res.message == 'Finished updating answers'){
        this.router.navigateByUrl('/account/sessions/view/'+this.sessionId)
      }
    })
  }

  addQuestion(type, placeholder, title, required) {
    const control = <FormArray>this.myForm.controls['questions'];
    const addrCtrl = this.initQuestion(type, placeholder, title, required);
    control.push(addrCtrl);
  }

  answerQuestion(type, placeholder, title, required, answer) {
    const control = <FormArray>this.myForm.controls['questions'];
    const addrCtrl = this.initAnswerQuestion(type, placeholder, title, required, answer);
    control.push(addrCtrl);
  }

  removeQuestion(i: number) {
    const control = <FormArray>this.myForm.controls['questions'];
    control.removeAt(i);
  }

  subcribeToFormChanges() {
    const myFormStatusChanges$ = this.myForm.statusChanges;
    const myFormValueChanges$ = this.myForm.valueChanges;
    myFormStatusChanges$.subscribe(x => this.events.push({ event: 'STATUS_CHANGED', object: x }));
    myFormValueChanges$.subscribe(x => this.events.push({ event: 'VALUE_CHANGED', object: x }));
  }

}

这是表单的 html:

<div class="container animated fadeIn" style="padding-top:140px" >
  <div class="session-container">
  <div class="row nomargin">
    <div class="session-container-header">
      <div *ngIf="introCompleted" class="back" [routerLink]="['/account/sessions/view', sessionId]">
        <i class="fa fa-arrow-circle-left"></i>Session Dashboard
      </div>
      <div *ngIf="!introCompleted" class="back" [routerLink]="['/account/sessions']">
        <i class="fa fa-arrow-circle-left"></i>All Sessions
      </div>
      <div class="title">
        Session Introduction
      </div>
      <div *ngIf="introCompleted" class="settings-icon">
                    <i class="fa fa-gear" [class.fa-spin]="mouseOvered" (mouseover)="mouseOvered=true" (mouseleave)="mouseOvered=false" [routerLink]="['/account/sessions/view/'+id+'/settings']"></i>
                </div>
    </div>
    </div>
    <div class="row nomargin">
      <div *ngIf="!loading" class="session-container-content">
        <div class="row nomargin">



        <div class="intro-container">
          <div class="intro-welcome">
            <a>{{welcomeHeader}}</a>
          </div>
          <div class="intro-photo">

          </div>
          <div class="intro-description">
            <a>{{welcomeDescription}}</a>
          </div>


          <div class="intro-files">
            <div class="intro-border">
              <a>Introduction Files</a>
            </div>
            <div class="files-row">
              <div *ngFor="let file of welcomeFiles" class="file-container" [style.background]="'url('+file.url+')'">
                <div class="overlay">
                  <a [href]="file.url" class="file-url"><div class="download-button">
                     <a><i class="fa fa-cloud-download" style="margin-right: 5px;"></i>Download</a>
                  </div></a>
                </div>
              </div>
            </div>
          </div>



          <div class="intro-questions">
            <div class="intro-border">
              <a>Introduction Questions</a>
            </div>
            <form *ngIf="isForm == true;" [formGroup]="myForm"> 
            <div class="intro-answers-container">

              <div *ngFor="let question of myForm.controls.questions.controls; let i=index" class="panel panel-default">
                  <answer [group]="myForm.controls.questions[i]"></answer>
              </div>


            </div>           
            </form>

          </div>

          <div *ngIf="!introCompleted && !loading" class="intro-submit">
            <button *ngIf="myForm.valid" class="submit-button" (click)="save(myForm.value, true)">Complete Program Introduction</button>
            <button *ngIf="!myForm.valid" class="submit-button invalid" >Complete Program Introduction</button>
          </div>
        </div>

        </div>


      </div>
      <div *ngIf="loading" class="session-container-content">
        <img class="loading-ring" src="../assets/img/ring.svg">
      </div>
    </div>
  </div>
</div>

这是自定义答案组件打字稿

import { Component, Input, OnInit } from '@angular/core';
import { FormGroup } from '@angular/forms';

@Component({
    moduleId: module.id,
    selector: 'answer',
    templateUrl: 'answer.component.html',
    styleUrls: ['./answer.component.css']
})
export class AnswerComponent implements OnInit {
    @Input('group')

    public answerForm: FormGroup;


    type;
    title;
    required;
    disabled;

    ngOnInit(){
        this.type = this.answerForm.controls['type'].value;
        this.required = this.answerForm.controls['required'].value;
        this.title = this.answerForm.controls['title'].value;
        if(this.answerForm.controls['answer'].value != null && this.answerForm.controls['answer'].value != ''){
            this.disabled = true;

            console.log('disabled');
        }
    }





}

最后,这里是自定义组件html

    <div [formGroup]="answerForm">
    <div class="form-group">

        <div class="left-hand">
            <div class="question-text"><a>{{title}}</a></div>
        </div>

        <div class="right-hand">
            <textarea *ngIf="type=='text'" class="form-answer" placeholder="Type answer here..." formControlName="answer"></textarea>
            <input class="number" type="number" placeholder="Enter answer here..." formControlName="answer" *ngIf="type=='number'" >
            <select *ngIf="type=='yesno'" formControlName="answer" >
                <option value="" disabled selected>Choose answer</option>
                <option>Yes</option>
                <option>No</option>
            </select>
        </div>


    </div>
</div>

我发现了问题,数组中 ngFor 对象的响应之一是空值,因此弄乱了组件。