3 methods to retrieve an asset in Angular CLI
This post was inspired by a question on StackOverflow about loading (content) of an SVG file in Angular. In general, about retrieving the content of any file.
Table of contents
The problem
There was an SVG file in an Angular project that the OP wanted to include in a component:
Surprisingly, nearly every approach worked in StackBlitz, even if it was reported as an error. Unfortunately, they stopped working in a local Angular CLI project. This is one example. It’s not possible to import anything from logo.svg because logo.svg is not a module that exports anything:
error TS2307: Cannot find module '../assets/logo.svg'.
I had three suggestions for solving this problem.
Note: I'll soon be sharing short, practical tips on Angular — a good way to pick up something new if you're interested.
Use it as an image source
Assuming that the SVG is known at the compilation time, it is possible to use the SVG file as any other image (PNG, JPEG) in HTML or CSS. For example, that <div> could have set background-image to the SVG file, or there could be simply an <img> tag with the icon:
Refer to my other article for more examples and details on importing assets.
Apparently, the SVG file in the example was invalid so it wasn’t immediately displayed using these methods. Its <svg> tag was:
while it should contain the namespaces:
After adding the missing information, the logo was shown.
Load dynamically using AJAX
If static loading is not the case, you may load the file dynamically using an AJAX web request. This method involves the use of HttpClient.get method:
The responseType: 'text' is necessary because the default value of responseType is json. A JSON parser trying to load an SVG file will throw an error such as:
"SyntaxError: Unexpected token < in JSON at position 0
at JSON.parse (<anonymous>)
at XMLHttpRequest.onLoad (http://localhost:4200/vendor.js:7932:51)
Naturally, the error
NullInjectorError: No provider for HttpClient!
means that the HttpClientModule was not imported by the module containing your component. This can be quickly fixed by adding:
Require the file
The last method is a bit hacky, as it includes the Node’s require function:
If launched without further configuration, you will see a strange error:
ERROR in src/app/app.component.ts(4,14): error TS2580: Cannot find name 'require'. Do you need to install type definitions for node? Try `npm i @types/node` and then add `node` to the types field in your tsconfig.
Do as suggested – add the @types/node typings to your project by running npm install @types/node and edit tsconfig.app.json to set:
In previous versions of Angular, where you used own Webpack configuration, this solution could work, depending on your configuration. In Angular CLI it should not work.
Angular 7 CLI
If you take a look at the Angular’s Webpack configuration (node_modules\@angular-devkit\build-angular\src\angular-cli-files\models\webpack-configs\common.js), you will find the following lines:
As you can see, the svg extension is handled with file-loader. According to the documentation, it returns URL to the file and copies the file to the output directory. That’s why require('../assets/logo.svg') returned logo.svg – path to the output file.
In order to get the contents of the file, we have to use the raw-loader handler, which allows importing files as a String. Using the syntax from this manual solves our problem:
More about this particular syntax can be found in the Webpack documentation. Briefly, !! disables any configured loaders and ! separates the loader from the path.
Angular 8 CLI
The previous method used in Angular 8 results in a surprising result – the output is a Module:
Module {default: "<svg viewBox="0 0 104 104" version="1.1" xmlns=…EFF" stroke-width="4" fill="#00FF98" /></svg>", __esModule: true, Symbol(Symbol.toStringTag): "Module"}
default: "<svg viewBox="0 0 104 104" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<circle cx="52" cy="52" r="50" stroke="#003EFF" stroke-width="4" fill="#00FF98" />
</svg>
"
Symbol(Symbol.toStringTag): "Module"
This is caused by using newer raw-loader module. You can find the detailed commit here:
- return `module.exports = ${json}`;
+ return `export default ${json}`;
One solution is extracting the default member:
Another, recommended way, is dropping the require support (and Node typings) and using the import keyword:
Thanks you, it really helped me a lot.
You’re welcome 🙂
Thanks
Your thorough analysis and expert knowledge make this article a valuable read for anyone interested in the subject
Excellent post! Your insights on this topic are very valuable and have given me a new perspective. I appreciate the detailed information and thoughtful analysis you provided. Thank you for sharing your knowledge and expertise with us. Looking forward to more of your posts.
Hi! I just finished reading your blog post, and I must say, it was excellent. Your ability to explain complicated concepts in a simple and engaging way is truly remarkable. Thank you for providing such valuable content. I can’t wait to read more from you in the future.