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

Angular 7组件调用服务方法报错:Cannot read property 'json' of undefined

解决Angular 7中税号计算表单的"Cannot read property 'json' of undefined"错误

让我们一步步拆解并解决你的问题:

错误根源分析

你遇到的TypeError: Cannot read property 'json' of undefined主要来自两个核心问题:

  1. 同步验证器中调用异步HTTP请求:你的自定义验证器validatoreCf是同步函数,但它调用的getCF最终依赖getCCC里的HTTP请求——这是异步操作,同步验证器无法等待异步操作完成,导致返回值为undefined。
  2. HTTP请求处理错误:getCCC方法里错误地处理了HttpClient的响应,并且在异步操作完成前就返回了变量,导致risultato始终是undefined,后续的JSON.parse(risultato)自然会报错。

另外你的代码还有几个隐性bug,比如quintaPT中使用未定义的cf参数、terzaPT里用getDay()获取日期(这是星期几,应该用getDate())、somma未初始化导致NaN等,我会一起修复。


步骤1:修复HTTP请求方法(getCCC)

把getCCC改成返回Observable,因为HTTP请求是异步的,我们需要用Observable来处理异步流:

// 修改服务中的getCCC方法
getCCC(nomeCitta: string): Observable<string> {
  return this.http.get(`http://www.gerriquez.com/comuni/ws.php?dencomune=${nomeCitta}`)
    .pipe(
      map((dato: any) => dato.CodiceCatastaleDelComune.value)
    );
}

步骤2:将同步验证器改为异步验证器

Angular表单支持异步验证器,我们需要把validatoreCf改成异步验证器,并且调整getCF的逻辑来处理异步的城市代码请求:

首先修改组件中的表单初始化,把codiceFiscale的验证器改为异步验证器(放在第三个参数位置):

ngOnInit() {
  this.mioForm = new FormGroup({
    // ...其他表单控件保持不变
    codiceFiscale: new FormControl('', 
      [Validators.required], // 同步验证器
      [this.validatoreCf.bind(this)] // 异步验证器,注意bind(this)保证上下文
    )
  });
}

然后重写validatoreCf为异步验证器:

import { of } from 'rxjs';
import { map, catchError } from 'rxjs/operators';

// ...其他代码保持不变

validatoreCf(control: AbstractControl): Observable<{ [key: string]: any } | null> {
  if (!control.value || isNaN(control.value)) {
    return of({ validCF: true });
  }

  const cognome = this.mioForm.get('cognome').value;
  const nome = this.mioForm.get('nome').value;
  const dataDiNascita = this.mioForm.get('dataDiNascita').value;
  const sesso = this.mioForm.get('sesso').value;
  const citta = this.mioForm.get('citta').value;

  return this.json.getCF(cognome, nome, dataDiNascita, sesso, citta).pipe(
    map(codiceCalcolato => {
      const codiceInserito = control.value.toString().trim().toUpperCase();
      return codiceCalcolato === codiceInserito ? null : { validCF: true };
    }),
    catchError(() => of({ validCF: true })) // 请求出错时也标记为验证失败
  );
}

步骤3:修改服务中的getCF及相关方法

调整getCF来处理异步的城市代码请求,同时修复其他隐性bug:

// 修改服务中的getCF方法
getCF(cognome: string, nome: string, dataDiNascita: string, sesso: string, cittaNome: string): Observable<string> {
  const pt1 = this.primaPT(cognome);
  const pt2 = this.secondaPT(nome);
  const pt3 = this.terzaPT(dataDiNascita, sesso);

  return this.getCCC(cittaNome).pipe(
    map(pt4 => {
      const cfParziale = pt1 + pt2 + pt3 + pt4;
      const pt5 = this.quintaPT(cfParziale);
      return (pt1 + pt2 + pt3 + pt4 + pt5).trim().toUpperCase();
    })
  );
}

// 修复terzaPT中的日期获取错误(getDay()改为getDate())
terzaPT(dataDiNascita: string, sesso: string): string {
  if (!dataDiNascita || !sesso) return "";
  
  const listaMesi = mesi;
  const data = new Date(dataDiNascita);
  let dataStr = data.getFullYear().toString().slice(2); // 取年份后两位,税号只需要后两位
  
  const meseIndex = data.getMonth();
  for (let a of listaMesi) {
    if (a.Mese === meseIndex) {
      dataStr += a.lettera;
      break;
    }
  }

  let giorno = data.getDate(); // 这里用getDate()获取当月日期,不是getDay()
  if (sesso.toUpperCase() === 'F') giorno += 40;
  dataStr += giorno.toString().padStart(2, '0'); // 保证两位数字

  return dataStr;
}

