From 905dc7c81e39a5342a331fe230db48b92857015e Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Thu, 28 May 2026 09:21:50 +0200 Subject: [PATCH] fix(material/badge): allow badge defaults to be configured Allows users to configure the defaults for badges for the entire app. Fixes #27844. --- goldens/material/badge/index.api.md | 12 +++++++++ src/material/badge/badge.spec.ts | 41 +++++++++++++++++++++++++++-- src/material/badge/badge.ts | 33 ++++++++++++++++++++--- src/material/badge/public-api.ts | 2 +- 4 files changed, 81 insertions(+), 7 deletions(-) diff --git a/goldens/material/badge/index.api.md b/goldens/material/badge/index.api.md index d5a3c59eae67..da5471d1382f 100644 --- a/goldens/material/badge/index.api.md +++ b/goldens/material/badge/index.api.md @@ -8,9 +8,13 @@ import { AfterViewInit } from '@angular/core'; import * as i0 from '@angular/core'; import * as i1 from '@angular/cdk/a11y'; import * as i2 from '@angular/cdk/bidi'; +import { InjectionToken } from '@angular/core'; import { OnDestroy } from '@angular/core'; import { OnInit } from '@angular/core'; +// @public +export const MAT_BADGE_CONFIG: InjectionToken; + // @public export class MatBadge implements OnInit, AfterViewInit, OnDestroy { constructor(); @@ -46,6 +50,14 @@ export class MatBadge implements OnInit, AfterViewInit, OnDestroy { static ɵfac: i0.ɵɵFactoryDeclaration; } +// @public +export interface MatBadgeConfig { + color?: ThemePalette; + overlap?: boolean; + position?: MatBadgePosition; + size?: MatBadgeSize; +} + // @public (undocumented) export class MatBadgeModule { // (undocumented) diff --git a/src/material/badge/badge.spec.ts b/src/material/badge/badge.spec.ts index 941dd95c7ce3..dd763028231f 100644 --- a/src/material/badge/badge.spec.ts +++ b/src/material/badge/badge.spec.ts @@ -10,6 +10,7 @@ import { import {By} from '@angular/platform-browser'; import {MatBadge, MatBadgeModule, MatBadgePosition, MatBadgeSize} from './index'; import {ThemePalette} from '../core'; +import {MAT_BADGE_CONFIG, MatBadgeConfig} from './badge'; describe('MatBadge', () => { let fixture: ComponentFixture; @@ -265,6 +266,31 @@ describe('MatBadge', () => { .toBeFalse(); }); }); + + it('should be able to specify default values through DI', () => { + TestBed.configureTestingModule({ + providers: [ + { + provide: MAT_BADGE_CONFIG, + useValue: { + color: 'accent', + overlap: false, + position: 'below before', + size: 'large', + } satisfies MatBadgeConfig, + }, + ], + }); + + const fixture = TestBed.createComponent(SimpleBadge); + fixture.detectChanges(); + const badge = fixture.componentInstance.badgeInstance; + + expect(badge.color).toBe('accent'); + expect(badge.overlap).toBe(false); + expect(badge.position).toBe('below before'); + expect(badge.size).toBe('large'); + }); }); /** Test component that contains a MatBadge. */ @@ -333,9 +359,20 @@ class PreExistingBadge {} class NestedBadge {} @Component({ - template: ` - Notifications`, + template: `Notifications`, imports: [MatBadgeModule], changeDetection: ChangeDetectionStrategy.Eager, }) class BadgeOnTemplate {} + +@Component({ + template: ` + + `, + imports: [MatBadgeModule], +}) +class SimpleBadge { + @ViewChild(MatBadge) badgeInstance!: MatBadge; +} diff --git a/src/material/badge/badge.ts b/src/material/badge/badge.ts index de816f0a94c4..16af2eec291d 100644 --- a/src/material/badge/badge.ts +++ b/src/material/badge/badge.ts @@ -22,6 +22,7 @@ import { ViewEncapsulation, DOCUMENT, AfterViewInit, + InjectionToken, } from '@angular/core'; import {_animationsDisabled, ThemePalette} from '../core'; import {_CdkPrivateStyleLoader, _VisuallyHiddenLoader} from '@angular/cdk/private'; @@ -40,6 +41,24 @@ export type MatBadgePosition = /** Allowed size options for matBadgeSize */ export type MatBadgeSize = 'small' | 'medium' | 'large'; +/** Object that can be used to configure the default options for the badge component. */ +export interface MatBadgeConfig { + /** Default position for badges. */ + position?: MatBadgePosition; + + /** Default size for badges. */ + size?: MatBadgeSize; + + /** Default color to apply to all badges. */ + color?: ThemePalette; + + /** Whether badges should overlap by default. */ + overlap?: boolean; +} + +/** Injection token that can be used to configure the default options for the badge component. */ +export const MAT_BADGE_CONFIG = new InjectionToken('MAT_BADGE_CONFIG'); + const BADGE_CONTENT_CLASS = 'mat-badge-content'; /** @@ -93,10 +112,10 @@ export class MatBadge implements OnInit, AfterViewInit, OnDestroy { this._setColor(value); this._color = value; } - private _color: ThemePalette = 'primary'; + private _color: ThemePalette; /** Whether the badge should overlap its contents or not */ - @Input({alias: 'matBadgeOverlap', transform: booleanAttribute}) overlap: boolean = true; + @Input({alias: 'matBadgeOverlap', transform: booleanAttribute}) overlap: boolean; /** Whether the badge is disabled. */ @Input({alias: 'matBadgeDisabled', transform: booleanAttribute}) disabled: boolean = false; @@ -105,7 +124,7 @@ export class MatBadge implements OnInit, AfterViewInit, OnDestroy { * Position the badge should reside. * Accepts any combination of 'above'|'below' and 'before'|'after' */ - @Input('matBadgePosition') position: MatBadgePosition = 'above after'; + @Input('matBadgePosition') position: MatBadgePosition; /** The content for the badge */ @Input('matBadge') @@ -128,7 +147,7 @@ export class MatBadge implements OnInit, AfterViewInit, OnDestroy { private _description!: string; /** Size of the badge. Can be 'small', 'medium', or 'large'. */ - @Input('matBadgeSize') size: MatBadgeSize = 'medium'; + @Input('matBadgeSize') size: MatBadgeSize; /** Whether the badge is hidden. */ @Input({alias: 'matBadgeHidden', transform: booleanAttribute}) hidden: boolean = false; @@ -148,10 +167,16 @@ export class MatBadge implements OnInit, AfterViewInit, OnDestroy { private _document = inject(DOCUMENT); constructor() { + const config = inject(MAT_BADGE_CONFIG, {optional: true}); const styleLoader = inject(_CdkPrivateStyleLoader); styleLoader.load(_MatBadgeStyleLoader); styleLoader.load(_VisuallyHiddenLoader); + this._color = config?.color || 'primary'; + this.overlap = config?.overlap ?? true; + this.position = config?.position || 'above after'; + this.size = config?.size || 'medium'; + if (typeof ngDevMode === 'undefined' || ngDevMode) { const nativeElement = this._elementRef.nativeElement; diff --git a/src/material/badge/public-api.ts b/src/material/badge/public-api.ts index 508c4cb8f493..fa5b01d52866 100644 --- a/src/material/badge/public-api.ts +++ b/src/material/badge/public-api.ts @@ -7,4 +7,4 @@ */ export * from './badge-module'; -export {MatBadge, MatBadgePosition, MatBadgeSize} from './badge'; +export {MatBadge, MatBadgePosition, MatBadgeSize, MAT_BADGE_CONFIG, MatBadgeConfig} from './badge';