Angular 7组件调用服务方法报错:Cannot read property 'json' of undefined
解决Angular 7中税号计算表单的"Cannot read property 'json' of undefined"错误
让我们一步步拆解并解决你的问题:
错误根源分析
你遇到的TypeError: Cannot read property 'json' of undefined主要来自两个核心问题:
- 同步验证器中调用异步HTTP请求:你的自定义验证器
validatoreCf是同步函数,但它调用的getCF最终依赖getCCC里的HTTP请求——这是异步操作,同步验证器无法等待异步操作完成,导致返回值为undefined。 - 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
相关产品推荐
相关产品推荐

