Skip to content

Commit 426afa0

Browse files
authored
feat(material/icon): add default options (#23638)
closes #23548
1 parent 3d2aefb commit 426afa0

File tree

3 files changed

+120
-5
lines changed

3 files changed

+120
-5
lines changed

src/material/icon/icon.spec.ts

+80-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import {
55
HttpTestingController,
66
TestRequest,
77
} from '@angular/common/http/testing';
8-
import {Component, ErrorHandler, ViewChild} from '@angular/core';
9-
import {MatIconModule, MAT_ICON_LOCATION} from './index';
8+
import {Component, ErrorHandler, Provider, Type, ViewChild} from '@angular/core';
9+
import {MAT_ICON_DEFAULT_OPTIONS, MAT_ICON_LOCATION, MatIconModule} from './index';
1010
import {MatIconRegistry, getMatIconNoHttpProviderError} from './icon-registry';
1111
import {FAKE_SVGS} from './fake-svgs';
1212
import {wrappedErrorMessage} from '../../cdk/testing/private';
@@ -41,6 +41,19 @@ function verifyPathChildElement(element: Element, attributeValue: string): void
4141
expect(pathElement.getAttribute('name')).toBe(attributeValue);
4242
}
4343

44+
/** Creates a test component fixture. */
45+
function createComponent<T>(component: Type<T>, providers: Provider[] = []) {
46+
TestBed.configureTestingModule({
47+
imports: [MatIconModule],
48+
declarations: [component],
49+
providers: [...providers],
50+
});
51+
52+
TestBed.compileComponents();
53+
54+
return TestBed.createComponent<T>(component);
55+
}
56+
4457
describe('MatIcon', () => {
4558
let fakePath: string;
4659
let errorHandler: jasmine.SpyObj<ErrorHandler>;
@@ -1237,6 +1250,71 @@ describe('MatIcon without HttpClientModule', () => {
12371250
});
12381251
});
12391252

1253+
describe('MatIcon with default options', () => {
1254+
it('should be able to configure color globally', fakeAsync(() => {
1255+
const fixture = createComponent(IconWithLigature, [
1256+
{provide: MAT_ICON_DEFAULT_OPTIONS, useValue: {color: 'accent'}},
1257+
]);
1258+
const iconElement = fixture.debugElement.nativeElement.querySelector('mat-icon');
1259+
fixture.detectChanges();
1260+
expect(iconElement.classList).not.toContain('mat-icon-no-color');
1261+
expect(iconElement.classList).toContain('mat-accent');
1262+
}));
1263+
1264+
it('should use passed color rather then color provided', fakeAsync(() => {
1265+
const fixture = createComponent(IconWithColor, [
1266+
{provide: MAT_ICON_DEFAULT_OPTIONS, useValue: {color: 'warn'}},
1267+
]);
1268+
const iconElement = fixture.debugElement.nativeElement.querySelector('mat-icon');
1269+
fixture.detectChanges();
1270+
expect(iconElement.classList).not.toContain('mat-warn');
1271+
expect(iconElement.classList).toContain('mat-primary');
1272+
}));
1273+
1274+
it('should use default color if no color passed', fakeAsync(() => {
1275+
const fixture = createComponent(IconWithColor, [
1276+
{provide: MAT_ICON_DEFAULT_OPTIONS, useValue: {color: 'accent'}},
1277+
]);
1278+
const component = fixture.componentInstance;
1279+
const iconElement = fixture.debugElement.nativeElement.querySelector('mat-icon');
1280+
component.iconColor = '';
1281+
fixture.detectChanges();
1282+
expect(iconElement.classList).not.toContain('mat-icon-no-color');
1283+
expect(iconElement.classList).not.toContain('mat-primary');
1284+
expect(iconElement.classList).toContain('mat-accent');
1285+
}));
1286+
1287+
it('should be able to configure font set globally', fakeAsync(() => {
1288+
const fixture = createComponent(IconWithLigature, [
1289+
{provide: MAT_ICON_DEFAULT_OPTIONS, useValue: {fontSet: 'custom-font-set'}},
1290+
]);
1291+
const iconElement = fixture.debugElement.nativeElement.querySelector('mat-icon');
1292+
fixture.detectChanges();
1293+
expect(iconElement.classList).toContain('custom-font-set');
1294+
}));
1295+
1296+
it('should use passed fontSet rather then default one', fakeAsync(() => {
1297+
const fixture = createComponent(IconWithCustomFontCss, [
1298+
{provide: MAT_ICON_DEFAULT_OPTIONS, useValue: {fontSet: 'default-font-set'}},
1299+
]);
1300+
const component = fixture.componentInstance;
1301+
const iconElement = fixture.debugElement.nativeElement.querySelector('mat-icon');
1302+
component.fontSet = 'custom-font-set';
1303+
fixture.detectChanges();
1304+
expect(iconElement.classList).not.toContain('default-font-set');
1305+
expect(iconElement.classList).toContain('custom-font-set');
1306+
}));
1307+
1308+
it('should use passed empty fontSet rather then default one', fakeAsync(() => {
1309+
const fixture = createComponent(IconWithCustomFontCss, [
1310+
{provide: MAT_ICON_DEFAULT_OPTIONS, useValue: {fontSet: 'default-font-set'}},
1311+
]);
1312+
const iconElement = fixture.debugElement.nativeElement.querySelector('mat-icon');
1313+
fixture.detectChanges();
1314+
expect(iconElement.classList).not.toContain('default-font-set');
1315+
}));
1316+
});
1317+
12401318
@Component({template: `<mat-icon>{{iconName}}</mat-icon>`})
12411319
class IconWithLigature {
12421320
iconName = '';

src/material/icon/icon.ts

+28-1
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ import {
2121
Input,
2222
OnDestroy,
2323
OnInit,
24+
Optional,
2425
ViewEncapsulation,
2526
} from '@angular/core';
26-
import {CanColor, mixinColor} from '@angular/material/core';
27+
import {CanColor, ThemePalette, mixinColor} from '@angular/material/core';
2728
import {Subscription} from 'rxjs';
2829
import {take} from 'rxjs/operators';
2930

@@ -37,6 +38,19 @@ const _MatIconBase = mixinColor(
3738
},
3839
);
3940

