Skip to content

Commit c39c842

Browse files
committed
feat(material/icon): add default options
closes #23548
1 parent dd59b4a commit c39c842

File tree

3 files changed

+204
-5
lines changed

3 files changed

+204
-5
lines changed

src/material/icon/icon.spec.ts

+157-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,148 @@ describe('MatIcon without HttpClientModule', () => {
12371250
});
12381251
});
12391252

1253+
describe('MatIcon with default options', () => {
1254+
it('should be able to configure default color globally', fakeAsync(() => {
1255+
const fixture = createComponent(IconWithLigature, [
1256+
{provide: MAT_ICON_DEFAULT_OPTIONS, useValue: {defaultColor: 'accent'}},
1257+
]);
1258+
const component = fixture.componentInstance;
1259+
const matIconElement = fixture.debugElement.nativeElement.querySelector('mat-icon');
1260+
1261+
component.iconName = 'home';
1262+
fixture.detectChanges();
1263+
expect(sortedClassNames(matIconElement)).toEqual([
1264+
'mat-accent',
1265+
'mat-icon',
1266+
'material-icons',
1267+
'notranslate',
1268+
]);
1269+
}));
1270+
1271+
it('should be able to configure color globally', fakeAsync(() => {
1272+
const fixture = createComponent(IconWithLigature, [
1273+
{provide: MAT_ICON_DEFAULT_OPTIONS, useValue: {color: 'warn'}},
1274+
]);
1275+
const component = fixture.componentInstance;
1276+
const matIconElement = fixture.debugElement.nativeElement.querySelector('mat-icon');
1277+
1278+
component.iconName = 'home';
1279+
fixture.detectChanges();
1280+
expect(sortedClassNames(matIconElement)).toEqual([
1281+
'mat-icon',
1282+
'mat-warn',
1283+
'material-icons',
1284+
'notranslate',
1285+
]);
1286+
}));
1287+
1288+
it('should be able to configure default color and color globally', fakeAsync(() => {
1289+
const fixture = createComponent(IconWithLigature, [
1290+
{provide: MAT_ICON_DEFAULT_OPTIONS, useValue: {defaultColor: 'accent', color: 'warn'}},
1291+
]);
1292+
const component = fixture.componentInstance;
1293+
const matIconElement = fixture.debugElement.nativeElement.querySelector('mat-icon');
1294+
1295+
component.iconName = 'home';
1296+
fixture.detectChanges();
1297+
expect(sortedClassNames(matIconElement)).toEqual([
1298+
'mat-icon',
1299+
'mat-warn',
1300+
'material-icons',
1301+
'notranslate',
1302+
]);
1303+
}));
1304+
1305+
it('should use passed color rather then default color or color provided', fakeAsync(() => {
1306+
const fixture = createComponent(IconWithColor, [
1307+
{provide: MAT_ICON_DEFAULT_OPTIONS, useValue: {defaultColor: 'accent', color: 'warn'}},
1308+
]);
1309+
const component = fixture.componentInstance;
1310+
const matIconElement = fixture.debugElement.nativeElement.querySelector('mat-icon');
1311+
1312+
component.iconName = 'home';
1313+
fixture.detectChanges();
1314+
expect(sortedClassNames(matIconElement)).toEqual([
1315+
'mat-icon',
1316+
'mat-primary',
1317+
'material-icons',
1318+
'notranslate',
1319+
]);
1320+
}));
1321+
1322+
it('should use default color if no passed color', fakeAsync(() => {
1323+
const fixture = createComponent(IconWithColor, [
1324+
{provide: MAT_ICON_DEFAULT_OPTIONS, useValue: {defaultColor: 'accent', color: 'warn'}},
1325+
]);
1326+
const component = fixture.componentInstance;
1327+
const matIconElement = fixture.debugElement.nativeElement.querySelector('mat-icon');
1328+
1329+
component.iconName = 'home';
1330+
component.iconColor = '';
1331+
fixture.detectChanges();
1332+
expect(sortedClassNames(matIconElement)).toEqual([
1333+
'mat-accent',
1334+
'mat-icon',
1335+
'material-icons',
1336+
'notranslate',
1337+
]);
1338+
}));
1339+
1340+
it('should be able to configure font set globally', fakeAsync(() => {
1341+
const fixture = createComponent(IconWithLigature, [
1342+
{provide: MAT_ICON_DEFAULT_OPTIONS, useValue: {fontSet: 'custom-font-set'}},
1343+
]);
1344+
const component = fixture.componentInstance;
1345+
const matIconElement = fixture.debugElement.nativeElement.querySelector('mat-icon');
1346+
1347+
component.iconName = 'home';
1348+
fixture.detectChanges();
1349+
expect(sortedClassNames(matIconElement)).toEqual([
1350+
'custom-font-set',
1351+
'mat-icon',
1352+
'mat-icon-no-color',
1353+
'notranslate',
1354+
]);
1355+
}));
1356+
1357+
it('should use passed fontSet rather then default one', fakeAsync(() => {
1358+
const fixture = createComponent(IconWithCustomFontCss, [
1359+
{provide: MAT_ICON_DEFAULT_OPTIONS, useValue: {fontSet: 'default-font-set'}},
1360+
]);
1361+
const component = fixture.componentInstance;
1362+
const matIconElement = fixture.debugElement.nativeElement.querySelector('mat-icon');
1363+
1364+
component.fontSet = 'custom-font-set';
1365+
component.fontIcon = 'house';
1366+
fixture.detectChanges();
1367+
expect(sortedClassNames(matIconElement)).toEqual([
1368+
'custom-font-set',
1369+
'house',
1370+
'mat-icon',
1371+
'mat-icon-no-color',
1372+
'notranslate',
1373+
]);
1374+
}));
1375+
1376+
it('should use passed empty fontSet rather then default one', fakeAsync(() => {
1377+
const fixture = createComponent(IconWithCustomFontCss, [
1378+
{provide: MAT_ICON_DEFAULT_OPTIONS, useValue: {fontSet: 'default-font-set'}},
1379+
]);
1380+
const component = fixture.componentInstance;
1381+
const matIconElement = fixture.debugElement.nativeElement.querySelector('mat-icon');
1382+
1383+
component.fontIcon = 'house';
1384+
fixture.detectChanges();
1385+
expect(sortedClassNames(matIconElement)).toEqual([
1386+
'house',
1387+
'mat-icon',
1388+
'mat-icon-no-color',
1389+
'material-icons',
1390+
'notranslate',
1391+
]);
1392+
}));
1393+
});
1394+
12401395
@Component({template: `<mat-icon>{{iconName}}</mat-icon>`})
12411396
class IconWithLigature {
12421397
iconName = '';

src/material/icon/icon.ts

+34-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,21 @@ const _MatIconBase = mixinColor(
3738
},
3839
);
3940

