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

MaterializeCSS Angular5动态生成下拉菜单内容报错问题

Fixing "Cannot set property 'tabIndex' of null" with Dynamic Materialize Dropdowns in Angular 5

Let's break down why you're hitting this error and how to fix it properly with Angular best practices.

The Root Cause

Your issue stems from a mismatch between Angular's asynchronous view rendering and Materialize's direct DOM dependency. When you use *ngFor to generate dropdowns, Angular doesn't immediately render those elements to the DOM—especially if nodeLabSystems is loaded asynchronously (like from an API). When you call document.getElementById(dropdown) in your open() method, there's a good chance the target dropdown element hasn't been rendered yet, returning null. Materialize's Dropdown.init() then tries to set tabIndex on this null value, causing the error.

The Solution: Use Angular's View Querying & Lifecycle Hooks

Instead of manually querying the DOM with document.getElementById, let Angular manage the elements for you. We'll use ViewChildren to track dynamically generated dropdown elements, initialize Materialize instances once the view is ready, and reuse those instances when clicking triggers.

Step 1: Update Your Template

Add a template reference variable (#dropdowns) to your dynamic dropdown content elements, and keep the data-target association intact:

<!-- Dynamic dropdown content -->
<ul #dropdowns class="dropdown-content" *ngFor="let labSystem of nodeLabSystems" id="content-{{labSystem.system.name}}">
  <li><a href="#!">one</a></li>
</ul>

<!-- Dropdown triggers -->
<div class="col s12 m12 l12 xl12 blue-grey lighten-1">
  <div id="instrumentStatus">
    <span class="right" *ngFor="let labSystem of nodeLabSystems">
      <a class="btn btn-flat dropdown-trigger" 
         data-target="content-{{labSystem.system.name}}" 
         (click)="open(labSystem.system.name)">
        {{labSystem.system.name}}
      </a>
    </span>
  </div>
</div>

Step 2: Modify Your Component Code

Use Angular's AfterViewInit lifecycle hook to initialize Materialize dropdowns once the view is fully rendered, and track instances in an object for easy access:

import { Component, AfterViewInit, ViewChildren, QueryList, ElementRef } from '@angular/core';

@Component({
  selector: 'your-component-selector',
  templateUrl: './your-component.component.html',
  styleUrls: ['./your-component.component.css']
})
export class YourComponent implements AfterViewInit {
  @ViewChildren('dropdowns') dropdownElements: QueryList<ElementRef>;
  // Store Materialize dropdown instances keyed by system name
  dropdownInstances: { [systemName: string]: any } = {};

  ngAfterViewInit(): void {
    // Initialize dropdowns when the view first loads
    this.initDropdowns();

    // Re-initialize if the list of lab systems changes (e.g., new data from API)
    this.dropdownElements.changes.subscribe(() => {
      this.initDropdowns();
    });
  }

  private initDropdowns(): void {
    // Clear old instances first
    this.dropdownInstances = {};
    
    this.dropdownElements.forEach(elem => {
      const elementId = elem.nativeElement.id;
      // Extract the system name from the dropdown ID (e.g., "content-SystemA" → "SystemA")
      const systemName = elementId.replace('content-', '');
      // Initialize Materialize dropdown
      this.dropdownInstances[systemName] = M.Dropdown.init(elem.nativeElement, {
        constrainWidth: true
      });
    });
  }

  open(systemName: string): void {
    // Only open if the instance exists (avoids null errors)
    if (this.dropdownInstances[systemName]) {
      this.dropdownInstances[systemName].open();
    }
  }
}

Key Improvements

  1. No More Direct DOM Queries: Angular's ViewChildren ensures we only access elements that have been rendered to the DOM.
  2. One-Time Initialization: We initialize each dropdown exactly once, instead of trying to re-initialize on every click (which causes its own issues).
  3. Handles Dynamic Data: The changes subscription re-initializes dropdowns if nodeLabSystems updates (e.g., new systems are added).

Additional Checks

  • Make sure you've properly included Materialize's JavaScript file in your Angular project (add it to the scripts array in angular.json).
  • If nodeLabSystems is loaded asynchronously, ensure you're not trying to initialize dropdowns before the data is available (the changes subscription will handle this automatically, but you can also call initDropdowns() after your data is fetched).

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 07:40:47