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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 01:07:07