2
votes

I am trying to implement buttons that have custom SVG icons using Angular Material 7.2 and Angular 7.2.11. I have not encountered any issues using the standard Material icon font. However custom icons just result in "Error retrieving icon: <svg> tag not found" on the console.

I have been digging through the code in @angular/material/esm2015/icon.js and have found that _fetchUrl() appears to be receiving the SVG, but the body of the HttpResponse object is empty despite the ACTUAL response containing the SVG as verified by Firefox. The headers in the HttpResponse object include a correct content length.

Pasting the URL reported by the HttpResponse object into a browser renders the correct SVG.

_fetchUrl() is using the 'text' responseType option to HttpClient::get(), which should be fine, as far as I can tell, despite the response specifying content-type: image/svg+xml. Just to be sure, I renamed the SVG and gave it a 'txt' extension. The subsequent response had the text/plain content type, but the body of the HttpResponse was still empty.

Removing the Material icon registry from the equation and just attempting a simple get() yields the same issue. The request completes successfully, the SVG is sent, but the HttpResponse has an empty body.

App module:

import { HttpClientModule, ... } from '@angular/common/http';
import {
 ...
 MatIconModule,
 ...
} from '@angular/material';

@NgModule({
 ...
 imports: [
  ...
  HttpClientModule,
  MatIconModule
  ...
 ],
 ...
})

App component:

@Component({
 ...
})
export class AppComponent {
  constructor(private router: Router, private matIconRegistry: MatIconRegistry, private domSanitizer: DomSanitizer, private http: HttpClient, ...)
  {
    this.matIconRegistry.addSvgIcon('test_icon',
      this.domSanitizer.bypassSecurityTrustResourceUrl('/assets/svg/test.svg'));

    http.get('/assets/svg/test.svg', { responseType: 'text' })    .subscribe(svg => console.log(`Response: ${svg}`));
  }
}

Component HTML:

<button mat-icon-button>
  <mat-icon svgIcon="test_icon"></mat-icon>
</button>

Test SVG Files:

<?xml version="1.0" encoding="UTF-8" ?>
<svg width="64" height="64" version="1.1" xmlns="http://www.w3.org/2000/svg">
  <rect x="0" y="0" width="64" height="64" style="fill:rgb(0,0,0,0)" />
</svg>
<svg>Test</svg>

Console Output:

Request to access cookie or storage on “https://fonts.googleapis.com/icon?family=Material+Icons” was blocked because we are blocking all third-party storage access requests and content blocking is enabled. calendar

Angular is running in the development mode. Call enableProdMode() to enable the production mode. core.js:21273

Error retrieving icon: <svg> tag not found icon.js:867:81

Request / Response:

GET /assets/svg/test.svg HTTP/1.1

HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
Accept-Ranges: bytes
Content-Type: image/svg+xml; charset=UTF-8
Content-Length: 219
ETag: W/"d9-8SqOE9sCdf/cpMgr8wAdHVFXV+g"
Date: Tue, 02 Apr 2019 00:36:12 GMT
Connection: keep-alive
<?xml version="1.0" encoding="utf-8" ?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
  <rect x="0" y="0" width="32" height="32" style="fill:rgb(0,0,0,0)" />
</svg>
3

3 Answers

3
votes

Finally tracked the issue down to an HTTP interceptor in the project. It was wiping out the body of the response.

1
votes

You can follow the this code snippet to each add customize material icons in material registery. Icons I am maintaining an enum list of icons and used to load the icon methods. Step-1: First Create a IconService.

import { Injectable } from '@angular/core';
import { MatIconRegistry } from '@angular/material/icon';
import { DomSanitizer } from '@angular/platform-browser';
import { environment } from 'src/environments/environment';
import { Icons } from '../../enums/icons.enum';

@Injectable({
  providedIn: 'root'
})
export class IconService {

  baseURL: string = environment.baseURL;
  constructor(
    private matIconRegistry: MatIconRegistry,
    private domSanitizer: DomSanitizer
  ) { }

  public registerIcons(): void {
    this.loadIcons(Object.values(Icons), this.baseURL + 'assets/svg/icons');
  }

  private loadIcons(iconKeys: string[], iconUrl: string): void {
    iconKeys.forEach(key => {
      debugger;
      this.matIconRegistry.addSvgIcon(key, this.domSanitizer.bypassSecurityTrustResourceUrl(`${iconUrl}/${key}.svg`));
    });
  }

}

Step-2: Inject the service in your app.module.ts under the provider section


@NgModule({
  declarations: [
    AppComponent,
    NavMenuComponent
  ],
  imports: [
    AppRoutes,
    NgbModule,
    BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),
    HttpClientModule,
    FormsModule,
    BrowserAnimationsModule,
    MatMenuModule,
    MatSidenavModule,
    MatIconModule,
    MatTooltipModule,
    ToastrModule.forRoot(),
  ],
  providers: [ ToastrService , CookieService, ErrorService, HttpService, StorageService, IconService
   ],
  bootstrap: [AppComponent]
})
export class AppModule { }

Step-3: Inject the Icon Service in Constructor app.component.ts file and call the registerIcons methods

import { Component } from '@angular/core';
import { IconService } from './core/services/icon/icon.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'

})
export class AppComponent {

  constructor(private iconService: IconService) {
    this.iconService.registerIcons();
  }

}

0
votes
this.domSanitizer.bypassSecurityTrustResourceUrl('./assets/svg/test.svg'));

Do you see the difference?