Angular standalone component's providers not working or not applied

4.4k Views Asked by At

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

1

There are 1 best solutions below

3
Christian Fuchs On

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.