From be5e3fac1a526ce4f727146d2bc112e5c3dedc04 Mon Sep 17 00:00:00 2001 From: Dominik Rabij Date: Fri, 19 Dec 2025 15:01:16 +0100 Subject: [PATCH] refactor(material/snack-bar): eliminate any type --- goldens/material/snack-bar/index.api.md | 29 +++-- src/material/snack-bar/simple-snack-bar.ts | 14 ++- src/material/snack-bar/snack-bar-config.ts | 4 +- src/material/snack-bar/snack-bar-container.ts | 4 +- src/material/snack-bar/snack-bar-ref.ts | 6 +- src/material/snack-bar/snack-bar.spec.ts | 25 +++-- src/material/snack-bar/snack-bar.ts | 104 +++++++++++------- 7 files changed, 117 insertions(+), 69 deletions(-) diff --git a/goldens/material/snack-bar/index.api.md b/goldens/material/snack-bar/index.api.md index 47dd8b7bcac1..493828be384e 100644 --- a/goldens/material/snack-bar/index.api.md +++ b/goldens/material/snack-bar/index.api.md @@ -32,10 +32,10 @@ import { TemplateRef } from '@angular/core'; import { ViewContainerRef } from '@angular/core'; // @public -export const MAT_SNACK_BAR_DATA: InjectionToken; +export const MAT_SNACK_BAR_DATA: InjectionToken; // @public -export const MAT_SNACK_BAR_DEFAULT_OPTIONS: InjectionToken>; +export const MAT_SNACK_BAR_DEFAULT_OPTIONS: InjectionToken>; // @public export class MatSnackBar implements OnDestroy { @@ -45,10 +45,10 @@ export class MatSnackBar implements OnDestroy { // (undocumented) ngOnDestroy(): void; open(message: string, action?: string, config?: MatSnackBarConfig): MatSnackBarRef; - get _openedSnackBarRef(): MatSnackBarRef | null; - set _openedSnackBarRef(value: MatSnackBarRef | null); - openFromComponent(component: ComponentType, config?: MatSnackBarConfig): MatSnackBarRef; - openFromTemplate(template: TemplateRef, config?: MatSnackBarConfig): MatSnackBarRef>; + get _openedSnackBarRef(): MatSnackBarRef | null; + set _openedSnackBarRef(value: MatSnackBarRef | null); + openFromComponent(component: ComponentType, config?: MatSnackBarConfig): MatSnackBarRef; + openFromTemplate(template: TemplateRef, config?: MatSnackBarConfig): MatSnackBarRef>; simpleSnackBarComponent: typeof SimpleSnackBar; snackBarContainerComponent: typeof MatSnackBarContainer; // (undocumented) @@ -74,7 +74,7 @@ export class MatSnackBarActions { } // @public -export class MatSnackBarConfig { +export class MatSnackBarConfig { announcementMessage?: string; data?: D | null; direction?: Direction; @@ -168,7 +168,7 @@ export class SimpleSnackBar implements TextOnlySnackBar { constructor(...args: unknown[]); action(): void; // (undocumented) - data: any; + data: TextOnlySnackBarData; get hasAction(): boolean; // (undocumented) snackBarRef: MatSnackBarRef; @@ -183,16 +183,21 @@ export interface TextOnlySnackBar { // (undocumented) action: () => void; // (undocumented) - data: { - message: string; - action: string; - }; + data: TextOnlySnackBarData; // (undocumented) hasAction: boolean; // (undocumented) snackBarRef: MatSnackBarRef; } +// @public +export interface TextOnlySnackBarData { + // (undocumented) + action: string; + // (undocumented) + message: string; +} + // (No @packageDocumentation comment for this package) ``` diff --git a/src/material/snack-bar/simple-snack-bar.ts b/src/material/snack-bar/simple-snack-bar.ts index 09070641000e..e9541d04444f 100644 --- a/src/material/snack-bar/simple-snack-bar.ts +++ b/src/material/snack-bar/simple-snack-bar.ts @@ -12,12 +12,18 @@ import {MatSnackBarRef} from './snack-bar-ref'; import {MAT_SNACK_BAR_DATA} from './snack-bar-config'; import {MatSnackBarAction, MatSnackBarActions, MatSnackBarLabel} from './snack-bar-content'; +/** Input data for a simple snack bar component that has a message and a single action. */ +export interface TextOnlySnackBarData { + message: string; + action: string; +} + /** * Interface for a simple snack bar component that has a message and a single action. */ export interface TextOnlySnackBar { - data: {message: string; action: string}; - snackBarRef: MatSnackBarRef; + data: TextOnlySnackBarData; + snackBarRef: MatSnackBarRef; action: () => void; hasAction: boolean; } @@ -35,8 +41,8 @@ export interface TextOnlySnackBar { }, }) export class SimpleSnackBar implements TextOnlySnackBar { - snackBarRef = inject>(MatSnackBarRef); - data = inject(MAT_SNACK_BAR_DATA); + snackBarRef = inject>(MatSnackBarRef); + data = inject(MAT_SNACK_BAR_DATA); constructor(...args: unknown[]); constructor() {} diff --git a/src/material/snack-bar/snack-bar-config.ts b/src/material/snack-bar/snack-bar-config.ts index 3ea23b9d8255..89dbe61b157c 100644 --- a/src/material/snack-bar/snack-bar-config.ts +++ b/src/material/snack-bar/snack-bar-config.ts @@ -11,7 +11,7 @@ import {AriaLivePoliteness} from '@angular/cdk/a11y'; import {Direction} from '@angular/cdk/bidi'; /** Injection token that can be used to access the data that was passed in to a snack bar. */ -export const MAT_SNACK_BAR_DATA = new InjectionToken('MatSnackBarData'); +export const MAT_SNACK_BAR_DATA = new InjectionToken('MatSnackBarData'); /** Possible values for horizontalPosition on MatSnackBarConfig. */ export type MatSnackBarHorizontalPosition = 'start' | 'center' | 'end' | 'left' | 'right'; @@ -22,7 +22,7 @@ export type MatSnackBarVerticalPosition = 'top' | 'bottom'; /** * Configuration used when opening a snack-bar. */ -export class MatSnackBarConfig { +export class MatSnackBarConfig { /** The politeness level for the MatAriaLiveAnnouncer announcement. */ politeness?: AriaLivePoliteness = 'polite'; diff --git a/src/material/snack-bar/snack-bar-container.ts b/src/material/snack-bar/snack-bar-container.ts index 15b11d8f0d31..98aed061df35 100644 --- a/src/material/snack-bar/snack-bar-container.ts +++ b/src/material/snack-bar/snack-bar-container.ts @@ -63,13 +63,13 @@ const EXIT_ANIMATION = '_mat-snack-bar-exit'; '(animationcancel)': 'onAnimationEnd($event.animationName)', }, }) -export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy { +export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy { private _ngZone = inject(NgZone); readonly _elementRef = inject>(ElementRef); private _changeDetectorRef = inject(ChangeDetectorRef); private _platform = inject(Platform); protected _animationsDisabled = _animationsDisabled(); - snackBarConfig = inject(MatSnackBarConfig); + snackBarConfig = inject>(MatSnackBarConfig); private _document = inject(DOCUMENT); private _trackedModals = new Set(); diff --git a/src/material/snack-bar/snack-bar-ref.ts b/src/material/snack-bar/snack-bar-ref.ts index 0cbc819e87f9..22f21cda2043 100644 --- a/src/material/snack-bar/snack-bar-ref.ts +++ b/src/material/snack-bar/snack-bar-ref.ts @@ -22,7 +22,7 @@ const MAX_TIMEOUT = Math.pow(2, 31) - 1; /** * Reference to a snack bar dispatched from the snack bar service. */ -export class MatSnackBarRef { +export class MatSnackBarRef { /** The instance of the component making up the content of the snack bar. */ instance!: T; @@ -30,7 +30,7 @@ export class MatSnackBarRef { * The instance of the component making up the content of the snack bar. * @docs-private */ - containerInstance: MatSnackBarContainer; + containerInstance: MatSnackBarContainer; /** Subject for notifying the user that the snack bar has been dismissed. */ private readonly _afterDismissed = new Subject(); @@ -51,7 +51,7 @@ export class MatSnackBarRef { private _dismissedByAction = false; constructor( - containerInstance: MatSnackBarContainer, + containerInstance: MatSnackBarContainer, private _overlayRef: OverlayRef, ) { this.containerInstance = containerInstance; diff --git a/src/material/snack-bar/snack-bar.spec.ts b/src/material/snack-bar/snack-bar.spec.ts index 9eca9d95978d..6c150a3ffdd8 100644 --- a/src/material/snack-bar/snack-bar.spec.ts +++ b/src/material/snack-bar/snack-bar.spec.ts @@ -19,7 +19,7 @@ import { MatSnackBarRef, SimpleSnackBar, } from './index'; -import {MAT_SNACK_BAR_DEFAULT_OPTIONS} from './snack-bar'; +import {MAT_SNACK_BAR_DEFAULT_OPTIONS, SnackBarTemplateContext} from './snack-bar'; import {MATERIAL_ANIMATIONS} from '../core'; describe('MatSnackBar', () => { @@ -571,7 +571,10 @@ describe('MatSnackBar', () => { }); it('should inject the snack bar reference into the component', () => { - const snackBarRef = snackBar.openFromComponent(BurritosNotification); + const snackBarRef = snackBar.openFromComponent< + BurritosNotification, + BurritosNotificationData + >(BurritosNotification); expect(snackBarRef.instance.snackBarRef) .withContext('Expected component to have an injected snack bar reference.') @@ -585,10 +588,11 @@ describe('MatSnackBar', () => { }); it('should be able to inject arbitrary user data', () => { + const data: BurritosNotificationData = { + burritoType: 'Chimichanga', + }; const snackBarRef = snackBar.openFromComponent(BurritosNotification, { - data: { - burritoType: 'Chimichanga', - }, + data, }); expect(snackBarRef.instance.data) @@ -997,17 +1001,22 @@ class ComponentWithChildViewContainer { `, }) class ComponentWithTemplateRef { - @ViewChild(TemplateRef) templateRef!: TemplateRef; + @ViewChild(TemplateRef) templateRef!: TemplateRef>; localValue!: string; } +interface BurritosNotificationData { + burritoType: string; +} + /** Simple component for testing ComponentPortal. */ @Component({ template: '

