MaterializeCSS Angular5动态生成下拉菜单内容报错问题
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
- No More Direct DOM Queries: Angular's
ViewChildrenensures we only access elements that have been rendered to the DOM. - One-Time Initialization: We initialize each dropdown exactly once, instead of trying to re-initialize on every click (which causes its own issues).
- Handles Dynamic Data: The
changessubscription re-initializes dropdowns ifnodeLabSystemsupdates (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
scriptsarray inangular.json). - If
nodeLabSystemsis loaded asynchronously, ensure you're not trying to initialize dropdowns before the data is available (thechangessubscription will handle this automatically, but you can also callinitDropdowns()after your data is fetched).
内容的提问来源于stack exchange,提问作者dmance