// 修复quintaPT中的somma初始化问题
quintaPT(cf: string): string {
  if (!cf) return "";
  
  const dispari = charDispari;
  const pari = charPari;
  const finali = finale;
  let somma = 0; // 初始化somma为0
  let carattereFinale = "";
  
  const ar = cf.split('');
  
  // 处理奇数位(注意税号的索引是从0开始,所以奇数位是索引0,2,4...)
  for (let i = 0; i < ar.length; i++) {
    if (i % 2 === 0) {
      const char = ar[i];
      const match = dispari.find(a => a.n1 === char);
      if (match) somma += match.n2;
    }
  }
  
  // 处理偶数位
  for (let i = 0; i < ar.length; i++) {
    if (i % 2 === 1) {
      const char = ar[i];
      const match = pari.find(a => a.n1 === char);
      if (match) somma += match.n2;
    }
  }
  
  const resto = somma % 26;
  const finaleMatch = finali.find(a => a.n2 === resto);
  if (finaleMatch) carattereFinale = finaleMatch.n1;
  
  return carattereFinale;
}

// 修复secondaPT和primaPT中的toString()问题(数组转字符串会带逗号,应该用join(''))
secondaPT(nome: string): string {
  if (!nome) return "";
  
  nome = nome.trim().toUpperCase();
  if (nome.length < 3) {
    const ar = nome.split('');
    while (ar.length < 3) ar.push('X');
    return ar.join(''); // 用join('')代替toString()
  }
  
  let cont = 0;
  const ar = nome.split('');
  let result = '';
  
  // 先取辅音
  for (const char of ar) {
    if (result.length < 3 && !['A','E','I','O','U'].includes(char)) {
      if (cont !== 1) { // 这里的cont逻辑你可以根据需求调整,原逻辑有点模糊
        result += char;
        cont++;
      }
    }
  }
  
  // 辅音不够补元音
  if (result.length < 3) {
    for (const char of ar) {
      if (result.length < 3 && ['A','E','I','O','U'].includes(char)) {
        result += char;
      }
    }
  }
  
  // 还不够补X
  while (result.length < 3) result += 'X';
  
  return result;
}

// 同理修复primaPT的toString()为join('')
primaPT(cognome: string): string {
  if (!cognome) return "";
  
  cognome = cognome.trim().toUpperCase();
  if (cognome.length < 3) {
    const ar = cognome.split('');
    while (ar.length < 3) ar.push('X');
    return ar.join('');
  }
  
  const ar = cognome.split('');
  let result = '';
  
  for (const char of ar) {
    if (result.length < 3 && !['A','E','I','O','U'].includes(char)) {
      result += char;
    }
  }
  
  // 辅音不够补元音
  if (result.length < 3) {
    for (const char of ar) {
      if (result.length < 3 && ['A','E','I','O','U'].includes(char)) {
        result += char;
      }
    }
  }
  
  while (result.length < 3) result += 'X';
  
  return result;
}

步骤4:调整HTML中的验证提示

因为异步验证器有个pending状态,你可以添加加载提示让用户知道正在验证:

<label>
  CODICE FISCALE:
  <input type="text" formControlName="codiceFiscale">
  <div *ngIf="mioForm.get('codiceFiscale').pending">Verifica in corso...</div>
  <div *ngIf="mioForm.get('codiceFiscale').hasError('validCF') && mioForm.get('codiceFiscale').touched">Errore: Codice Fiscale non corrisponde</div>
</label>

关键知识点总结

  • 异步验证器:当验证逻辑依赖异步操作(比如HTTP请求)时,必须使用Angular的异步验证器,它返回Observable或Promise,并且要放在表单控件的第三个参数位置。
  • HttpClient的响应处理:HttpClient的get方法默认会把JSON响应解析成JavaScript对象,不需要再调用JSON.parse。
  • 日期处理:getDay()返回的是星期几(0-6),获取当月日期要用getDate()。
  • 数组转字符串:Array.toString()会生成带逗号的字符串,税号需要连续的字符,所以要用Array.join('')。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 06:42:28