Bug or feature request.
When using <mat-icon>home</mat-icon>
, nothing should be displayed until the icon is loaded properly. As soon as the icon is loaded, it should render.
Depending on the speed of your connection, it is possible, that you see the text (for example home
) instead of the icon.
Network
-Tab choose a very slow network throttling profile. This is needed, as the sample app is very small and therefore the problem is not noticeable with normal connections.In an app I am currently working on, this happens pretty often. Even if I only change the route (which does not trigger a reload of the page) I often see the text of the icons before the icon is displayed properly, so it does not seem to be related to the loading of the icon font.
I am using @angular/material 6.3.1 together with TypeScript 2.7.2 on a Windows 10 PC.
The problem occures at least on Chrome, Opera, Firefox and Edge.
I just noticed, that the problem in my app was actually the change-detection. I am using CdkPortal
and CdkPortalOutlet
to dynamicaly change the ocntent of my app-bar (including some mat-icon
s).
As I am using ChangeDetectionStrategy.OnPush
, I had to call detectChanges()
after changing the content.
However, you can still see the text, while the font is loading. On normal connections it isn't really noticeable, but if you have a slow connection, the text stays for a while.
I guess it would be good, if the icons don't show anything as long as the used font is not fully loaded.
Not sure, if thats even possible...
Going to close this as "working as expected". The component on its own doesn't have a good way of knowing when the icon font is loaded. If your app knows when the assets are loaded, you can hide the icons until then, or you can potentially fall back to pngs or svgs.
@jelbourn would you reopen this if the component does in fact have a good way of knowing when the font is loaded?
if ("fonts" in document && !document.fonts.check(iconFontName)){
document.body.classList.add("mat-icon-font-missing");
document.fonts.load(iconFontName)
.then(() => document.body.classList.remove("mat-icon-font-missing"));
}
CSS should be easy enough once you have that. Of course, this doesn't avoid the FOUT for IE/Edge users, but there's just no helping some people.
@thw0rted Interesting, I didn't know about that API. We would need some way to know the name of the icon-font being used, since it isn't always necessarily the Material Design icon font. I imagine this would become a new optional setting on the icon registry. It's something that would be good for a community PR.
Is the icon registry responsible for the default behavior today? The docs say "By default, @font-face
definition. I was going to suggest a universal / top-level "icon font not loaded" class, but of course with the registry you can have an arbitrary number of fontSet
so you'd need to do the above logic for each font in use.
And I see what you were saying in your original response, none of the current registry methods seem to care about the font name. Moreover, it appears to be possible to use font-based mat-icon
without touching the registry at all -- I haven't done it myself, but it sounds like you just need to set the fontSet
/ fontIcon
params. I guess this sort of behavior would need to either be added to a current call (like an extra parameter for registerFontClassAlias
) or just be exposed as a new method.
Yeah, the registry has the default icon font css class. So in addition to that class, it would need to know about the font name.
Hi there,
document.body.classList.add("mat-icon-font-missing");
document.fonts.load(iconFontName)
Please keep in mind that it might break universal if you refer to classList
When you say "break universal", I guess technically Material2 could support IE9 but that's the only outlier I see on the list and frankly, if you're stuck on IE9 you have bigger problems in your life 馃槖
just checking if there is any PR WIP on this?
When you say "break universal", I guess technically Material2 could support IE9 but that's the only outlier I see on the list and frankly, if you're stuck on IE9 you have bigger problems in your life 馃槖
Not sure about IE9, universal is server side rendering ;-) fortunately I am not stuck with IE9 aahahah
I think I got the same issue: mat-icon
displays only text when using OnPush
in the parent component. When OnPush
is removed everything works.
Here's a Stackblitz example with angular 8 an angular material 8: https://stackblitz.com/edit/components-issue-ghjcpf?file=app/app.component.ts
Explanation of the code:
I have a custom IfDirective
(which I have modified to show the problem). The directive changes every 2 seconds its ViewContainer
contents.
What you should be able to see:
The first 2 seconds you can see the notifications
icon. After those 2 seconds the directive will clear the view container and displays the other template. This other template also contains a mat-icon
, but it will always display the string instead of the icon.
When you remove changeDetection: ChangeDetectionStrategy.OnPush
from the AppComponent
everything works as expected. But that's no solution.
When you change the directive input variable show
from within the AppComponent
everything is working fine including OnPush
, because in this case the component automatically runs the change detection, I guess.
The mat-raised-button
with text behave normal. Animations, text, styles are in place. The problem only happens with mat-icon
.
EDIT: "my" problem can be fixed by calling detectChanges()
within the directive:
this.viewContainer.createEmbeddedView(this.templateRef).detectChanges();
EDIT2: Another "solution" (or workaround) that works for me, is this:
ngOnInit() {
const ifTemplateRef = this.templateRef.createEmbeddedView(null);
const elseTemplateRef = this.appIfAuthenticatedElse.createEmbeddedView(null);
this.someObservable$.subscribe(val => {
this.viewContainer.detach();
if (val) {
this.viewContainer.insert(ifTemplateRef);
} else if (this.appIfAuthenticatedElse) {
this.viewContainer.insert(elseTemplateRef);
}
});
}
Is there a requirement for the font ligature identifier to be provided as the text inside the <mat-icon>
tag? I think you can introduce an attribute for this function, just like the svgIcon
or fontIcon
attributes, and the html-rendering issue would be solved. e.g.:
<mat-icon ligature="home"></mat-icon>
Here's how I am solving it, with fontfaceobserver
:
app.component.ts
export class AppComponent {
constructor(renderer: Renderer2) {
const materialIcons = new FontFaceObserver('Material Icons');
materialIcons.load(null, 10000)
.then(() => this.renderer.addClass(document.body, `${keyword}-loaded`))
.catch(() => this.renderer.addClass(document.body, `${keyword}-error`)); // this line not necessary for simple example
}
}
scss
// don't show mat-icon until font is loaded, unless using a different font set
:not(.material-icons-loaded) .mat-icon:not([fontSet]) {
display: none;
}
scss
.mat-icon:not([fontSet]) {
display: none;
.#{$class}-loaded & {
// loaded font successfully
display: inline-block;
}
.#{$class}-error & {
// could not load font - show fallback label if available
display: inline-block;
visibility: hidden;
&[data-label]:before {
content: attr(data-label);
visibility: visible;
}
}
}
html
<!-- displays nothing -->
<mat-icon>arrow_forward</mat-icon>
<!-- displays 'submit' -->
<mat-icon [data-label]="submit">arrow_forward</mat-icon>
I am working with an Angular 8 project where I am utilizing mat-icon's that were experiencing FOUT (Flash of Unstyled Text) resulting in the icon text getting displayed for a brief moment while the Web Application waited for the Material Icon font families to load. My solution was to add the npm package webfontloader
npm install --save webfontloader
Inside the app.module.ts:
import * as WebFont from 'webfontloader';
WebFont.load({
custom: { families: ['Material Icons', 'Material Icons Outline'], }
});
And in the css/sass file where the font families are imported:
.wf-loading body {
display: none;
}
Update: I'm not so sure my suggestion below actually works!
I think what may have happened is that Firefox specifically still flashes icons during the SSR handover, while Chrome doesn't.
Just in case, here's my original comment about what I thought would fix that with Universal:
Thanks for the discussion all, and suggested workarounds @cbejensen + @thekarlbrown.
I was seeing this issue in an Angular 9 Universal app, I think most visibly on even faster connections during the handover from server to client JS (possibly a side effect / deeper issue around where things load from, but this was at least the most visible symptom).
I had to adapt these solutions to get server renders working. I tried the Typekit webfontloader
first but found it had a window
dependency, at least when loaded with import
, which broke the SSR build.
Using fontfaceobserver
got round this problem, but giving Renderer2
a document.body
ref led to a runtime crash on the server side, for (in retrospect) obvious reasons.
So my approach is now based on @cbejensen's but hiding the app component instead of the body, and using ElementRef
:
app.component.ts
:
import { ElementRef, Inject, OnInit, Renderer2 } from '@angular/core';
import * as FontFaceObserver from 'fontfaceobserver';
// ...
export class AppComponent implements OnInit {
constructor(
private elementRef: ElementRef,
@Inject(PLATFORM_ID) private platformId: Object,
private renderer: Renderer2,
// ...
) {}
ngOnInit() {
if (isPlatformBrowser(this.platformId)) {
// Avoid flash of icon placeholder text during SSR -> client JS handover.
const materialIcons = new FontFaceObserver('Material Icons');
materialIcons.load(null, 10000)
.then(() => this.renderer.addClass(this.elementRef.nativeElement, 'material-icons-loaded'));
// ...existing client-side-only inits.
} else {
// On the server side, add the class to show icons immediately.
this.renderer.addClass(this.elementRef.nativeElement, 'material-icons-loaded');
}
}
}
...
app.component.scss
:
...
app-root:not(.material-icons-loaded) {
display: none;
}
...
Hope this helps anyone else trying to sort this with a Universal / dynamic SSR build.
There's a really straightforward fix for this.
If you self-host your material icons (which I recommend doing with all your fonts)
https://google.github.io/material-design-icons # Setup Method 2. Self hosting
You'll find 2 css snippets of @font-face
and .material-icons
In the @font-face
css add font-display: block
like this
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
font-display: block;
...
This will stop the font from showing anything until it loads.
https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display
There's a really straightforward fix for this.
If you self-host your material icons (which I recommend doing with all your fonts)
https://google.github.io/material-design-icons # Setup Method 2. Self hostingYou'll find 2 css snippets of
@font-face
and.material-icons
In the@font-face
css addfont-display: block
like this@font-face { font-family: 'Material Icons'; font-style: normal; font-weight: 400; font-display: block; ...
This will stop the font from showing anything until it loads.
https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display
Work for me !
Would be nice to allow hide text in case of failure.
There's a really straightforward fix for this.
If you self-host your material icons (which I recommend doing with all your fonts)
https://google.github.io/material-design-icons # Setup Method 2. Self hostingYou'll find 2 css snippets of
@font-face
and.material-icons
In the@font-face
css addfont-display: block
like this@font-face { font-family: 'Material Icons'; font-style: normal; font-weight: 400; font-display: block; ...
This will stop the font from showing anything until it loads.
https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display
Update on two things
font-display
property in the URL itself. So, self-hosting is not mandatory.https://fonts.googleapis.com/icon?family=Material+Icons|Material+Icons+Outlined|Material+Icons+Round&display=block"
font-display: block
gives only a short period of block
after that it works like swap
. which means, if you can throttle the network to slow 3G
, you still see FOUT after some time.
Most helpful comment
There's a really straightforward fix for this.
If you self-host your material icons (which I recommend doing with all your fonts)
https://google.github.io/material-design-icons # Setup Method 2. Self hosting
You'll find 2 css snippets of
@font-face
and.material-icons
In the
@font-face
css addfont-display: block
like thisThis will stop the font from showing anything until it loads.
https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display