Skip to content

Commit 1933342

Browse files
committed
feat(material/icon): add default options
closes #23548
1 parent 18d549e commit 1933342

File tree

3 files changed

+173
-5
lines changed

3 files changed

+173
-5
lines changed

src/material/icon/icon.spec.ts

+126-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,21 @@ 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: [
50+
...providers
51+
]
52+
});
53+
54+
TestBed.compileComponents();
55+
56+
return TestBed.createComponent<T>(component);
57+
}
58+
4459
describe('MatIcon', () => {
4560
let fakePath: string;
4661
let errorHandler: jasmine.SpyObj<ErrorHandler>;
@@ -1237,6 +1252,115 @@ describe('MatIcon without HttpClientModule', () => {
12371252
});
12381253
});
12391254

1255+
1256+
describe('MatIcon with default options', () => {
1257+
it('should be able to configure default color globally', fakeAsync(() => {
1258+
const fixture = createComponent(IconWithLigature, [
1259+
{ provide: MAT_ICON_DEFAULT_OPTIONS, useValue: { defaultColor: 'accent' } }
1260+
]);
1261+
const component = fixture.componentInstance;
1262+
const matIconElement = fixture.debugElement.nativeElement.querySelector('mat-icon');
1263+
1264+
component.iconName = 'home';
1265+
fixture.detectChanges();
1266+
expect(sortedClassNames(matIconElement))
1267+
.toEqual(['mat-accent', 'mat-icon', 'material-icons', 'notranslate']);
1268+
}));
1269+
1270+
it('should be able to configure color globally', fakeAsync(() => {
1271+
const fixture = createComponent(IconWithLigature, [
1272+
{ provide: MAT_ICON_DEFAULT_OPTIONS, useValue: { color: 'warn' } }
1273+
]);
1274+
const component = fixture.componentInstance;
1275+
const matIconElement = fixture.debugElement.nativeElement.querySelector('mat-icon');
1276+
1277+
component.iconName = 'home';
1278+
fixture.detectChanges();
1279+
expect(sortedClassNames(matIconElement))
1280+
.toEqual(['mat-icon', 'mat-warn', 'material-icons', 'notranslate']);
1281+
}));
1282+
1283+
it('should be able to configure default color and color globally', fakeAsync(() => {
1284+
const fixture = createComponent(IconWithLigature, [
1285+
{ provide: MAT_ICON_DEFAULT_OPTIONS, useValue: { defaultColor: 'accent', color: 'warn' } }
1286+
]);
1287+
const component = fixture.componentInstance;
1288+
const matIconElement = fixture.debugElement.nativeElement.querySelector('mat-icon');
1289+
1290+
component.iconName = 'home';
1291+
fixture.detectChanges();
1292+
expect(sortedClassNames(matIconElement))
1293+
.toEqual(['mat-icon', 'mat-warn', 'material-icons', 'notranslate']);
1294+
}));
1295+
1296+
it('should use passed color rather then default color or color provided', fakeAsync(() => {
1297+
const fixture = createComponent(IconWithColor, [
1298+
{ provide: MAT_ICON_DEFAULT_OPTIONS, useValue: { defaultColor: 'accent', color: 'warn' } }
1299+
]);
1300+
const component = fixture.componentInstance;
1301+
const matIconElement = fixture.debugElement.nativeElement.querySelector('mat-icon');
1302+
1303+
component.iconName = 'home';
1304+
fixture.detectChanges();
1305+
expect(sortedClassNames(matIconElement))
1306+
.toEqual(['mat-icon', 'mat-primary', 'material-icons', 'notranslate']);
1307+
}));
1308+
1309+
it('should use default color if no passed color', fakeAsync(() => {
1310+
const fixture = createComponent(IconWithColor, [
1311+
{ provide: MAT_ICON_DEFAULT_OPTIONS, useValue: { defaultColor: 'accent', color: 'warn' } }
1312+
]);
1313+
const component = fixture.componentInstance;
1314+
const matIconElement = fixture.debugElement.nativeElement.querySelector('mat-icon');
1315+
1316+
component.iconName = 'home';
1317+
component.iconColor = '';
1318+
fixture.detectChanges();
1319+
expect(sortedClassNames(matIconElement))
1320+
.toEqual(['mat-accent', 'mat-icon', 'material-icons', 'notranslate']);
1321+
}));
1322+
1323+
it('should be able to configure font set globally', fakeAsync(() => {
1324+
const fixture = createComponent(IconWithLigature, [
1325+
{ provide: MAT_ICON_DEFAULT_OPTIONS, useValue: { fontSet: 'custom-font-set' } }
1326+
]);
1327+
const component = fixture.componentInstance;
1328+
const matIconElement = fixture.debugElement.nativeElement.querySelector('mat-icon');
1329+
1330+
component.iconName = 'home';
1331+
fixture.detectChanges();
1332+
expect(sortedClassNames(matIconElement))
1333+
.toEqual(['custom-font-set', 'mat-icon', 'mat-icon-no-color', 'notranslate']);
1334+
}));
1335+
1336+
it('should use passed fontSet rather then default one', fakeAsync(() => {
1337+
const fixture = createComponent(IconWithCustomFontCss, [
1338+
{ provide: MAT_ICON_DEFAULT_OPTIONS, useValue: { fontSet: 'default-font-set' } }
1339+
]);
1340+
const component = fixture.componentInstance;
1341+
const matIconElement = fixture.debugElement.nativeElement.querySelector('mat-icon');
1342+
1343+
component.fontSet = 'custom-font-set';
1344+
component.fontIcon = 'house';
1345+
fixture.detectChanges();
1346+
expect(sortedClassNames(matIconElement))
1347+
.toEqual(['custom-font-set', 'house', 'mat-icon', 'mat-icon-no-color', 'notranslate']);
1348+
}));
1349+
1350+
it('should use passed empty fontSet rather then default one', fakeAsync(() => {
1351+
const fixture = createComponent(IconWithCustomFontCss, [
1352+
{ provide: MAT_ICON_DEFAULT_OPTIONS, useValue: { fontSet: 'default-font-set' } }
1353+
]);
1354+
const component = fixture.componentInstance;
1355+
const matIconElement = fixture.debugElement.nativeElement.querySelector('mat-icon');
1356+
1357+
component.fontIcon = 'house';
1358+
fixture.detectChanges();
1359+
expect(sortedClassNames(matIconElement))
1360+
.toEqual(['house', 'mat-icon', 'mat-icon-no-color', 'material-icons', 'notranslate']);
1361+
}));
1362+
});
1363+
12401364
@Component({template: `<mat-icon>{{iconName}}</mat-icon>`})
12411365
class IconWithLigature {
12421366
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;
@@ -88,7 +92,14 @@ export class MatIcon extends _MatIconBase implements OnInit, AfterViewChecked, C
8892
// (undocumented)
8993
static ɵcmp: i0.ɵɵComponentDeclaration<MatIcon, "mat-icon", ["matIcon"], { "color": "color"; "inline": "inline"; "svgIcon": "svgIcon"; "fontSet": "fontSet"; "fontIcon": "fontIcon"; }, {}, never, ["*"]>;
9094
// (undocumented)
91-
static ɵfac: i0.ɵɵFactoryDeclaration<MatIcon, [null, null, { attribute: "aria-hidden"; }, null, null]>;
95+
static ɵfac: i0.ɵɵFactoryDeclaration<MatIcon, [null, null, { attribute: "aria-hidden"; }, null, null, { optional: true; }]>;
96+
}
97+
98+
// @public
99+
export interface MatIconDefaultOptions {
100+
color?: ThemePalette;
101+
defaultColor?: ThemePalette;
102+
fontSet?: string;
92103
}
93104

94105
// @public

0 commit comments

Comments
 (0)