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

Angular中Cannot read properties of null(writeValue)错误解决求助

Angular checkbox控件绑定导致TypeError问题的解决方案

问题描述

开发Angular车辆管理组件时,根据ID获取车辆对象,存在则编辑,不存在则注册。编辑场景下能正常获取对象,但抛出错误:TypeError: Cannot read properties of null (reading 'writeValue'),排查发现是checkbox控件导致,移除后错误消失,但业务需要保留该控件,其绑定布尔类型的veiculo.portariaSEJUSP属性。

环境信息:

  • Angular CLI: 14.0.5
  • Node: 16.14.2
  • Package Manager: npm 8.5.0
  • OS: win32 x64

原HTML代码

<div class="card shadow-sm m-5 p-5 bg-light" style="min-width: 60%; max-width: 70%">
  <mat-toolbar>{{(veiculo && veiculo.id)?'Editar Veiculo':'Cadastrar Veiculo'}}</mat-toolbar>

  <div class="card-body">
      <div class="form-group" *ngIf="veiculo.id != null">
        <label class="form-label">ID</label>
        <input type="text" class="form-control" [(ngModel)]="veiculo.id" readonly />
      </div>
      <div class="form-group">
        <label class="form-label">Placa Oeste</label>
        <input type="text" class="form-control" [(ngModel)]="veiculo.placaOeste" />
      </div>
      <div class="form-group">
        <label class="form-label">Modelo</label>
        <input type="text" class="form-control" [(ngModel)]="veiculo.modelo">
      </div>
      <div class="form-group">
        <label class="form-label">Marca</label>
        <input type="text" class="form-control" [(ngModel)]="veiculo.marca">
      </div>
      <div class="form-group">
        <label class="form-label">Ano</label>
        <input type="text" class="form-control" [(ngModel)]="veiculo.ano">
      </div>
      <div class="form-group form-check">
        <input type="checkbox" class="form-check-input">
        <label class="form-check-label" [(ngModel)]="veiculo.portariaSEJUSP">Deriva da Portaria SEJUSP/DGPC nº
    133/2018</label>
</div>

      <div>
        <button class="btn btn-primary m-2" (click)="salvar()">
          Salvar
        </button>
        <button type="button" class="btn btn-warning mx-2" routerLink="/veiculos">
          Cancelar
        </button>
      </div>
  </div>
</div>

原TS代码

export class VeiculoCadastrarEditarComponent implements OnInit {
  veiculoForm!: FormGroup;
  veiculo = new Veiculo();
  id: any = 0;
  constructor(
    private service: VeiculoService,
    private router: Router,
    private activatedRoute: ActivatedRoute
  ) {}

  ngOnInit(): void {
    this.id = this.activatedRoute.snapshot.paramMap.get('id');
    if (this.id) {
      this.service.pesquisarPorId(this.id).subscribe({
        next: (data) => (this.veiculo = data),
        error: (err) => console.log(err),
      });
    }
  }

  salvar() {
    if (this.veiculo && this.veiculo.id) {
      this.service
        .atualizar(this.veiculoForm.value, this.veiculo.id)
        .subscribe({
          next: () => this.router.navigateByUrl('/veiculos'),
          // error: () => this.onErrorEdicao(),
        });
    } else {
      this.service.cadastrar(this.veiculoForm.value).subscribe({
        next: () => this.router.navigateByUrl('/veiculos'),
        // error: () => this.onErrorCadastro(),
      });
    }
  }

  deletar() {
    this.service.deletar(this.veiculoForm.value).subscribe({
      next: () => this.router.navigateByUrl('/veiculos'),
      // error: () => this.onErrorDelete(),
    });
  }
}

问题原因

  1. ngModel绑定位置错误:把[(ngModel)]绑在了<label>标签上,而非checkbox的<input>标签,导致Angular无法正确处理表单控件绑定逻辑,触发writeValue相关错误。
  2. 属性初始化缺失:如果Veiculo类中portariaSEJUSP属性无默认值,初始化veiculo对象时该属性为undefined,checkbox绑定布尔值会出现状态异常。
  3. 表单模式混用:组件声明了响应式表单的veiculoForm,但实际使用模板驱动表单的ngModel,且veiculoForm从未初始化,造成表单状态混乱。

解决步骤

步骤1:修正checkbox的ngModel绑定

将[(ngModel)]移到<input type="checkbox">上,同时添加name属性(模板驱动表单要求控件有唯一名称):

<div class="form-group form-check">
  <input type="checkbox" class="form-check-input" [(ngModel)]="veiculo.portariaSEJUSP" name="portariaSEJUSP">
  <label class="form-check-label">Deriva da Portaria SEJUSP/DGPC nº 133/2018</label>
</div>

步骤2:确保属性初始化

在Veiculo类中给portariaSEJUSP设置默认布尔值:

