Angular 14中ng-template变量未解析及功能异常求助
Angular 14 CRUD页面问题解决方案
问题概述
使用Angular 14.2.0、Angular Material ^14.2.2、Angular Localize ^14.2.2开发CRUD页面时遇到以下问题:
- 在
ng-template中绑定/访问变量时,IDE和浏览器均提示「Unresolved variable error」 - 点击创建、编辑按钮时,浏览器报错:
TypeError: Cannot read properties of null (reading 'first_name') - 点击删除按钮无任何反应
原始代码
HTML模板
<!-- Container --> <div class="table-container"> <!-- Create Button --> <button mat-raised-button color="primary" class="create-button" (click)="openCreateModal()">Create</button> <!-- Table --> <table mat-table [dataSource]="dataSource" class="mat-elevation-z8"> <!-- Column Definitions --> <ng-container matColumnDef="user_id"> <th mat-header-cell *matHeaderCellDef> User ID </th> <td mat-cell *matCellDef="let user"> {{ user.user_id }} </td> </ng-container> <ng-container matColumnDef="first_name"> <th mat-header-cell *matHeaderCellDef> First Name </th> <td mat-cell *matCellDef="let user"> {{ user.first_name }} </td> </ng-container> <ng-container matColumnDef="last_name"> <th mat-header-cell *matHeaderCellDef> Last Name </th> <td mat-cell *matCellDef="let user"> {{ user.last_name }} </td> </ng-container> <ng-container matColumnDef="actions"> <th mat-header-cell *matHeaderCellDef> Actions </th> <td mat-cell *matCellDef="let user"> <button mat-icon-button color="primary" (click)="openEditModal(user)"> <mat-icon>edit</mat-icon> </button> <button mat-icon-button color="warn" (click)="deleteUser(user)"> <mat-icon>delete</mat-icon> </button> </td> </ng-container> <!-- Table Header --> <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> <!-- Table Rows --> <tr mat-row *matRowDef="let user; columns: displayedColumns;"></tr> </table> <!-- Pagination --> <mat-paginator [length]="dataSource.length" [pageSize]="pageSize" [pageSizeOptions]="pageSizeOptions" (page)="onPageChange($event)"> </mat-paginator> </div> <!-- Create/Edit Modal --> <ng-template #modalTemplate let-modalUser> <div class="modal" [ngClass]="{'open': isModalOpen}"> <div class="modal-content"> <h2>{{ modalTitle }}</h2> <form (ngSubmit)="saveUser()"> <!-- Input fields for creating/editing user data --> <mat-form-field> <input matInput placeholder="First Name" [(ngModel)]="modalUser.first_name" name="firstName" required> </mat-form-field> <mat-form-field> <input matInput placeholder="Last Name" [(ngModel)]="modalUser.last_name" name="lastName" required> </mat-form-field> <div class="modal-actions"> <button mat-raised-button color="primary" type="submit">Save</button> <button mat-raised-button color="warn" (click)="closeModal()">Cancel</button> </div> </form> </div> </div> </ng-template>
TS代码
import {ChangeDetectorRef, Component, OnInit, TemplateRef, ViewChild} from '@angular/core'; import {MatDialog} from "@angular/material/dialog"; @Component({ selector: 'app-company-crud-component', templateUrl: './company-crud-component.component.html', styleUrls: ['./company-crud-component.component.scss'] }) export class CompanyCrudComponentComponent implements OnInit { displayedColumns: string[] = ['user_id', 'first_name', 'last_name', 'actions']; dataSource: any[] = []; pageSize = 5; pageSizeOptions: number[] = [5, 10, 25, 100]; // Modal properties isModalOpen = false; modalTitle = ''; modalAction = ''; modalUser: any = {}; @ViewChild('modalTemplate', { static: true }) modalTemplate!: TemplateRef<any>; constructor(private dialog: MatDialog, private changeDetectorRef: ChangeDetectorRef) {} ngOnInit() { // Dummy data this.dataSource = [ { user_id: 1, first_name: 'Ivette', last_name: 'Hansley' }, { user_id: 2, first_name: 'Kelsey', last_name: 'Wines' }, { user_id: 3, first_name: 'Janek', last_name: 'Nell' } ]; } openCreateModal(): void { this.modalTitle = 'Create User'; this.modalAction = 'Save'; this.modalUser = {}; // Initialize with an empty object this.modalUser.first_name = ''; this.modalUser.last_name = ''; this.openModal(); } openEditModal(user: any): void { this.modalTitle = 'Edit User'; this.modalAction = 'Update'; this.modalUser = { ...user }; this.openModal(); } saveUser(): void { // Add or update the user data based on the modal action if (this.modalAction === 'Save') { // Add new user const newUser = { user_id: this.getNextUserId(), first_name: this.modalUser.first_name, last_name: this.modalUser.last_name }; this.dataSource.push(newUser); } else if (this.modalAction === 'Update') { // Update existing user const index = this.dataSource.findIndex(user => user.user_id === this.modalUser.user_id); if (index !== -1) { this.dataSource[index].first_name = this.modalUser.first_name; this.dataSource[index].last_name = this.modalUser.last_name; } } // Close the modal this.closeModal(); } private getNextUserId(): number { const maxUserId = Math.max(...this.dataSource.map(user => user.user_id)); return maxUserId + 1; } closeModal(): void { // Close the modal this.isModalOpen = false; } private openModal(): void { // Open the modal this.isModalOpen = true; this.dialog.open(this.modalTemplate); } deleteUser(userId: number): void { // Delete the user based on user ID const index = this.dataSource.findIndex(user => user.user_id === userId); if (index !== -1) { this.dataSource.splice(index, 1); } } onPageChange(event: any) { // Handle pagination change console.log('Page changed:', event); const pageIndex = event.pageIndex; const pageSize = event.pageSize; const startIndex = pageIndex * pageSize; const endIndex = startIndex + pageSize; // Update the displayed data based on the selected page this.dataSource = this.dataSource.slice(startIndex, endIndex); } }
浏览器错误信息
error-handler.service.ts:26 ERROR TypeError: Cannot read properties of null (reading 'first_name') at CompanyCrudComponentComponent_ng_template_19_Template (company-crud-component.component.html:59:62) at executeTemplate (core.mjs:12126:9) at refreshView (core.mjs:12011:13) at refreshEmbeddedViews (core.mjs:13039:17) at refreshView (core.mjs:12035:9) at refreshComponent (core.mjs:13085:13) at refreshChildComponents (core.mjs:11801:9) at refreshView (core.mjs:12061:13) at detectChangesInternal (core.mjs:13229:9) at RootViewRef.detectChanges (core.mjs:13741:9)
问题分析与修复
1. 修复ng-template变量绑定错误及创建/编辑按钮报错
问题根源:ng-template中声明的let-modalUser未被传递任何数据,调用MatDialog.open()时没有传入参数,导致modalUser为null,触发读取属性失败的错误。实际上组件类中已经定义了modalUser变量,无需通过模板输入变量传递。
修复步骤:
移除ng-template的let-modalUser声明,直接使用组件类的modalUser变量:
<!-- Create/Edit Modal --> <ng-template #modalTemplate> <!-- 移除let-modalUser --> <div class="modal" [ngClass]="{'open': isModalOpen}"> <div class="modal-content"> <h2>{{ modalTitle }}</h2> <form (ngSubmit)="saveUser()"> <!-- Input fields for creating/editing user data --> <mat-form-field> <input matInput placeholder="First Name" [(ngModel)]="modalUser.first_name" name="firstName" required> </mat-form-field> <mat-form-field> <input matInput placeholder="Last Name" [(ngModel)]="modalUser.last_name" name="lastName" required> </mat-form-field> <div class="modal-actions"> <button mat-raised-button color="primary" type="submit">Save</button> <button mat-raised-button color="warn" (click)="closeModal()">Cancel</button> </div> </form> </div> </div> </ng-template>
修改后模板直接引用组件类中已初始化的modalUser,不会再出现null错误,IDE的未解析变量提示也会消失。
2. 修复删除按钮无反应问题
问题根源:
HTML中删除按钮传递的是user对象,但组件类的deleteUser方法接收的是number类型的userId,导致findIndex无法匹配到正确索引,删除操作无效。
修复方案(二选一):
- 方案一:修改HTML调用,传递用户ID
<button mat-icon-button color="warn" (click)="deleteUser(user.user_id)"> <mat-icon>delete</mat-icon> </button>
- 方案二:修改组件类方法,接收用户对象
deleteUser(user: any): void { const index = this.dataSource.findIndex(u => u.user_id === user.user_id); if (index !== -1) { this.dataSource.splice(index, 1); } }
3. 分页逻辑优化(可选)
当前分页逻辑会修改原始dataSource导致数据丢失,无法切换回之前的页面。建议保留原始数据,新增展示用变量:
// 新增原始数据存储变量 originalDataSource: any[] = []; // 展示用数据变量 dataSource: any[] = []; ngOnInit() { this.originalDataSource = [ { user_id: 1, first_name: 'Ivette', last_name: 'Hansley' }, { user_id: 2, first_name: 'Kelsey', last_name: 'Wines' }, { user_id: 3, first_name: 'Janek', last_name: 'Nell' } ]; // 初始化第一页数据 this.dataSource = this.originalDataSource.slice(0, this.pageSize); } onPageChange(event: any) { const pageIndex = event.pageIndex; const pageSize = event.pageSize; const startIndex = pageIndex * pageSize; const endIndex = startIndex + pageSize; // 从原始数据中切片生成当前页数据 this.dataSource = this.originalDataSource.slice(startIndex, endIndex); }
同时修改分页器的长度绑定:
<mat-paginator [length]="originalDataSource.length" [pageSize]="pageSize" [pageSizeOptions]="pageSizeOptions" (page)="onPageChange($event)"> </mat-paginator>
内容的提问来源于stack exchange,提问作者Vivek Karn
相关产品推荐
相关产品推荐

