In Chapter 4, Automated Testing, CI, and Release to Production, we mentioned the need to deliver a high-quality application. Currently, the app has a terrible look and feel to it that is only fit for a website created in the late 1990s. The first impression a user or a client gets about your product or your work is very important, so we must be able to create a great-looking application that also delivers a great user experience across mobile and desktop browsers.
As full-stack developers, it is difficult to focus on the polish of your application. This often gets worse as the feature set of an application rapidly grows. It is no fun to write great modular code backing your views, but then revert to CSS hacks and inline styles in a rush to improve the look and feel of your application.
Developed in close coordination with Angular, Angular Material is amazing. If you learn how to leverage Angular Material effectively, the features you create will look and work great from the get-go, whether you're working on small or large applications.
Angular Material will make you a far more effective web developer because it ships with a wide variety of user controls that you can leverage, and you won't have to worry about browser compatibility. As an added bonus, writing custom CSS will become a rarity.
While this chapter covers how to create an attractive user interface (UI) relying on Angular Material for a decent user experience (UX) out of the box, it is also important to know what not to do. There's a great website called User Interface, which demonstrates UI/UX worst practices, at https://userinyerface.com.
In this chapter, you are going to learn about the following:
The most up-to-date versions of the sample code for the book are on GitHub at the following repository linked. The repository contains the final and completed state of the code. You can verify your progress at the end of this chapter by looking for the end-of-chapter snapshot of code under the projects
folder.
For Chapter 5:
npm install
on the root folder to install dependenciesprojects/ch5
npx ng serve ch5
npx ng test ch5 --watch=false
npx ng e2e ch5
npx ng build ch5 --prod
Note that the dist/ch5
folder at the root of the repository will contain the compiled result.
Beware that the source code in the book or on GitHub may not always match the code generated by Angular CLI. There may also be slight differences in implementation between the code in the book and what's on GitHub because the ecosystem is ever-evolving. It is natural for the sample code to change over time. Also on GitHub, expect to find corrections, fixes to support newer versions of libraries, or side-by-side implementations of multiple techniques for you to observe. You are only expected to implement the ideal solution recommended in the book. If you find errors or have questions, please create an issue or submit a pull request on GitHub for the benefit of all readers.
Let's begin by understanding what makes Angular Material an excellent choice as a UI/UX library.
The goal of an Angular Material project is to provide a collection of useful and standard-setting high-quality UI components. The library implements Google's Material Design specification, which is pervasive in Google's mobile apps, web properties, and the Android operating system. Material Design has a particular digital and boxy look and feel, but it is not just another CSS library like Bootstrap. Consider the login experience coded using Bootstrap here:
Figure 5.1: Bootstrap login experience
Note that input fields and their labels are on separate lines, the checkbox is a small target to hit, the error messages are displayed as an ephemeral toast notification, and the Submit button just sits in the corner. Now, consider the following Angular Material sample:
Figure 5.2: Angular Material login experience
The input fields and their labels are initially combined, grabbing the user's attention in a compact form factor. The checkbox is touch-friendly and the Submit button stretches to take up the available space for a more responsive UX by default. Once a user clicks on a field, the label tucks away to the top-left corner of the input field, as shown:
Figure 5.3: Angular Material animations and error
In addition, the validation error messages are shown inline, combined with a color change in the label, keeping the user's attention on the input field.
Material Design helps you design a modular UI with your own branding and styling, while also defining animations that allow the user to have a better UX when using your application. The human brain subconsciously keeps track of objects and their locations. Any kind of animation that aids in transitions or reactions to changes that result from human input results in reduced cognitive load on the user, therefore allowing the user to focus on processing the content instead of trying to figure out the quirks of your particular app.
A combination of modular UI design and fluid motion creates a great UX. Look at how Angular Material implements a simple button:
Figure 5.4: Angular Material button animation
In the preceding screenshot, note how the click animation on the button originates from the actual location that the user has clicked on. However subtle, this creates a continuity motion, resulting in an appropriate onscreen reaction to a user's action. This particular effect becomes more pronounced when the button is used on a mobile device, leading to an even more natural human-computer interaction. Most users can't articulate what makes an intuitive UX actually intuitive, and these subtle yet crucial cues in design and experience allow you to make tremendous progress in designing such an experience for your users.
Angular Material also aims to become the reference implementation for high-quality UI components for Angular. If you intend to develop your own custom controls, the source code for Angular Material should be your first and foremost resource. The term high quality is used often, and it's really important to quantify what that means. The Angular Material team puts it well on their website:
What do we mean by "high quality"?
Internationalized and accessible so that all users can use them. Straightforward APIs that don't confuse developers and behave as expected across a wide variety of use cases without bugs. Behavior is well tested with both unit and integration tests. Customizable within the bounds of the Material Design specification. Performance cost is minimized. Code is clean and well documented to serve as an example for Angular devs. Browser and screen reader support.
Angular Material supports the most recent two versions of all major browsers: Chrome (including Android), Firefox, Safari (including iOS), and IE11/Edge.
Building web applications, especially ones that are also mobile-compatible, is really difficult. There are a lot of nuances that you must be aware of. Angular Material abstracts away these nuances, including supporting all major browsers, so that you can focus on creating your application. Angular Material is no fad, and it's not to be taken lightly. If used correctly, you can greatly increase your productivity and the perceived quality of your work.
It won't always be possible to use Angular Material in your projects. I would recommend either PrimeNG, found at https://www.primefaces.org/primeng, or Clarity, found at https://vmware.github.io/clarity, as component toolkits that can satisfy most, if not all, of your user-control needs. The one thing to avoid here would be to pull dozens of user controls from different sources and end up with a hodgepodge library with hundreds of quirks and bugs to learn, maintain, or work around.
One of the most significant challenges when working with UI components is the amount of bulk they can add to your app's bundle size. Next, let's see how using a cohesive component library can help keep the performance of your app in tip-top shape and configure Angular Material for your app.
Angular Material is configured by default to optimize the package size of your final deliverable. In Angular JS and Angular Material 1.x, the entire dependent library would be loaded. However, now with Angular Material, we are able to specify only the components that we intend to use, resulting in dramatic performance improvements.
In the following table, you can see improvement of the performance characteristics of a typical Angular 1.x + Angular Material 1.x versus an Angular 6 + Material 6 application over a fiber connection with high speed and low latency:
Fiber Network | Angular 6 + Material 6 | Angular 1.5 + Material 1.1.5 | % Diff |
First paint (DOMContentLoaded) * |
0.61 s |
1.69 s** |
~2.8x faster |
JS bundle size* |
113 KB |
1,425 KB |
12.6x smaller |
*Images and other media content have not been included in the results for a fair comparison
**Average value: Lower quality infrastructure causes a wide range of render times from 0.9s to 2.5s
Under the ideal conditions of a high-speed and low-latency connection, Angular 6 + Material 6 apps load under a second. However, when we switch over to a more common moderate-speed and high-latency fast 3G mobile network, the differences become more pronounced, as in the following table:
Fast 3G Mobile Network | Angular 6 + Material 6 | Angular 1.5 + Material 1.1.5 | % Diff |
First paint* |
1.94 s |
11.02 s |
5.7x faster |
JS bundle size* |
113 KB |
1,425 KB |
12.6x smaller |
*Images or other media content have not been included in the results for a fair comparison
Even though the size differences of the apps remain consistent, you can see that the additional latency introduced by a mobile network results in a dramatic slowdown of the legacy Angular application to an unacceptable level.
Adding all components to Material will result in about ~1.3 MB of additional payload that will need to be delivered to the user. As you can see from the earlier comparison, this must be avoided at all costs. To deliver the smallest app possible, which is crucial in mobile- and sales-related scenarios, where every 100 ms of load time has an impact on user retention, you may load and include modules individually. Webpack's tree-shaking process will divide modules into different files, trimming down the initial download size.
As a real-world example, when you're done building the final version of the LocalCast Weather app, your app's bundle size will come in at around 800 KB, with a first paint happening in just over 2 seconds on a fast 3G connection with Angular 9 + Material 9. A fully-featured multi-page application that leverages lazy loading only loads around ~300 KB of dependencies, while maintaining a sub 2 second first paint.
Note that the sample app contains example code that can be trimmed away to make the app even smaller. This is a testament to how the Angular ecosystem can deliver a rich and optimized UX.
Next, let's set up Angular Material.
There are two ways you can configure Angular Material for your Angular app:
Let's get started with the task and improve the UX of the weather app with Angular Material. Let's move the Improve the UX of the app task to In Progress on our GitHub project. Here, you can see the status of my Kanban board:
Figure 5.5: GitHub project Kanban board
Since Angular 6, you can automatically add Angular Material to your project, saving a lot of time in the process:
add
command, as shown:
$ npx ng add @angular/material
indigo-pink
Set up global Angular Material typography styles?
" enter "no
"Set up browser animations for Angular Material?
" enter "yes
"Installing packages for tooling via npm.
Installed packages for tooling via npm.
? Choose a prebuilt theme name, or "custom" for a custom theme: Indigo/Pink [ Preview: https://material.angular.io?theme=indigo-pink ]
? Set up global Angular Material typography styles? No
? Set up browser animations for Angular Material? Yes
UPDATE package.json (1348 bytes)
√ Packages installed successfully.
UPDATE src/app/app.module.ts (423 bytes)
UPDATE angular.json (3740 bytes)
UPDATE src/index.html (487 bytes)
UPDATE src/styles.css (181 bytes)
Note that the index.html
file has been modified to add the icons library and the default font, as follows:
src/index.html
<head>
...
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
The angular.json
file has been updated to set up the default theme:
angular.json
...
"styles": [
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
"src/styles.css"
],
...
styles.css
has been updated with the default global CSS styles:
src/styles.css
html,
body {
height: 100%;
}
body {
margin: 0;
font-family: Roboto, "Helvetica Neue", sans-serif;
}
Also note that app.module.ts
has been updated to import BrowserAnimationsModule
, as shown:
src/app/app.module.ts
import { BrowserAnimationsModule } from '@angular/platform- browser/animations';
@NgModule({
declarations: [
AppComponent
],
imports: [
...
BrowserAnimationsModule
],
$ npm start
With that, you're done. Your app should be configured with Angular Material. You can now skip over to the Importing modules section to see how you can import Material modules in a robust manner.
I strongly recommend skimming over all the manual installation and configuration steps. The more you know!
It is still important to understand all the various components that make up Angular Material or maybe you dislike automatic things; in the next sections, we will go over the manual installation and configuration steps.
We will begin by installing all the required libraries. As of Angular 5, the major version of Angular Material should match the version of your Angular installation and with Angular 6, the versions should be synced:
npm install @angular/material @angular/cdk
package.json
versions:
package.json
"dependencies": {
"@angular/cdk": "9.0.0",
"@angular/material": "9.0.0",
...
In this case, all libraries have the same major and minor versions. If your major and minor versions don't match, you can rerun the npm install
command to install a specific version or choose to upgrade your version of Angular by appending the server version of the package to the install
command:
$ npm install @angular/material@9.0.0 @angular/cdk@9.0.0
If you are working on a Bash-like shell, you can save some typing by using the bracket syntax to avoid having to repeat portions of the command, in the form of npm install @angular/{material,cdk}@9.0.0
.
If you need to update your version of Angular, refer to the Updating Angular section in Appendix C, Keeping Angular and Tools Evergreen. You can find this appendix online from https://static.packt-cdn.com/downloads/9781838648800_Appendix_C_Keeping_Angular_and_Tools_Evergreen.pdf or at https://expertlysimple.io/stay-evergreen.
Let's look at what exactly we are installing:
@angular/material
is the official Material library.@angular/cdk
is a peer dependency, not something you use directly unless you intend to build your own components.@angular/animations
enables some of the animations for some Material modules. It can be omitted to keep the app size minimal. You may use NoopAnimationsModule
to disable animations in the modules that require this dependency. As a result, you will lose some of the UX benefits of Angular Material.Now that the dependencies are installed, let's configure Angular Material in our Angular app. Note that if you used ng add @angular/material
to install Angular Material, some of this work will be done for you.
We will start by creating a separate module file to house all of our Material module imports:
material.module.ts
:
$ npx ng g m material --flat -m app
Note the use of the --flat
flag, which indicates that an additional directory shouldn't be created for material.module.ts
. Also, note that -m
, an alias for --module
, is specified so that our new module is automatically imported into app.module.ts
.
material.module.ts
and remove CommonModule
:
src/app/material.module.ts
import { NgModule } from '@angular/core'
@NgModule({
imports: [],
declarations: [],
})
export class MaterialModule {}
app.module.ts
:
src/app/app.module.ts
import { MaterialModule } from './material.module'
...
@NgModule({
...
imports: [..., MaterialModule],
}
src/app/app.module.ts
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
@NgModule({
...
imports: [..., MaterialModule, BrowserAnimationsModule],
}
material.module.ts
to import and export basic components for MatButton
, MatToolbar
, and MatIcon
:
src/app/material.module.ts
import { NgModule } from '@angular/core'
import { MatButtonModule } from '@angular/material/button'
import { MatIconModule } from '@angular/material/icon'
import { MatToolbarModule } from '@angular/material/toolbar'
@NgModule({
imports: [
MatButtonModule, MatToolbarModule, MatIconModule
],
exports: [
MatButtonModule, MatToolbarModule, MatIconModule
],
})
export class MaterialModule {}
The imports
and exports
arrays can sometimes become long and duplicative. If you miss an element in one of the arrays, you could be chasing bugs for hours. Consider implementing a single array as a constant that you can assign to imports
and exports
properties for a more reliable configuration. Thanks to Brendon Caulkins for the tip.
src/app/material.module.ts
...
const modules =
[MatButtonModule, MatToolbarModule, MatIconModule]
@NgModule({
declarations: [],
imports: modules,
exports: modules,
})
export class MaterialModule {}
Material is now imported into the app; let's now configure a theme and add the necessary CSS to our app.
A base theme is necessary in order to use Material components. We already selected a default theme when installing Angular Material. We can define or change the default theme in angular.json
:
angular.json
...
"styles": [
{
"input":
"node_modules/@angular/material/prebuilt-themes/indigo-pink.css"
},
"src/styles.css"
],
...
Choose a new option from here:
deeppurple-amber.css
indigo-pink.css
pink-bluegrey.css
purple-green.css
Update angular.json
to use the new Material theme.
You may create your own themes as well, which is covered in the Custom themes section of this chapter. For more information, visit https://material.angular.io/guide/theming.
Note that any CSS implemented in styles.css
will be globally available throughout the application. That said, do not include view-specific CSS in this file. Every component has its own CSS file for this purpose.
You can get access to a good default set of iconography by adding the Material Icon web font to your application. Clocking in at 48 KB in size, this is a very lightweight library.
For icon support, import the font in index.html
:
src/index.html
<head>
...
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
Discover and search through the icons at https://material.io/resources/icons.
For a richer set of icons, check out MaterialDesignIcons.com. This icon set contains the base set of Material icons, plus a rich set of third-party icons that contains useful imagery from social media sites for a rich set of actions that cover a lot of ground. This font is 118 KB in size.
Our UI/UX library for Angular Material is now configured. We also need a layout library that can make life easier when placing components on the page.
Next, let's learn about different layout techniques, from Bootstrap to Flexbox CSS, and why Angular Flex Layout is a great tool to manage your layout. After configuring Angular Flex Layout for our app, we will be ready to implement Material UI components in our app.
Before you can make effective use of Material, you must be aware of its layout engine. If you have been doing web development for a while, you may have encountered Bootstrap's 12-column layout system. I find it enormously irritating, since it falls foul of a mathematical barrier in my brain, which is wired to divvy things up as parts of 100%. Bootstrap also demands strict adherence to a div
column and row hierarchy that must be precisely managed from your top-level HTML to the bottom. This can make for a very frustrating development experience.
In the following screenshot, you can see how Bootstrap's 12-column scheme looks:
Figure 5.6: Bootstrap's 12-column layout scheme
Bootstrap's custom grid-layout system was revolutionary for its time, but then CSS3 Flexbox arrived on the scene. In combination with media queries, these two technologies allow for the creation of responsive UIs. However, it is very laborious to leverage these technologies effectively. As of Angular v4.1, the Angular team introduced its Flex Layout system that just works.
The Angular Flex Layout documentation on GitHub aptly explains the following:
Angular Flex Layout provides a sophisticated layout API using FlexBox CSS and mediaQuery. This module provides Angular (v4.1 and higher) developers with component layout features using a custom Layout API, mediaQuery observables, and injected DOM flexbox-2016 CSS stylings.
Angular's excellent implementation makes it very easy to use Flexbox. As the documentation further explains:
The Layout engine intelligently automates the process of applying appropriate FlexBox CSS to browser view hierarchies. This automation also addresses many of the complexities and workarounds encountered with the traditional, manual, CSS-only application of Flexbox CSS.
The library is highly capable and can accommodate any kind of grid layout you can imagine, including integration with all CSS features that you would expect, such as the calc()
function. In the next illustration, you can see how columns can be described using CSS Flexbox:
Figure 5.7: Angular Flex Layout scheme
The great news is that Angular Flex Layout is in no way coupled with Angular Material and can be used independently of it. This is a very important decoupling that resolves one of the major pain points of using AngularJS with Material v1, where version updates to Material would often result in bugs in layout.
For more details, check out https://github.com/angular/flex-layout/wiki.
You will notice that @angular/flex-layout
is installed with a beta tag. This has been the status quo for this library for a very long time. It hasn't been possible for the library to cover every edge case going back to Internet Explorer 11, which prevents it from exiting beta. However, in evergreen browsers, I've found the behavior of the library to reliable and consistent. Further, CSS Grid is poised to supersede CSS Flexbox and as a result, the underlying technology that this library uses may change. My wish is that this library acts as an abstraction layer to the layout engine underneath.
All UIs you design and build should be mobile-first UIs. This is not just to serve mobile phone browsers, but also cases where a laptop user may use your application in a window side by side with another one. There are many nuances to getting mobile-first design right.
The following is the Mozilla Holy Grail Layout, which demonstrates "the ability to dynamically change the layout for different screen resolutions" while optimizing the display content for mobile devices.
You can read more about basic concepts of Flexbox at https://mzl.la/2vvxj25.
This is a representation of how the UI looks on a large screen:
Figure 5.8: Mozilla Holy Grail Layout on a large screen
The same layout is represented on a small screen as follows:
Figure 5.9: Mozilla Holy Grail Layout on a small screen
Mozilla's reference implementation takes 85 lines of code to accomplish this kind of responsive UI. Angular Flex Layout accomplishes the same task with only half the code.
Let's install and add Angular Flex Layout to our project:
npm i @angular/flex-layout
To get around peer dependency errors, execute npm i @angular/flex-layout@next
or npm i @angular/flex-layout --force
, as mentioned in Appendix C, Keeping Angular and Tools Evergreen. You can find this appendix online from https://static.packt-cdn.com/downloads/9781838648800_Appendix_C_Keeping_Angular_and_Tools_Evergreen.pdf or at https://expertlysimple.io/stay-evergreen.
app.module.ts
, as shown:
src/app.module.ts
import { FlexLayoutModule } from '@angular/flex-layout'
imports: [..., FlexLayoutModule ],
With Flex Layout installed, let's cover the basics of how the library works.
Bootstrap and CSS Flexbox are different beasts than Angular Flex Layout. If you learn Angular Flex Layout, you will find yourself using a lot less layout code, because Angular Material automatically does the right thing most of the time, but you'll be in for a disappointment once you realize how much more code you have to write to get things working once you leave the protective cocoon of Angular Flex Layout. However, your skills still translate over since the concepts are mostly the same.
Let's review the Flex Layout APIs in the following sections.
If you're new to CSS or even Flexbox, some of the abbreviations used may not make sense. I recommend that you experiment with the live demo app provided in the documentation to get a better sense of the capabilities of the library at a more intuitive level. For more information and a link to the live demo visit https://github.com/angular/flex-layout/wiki/Declarative-API-Overview.
These directives can be used on DOM containers such as <div>
or <span>
to manipulate their layout direction, alignment, or gaps in between elements.
Consider the following example:
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="15px">...</div>
The div
is laid out as a row, so multiple div
s would be rendered on top of each other, versus a column layout where they would render next to each other.
The div
is aligned to horizontally left-aligned and vertically centered within its parent container.
The div
has a 15-px gap between its surrounding elements.
Consider the following diagram to map fxLayout
terminology spatially:
Figure 5.10: Spatial mapping of Angular Flex Layout terminology
The full list of options is presented in the following table:
HTML API | Allowed values |
|
Use: row | column | row-reverse | column-reverse |
|
|
|
% | px | vw | vh |
These directives influence how DOM elements act within their containers.
Consider the following example:
<div fxLayout="column">
<input fxFlex />
</div>
The input
element will grow to fill all available space that the parent div
provides. If fxFlex
was set to fxFlex="50%"
, it would fill only half of the available space. In this case the fxFlexAlign
property could be used to left-, right-, or center-align the element within the div
.
The full list of options is presented in the following table:
HTML API | Allowed values |
|
"" | px | % | vw | vh | |
|
|
|
% | px | vw | vh |
|
start | baseline | center | end |
|
none |
The following directives can be applied to any HTML element to show, hide, or change the look and feel of these elements.
Consider the following example:
<div fxShow fxHide.lt-sm></div>
fxShow
, set to true
by default, will show the div
element. Unless the lt-sm
condition becomes true
, which occurs when the browser window shrinks below the threshold of small. Small is defined as a pixel value of 468 px. So, if the width of the browser window shrinks to 467 px or less, fxHide
would hide the div
element.
The full list of options is presented in the following table:
HTML API | Allowed values |
|
TRUE | FALSE | 0 | "" |
|
TRUE | FALSE | 0 | "" |
|
|
|
|
This section covers the basics of Static Layouts. You can read more about the Static APIs at https://github.com/angular/flex-layout/wiki/Declarative-API-Overview. We'll cover the Responsive APIs in Chapter 11, Recipes – Reusability, Routing, and Caching. You can read more about the Responsive APIs at https://github.com/angular/flex-layout/wiki/Responsive-API.
Now that our layout engine is configured and you have a rudimentary understanding of how it works, we can start building the screens for our app.
Now that we have all the various dependencies installed, we can start modifying our Angular app to add Material components. We will add a toolbar and a Material Design card element, and cover accessibility and typography concerns alongside basic layout techniques.
Since Angular 6 and the introduction of schematics, libraries like Material can provide their own code generators. At the time of publication, Angular Material ships with three rudimentary generators to create Angular components with a side navigation, a dashboard layout, or a data table. You can read more about generator schematics at https://material.angular.io/guide/schematics.
For example, you can create a side navigation layout by executing the following command:
$ ng generate @angular/material:material-nav --name=side-nav
CREATE src/app/side-nav/side-nav.component.css (110 bytes) CREATE src/app/side-nav/side-nav.component.html (945 bytes) CREATE src/app/side-nav/side-nav.component.spec.ts (619 bytes) CREATE src/app/side-nav/side-nav.component.ts (489 bytes) UPDATE src/app/app.module.ts (882 bytes)
This command updates app.module.ts
, directly importing Material modules into that file, breaking my suggested material.module.ts
pattern from earlier. Further, a new SideNavComponent
is added to the app as a separate component, but as mentioned in the Side navigation section in Chapter 8, Designing Authentication and Authorization, such a navigation experience needs to be implemented at the very root of your application.
In short, Angular Material Schematics makes it a lot less cumbersome to add various Material modules and components to your Angular app; however, as provided, these schematics are not suitable for creating a flexible, scalable, and well-architected code base, which is the goal pursued by this book.
For the time being, I would recommend using these schematics for rapid prototyping or experimentation purposes.
Now, let's start manually adding some components to our LocalCast Weather app.
Before we start making further changes to app.component.ts
, let's switch the component to use inline templates and inline styles, so we don't have to switch back and forth between files for a relatively simple component:
app.component.ts
to use an inline template. Cut and paste the contents of app.component.html
to app.component.ts
and remove the styleUrls
property as shown below:
src/app/app.component.ts
import { Component } from '@angular/core'
@Component({
selector: 'app-root',
template: `
<div style="text-align:center">
<h1>
LocalCast Weather
</h1>
<div>Your city, your forecast, right now!</div>
<h2>Current Weather</h2>
<app-current-weather></app-current-weather>
</div>
`,
})
export class AppComponent {}
app.component.html
and app.component.css
.h1
tag in app.component.ts
:
src/app/app.component.ts
<h1>
LocalCast Weather
</h1>
h1
tag with mat-toolbar
:
src/app/app.component.ts
<mat-toolbar>
<span>LocalCast Weather</span>
</mat-toolbar>
mat-toolbar
with a more attention-grabbing color:
src/app/app.component.ts
<mat-toolbar color="primary">
Note that your app will fail to compile if you didn't import MatToolbarModule
as instructed in the earlier section Importing modules.
Note that Material adds the following style as a global style:
src/styles.css
body {
margin: 0;
}
Having a 0
margin provides a native app feeling, where the toolbar touches the edges of the browser. This works well both on large- and small-screen formats. When you place clickable elements such as a hamburger menu or a help button on the far-left or far-right side of the toolbar, you'll avoid the potential that the user will click on empty space. This is why Material buttons actually have a larger hit-area than visually represented. This makes a big difference in crafting frustration-free UXs.
Similarly, if you were building an information-dense application, note that your content would go all the way to the edges of the application, making your content more difficult to read, which is not a desirable outcome. In these cases, you should wrap your content area in a div
and apply the appropriate margins using CSS, as shown here:
example
.content-margin {
margin-left: 8px;
margin-right: 8px;
}
In the next screenshot, you can see the edge-to-edge toolbar with the primary color applied to it:
Figure 5.11: LocalCast Weather with improved toolbar
Now we have the toolbar configured, let's move on to making a container for the weather information.
Material cards are a great container to represent the current weather information. The card element is surrounded by a drop-shadow that delineates the content from its surroundings:
MatCardModule
in material.module
:
src/app/material.module.ts
import { MatCardModule } from '@angular/material/card'
...
const modules = […, MatCardModule]
<app-current-weather>
with <mat-card>
:
src/app/app.component.ts
...
template: `
...
<div style="text-align:center">
<mat-toolbar color="primary">
<span>LocalCast Weather</span>
</mat-toolbar>
<div>Your city, your forecast, right now!</div>
<mat-card>
<h2>Current Weather</h2>
<app-current-weather></app-current-weather>
</mat-card>
</div>
...
`,
...
Figure 5.12: LocalCast Weather with indistinguishable card element
To lay out the screen better, we need to switch to the Flex Layout engine. We'll start by removing the training wheels from the component template.
style="text-align:center"
from the outermost <div>
element.<mat-card>
with the following HTML, where the contents of <mat-card>
replaces the ellipses in the middle of the code:To center an element in a page, we need to create a row, assign a width to the center element, and create two additional columns on either side that can flex to take the empty space, such as this:
src/app/app.component.ts
...
<div fxLayout="row">
<div fxFlex></div>
<div fxFlex="300px">
...
</div>
<div fxFlex></div>
</div>
...
mat-card
element is properly centered, as follows:Figure 5.13: LocalCast Weather with centered card
Reading through the card documentation and looking through the examples on Material's documentation site at https://material.angular.io/components/card/overview, you'll note that mat-card
provides elements to house the title and content. We will implement this in the upcoming sections.
On material.angular.io
, you can view the source code of any example by clicking on the brackets icons or launch a working example in StackBlitz.io by clicking on the arrow icon.
Now, let's implement the title and content elements of mat-card
using mat-card-header
and mat-card-content
, as shown:
src/app/app.component.ts
...
<mat-toolbar color="primary">
<span>LocalCast Weather</span>
</mat-toolbar>
<div>Your city, your forecast, right now!</div>
<div fxLayout="row">
<div fxFlex></div>
<div fxFlex="300px">
<mat-card>
<mat-card-header>
<mat-card-title>Current Weather</mat-card-title>
</mat-card-header>
<mat-card-content>
<app-current-weather></app-current-weather>
</mat-card-content>
</mat-card>
</div>
<div fxFlex></div>
</div>
...
All Material elements have native support for the Flex Layout engine. This allows us to optimize our HTML and merge <div fxFlex="300px">
with <mat-card>
and simplify the code:
src/app/app.component.ts
...
<div fxLayout="row">
<div fxFlex></div>
<mat-card fxFlex="300px">
...
</mat-card>
<div fxFlex></div>
</div>
...
This has tremendous positive implications for maintainability in complicated UIs.
Don't forget: With Material, less is always more.
After we apply mat-card-header
and mat-card-content
, you can see this result:
Figure 5.14: LocalCast Weather card with title and content
Note that fonts within the card now match Material's Roboto font. However, Current Weather is no longer as attention-grabbing as it was before. If you add back in the h2
tag inside mat-card-title
, Current Weather will visually look bigger; however, the font won't match the rest of your application. To fix this issue, you must understand Material's typography features.
Material's documentation aptly puts it as follows:
Typography is a way of arranging type to make text legible, readable, and appealing when displayed.
Material offers a different level of typography that has different font-size
, line-height
, and font-weight
characteristics that you can apply to any HTML element, not just the components provided out of the box.
In the following table are CSS classes that you can use to apply Material's typography.
Consider the following example:
<div class="mat-display-4">Hello, Material world!</div>
The display-4
typography is applied to the div
by prepending it with "mat-
".
See the following table for a full list of typographical styles:
Class Name | Usage |
|
Large, one-off headers, usually at the top of the page (for example, a hero header) |
|
Section heading corresponding to the |
|
Section heading corresponding to the |
|
Section heading corresponding to the |
|
Section heading corresponding to the |
|
Base body text |
|
Bolder body text |
|
Smaller body and hint text |
|
Buttons and anchors |
You can read more about Material typography at https://material.angular.io/guide/typography.
There are multiple ways to apply typography. One way is to leverage the mat-typography
class and use the corresponding HTML tag, such as <h2>
:
example
<mat-card-header class="mat-typography">
<mat-card-title><h2>Current Weather</h2></mat-card-title>
</mat-card-header>
Another way is to apply the specific typography directly on an element, as in class="mat-title"
:
example
<mat-card-title>
<div class="mat-title">Current Weather</div>
</mat-card-title>
Note that class="mat-title"
can be applied to div
, span
, or an h2
with the same results.
As a rule of thumb, it is usually a better idea to implement the more specific and localized option, which is the second implementation here.
As we implement Material typography in the upcoming sections, we need to ensure the card title stands out from rest of the elements on the screen. In this context, I prefer the look of the mat-headline
typography to achieve this goal, so your implementation should look like:
src/app/app.component.ts
<mat-card-title>
<div class="mat-headline">Current Weather</div>
</mat-card-title>
Next, let's see how we can align the other elements on the screen.
We can center the tagline of the application using fxLayoutAlign
and give it a subdued look using the mat-caption
typography:
div
containing the tagline using fxLayoutAlign
:
src/app/app.component.ts
<div fxLayoutAlign="center">
<div>
Your city, your forecast, right now!
</div>
</div>
mat-caption
typography to the tagline:
src/app/app.component.ts
<div class="mat-caption">
Your city, your forecast, right now!
</div>
Figure 5.15: LocalCast Weather with centered tagline
Next, we need to work on laying out the elements to match the design.
There's still more work to do to make the UI look like the design. Observe the following design of the Current Weather card:
Figure 5.16: Lo-fi design of Current Weather
To design the layout, we'll leverage Angular Flex.
You'll be editing current-weather.component.html
, which uses the <div>
and <span>
tags to establish elements that live on separate lines or on the same line, respectively. With the switch over to Angular Flex, we need to switch all elements to <div>
and specify rows and columns using fxLayout
.
We need to start by implementing the rough scaffolding. Consider the current state of the template:
src/app/current-weather/current-weather.component.html
1 <div *ngIf="!current">
2 no data
3 </div>
4 <div *ngIf="current">
5 <div>
6 <span>{{current.city}}, {{current.country}}</span>
7 <span>{{current.date | date:'fullDate'}}</span>
8 </div>
9 <div>
10 <img [src]='current.image'>
11 <span>{{current.temperature | number:'1.0-0'}}˚F</span>
12 </div>
13 <div>
14 {{current.description}}
15 </div>
16 </div>
Let's go through the file step by step and update it. First let's make the structural changes to support Flex Layout:
<span>
elements to <div>
elements.<img>
element with a <div>
element.fxLayout="row"
property to the outer <div>
element that has multiple child elements.Next, apply the fxFlex
attribute to div
elements to determine how much horizontal space elements should take:
fxFlex="66%"
to the <div>
element.fxFlex
to the <div>
element to ensure that it fills up the rest of the horizontal space.fxFlex="66%"
to the new <div>
element, surrounding the <img>
element.fxFlex
to the <div>
element.The final state of the template should look like this:
src/app/current-weather/current-weather.component.html
5 <div fxLayout="row">
6 <div fxFlex="66%">{{current.city}}, ...</div>
7 <div fxFlex>{{current.date | date:'fullDate'}}</div>
8 </div>
9 <div fxLayout="row">
10 <div fxFlex="66%"><img [src]='current.image'></div>
11 <div fxFlex>{{current.temperature | number:'1.0-0'}}˚F</div>
12 </div>
13 <div>
14 {{current.description}}
15 </div>
You can be more verbose in adding Angular Flex attributes; however, the more code you write, the more you'll need to maintain, making future changes more difficult. For example, on line 13, the <div>
element doesn't need fxLayout="row"
, since a <div>
implicitly gets a new line. Similarly, on lines 7 and 11, the right-hand column doesn't need an explicit fxFlex
attribute, since the left-hand element automatically squeezes it. However, we are going to keep those fxFlex
attributes in.
From a grid placement perspective, all your elements are now in the correct cell, as shown here:
Figure 5.17: LocalCast Weather with layout scaffolding
With a responsive design implemented, next, let's work on alignment of major elements.
Now, we need to align and style each cell to match the design. For this purpose, we rely on CSS over fxLayoutAlign
. The date and temperature need to be right-aligned and the description centered:
.right
in current-weather.component.css
:
src/app/current-weather/current-weather.component.css
.right {
text-align: right
}
class="right"
to the <div>
element on lines 7 and 11.<div>
element for the description in the same way you centered the app's tagline earlier in the chapter. Use a surrounding div
with an fxLayoutAlign="center"
attribute.Figure 5.18: LocalCast Weather with correct alignments
After aligning the major elements, let's apply the first layer of styling to every element to match the design.
Finalizing the styling of elements is usually the most time-consuming part of front-end development. I recommend doing multiple passes to achieve a close-enough version of the design with minimal effort first and then have your client or team decide whether it's worth the extra resources to spend more time to polish the design:
src/app/current-weather/current-weather.component.css
.no-margin {
margin-bottom: 0
}
class="mat-title no-margin"
."mat-h3 no-margin"
to class="right"
.'fullDate'
to 'EEEE MMM d'
so it matches the design.<img>
to add style="zoom: 175%"
."mat-display-3 no-margin"
to class="right"
.class="mat-caption"
.This is the final state of the template:
src/app/current-weather/current-weather.component.html
<div *ngIf="!current">
no data
</div>
<div *ngIf="current">
<div fxLayout="row">
<div fxFlex="66%" class="mat-title no-margin">
{{current.city}}, {{current.country}}
</div>
<div fxFlex class="right mat-h3 no-margin">
{{current.date | date:'EEEE MMM d'}}
</div>
</div>
<div fxLayout="row">
<div fxFlex="66%">
<img style="zoom: 175%" [src]='current.image'>
</div>
<div fxFlex class="right mat-display-3 no-margin">
{{current.temperature | number:'1.0-0'}}˚F
</div>
</div>
<div fxLayoutAlign="center">
<div class="mat-caption">
{{current.description}}
</div>
</div>
</div>
Figure 5.19: LocalCast Weather with styling
We're done with adding the first layer of styles for our design. Next, let's fine-tune the spacing and alignment of elements.
The tagline can benefit from some top and bottom margins. This is common CSS that we're likely to use across the application, so let's put it in styles.css
:
vertical-margin
in the global styles.css
:
src/styles.css
.vertical-margin {
margin-top: 16px;
margin-bottom: 16px;
}
app.component.ts
, apply vertical-margin
to the app's tagline:
src/app/app.component.ts
<div class="mat-caption vertical-margin">
Your city, your forecast, right now!
</div>
current-weather.component.html
, the image and the temperature aren't centered, so add fxLayoutAlign="center center"
to the outer div
surrounding these elements:
src/app/current-weather/current-weather.component.html
<div fxLayout="row" fxLayoutAlign="center center">
...
</div>
Figure 5.20: LocalCast Weather finalized layout
Finally, let's add some more visual flair by tightening up our design, like fixing the missing line break between the day and the month, and adding some nice-to-have features.
This is an area where you may spend a significant amount of time. If we were following the 80-20 principle, pixel-perfect tweaks usually end up being the last 20% that takes 80% of the time to complete. Let's examine the differences between our implementation in the previous figure, and the original design as shown in the following figure, and what it would take to bridge the gap:
Figure 5.21: LocalCast Weather original design
The date needs further customization. The numeric ordinal th is missing in our implementation; to accomplish this, we will need to bring in a third-party library such as moment
or implement our own solution and bind it next to the date on the template:
getOrdinal
function in CurrentWeatherComponent
:
src/app/current-weather/current-weather.component.ts
export class CurrentWeatherComponent implements OnInit {
...
getOrdinal(date: number) {
const n = new Date(date).getDate()
return n > 0
? ['th', 'st', 'nd', 'rd'][(n > 3 && n < 21) ||
n % 10 > 3 ? 0 : n % 10]
: ''
}
...
}
current.date
to append an ordinal to it:
src/app/current-weather/current-weather.component.html
<div fxFlex class="right mat-h3 no-margin">
{{current.date |
date:'EEEE MMM d'}}{{getOrdinal(current.date)}}
</div>
Note that the implementation of getOrdinal
boils down to a complicated one-liner that isn't very readable and is very difficult to maintain. Such functions, if critical to your business logic, should be heavily unit tested.
Next, let's fix the missing line break between the day of the week and the month. On certain days like Monday Mar 23rd, Monday and Mar will be on the first line, leaving 23rd by itself on the second line. However, on Tuesday Mar 24th, the issue doesn't exist and Mar and 24th fall on the same line. Angular, at the time of publishing, doesn't support new line breaks in the date template; ideally, we should be able to specify the date format as "EEEE\nMMM d" to ensure that the line break is always consistent. We can, however, throw some inefficient code at the problem and enforce the behavior we desire.
<br>
, then remove the class right from the outer div
:
src/app/current-weather/current-weather.component.html
<div fxFlex class="mat-h3 no-margin">
{{current.date | date:'EEEE'}}<br>
{{current.date | date:'MMM d'}}{{getOrdinal(current.date)}}
</div>
Never use <br>
for layout purposes. It's acceptable in this limited case, because we're breaking up content within a div
or a p
tag.
Now, let's add some visual flair, when displaying the temperature unit. To accomplish this, the temperature implementation needs to separate the digits from the unit with a <span>
element, surrounded with a <p>
element, so a superscript style can be applied to the unit along the lines of <span class="unit">
˚
F</span>
, where unit
is a CSS class that makes its content look like a superscript element.
unit
CSS class:
src/app/current-weather/current-weather.component.css
.unit {
vertical-align: super;
}
p
tag and apply mat-display-3
on the p
tag. Then implement a span
around the temperature unit with the p
tag, and apply the classes unit
and mat-display-1
to the span
:
src/app/current-weather/current-weather.component.html
<div fxFlex="55%">
<img style="zoom: 175%" [src]='current.image'>
</div>
<div fxFlex class="right no-margin">
<p class="mat-display-3">
{{current.temperature | number:'1.0-0'}}
<span class="mat-display-1 unit">˚F</span>
</p>
</div>
You usually need to experiment with how much space the forecast image should have by tweaking the fxFlex
value on the preceding first line. If it takes too much space, the temperature overflows to the next line. Your settings can further be affected by the size of your browser window. 60% seems to work well, but when I was coding this sample the current weather was 55˚F, so for entirely poetic reasons I decided to go with 55%. See the polished version of our app here:
Figure 5.22: LocalCast Weather after tweaks
As always, it is possible to further tweak the margins and paddings to further customize the design. However, each deviation from the library will have maintainability consequences down the line. Unless you're truly building a business around displaying weather data, you should defer any further optimizations to the end of the project, if time permits, and if experience is any guide, you will not be making this optimization.
With two negative margin-bottom hacks, you can attain a design fairly close to the original, but I will not include those hacks here and leave it as an exercise for the reader to discover on the GitHub repository. Such hacks are sometimes necessary evils, but in general, they point to a disconnect between design and implementation realities. The solution leading up to the tweaks section is the sweet spot, where Angular Material thrives. Beyond that you're probably wasting your time. I went ahead and wasted my time for you and here's the result:
Figure 5.23: LocalCast Weather after tweaks and hacks
Now that our layout and design is in great shape, let's look into creating a custom theme using Angular Material.
As we previously discussed, Material ships with some default themes including deeppurple-amber
, indigo-pink
, pink-blue-grey
, and purple-green
. However, your company or product may have its own color scheme. For this, you can create a custom theme to change the look of your application.
In order to create a new theme, you must implement a new SCSS file:
angular.json
.npx ng add @angular/material
.Custom
as the theme.index.html
and styles.css
files have not been modified. If so, revert the changes.src
called custom-theme.scss.
Rename it to localcast-theme.scss
, shown as follows:
src/localcast-theme.scss
// Custom Theming for Angular Material
// For more information: https://material.angular.io/guide/theming
@import '~@angular/material/theming';
// Plus imports for other components in your app.
// Include the common styles for Angular Material.
// We include this here so that you only have to
// load a single css file for Angular Material in your app.
// Be sure that you only ever include this mixin once!
@include mat-core();
// Define the palettes for your theme using
// the Material Design palettes available in palette.scss
// (imported above). For each palette, you can optionally
// specify a default, lighter, and darker hue.
// Available color palettes: https://material.io/design/color/
$local-weather-app-primary: mat-palette($mat-indigo);
$local-weather-app-accent: mat-palette(
$mat-pink,
A200, A100, A400
);
// The warn palette is optional (defaults to red).
$local-weather-app-warn: mat-palette($mat-red);
// Create the theme object (a Sass map containing
// all of the palettes).
$local-weather-app-theme: mat-light-theme(
$local-weather-app-primary,
$local-weather-app-accent,
$local-weather-app-warn
);
// Custom Theming for Angular Material
// For more information: https://material.angular.io/guide/theming
@import '~@angular/material/theming';
// Plus imports for other components in your app.
// Include the common styles for Angular Material.
// We include this here so that you only have to
// load a single css file for Angular Material in your app.
// Be sure that you only ever include this mixin once!
@include mat-core();
// Define the palettes for your theme using
// the Material Design palettes available in palette.scss
// (imported above). For each palette, you can optionally
// specify a default, lighter, and darker hue.
// Available color palettes: https://material.io/design/color/
$local-weather-app-primary: mat-palette($mat-indigo);
$local-weather-app-accent: mat-palette($mat-pink, A200, A100, A400);
// The warn palette is optional (defaults to red).
$local-weather-app-warn: mat-palette($mat-red);
// Create the theme object (a Sass map containing
// all of the palettes).
$local-weather-app-theme: mat-light-theme(
$local-weather-app-primary,
$local-weather-app-accent,
$local-weather-app-warn
);
// Include theme styles for core and each component used in
// your app. Alternatively, you can import and @include the
// theme mixins for each component that you are using.
@include angular-material-theme($local-weather-app-theme);
You can find the Material theme guide at https://material.angular.io/guide/theming for more detailed information.
Note that mat-core()
should only be included once in your application; otherwise, you'll introduce unnecessary and duplicated CSS payloads in your application.
mat-core()
contains the necessary SCSS functions to be able to inject your custom colors into Material, such as mat-palette
, mat-light-theme
, and mat-dark-theme
.
At a minimum, we must define a new primary and an accent color. Defining new colors, however, is not a straightforward process. Material requires a palette to be defined through mat-palette
, which needs to be seeded by a complicated color object that can't just be overridden by a simple hex value such as #BFB900
.
To pick your colors, you can use the Material Design Color Tool, located at https://material.io/resources/color. Here's a screenshot of the tool:
Figure 5.24: Material.io Color Tool
Figure 5.25: Material.io Color Tool Accessibility tab
The tool is warning us that our selections result in illegible text, when white text is used over the primary color. You should either take care to avoid displaying white text over your primary color or change your selection.
If you want to create your own palette, then the interface for mat-palette
looks like this:
mat-palette($base-palette, $default: 500, $lighter: 100, $darker: 700)
mat-palette
objects using the default hue from the tool:
src/localcast-theme.scss
$local-weather-app-primary: mat-palette($mat-red, 500);
$local-weather-app-accent: mat-palette($mat-indigo, A400);
Even though your theme is in SCSS, you may continue using CSS in the rest of your application. The Angular CLI supports compiling both SCSS and CSS. If you would like to change the default behavior, you may switch to SCSS altogether by changing the defaults.styleExt
property in the angular.json
file from CSS to SCSS.
You may also choose to eliminate styles.css
and merge its contents with localcast-theme.scss
or convert styles.css
to a SASS file by simply renaming it to styles.scss
. If you do this, don't forget to update angular.json
.
Congratulations! Your application should now bear your trademark color scheme:
Figure 5.26: LocalCast Weather with custom theme
Push your code to GitHub and check out your CircleCI pipeline.
Once you commit your code, you will notice that your pipeline is now failing due to failed tests. In order to keep your unit tests running, you will need to import MaterialModule
to any component's spec
file that uses Angular Material:
*.component.spec.ts
...
beforeEach(async(() => {
TestBed.configureTestingModule({
...
imports: [..., MaterialModule],
}).compileComponents()
})
)
You will also need to update any test, including e2e tests, that search for a particular HTML element.
For example, since the app's title, LocalCast Weather, is not in an h1
tag anymore, you must update the spec
file to look for it in a span
element:
src/app/app.component.spec.ts
expect(compiled.querySelector('span').textContent).toContain('LocalCast Weather')
Another example is in the CurrentWeather
component, where the surrounding element for city
is no longer span
, so you can use the mat-title
CSS class instead:
src/app/current-weather/current-weather.component.spec.ts
import { By } from '@angular/platform-browser'
// Assert on DOM
const debugEl = fixture.debugElement
const titleEl: HTMLElement =
debugEl.query(By.css('.mat-title')).nativeElement
expect(titleEl.textContent).toContain('Bethesda')
Similarly, in e2e tests, you will need to update your page object function to retrieve the text from the correct location:
src/e2e/app.po.ts
getParagraphText() {
return element(by.css('app-root mat-toolbar span'))
.getText() as Promise<string>
}
Once your tests are passing, push your code to GitHub again. When your CircleCI pipeline succeeds, ship your app using Vercel Now. Remember, if you don't ship it, it never happened!
We can now move the UX task to the Done column:
Figure 5.27: GitHub project Kanban board status
In Chapter 7, Creating a Router-First Line-of-Business App, you will learn about more sophisticated tools to be able to further customize the look and feel of your Material theme to create a truly unique experience that fits the brand you're representing.
It is important to be aware of potential accessibility issues with your app. You may familiarize yourself with accessibility concerns by visiting the A11Y project website at https://a11yproject.com. Material itself provides additional tooling to help you improve accessibility; you can read about it at https://material.angular.io/cdk/a11y/overview.
Leveraging such Material features may feel unnecessary; however, you must consider responsiveness, styling, spacing, and accessibility concerns when designing your app. The Material team has put in a lot of effort so that your code works correctly under most circumstances and can serve the largest possible user base with a high-quality UX. This can include visually impaired or keyboard-primary users, who must rely on specialized software or keyboard features such as tabs to navigate your app. Leveraging Material elements provides crucial metadata for these users to be able to navigate your app.
Material claims support for the following screen-reader software:
Beyond Material, you may be required or have a desire to support specific accessibility standards, like the US-based Section 508 or the W3C-defined Web Content Accessibility Guidelines (WCAG). Claiming official support for such standards requires expensive certifications and qualified testers to ensure compliance.
Consider pa11y, which is a command-line tool that automates accessibility testing. Since it is a CLI tool, you can easily integrate it with your CI pipeline. Being able to catch accessibility issues automatically and early in the development cycle dramatically decreases the cost of implementing accessibility features in your application.
A11y is short for accessibility, as there are 11 characters between a and y in the word accessibility. You may read more about why accessibility support matters at https://a11yproject.com/.
You may learn more about pa11y at https://pa11y.org/. Next, let's configure the pa11y CLI tool in our project.
pa11y is an automated accessibility tool that you can execute from the command line to check your web app against various accessibility rulesets like Section 508 or WCAG 2 AAA. You may configure pa11y to run on your project locally or your CI server. In both cases, you must be running the tests against a deployed version of your application.
Let's start with configuring pa11y for a local run:
npm i -D pa11y pa11y-ci http-server
npm
scripts to execute pa11y for local runs, checking for Section 508 compliance issues:
package.json
...
"scripts": {
...
"test:a11y": "pa11y --standard Section508 http://localhost:5000"
}
npm start
.npm run test:a11y
. The output should be as follows:
Welcome to Pa11y
> Running Pa11y on URL http://localhost:5000
Results for URL: http://localhost:5000/
• Error: Img element missing an alt attribute. Use the alt attribute to specify a short text alternative.
├── Section508.A.Img.MissingAlt
├── html > body > app-root > div:nth-child(4) > mat-card > mat-card-content > app-current-weather > div > div:nth-child(2) > div:nth-child(1) > img
└── <img _ngcontent-pbr-c132="" style="margin-bottom:32px; zoom:175%" src="">
1 Errors
Note that we have one error. The error message indicates that under app-current-weather
, the image we display inside mat-card-content
is missing an alt
attribute. Observe the following line of code that caused the error:
src/app/current-weather/current-weather.component.html
...
<img style="zoom: 175%" [src]="current.image" />
The preceding code refers to the image that we grab from the OpenWeatherMap API. A visually impaired user, relying on a screen reader, would not be able to determine what the image is for without an alt
attribute present. Since this is a dynamic image, a static alt
attribute like the Current weather icon would be a disservice to our user. However, it would be appropriate to bind the current weather description value as the attribute. We can fix the accessibility issue as shown:
src/app/current-weather/current-weather.component.html
...
<img style="zoom: 175%" [src]="current.image"
[alt]="current.description" />
Now users relying on screen readers quickly figure out that the image on the page reflects the current weather. In this case, we already have the description on our page. This is a very important issue to fix because it is crucial to avoid having mystery elements on our page that an entire class of users is unable to decipher.
Now let's configure pa11y for our CI pipeline.
.pa11yci
configuration file in the root of your project:
.pa11yci
{
"default": {
"timeout": 1000,
"page": {
"viewport": {
"width": 320,
"height": 480
}
}
},
"urls": [
"https://localcast-weather.duluca.now.sh/"
]
}
npm
scripts to execute pa11y for local runs, checking for Section 508 compliance issues:
package.json
...
"scripts": {
...
"test:a11y:ci": "pa11y-ci"
}
Now we can add the command npm run test:a11y:ci
to .circleci/config.yml
. However, as you may notice, we would be running the test against the already deployed version of our app. To overcome this challenge, you must create an alternative now:publish
command that will deploy our branch to a different URL, update .pa11yci
to check against the new URL, and perform a deployment in your pipeline. Since all actions involved here are CLI commands, you may execute them sequentially. I leave this as an exercise for the user to complete.
More advanced uses for CircleCI are covered in Chapter 9, DevOps Using Docker. Next, we are going to go over how you can build an interactive prototype to discover UI/UX issues early in development to reduce your development costs.
Appearances do matter. Whether you're working in a development team or as a freelancer, your colleagues, bosses, or clients will always take a well-put-together presentation more seriously. In Chapter 3, Creating a Basic Angular App, I mentioned the time and information management challenges of being a full-stack developer. We must pick a tool that can achieve the best results with the least amount of work. This usually means going down the paid-tool route, but UI/UX design tools are rarely free or cheap.
A prototyping tool will help you create a better, more professional-looking, mock-up of the app. Whatever tool you choose should also support the UI framework you choose to use, in this case, Material.
If a picture is worth a thousand words, an interactive prototype of your app is worth a thousand lines of code. An interactive mock-up of the app will help you vet ideas before you write a single line of code and save you a lot of code writing.
I've picked MockFlow WireframePro, available at https://mockflow.com, as an easy-to-use, capable, online tool that supports Material Design UI elements and allows you to create multiple pages, which can then be linked together to create the illusion of a working application.
Most importantly, at the time of publishing, MockFlow allows one free project forever with the full feature set and capabilities available. This will give you a chance to truly vet the usefulness of the tool without artificial limits or a trial period that always seems to go by much quicker than you expect.
Balsamiq (available at https://balsamiq.com) is a better-known wireframing tool; however, it doesn't offer any free usage. If you are looking for a tool without a monthly cost, I would highly recommend Balsamiq's desktop application Mockups, which has a one-time purchase cost.
We start by adding a new task to create an interactive prototype and at the end of the task, I'll attach all artifacts to this task so that they're stored on GitHub and are accessible to all team members and can also be linked from the wiki page for persistent documentation.
Let's pull this new task to the In Progress column and take a look at the status of our Kanban board from Waffle.io:
Figure 5.28: Current Kanban board status
WireframePro is pretty intuitive as a drag and drop design interface, so I won't go into the details of how the tool works, but I will highlight some tips:
Figure 5.29: MockFlow.com WireFrame Pro
I would recommend sticking to the hand-drawn UI look and feel, because it sets the right expectations for your audience. If you present a very high-quality mock-up on your first meeting with a client, your first demo will be an understatement. You will, at best, merely meet expectations and, at worst, underwhelm your audience.
Here's the new mock-up of the home screen that we just created:
Figure 5.30: LocalCast Weather home screen wireframe
You'll note some differences, such as the app toolbar being conflated with the browser bar and the intentional vagueness of the repeating elements. I have made these choices to reduce the amount of design time I would need to spend on each screen. I simply used horizontal and vertical line objects to create the grid.
The search screen similarly remains intentionally vague to avoid having to maintain any kind of detailed information. Surprisingly, your audience is far more likely to focus on what your test data is rather than focusing on the design elements.
By being vague, we intentionally keep the audience's attention on what matters. Here's the search screen mock-up:
Figure 5.31: LocalCast Weather search screen wireframe
The Settings pane is a separate screen with the elements from the home screen copied over and with 85% opacity applied to create a model-like experience. The Settings pane itself is just a rectangle with a black border and a solid white background.
Take a look at the following mock-up:
Figure 5.32: LocalCast Weather settings wireframe
Being able to click around a mock-up and get a feel for the navigational workflow is an indispensable tool to get early user feedback. This will save you and your clients a lot of frustration, time, and money.
To link elements together, do as follows:
Figure 5.33: Adding a link in Wireframe Pro
Now, when you click on the gear icon, the tool will display the Settings page, which will create the effect of the sidebar actually being displayed on the same page. To go back to the home screen, you can link the gear icon and the section outside of the sidebar back to that page so that the user can navigate back and forth.
Once your prototype is completed, you can export it as various formats:
Figure 5.34: Wireframe Pro's Export Wireframe menu option
Figure 5.35: File formats in Wireframe Pro
I prefer the HTML format for flexibility; however, your workflow and needs will differ.
Figure 5.36: Interactive wireframe in Wireframe Pro
The interactive elements are highlighted in yellow (light gray in print) and pointed out by the fat arrows in the preceding screenshot. You can enable or disable this behavior with the Reveal Links option in the bottom-left corner of the screen.
Now add all assets to comment on the GitHub issue, including the ZIP bundle, and we are done.
You can also publish your prototype's HTML project using Vercel Now, as discussed in Chapter 4, Automated Testing, CI, and Release to Production.
In this chapter, you learned what Angular Material is, how to use the Angular Flex Layout engine, the impact of UI libraries on performance, and how to apply specific Angular Material components to your application. You became aware of pitfalls of over-optimized UI design with individual CSS tweaks and how to add a custom theme to your application.
We also went over how you can improve the accessibility of your application and build an interactive prototype to vet your designs before implementing them.
In the next chapter, we will update the weather app to respond to user input with reactive forms and keep our components decoupled, while also enabling data exchange between them using BehaviorSubject
. After the next chapter, we will be done with the weather app and shift our focus to building larger line-of-business applications.
See Appendix C, Keeping Angular and Tools Evergreen, for information on how you can upgrade Angular Material. You can find this appendix online from https://static.packt-cdn.com/downloads/9781838648800_Appendix_C_Keeping_Angular_and_Tools_Evergreen.pdf or at https://expertlysimple.io/stay-evergreen.
Implement pa11y in your CI pipeline by implementing an alternative Now deployment so that you can test against the changes in your branch.
Answer the following questions as best as you can to ensure that you've understood the key concepts from this chapter without Googling. Do you need help answering the questions? See Appendix D, Self-Assessment Answers online at https://static.packt-cdn.com/downloads/9781838648800_Appendix_D_Self-Assessment_Answers.pdf or visit https://expertlysimple.io/angular-self-assessment.