2

I have a standalone component and I'd like to open a modal from in it (which is another standalone component but it is not matter now I guess).

I want to apply global rules for the material dialog, so I provide it in my component's provider. But it is not working.

The SummaryComponent looks like this:

import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component } from '@angular/core';
import {
    DialogPosition,
    MatDialog,
    MatDialogConfig,
    MatDialogModule,
    MAT_DIALOG_DEFAULT_OPTIONS,
} from '@angular/material/dialog';
import { matDialogGlobalConfig } from 'src/app/utils/mat-dialog';
import { ModalComponent } from './modal-modal/modal-modal.component';

@Component({
    selector: 'app-summary',
    templateUrl: './summary.component.html',
    styleUrls: ['./summary.component.scss'],
    standalone: true,
    changeDetection: ChangeDetectionStrategy.OnPush,
    imports: [CommonModule, MatDialogModule],
    providers: [
        {
            provide: MAT_DIALOG_DEFAULT_OPTIONS,
            useValue: <MatDialogConfig>{ matDialogGlobalConfig },
        },
    ],
})
export class SummaryComponent {
    constructor(private matDialog: MatDialog) {}

    protected onInfoClicked(): void {
        this.matDialog.open(ModalComponent, <MatDialogConfig>{
            panelClass: 'rounded-modal',
            width: '100vw',
            position: <DialogPosition>{
                bottom: '0',
            },
        });
    }
}

The ModalComponent I want to open looks like this:

import { Component } from '@angular/core';

@Component({
    selector: 'app-modal',
    templateUrl: './modal.component.html',
    styleUrls: ['./modal.component.scss'],
    standalone: true,
})
export class ModalComponent {}

The matDialogGlobalConfig object contains 2 rules I want to apply as a global config:

export const matDialogGlobalConfig: MatDialogConfig = {
    backdropClass: 'custom-modal-dark-background',
    autoFocus: false,
};

If I put my global config into an "old" NgModule' providers everything works like a charm (even if the SummaryComponent is not part of it, wtf).

I have the same problem with material Snackbars, but I do not have any problems with pipes for example.

Result of this code: The <MatDialogConfig> rules provided in onInfoClicked() are applied, but the matDialogGlobalConfig are not.

Am I wrong or is that a known issue?

*Angular version: 15.1.3

Procisz
  • 21
  • 5

1 Answers1

0

Building on @Jordi Riera's comment to your problem, instead of:

@Component({
    …,
    providers: [
        {
            provide: MAT_DIALOG_DEFAULT_OPTIONS,
            useValue: <MatDialogConfig>{ matDialogGlobalConfig },
        },
    ],
})
export class SummaryComponent {
    constructor(private matDialog: MatDialog) {}

    protected onInfoClicked(): void {
        this.matDialog.open(ModalComponent, <MatDialogConfig>{
            panelClass: 'rounded-modal',
            width: '100vw',
            position: <DialogPosition>{
                bottom: '0',
            },
        });
    }
}

it should probably more look like:

@Component({
    …,
    providers: [
        {
            provide: MAT_DIALOG_DEFAULT_OPTIONS,
            useValue: matDialogGlobalConfig,
        },
    ],
})
export class SummaryComponent {
    constructor(
        private matDialog: MatDialog,
        @Inject(MAT_DIALOG_DEFAULT_OPTIONS) private defaultOptions: MatDialogConfig
    ) {}

    protected onInfoClicked(): void {
        this.matDialog.open(ModalComponent,
        {
            ...defaultOptions,
            panelClass: 'rounded-modal',
            width: '100vw',
            position: {
                bottom: '0'
            },
        });
    }
}

(using spread syntax to merge defaultOptions and local overrides; and the type assertions have been dropped, they only act as a switch-off for the type checker; that's usually not desirable.)

(I don't have enough code to run this through a compiler, so this may not work out-of-the box, but the idea should be clear.)

Of course, due to the limited scope of node injectors, this only helps to lift the MatDialogConfig value assignment from the class body to the annotation, but does not make it available globally. I think for Angular 15+, the so-called environment injectors encompass the available options to define your MatDialogConfig globally, or at least in a larger scope.

PS: Your example used a lot of type assertions; as others pointed out elsewhere, this disables proper type checking, and is probably not what you want. See e.g. https://timdeschryver.dev/blog/stop-misusing-typescript-type-assertions (he is a really helpful guy from around the NgRx community). In particular <MatDialogConfig>{ matDialogGlobalConfig } will not result in the value you most probably expected, this is an ES6 shorthand notation, its actual result hidden from the type checker by the type assertion.

Christian Fuchs
  • 430
  • 3
  • 9
  • 1
    Ty for your answer. I've read the "Dependency injection and injectors hierarchy" topic carefully. If I understand what you and the documentation say, there is a difference between the the 2 types of providers. If I provide something with the module injector so I can reach and use it in all of my components declared in my module OR app-wide (if it is provided in root). Clear. If I provide something in my component's provider, I can reach and use it only in my component. Clear as dark. But my problem is that I can't even access it in my own component... I'll try this EnvironmentInjector – Procisz Mar 01 '23 at 16:27
  • I'll keep you updated. – Procisz Mar 01 '23 at 16:28
  • I thought about adding another answer to that problem, but stack overflow told me to better rewrite my existing answer... so I rewrote it XD. Hope it does not add confusion. – Christian Fuchs Mar 01 '23 at 19:27