export class Veiculo {
  id?: number;
  placaOeste?: string;
  modelo?: string;
  marca?: string;
  ano?: string;
  portariaSEJUSP: boolean = false; // 设置默认值
}

步骤3:统一表单模式(二选一)

选项A:继续使用模板驱动表单

删除未使用的veiculoForm,修改salvar方法直接使用veiculo对象:

export class VeiculoCadastrarEditarComponent implements OnInit {
  veiculo = new Veiculo();
  id: any = 0;
  constructor(
    private service: VeiculoService,
    private router: Router,
    private activatedRoute: ActivatedRoute
  ) {}

  ngOnInit(): void {
    this.id = this.activatedRoute.snapshot.paramMap.get('id');
    if (this.id) {
      this.service.pesquisarPorId(this.id).subscribe({
        next: (data) => (this.veiculo = data),
        error: (err) => console.log(err),
      });
    }
  }

  salvar() {
    if (this.veiculo && this.veiculo.id) {
      this.service
        .atualizar(this.veiculo, this.veiculo.id)
        .subscribe({
          next: () => this.router.navigateByUrl('/veiculos'),
          // error: () => this.onErrorEdicao(),
        });
    } else {
      this.service.cadastrar(this.veiculo).subscribe({
        next: () => this.router.navigateByUrl('/veiculos'),
        // error: () => this.onErrorCadastro(),
      });
    }
  }

  deletar() {
    this.service.deletar(this.veiculo).subscribe({
      next: () => this.router.navigateByUrl('/veiculos'),
      // error: () => this.onErrorDelete(),
    });
  }
}

选项B:切换为响应式表单(推荐)

初始化veiculoForm并绑定所有控件:

import { FormBuilder, FormGroup } from '@angular/forms';

export class VeiculoCadastrarEditarComponent implements OnInit {
  veiculoForm!: FormGroup;
  id: any = 0;
  constructor(
    private service: VeiculoService,
    private router: Router,
    private activatedRoute: ActivatedRoute,
    private fb: FormBuilder
  ) {}

  ngOnInit(): void {
    this.veiculoForm = this.fb.group({
      id: [{value: '', disabled: true}],
      placaOeste: [''],
      modelo: [''],
      marca: [''],
      ano: [''],
      portariaSEJUSP: [false]
    });

    this.id = this.activatedRoute.snapshot.paramMap.get('id');
    if (this.id) {
      this.service.pesquisarPorId(this.id).subscribe({
        next: (data) => this.veiculoForm.patchValue(data),
        error: (err) => console.log(err),
      });
    }
  }

  salvar() {
    if (this.id) {
      this.service
        .atualizar(this.veiculoForm.getRawValue(), this.id)
        .subscribe({
          next: () => this.router.navigateByUrl('/veiculos'),
          // error: () => this.onErrorEdicao(),
        });
    } else {
      this.service.cadastrar(this.veiculoForm.value).subscribe({
        next: () => this.router.navigateByUrl('/veiculos'),
        // error: () => this.onErrorCadastro(),
      });
    }
  }

  deletar() {
    this.service.deletar(this.veiculoForm.getRawValue()).subscribe({
      next: () => this.router.navigateByUrl('/veiculos'),
      // error: () => this.onErrorDelete(),
    });
  }
}

对应HTML修改为响应式绑定:

<div class="card shadow-sm m-5 p-5 bg-light" style="min-width: 60%; max-width: 70%" [formGroup]="veiculoForm">
  <mat-toolbar>{{id?'Editar Veiculo':'Cadastrar Veiculo'}}</mat-toolbar>

  <div class="card-body">
      <div class="form-group" *ngIf="id">
        <label class="form-label">ID</label>
        <input type="text" class="form-control" formControlName="id" readonly />
      </div>
      <div class="form-group">
        <label class="form-label">Placa Oeste</label>
        <input type="text" class="form-control" formControlName="placaOeste" />
      </div>
      <div class="form-group">
        <label class="form-label">Modelo</label>
        <input type="text" class="form-control" formControlName="modelo">
      </div>
      <div class="form-group">
        <label class="form-label">Marca</label>
        <input type="text" class="form-control" formControlName="marca">
      </div>
      <div class="form-group">
        <label class="form-label">Ano</label>
        <input type="text" class="form-control" formControlName="ano">
      </div>
      <div class="form-group form-check">
        <input type="checkbox" class="form-check-input" formControlName="portariaSEJUSP">
        <label class="form-check-label">Deriva da Portaria SEJUSP/DGPC nº 133/2018</label>
      </div>

      <div>
        <button class="btn btn-primary m-2" (click)="salvar()">
          Salvar
        </button>
        <button type="button" class="btn btn-warning mx-2" routerLink="/veiculos">
          Cancelar
        </button>
      </div>
  </div>
</div>

验证

完成修改后重新运行项目,编辑场景下checkbox可正常绑定veiculo.portariaSEJUSP属性,不会再抛出writeValue相关错误,业务功能正常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 06:45:41