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

如何创建可根据屏幕尺寸切换为Mat Dialog或Mat Bottom Sheet的Angular组件

解决Angular组件同时兼容Mat Dialog和Mat Bottom Sheet的依赖问题

问题说明

需要实现一个Angular组件:当屏幕宽度大于500px时以Mat Dialog形式展示,小于500px时切换为Mat Bottom Sheet形式。由于组件代码复杂(包含数百行HTML、SCSS和TS逻辑),不想维护两个几乎完全相同的组件,但同时注入Dialog和Bottom Sheet的依赖时会出现报错:

  • 打开Bottom Sheet时提示 NullInjectorError: No provider for InjectionToken MatDialogData!
  • 打开Dialog时提示 Error: NullInjectorError: No provider for InjectionToken MatBottomSheetData!

移除其中一组依赖后对应模式可正常工作,但无法同时兼容两种展示方式,需要解决该依赖冲突问题。

现有代码

Dialog/Bottom Sheet 组件

export interface MyComponentData {
    firstName: string,
    lastName: string
}

@Component({
    selector: "my-component",
    templateUrl: "./my-component.component.html",
    styleUrls: ["./my-component.component.scss"]
})
export class MyComponent {

    constructor(
        @Inject(MAT_BOTTOM_SHEET_DATA) public bottomSheetData: MyComponentData,
        @Inject(MAT_DIALOG_DATA) public dialogData: MyComponentData,
        public bottomSheetRef: MatBottomSheetRef<MyComponent>,
        public dialogRef: MatDialogRef<MyComponent>
    ) {}
}

主组件

@Component({
    selector: "main-component",
    templateUrl: "./main-component.component.html",
    styleUrls: ["./main-component.component.scss"]
})
export class MainComponent {
    constructor(private bottomSheet: MatBottomSheet, private dialog: MatDialog) {}

    onClick(temp: MyComponentData): void {
        if(window.innerWidth > 500) {
            this.dialog.open(MyComponent, {data: temp})
        } else {
            this.bottomSheet.open(MyComponent, {data: temp})
        }
    }
}

解决方案

核心思路是使用@Optional()装饰器标记Dialog和Bottom Sheet的依赖,这样当组件以其中一种形式打开时,缺失的依赖会被注入为null而非触发报错,再统一封装数据和关闭逻辑,避免在业务代码中区分两种模式。

修改后的Dialog/Bottom Sheet组件

import { Component, Inject, Optional } from '@angular/core';
import { MAT_BOTTOM_SHEET_DATA, MatBottomSheetRef } from '@angular/material/bottom-sheet';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';

export interface MyComponentData {
    firstName: string,
    lastName: string
}

@Component({
    selector: "my-component",
    templateUrl: "./my-component.component.html",
    styleUrls: ["./my-component.component.scss"]
})
export class MyComponent {
    // 统一对外的数据源,无需区分Dialog/Bottom Sheet
    public data: MyComponentData;
    // 统一的关闭引用
    private closeRef: MatDialogRef<MyComponent> | MatBottomSheetRef<MyComponent>;

    constructor(
        @Optional() @Inject(MAT_BOTTOM_SHEET_DATA) bottomSheetData: MyComponentData,
        @Optional() @Inject(MAT_DIALOG_DATA) dialogData: MyComponentData,
        @Optional() bottomSheetRef: MatBottomSheetRef<MyComponent>,
        @Optional() dialogRef: MatDialogRef<MyComponent>
    ) {
        // 自动识别当前模式的数据源
        this.data = dialogData || bottomSheetData;
        // 自动绑定当前模式的关闭引用
        this.closeRef = dialogRef || bottomSheetRef;
    }

    // 统一的关闭方法,模板和业务逻辑直接调用即可
    public close(result?: any): void {
        this.closeRef?.dismiss(result);
    }
}

优化主组件(可选)

原代码使用window.innerWidth判断屏幕宽度,建议改用Angular CDK的BreakpointObserver,更符合Angular响应式设计,适配不同设备断点:

import { Component } from '@angular/core';
import { MatBottomSheet } from '@angular/material/bottom-sheet';
import { MatDialog } from '@angular/material/dialog';
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
import { MyComponentData } from './my-component.component';

@Component({
    selector: "main-component",
    templateUrl: "./main-component.component.html",
    styleUrls: ["./main-component.component.scss"]
})
export class MainComponent {
    constructor(
        private bottomSheet: MatBottomSheet, 
        private dialog: MatDialog,
        private breakpointObserver: BreakpointObserver
    ) {}

    onClick(temp: MyComponentData): void {
        // 使用预设的断点判断是否为移动端屏幕
        const isMobile = this.breakpointObserver.isMatched(Breakpoints.Handset);
        if (!isMobile) {
            this.dialog.open(MyComponent, { data: temp });
        } else {
            this.bottomSheet.open(MyComponent, { data: temp });
        }
    }
}

模板使用示例

修改后模板中直接使用统一的data和close()方法即可,无需区分当前是Dialog还是Bottom Sheet:

<div>
  <h2>Hello {{ data.firstName }} {{ data.lastName }}</h2>
  <button (click)="close()">关闭</button>
</div>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 16:02:09