41+
/** Default options for `mat-icon`. */
42+
export interface MatIconDefaultOptions {
43+
/** Theme color palette for the icon. */
44+
color?: ThemePalette;
45+
/** Default color for the icon to fall back to if no color is set. */
46+
defaultColor?: ThemePalette;
47+
/** Font set that the icon is a part of. */
48+
fontSet?: string;
49+
}
50+
51+
/** Injection token to be used to override the default options for `mat-icon`. */
52+
export const MAT_ICON_DEFAULT_OPTIONS = new InjectionToken<MatIconDefaultOptions>(
53+
'MAT_ICON_DEFAULT_OPTIONS',
54+
);
55+
4056
/**
4157
* Injection token used to provide the current location to `MatIcon`.
4258
* Used to handle server-side rendering and to stub out during unit tests.
@@ -216,9 +232,26 @@ export class MatIcon extends _MatIconBase implements OnInit, AfterViewChecked, C
216232
@Attribute('aria-hidden') ariaHidden: string,
217233
@Inject(MAT_ICON_LOCATION) private _location: MatIconLocation,
218234
private readonly _errorHandler: ErrorHandler,
235+
@Optional()
236+
@Inject(MAT_ICON_DEFAULT_OPTIONS)
237+
defaults?: MatIconDefaultOptions,
219238
) {
220239
super(elementRef);
221240

241+
if (defaults) {
242+
if (defaults.defaultColor) {
243+
this.defaultColor = defaults.defaultColor;
244+
}
245+
246+
if (defaults.color || defaults.defaultColor) {
247+
this.color = defaults.color || defaults.defaultColor;
248+
}
249+
250+
if (defaults.fontSet) {
251+
this.fontSet = defaults.fontSet;
252+
}
253+
}
254+
222255
// If the user has not explicitly set aria-hidden, mark the icon as hidden, as this is
223256
// the right thing to do for the majority of icon use-cases.
224257
if (!ariaHidden) {

tools/public_api_guard/material/icon.md

+13-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,14 @@ 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+
defaultColor?: ThemePalette;
100+
fontSet?: string;
90101
}
91102

92103
// @public

0 commit comments

Comments
 (0)