Angular 16+按钮指令报错:NullInjectorError及多模块重复声明问题
解决Angular按钮指令的两个问题
问题1:NullInjectorError: No provider for MatButton!
原因
指令选择器为button,会匹配页面所有<button>元素,但只有带有mat-button(或Material其他按钮指令)的元素才存在MatButton提供者,普通按钮没有该提供者,导致注入失败。
解决方法
修改指令选择器,仅匹配Material按钮组件:
@Directive({ selector: `button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab]`, })
该配置会让指令仅作用于Material按钮,避免给普通按钮注入MatButton时出错。
如果需要兼容普通按钮(不推荐,因为指令逻辑依赖MatButton),可以给MatButton注入添加@Optional()装饰器:
constructor( @Optional() private matButton: MatButton, private viewContainerRef: ViewContainerRef, private renderer: Renderer2 ) {}
问题2:The Directive 'ButtonDirective' is declared by more than one NgModule.
原因
Angular规则:一个指令只能被一个NgModule声明,多模块重复声明会触发该错误。
解决方法
创建共享模块(SharedModule),将ButtonDirective放入该模块声明并导出,其他模块只需导入SharedModule即可使用指令。
1. 创建SharedModule
// src/app/shared/shared.module.ts import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { MatButtonModule } from '@angular/material/button'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { ButtonDirective } from '../utility/button.directive'; @NgModule({ declarations: [ButtonDirective], imports: [ CommonModule, MatButtonModule, MatProgressSpinnerModule ], exports: [ButtonDirective] }) export class SharedModule { }
2. 修改LoginModule和RegisterModule
移除declarations中的ButtonDirective,改为导入SharedModule:
// login.module.ts import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { LoginRoutingModule } from './login-routing.module'; import { LoginComponent } from './login.component'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { SharedModule } from '../shared/shared.module'; @NgModule({ declarations: [ LoginComponent // 移除ButtonDirective的声明 ], imports: [ CommonModule, FormsModule, ReactiveFormsModule, LoginRoutingModule, MatFormFieldModule, MatInputModule, MatButtonModule, MatIconModule, SharedModule ] }) export class LoginModule { }
RegisterModule执行相同修改:移除ButtonDirective声明,导入SharedModule。
最终完整的button.directive.ts
import { ComponentRef, Directive, Input, OnChanges, Renderer2, SimpleChanges, ViewContainerRef } from '@angular/core'; import { MatButton } from '@angular/material/button'; import { ThemePalette } from '@angular/material/core'; import { MatProgressSpinner } from '@angular/material/progress-spinner'; @Directive({ selector: `button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab]`, }) export class ButtonDirective implements OnChanges { private spinner!: ComponentRef<MatProgressSpinner> | null; @Input() loading = false; @Input() disabled = false; @Input() color: ThemePalette; constructor( private matButton: MatButton, private viewContainerRef: ViewContainerRef, private renderer: Renderer2 ) {} ngOnChanges(changes: SimpleChanges): void { if (!changes['loading']) { return; } if (changes['loading'].currentValue) { this.matButton._elementRef.nativeElement.classList.add('button-loading'); this.matButton.disabled = true; this.createSpinner(); } else if (!changes['loading'].firstChange) { this.matButton._elementRef.nativeElement.classList.remove( 'button-loading' ); this.matButton.disabled = this.disabled; this.destroySpinner(); } } private createSpinner(): void { if (!this.spinner) { this.spinner = this.viewContainerRef.createComponent(MatProgressSpinner); this.spinner.instance.color = this.color; this.spinner.instance.diameter = 20; this.spinner.instance.mode = 'indeterminate'; this.renderer.appendChild( this.matButton._elementRef.nativeElement, this.spinner.instance._elementRef.nativeElement ); } } private destroySpinner(): void { if (this.spinner) { this.spinner.destroy(); this.spinner = null; } } }
内容的提问来源于stack exchange,提问作者prashant kushwah
相关产品推荐
相关产品推荐