41+
/** Default options for `mat-icon`. */
42+
export interface MatIconDefaultOptions {
43+
/** Default color of the icon. */
44+
color?: ThemePalette;
45+
/** Font set that the icon is a part of. */
46+
fontSet?: string;
47+
}
48+
49+
/** Injection token to be used to override the default options for `mat-icon`. */
50+
export const MAT_ICON_DEFAULT_OPTIONS = new InjectionToken<MatIconDefaultOptions>(
51+
'MAT_ICON_DEFAULT_OPTIONS',
52+
);
53+
4054
/**
4155
* Injection token used to provide the current location to `MatIcon`.
4256
* Used to handle server-side rendering and to stub out during unit tests.
@@ -216,9 +230,22 @@ export class MatIcon extends _MatIconBase implements OnInit, AfterViewChecked, C
216230
@Attribute('aria-hidden') ariaHidden: string,
217231
@Inject(MAT_ICON_LOCATION) private _location: MatIconLocation,
218232
private readonly _errorHandler: ErrorHandler,
233+
@Optional()
234+
@Inject(MAT_ICON_DEFAULT_OPTIONS)
235+
defaults?: MatIconDefaultOptions,
219236
) {
220237
super(elementRef);
221238

239+
if (defaults) {
240+
if (defaults.color) {
241+
this.color = this.defaultColor = defaults.color;
242+
}
243+
244+
if (defaults.fontSet) {
245+
this.fontSet = defaults.fontSet;
246+
}
247+
}
248+
222249
// If the user has not explicitly set aria-hidden, mark the icon as hidden, as this is
223250
// the right thing to do for the majority of icon use-cases.
224251
if (!ariaHidden) {

tools/public_api_guard/material/icon.md

+12-2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { OnInit } from '@angular/core';
2222
import { Optional } from '@angular/core';
2323
import { SafeHtml } from '@angular/platform-browser';
2424
import { SafeResourceUrl } from '@angular/platform-browser';
25+
import { ThemePalette } from '@angular/material/core';
2526

2627
// @public
2728
export function getMatIconFailedToSanitizeLiteralError(literal: SafeHtml): Error;
@@ -54,6 +55,9 @@ export interface IconOptions {
5455
// @public
5556
export type IconResolver = (name: string, namespace: string) => SafeResourceUrl | SafeResourceUrlWithIconOptions | null;
5657

58+
// @public
59+
export const MAT_ICON_DEFAULT_OPTIONS: InjectionToken<MatIconDefaultOptions>;
60+
5761
// @public
5862
export const MAT_ICON_LOCATION: InjectionToken<MatIconLocation>;
5963

@@ -62,7 +66,7 @@ export function MAT_ICON_LOCATION_FACTORY(): MatIconLocation;
6266

6367
// @public
6468
export class MatIcon extends _MatIconBase implements OnInit, AfterViewChecked, CanColor, OnDestroy {
65-
constructor(elementRef: ElementRef<HTMLElement>, _iconRegistry: MatIconRegistry, ariaHidden: string, _location: MatIconLocation, _errorHandler: ErrorHandler);
69+
constructor(elementRef: ElementRef<HTMLElement>, _iconRegistry: MatIconRegistry, ariaHidden: string, _location: MatIconLocation, _errorHandler: ErrorHandler, defaults?: MatIconDefaultOptions);
6670
get fontIcon(): string;
6771
set fontIcon(value: string);
6872
get fontSet(): string;
@@ -86,7 +90,13 @@ export class MatIcon extends _MatIconBase implements OnInit, AfterViewChecked, C
8690
// (undocumented)
8791
static ɵcmp: i0.ɵɵComponentDeclaration<MatIcon, "mat-icon", ["matIcon"], { "color": "color"; "inline": "inline"; "svgIcon": "svgIcon"; "fontSet": "fontSet"; "fontIcon": "fontIcon"; }, {}, never, ["*"]>;
8892
// (undocumented)
89-
static ɵfac: i0.ɵɵFactoryDeclaration<MatIcon, [null, null, { attribute: "aria-hidden"; }, null, null]>;
93+
static ɵfac: i0.ɵɵFactoryDeclaration<MatIcon, [null, null, { attribute: "aria-hidden"; }, null, null, { optional: true; }]>;
94+
}
95+
96+
// @public
97+
export interface MatIconDefaultOptions {
98+
color?: ThemePalette;
99+
fontSet?: string;
90100
}
91101

92102
// @public

0 commit comments

Comments
 (0)