Burritos are on the way.

', }) class BurritosNotification { - snackBarRef = inject>(MatSnackBarRef); - data = inject(MAT_SNACK_BAR_DATA); + snackBarRef = + inject>(MatSnackBarRef); + data = inject(MAT_SNACK_BAR_DATA); } @Component({ diff --git a/src/material/snack-bar/snack-bar.ts b/src/material/snack-bar/snack-bar.ts index 5ebca0059f8f..b123288e00b4 100644 --- a/src/material/snack-bar/snack-bar.ts +++ b/src/material/snack-bar/snack-bar.ts @@ -25,7 +25,7 @@ import { TemplateRef, inject, } from '@angular/core'; -import {SimpleSnackBar, TextOnlySnackBar} from './simple-snack-bar'; +import {SimpleSnackBar, TextOnlySnackBar, TextOnlySnackBarData} from './simple-snack-bar'; import {MatSnackBarContainer} from './snack-bar-container'; import {MAT_SNACK_BAR_DATA, MatSnackBarConfig} from './snack-bar-config'; import {MatSnackBarRef} from './snack-bar-ref'; @@ -34,7 +34,7 @@ import {takeUntil} from 'rxjs/operators'; import {_animationsDisabled} from '../core'; /** Injection token that can be used to specify default snack bar. */ -export const MAT_SNACK_BAR_DEFAULT_OPTIONS = new InjectionToken( +export const MAT_SNACK_BAR_DEFAULT_OPTIONS = new InjectionToken>( 'mat-snack-bar-default-options', { providedIn: 'root', @@ -42,6 +42,11 @@ export const MAT_SNACK_BAR_DEFAULT_OPTIONS = new InjectionToken { + $implicit?: D | null; + snackBarRef?: MatSnackBarRef>, D>; +} + /** * Service to dispatch Material Design snack bar messages. */ @@ -59,7 +64,7 @@ export class MatSnackBar implements OnDestroy { * If there is a parent snack-bar service, all operations should delegate to that parent * via `_openedSnackBarRef`. */ - private _snackBarRefAtThisLevel: MatSnackBarRef | null = null; + private _snackBarRefAtThisLevel: MatSnackBarRef | null = null; /** The component that should be rendered as the snack bar's simple component. */ simpleSnackBarComponent = SimpleSnackBar; @@ -71,12 +76,12 @@ export class MatSnackBar implements OnDestroy { handsetCssClass = 'mat-mdc-snack-bar-handset'; /** Reference to the currently opened snackbar at *any* level. */ - get _openedSnackBarRef(): MatSnackBarRef | null { + get _openedSnackBarRef(): MatSnackBarRef | null { const parent = this._parentSnackBar; return parent ? parent._openedSnackBarRef : this._snackBarRefAtThisLevel; } - set _openedSnackBarRef(value: MatSnackBarRef | null) { + set _openedSnackBarRef(value: MatSnackBarRef | null) { if (this._parentSnackBar) { this._parentSnackBar._openedSnackBarRef = value; } else { @@ -94,11 +99,11 @@ export class MatSnackBar implements OnDestroy { * @param component Component to be instantiated. * @param config Extra configuration for the snack bar. */ - openFromComponent( + openFromComponent( component: ComponentType, config?: MatSnackBarConfig, - ): MatSnackBarRef { - return this._attach(component, config) as MatSnackBarRef; + ): MatSnackBarRef { + return this._attach(component, config); } /** @@ -108,10 +113,10 @@ export class MatSnackBar implements OnDestroy { * @param template Template to be instantiated. * @param config Extra configuration for the snack bar. */ - openFromTemplate( - template: TemplateRef, - config?: MatSnackBarConfig, - ): MatSnackBarRef> { + openFromTemplate( + template: TemplateRef>, + config?: MatSnackBarConfig, + ): MatSnackBarRef>, D> { return this._attach(template, config); } @@ -124,13 +129,18 @@ export class MatSnackBar implements OnDestroy { open( message: string, action: string = '', - config?: MatSnackBarConfig, - ): MatSnackBarRef { - const _config = {...this._defaultConfig, ...config}; - - // Since the user doesn't have access to the component, we can - // override the data to pass in our own message and action. - _config.data = {message, action}; + config?: MatSnackBarConfig, + ): MatSnackBarRef { + const _config: MatSnackBarConfig = { + ...this._defaultConfig, + ...config, + // Since the user doesn't have access to the component, we can + // override the data to pass in our own message and action. + data: { + message, + action, + }, + }; // Since the snack bar has `role="alert"`, we don't // want to announce the same message twice. @@ -160,10 +170,10 @@ export class MatSnackBar implements OnDestroy { /** * Attaches the snack bar container component to the overlay. */ - private _attachSnackBarContainer( + private _attachSnackBarContainer( overlayRef: OverlayRef, - config: MatSnackBarConfig, - ): MatSnackBarContainer { + config: MatSnackBarConfig, + ): MatSnackBarContainer { const userInjector = config && config.viewContainerRef && config.viewContainerRef.injector; const injector = Injector.create({ parent: userInjector || this._injector, @@ -171,11 +181,11 @@ export class MatSnackBar implements OnDestroy { }); const containerPortal = new ComponentPortal( - this.snackBarContainerComponent, + this.snackBarContainerComponent as typeof MatSnackBarContainer, config.viewContainerRef, injector, ); - const containerRef: ComponentRef = overlayRef.attach(containerPortal); + const containerRef: ComponentRef> = overlayRef.attach(containerPortal); containerRef.instance.snackBarConfig = config; return containerRef.instance; } @@ -183,26 +193,41 @@ export class MatSnackBar implements OnDestroy { /** * Places a new component or a template as the content of the snack bar container. */ - private _attach( - content: ComponentType | TemplateRef, - userConfig?: MatSnackBarConfig, - ): MatSnackBarRef> { - const config = {...new MatSnackBarConfig(), ...this._defaultConfig, ...userConfig}; + private _attach( + content: ComponentType, + userConfig?: MatSnackBarConfig, + ): MatSnackBarRef; + private _attach( + content: TemplateRef>, + userConfig?: MatSnackBarConfig, + ): MatSnackBarRef>, D>; + private _attach( + content: ComponentType | TemplateRef>, + userConfig?: MatSnackBarConfig, + ): MatSnackBarRef>, D> { + const config: MatSnackBarConfig = { + ...new MatSnackBarConfig(), + ...(this._defaultConfig as MatSnackBarConfig), + ...userConfig, + }; const overlayRef = this._createOverlay(config); const container = this._attachSnackBarContainer(overlayRef, config); - const snackBarRef = new MatSnackBarRef>(container, overlayRef); + const snackBarRef = new MatSnackBarRef>, D>( + container, + overlayRef, + ); if (content instanceof TemplateRef) { - const portal = new TemplatePortal(content, null!, { + const portal = new TemplatePortal>(content, null!, { $implicit: config.data, - snackBarRef, - } as any); + snackBarRef: snackBarRef as MatSnackBarRef>, D>, + }); snackBarRef.instance = container.attachTemplatePortal(portal); } else { const injector = this._createInjector(config, snackBarRef); const portal = new ComponentPortal(content, undefined, injector); - const contentRef = container.attachComponentPortal(portal); + const contentRef = container.attachComponentPortal(portal); // We can't pass this via the injector, because the injector is created earlier. snackBarRef.instance = contentRef.instance; @@ -227,11 +252,11 @@ export class MatSnackBar implements OnDestroy { this._animateSnackBar(snackBarRef, config); this._openedSnackBarRef = snackBarRef; - return this._openedSnackBarRef; + return snackBarRef; } /** Animates the old snack bar out and the new one in. */ - private _animateSnackBar(snackBarRef: MatSnackBarRef, config: MatSnackBarConfig) { + private _animateSnackBar(snackBarRef: MatSnackBarRef, config: MatSnackBarConfig) { // When the snackbar is dismissed, clear the reference to it. snackBarRef.afterDismissed().subscribe(() => { // Clear the snackbar ref if it hasn't already been replaced by a newer snackbar. @@ -266,7 +291,7 @@ export class MatSnackBar implements OnDestroy { * Creates a new overlay and places it in the correct location. * @param config The user-specified snack bar config. */ - private _createOverlay(config: MatSnackBarConfig): OverlayRef { + private _createOverlay(config: MatSnackBarConfig): OverlayRef { const overlayConfig = new OverlayConfig(); overlayConfig.direction = config.direction; @@ -302,7 +327,10 @@ export class MatSnackBar implements OnDestroy { * @param config Config that was used to create the snack bar. * @param snackBarRef Reference to the snack bar. */ - private _createInjector(config: MatSnackBarConfig, snackBarRef: MatSnackBarRef): Injector { + private _createInjector( + config: MatSnackBarConfig, + snackBarRef: MatSnackBarRef, + ): Injector { const userInjector = config && config.viewContainerRef && config.viewContainerRef.injector; return Injector.create({