This article discusses the implementation of ngx-google-fonts in detail. To summarize, this is a pure Angular implementation of a component which loads the list of Google fonts, then presents it to the user as a selection-like input to allow them to select any of the fonts. It also handles lazy-loading each font as it is needed. If you are interested in using the project, please visit our getting started page, which covers how to install and get things up and running with this component, then come back here to continue reading the implementation details.
Component Preview
The component below is a preview of the font selector. It takes care of loading the list of fonts, presenting them on click, and dynamically lazy-loading each font as it comes into view. Try it out with a few different selections and observe how it behaves:
This paragraph is rendered with the font selected in the input element above. It will dynamically update as you make different selections.
This paragraph is rendered with the font currently being hovered over in the input element above. It will dynamically update as the hover event changes. If a hover is no longer happening, this will reset to some default value.
Working with the Project
If you would like to get hands on with the project itself, in terms of exploring the code, running it locally, or even making code changes, our code is open source and available on GitHub. To get and run the project:
- Navigate to our project on GitHub.
- Copy the repo url under Code and clone the repo to your computer.
- Install the dependencies by running npm install in the terminal.
- Run ng serve to serve a runner-app which includes the component and instructions for using it.
From there, you will be able to make updates to the code and have the browser page automatically reload.
Storing and Indexing Fonts
In terms of font retrieval, storage, and indexing, this component must:
- Load the list of Google fonts and store it, combined with the list of web safe fonts.
- Present an interface of a list of all Google fonts and web safe fonts in alphabetical order.
- Allow the user to filter the list of fonts based on some prefix, such as the first few letters of a font name.
With these requirements, the data structure we select must support fast indexing and prefix lookup. Performance for adding fonts is not as important, as this will only be done once. With these points in mind, we've selected a trie data structure. If you are not familiar with tries, or would like a referesher, please visit our introduction to tries. To keep the list in order, we are using an AVL tree. If you're not familiar with this structure, or would like a refresher, please visit our introduction to AVL trees. Google returns its list of fonts in alphabetical order by default, but we also need to combine with the list of web safe fonts. For this purpose, we are using the AVL tree as preliminary storage for the fonts. First, the web safe fonts are added, then the list of Google fonts are added. As each font is added, it is placed in its correct sorted position. This approach is faster than assembling the entire list, then sorting that list. Once the list is complete, each item is added to the trie, thus guaranteeing the words of the trie is also in sorted order. Since the entire trie is in order, any prefix lookup will also be in sorted order.
To summarize the flow:
- The API key is provided to the internal GoogleFontService.
- The service creates an AVL tree which contains a list of web safe fonts. These fonts are stored with the same type definition iGoogleFont as the Google fonts themselves.
- An http call is made to Google Fonts api, and the list of fonts is returned.
- Each font is added to the AVL search tree one by one, and as each is added, it is put in the appropriate sorted position.
- Once sorted, the AVL tree is iterated over once. For each item, we add the entry to the trie. Each word is stored as a lowercase word, and each word has the associated iGoogleFont data stored with it.
Lazy Loading Fonts
The component initially loads the full list of fonts before rendering, but it does not initially load any of those fonts themselves, as this operation would be too expensive to do all at once. Instead, it lazy-loads each font only when it is required. A font needs to be loaded when:
- The font is set as the value of the component. This covers the scenario where the font is initially set and needs to be rendered before the overlay component is initially opened.
- The font comes into view in the font selector overlay component.
The GoogleFontService has a method tryLoadFont which will attempt to load a font if it is a Google font, or return a true status if it is a web safe font (and thus already loaded). For the first scenario above, we can do an initial call to load the font if the value is already set. For the section scenario, we are using JavaScript's IntersectionObserver functionality. For each preview font element, we will create an intersection observer object which will invoke some callback when the element scrolls into view, or invoke immediately if the element is already in view. This code is concice and can be summarized as follows:
Google Font Data Type
This interface contains information about each Google font as it appears in Google's api response. If the user selects a web safe font in the input element, this data type will stil be emitted, but anything other than family will be an empty string, empty array, or empty object, except for category and kind, which will appear as "Web Safe Font". In most cases, these additional fields will not be needed.
Project Dependencies
This project uses a few lightweight dependencies:
Dependency | Description |
---|---|
webfontloader | This is used to help lazy-load each individual Google font. It is not used for loading the initial list of Google Fonts. |
@byte-this/funscript | This is used to add a decorator to the initial fonts list load method so only one call happens at once. |
@byte-this/collections | This holds the implementation for tries and AVL trees which this project uses. |
Future Plans and Considerations
This component currently only supports Google Fonts and web safe fonts, but the implementation approach is almost generic enough to be reusable across any number of different font APIs. When the use case arises, we can add a level of abstraction at the service level to handle loading fonts from different sources and merging the responses to the same data type. If you'd like to make a contribution to this project on this, or any other apsect of the project, pull requests are welcome!