Professional Documents
Culture Documents
Angular15_CollectedEssays_Coding
Angular15_CollectedEssays_Coding
ANGULAR 15
COLLECTED ESSAYS ON PROGRAMMING
By Jeffry Houser
https://www.learn-with.com
https://www.jeffryhouser.com
https://www.dot-com-it.com
Version: 2022.15.001
Copyright © 2022 by DotComIt, LLC
About the Author
Jeffry Houser is a technical entrepreneur that likes to share cool stuff with other people.
In the days before business met the Internet, Jeffry obtained a Computer Science degree. He has solved
a problem or two in his programming career. In 1999, Jeffry started DotComIt; a company specializing in
custom application development.
During the Y2K era, Jeffry wrote three books for Osborne McGraw-Hill. He is a member of the Apache
Flex Project and created Flextras; a library of Open Source Flex Components. Jeffry has spoken all over
the US. He has produced hundreds of podcasts, written over 30 professional articles, and blog on a
weekly basis.
In 2014, Jeffry created Life After Flex; an AngularJS training course for Flex Developers. In 2016, Jeffry
launched the Learn With series with books focusing on using Angular with other technologies. Jeffry has
worked with multiple clients building applications across industries including publishing, health care,
real estate, retail, and entertainment streaming.
Contents
This book is about my experiences building Angular applications. Angular is an HTML5 framework built
by Google for building smart user interfaces. It is built on TypeScript and allows you to build dynamic
views in HTML5. It is fully testable, which is important to many enterprise-level applications. It has a
large developer community, ready to help with problems. I greatly enjoy building applications with
Angular.
This book contains a series of essays around important parts of Angular and the HTML5 ecosystem that
were not covered as part of the main series.
Introduction
What is this Book Series About?
The purpose of this series is to teach by example. The plan is to build an application using multiple
technologies. These books will document the process, with each book focusing on a specific technology
or framework. This entry is a companion to the Angular books and covers important Angular coding
concepts in depth that weren’t used in the main books.
Here are some topics we’ll touch upon in this book, and what you should know before continuing:
• TypeScript: This is the language behind Angular. It is a statically typed language that compiles to
JavaScript. The more you know about it, the better. If you are not familiar with it yet, check out
our tutorial lesson on learning the basics of TypeScript.
• Angular: If you read the main books of this series, then you should have more than enough
Angular experience to understand its usage here.
• NodeJS: We use these scripts to compile our TypeScript into JavaScript, process CSS, and copy
files. Familiarity with NodeJS will be beneficial
Common Conventions
I use some common conventions in the code behind this book.
• Classes: Class names are in proper case; the first character of the class in uppercase, and the
start of each new compound word being in uppercase. An example of a class name is MyClass.
When referencing class names in the book text, the file extension is usually referenced. For
TypeScript files that contain classes the extension will be “ts”. For JavaScript files, the extension
is “js”.
• Variables: Variable names are also in proper case, except the first letter of the first compound
word; it is always lowercase. This includes class properties, private variables, and method
arguments. A sample property name is myProperty.
• Constants: Constants are in all uppercase, with each word separated by an underscore. A
sample constant may be MY_CONSTANT.
• Method or Function Names: Method names use the same convention as property names. When
methods are referenced in text, open and close parentheses are typed after the name. A sample
method name may be myMethodName().
• Package or Folder Names: The package names—or folders—are named using proper case again.
In this text, package names are always referenced as if they were a directory relative to the
application root. A sample package name may be com/dotComIt/learnwith/myPackage.
Caveats
The goal of this book is to help you become productive creating HTML5 apps with a focus on Angular. It
leverages my experience building business apps but is not intended to cover everything you need to
know about building HTML5 Applications. This book purposely focuses on the tool chain and
complimentary libraries. If you want to know more about Angular explicitly, check out original series.
You should approach this book as part of your learning process and not as the last thing you’ll ever need
to know. Be sure that you keep educating yourself. I know I will.
I am writing these books as a labor of love and do not have the resources of a major publisher. Although
we try our hardest sometimes mistakes slip through. It might be a minor typo, or formatting issue. It
could be an error in code. I hate it when this stuff happens. If you find something that is off, be sure to
let us know and we’ll reward you with a free upgrade to the latest—or next—edition of this book, or
access to some of our books covering other technologies.
Want More?
You should check out this book’s web site at www.learn-with.com for more information, such as:
• Source Code: You can find links to all the source code for this book and others.
• Errata: If we make mistakes, we plan on fixing them. You can always get the most up-to-date
content available from the website. If you find mistakes, please let us know.
• Test the Apps: The web site will have runnable versions of the app for you to test.
• Bonus Content: You can find more articles and books expanding on the content of this book.
Chapter 1: Understanding the Angular Component Lifecycle
Angular components are one of the primary building blocks of an Angular application, and it helps to
understand how Angular creates a component and how your own components can leverage some
lifecycle hooks in order to make your components work as efficiently as possible.
• ngOnChanges(): This event is fired when Angular sets or changes input properties. It receives
an object of current and previous property values. I’ve used this for date formatting from UTC
to the user’s specific time zone.
• ngOnInit(): This method is run directly after ngOnChanges(). I use it to set up things or trigger
remote service calls, almost like a constructor for Angular. It is not uncommon to use this to
trigger a data loading service.
• ngDoCheck(): This method is called next, and is used to act on changes that Angular won’t
detect on its own. I’ve used this to update data flags, such as whether an object has been
changed or not.
• ngAfterContentInit(): This relates to the use of ng-content, or transclusion. After the wrapped
contents are placed inside the component’s template, this method fires. It is run after the first
ngDoCheck(). I’ve never actually used this one.
• ngAfterContentChecked(): After ngAfterContentInit(), this method is executed. I haven’t used
this one either.
• ngAfterViewInit(): After Angular finishes initializing the view template, including all the children.
I might use this to clear a loading symbol or something similar.
• ngAfterViewChecked(): This fires after Angular completes the check of the component’s views
and children. I might use this to clear a loading symbol or something similar.
Once a component is created, Angular is continuously evaluating it to look for changes due to user
interaction or responses from remote service calls. The process is similar, but simpler:
• ngOnChanges(): This event is fired when Angular sets or resets input properties. It receives an
object of current and previous property values.
• ngDoCheck(): This method is called immediately next, and is used to act on changes that
Angular won’t detect on its own.
• ngAfterContentChecked(): After ngDoCheck(), this method is executed.
• ngAfterViewChecked(): This fires after Angular completes the check of the component’s views
and children.
The biggest change here is that ngOnInit(), ngAfterContentInit(), and ngAftertViewInit() are not part of
the normal render lifecycle. Whether you use an init method or a check method depends on whether
you want to do something every time Angular checks the component, or just once. The ngOnChanges()
and ngDoCheck() are executed a lot and I try to avoid using them as much as possible. They can cause
performance issues and putting code in these methods is often hard to debug.
• ngOnDestroy(): This is called right before Angular removes a component from view. The most
common use case for this method is to unsubscribe from observables and to detach event
listeners to help improve app memory usage.
All these methods can be implemented within your component to help time changes for efficiency.
Set up a Project
Let’s create a sample project. First you’ll want to set a project with the Angular CLI:
ng new
I did not use strict typing but did add Angular routing and decided to use normal CSS.
and:
ng generate component view/comp2
Two things I want to highlight. There is an interface OnInit that the component implements. That
interface contains a single method, ngOnInit(). All the lifecycle methods have a similar interface.
I added a console.log() statement for each method in order to keep track of the order of operations in
the browser’s log. I also added a log to the constructor:
constructor() {
console.log('comp1 constructor');
}
Now we have two components with all the life cycle methods implemented, so let’s get to building some
tests.
Switch over to the app.module.ts and load the FormsModule into the angular module metadata:
imports: [
BrowserModule,
AppRoutingModule,
FormsModule
],
Now switch over to comp1.component.html. Replace all the contents with this:
<hr>
<input type="text" [(ngModel)]="title" (change)="onChange($event)"><br/>
<button (click)="onDeleteRequest()">Delete</button>
<hr>
<ng-content></ng-content>
There is an input that allows you to tweak the title. It will also call the onChange() event when
something changes. A Delete button makes a call to onDeleteRequest(). And finally, the wrapped
content is at the bottom with the ngContent tag.
This just sets the dispatches the titleChange event with the new title value. The title is automatically
updated when the model changes.
It emits the deleteRequest() method and does nothing else. Now let’s move onto creating the app.
First, replace the title with a title string with a titles array:
titles = [{title: 'componentlifecycle'}];
The titles array contains an array of objects, with each object having a single title property. To create, or
add, components on the fly we’ll just add, or remove, items from this array.
Open the app.component.html. First, let’s add an add button to add additional objects onto our array:
<button (click)="add()">Add</button><br/><br/>
This is the add() method inside of the app.component.ts:
add() {
this.titles.push({title: 'componentlifecycle'});
}
It just hard codes a new entry on the titles array, which Angular will automatically know to re-draw the
HTML Template to add more items.
Back to the app.component.html, we’re going to loop over the titles array to create instances of the
Comp1Component and inside that component we’ll add another input that edits the same data. The
two-way binding between the two components will fire off the life cycle methods:
<div *ngFor="let title of titles; let i = index">
<app-comp1 [(title)]="title.title" (deleteRequest)="onDeleteRequest(i)">
{{i}}. <input type="text" [(ngModel)]="title.title" >
</app-comp1>
</div>
Each input refers to the title property on one of our objects, as does each instance of the
Comp1Component.
The Comp1Component also includes a listener for the deleteRequest() event. The deleteRequest()
method has a single argument, the index of the item that is going to be deleted. Let’s open up
app.component.ts to see the onDeleteRequest() method:
onDeleteRequest(index) {
this.titles.splice(index, 1);
}
This uses the JavaScript splice() to remove the selected item from the array.
That sums up the component creation lifecycle. Notice as part of the loading process we immediately
jump into one redraw process:
The ngOnChanges() method did not execute for this redraw cycle because the title had not changed.
I cleared the console before clicking the delete, button, but you can see in the app our input is gone and
that the ngOnDestroy() lifecycle hook was executed. Feel free to click the add button again and play
around some more.
Embedded Components
Earlier in this chapter I created two separate components. We’ve fleshed out Comp1Component a lot
but have not done anything with Comp2Component. We’re going to add a title property to
Comp2Component and display it to demonstrate how the lifecycle events work between components in
a parent and child relationship.
Modify Component 2
For Comp2Component we’re going to pass in a title and display it. It won’t fire as many component
methods in Comp2 as it will for Comp2, but it should give us a sample of how they all work together.
First, create a title input in comp2.component.ts:
@Input()
title: string;
Now move over to comp1.component.html. Between the input and the delete button, drop an instance
of Comp2Component:
<input type="text" [(ngModel)]="title" (change)="onChange($event)"><br/>
<app-comp2 [title]="title"></app-comp2><br/>
<button (click)="onDeleteRequest()">Delete</button>
That sums up the component creation lifecycle. Comp2Component goes through its life cycle as part of
Comp1Component’s view initialization.
At the end of the image, we see an immediate jump into the redraw process for both comp1 and comp2
here:
The ngOnChanges() method does not execute for this redraw cycle because the title had not changed
for either component.
Click off the field and you’ll see a flurry of activity as the titleChange event dispatches out of
Comp1Component:
Once we lose focus and the titleChange event is emitted we have an ngOnChanges() event in
Comp1Component. The event triggers a change to the parent’s value, so this makes sense. Then the
redraw cycle happens three times. I said previously, the redraw cycle will happen a lot in the life of a
component.
I cleared the console before clicking the delete, button, but you can see in the app our input is gone and
that the ngOnDestroy() lifecycle hook was executed, first for Comp2Component and then for
Comp1Component. The deletions happen from the bottom up. You should click the add button again
and play around some more.
Final Thoughts
At this point you should understand the Angular Component LifeCycle methods, and when they execute,
and some inkling of what you might use them for. Understanding these methods and leveraging them is
important to writing performant Angular applications.
Chapter 2: Understanding Angular Directives
An Angular Directive is a way to create your own HTML Attribute. They can be applied to any tag,
including an existing HTML tag or your own Angular custom components. From an implementation
standpoint, directives are just like Angular components except they have no template. This article will
teach you how to use some directives that are part of the Angular library and will show you how to
create your own.
Types of Directives
There are two types of directives:
• Attribute Directives: Attribute directives are used to modify an element it placed on. Some
examples might be ngClass which is used to add more styles to the directive or click, which is
used to respond to custom user interaction.
• Structural Directives: Structural directives change the DOM of the HTML page by adding or
removing items. Examples of this might be *ngIf which removes or adds an element from the
page, or *ngFor which will create new elements.
We’ll go into full details of each type of directive and show you how to use existing directives, as well as
creating your own.
Project Setup
Before going into the samples, you’ll need to create an Angular project. I used the Angular CLI to do so:
ng new
I named the project ud, for Understanding Directives. I set up routing and used regular CSS.
In the app.component.html file, delete everything except the router-outlet, so the file looks like this:
<router-outlet></router-outlet>
You can run the app, but you won’t see much until we start adding routes.
Attribute Directives
Let’s start looking at attribute directives. I’m going to use an existing one that is built as part of the
Angular framework first, and then show you how to create your own.
Use an Attribute Directive
For this sample, we’re going to use ngClass in order to change styles to swap the look and feel of a
button, switching from black text on a white background to white text on a black background. First let’s
create a component:
Open up the app-routing.module.ts file and a sample1 route to the Routes array:
const routes: Routes = [
{path: 'sample1', component: UseAttributeDirectiveComponent},
{ path: '**', redirectTo: '/sample1'}
];
.reversed {
background: #000000;
color: #ffffff;
}
I created a regular style and a reversed style, setting both the background color and the text color.
A button exists, and the click handler to call a reverseState() method. The ngClass accepts an object as
an argument. The object contains the name of the CSS classes that will be applied to the button if the
value of the class is true. The regular CSS state is applied if the reversedState value is false, and
reversed CSS is applied of the reversedState value is true.
We’ll have to create the reverseState() method and the reversedState value inside the use-attribute-
directive.compoment.html, so let’s do that now. First the reversedState vallue:
reversedState = false;
I set the default condition to false, meaning the button will start in the regular state.
Try to run the app. You should see the default state:
First, we’ll do some setup. Create a component for creating our own attribute directive:
ng generate component create-attribute-directive
Open up the app-routing.module.ts file and a sample2 route to the Routes array:
const routes: Routes = [
{path: 'sample1', component: UseAttributeDirectiveComponent},
{path: 'sample2', component: CreateAttributeDirectiveComponent},
{ path: '**', redirectTo: '/sample1'}
];
I put the new directive in the directive directory. If nothing is specified it will be put in src/app by
default, but long term I like to keep my code better organized without a lot of things in the main app
directory. Open up the reverse.directive.ts:
import { Directive } from '@angular/core';
@Directive({
selector: '[appReverse]'
})
export class ReverseDirective {
constructor() { }
}
First, we import the Directive class. This is metadata used to define the directive. The only thing we
define, at this point in the metadata is the selector. We’ll use this when we apply the directive to an
HTML element, the Angular module, something like this:
<button appReverse></button>
Right now, the directive doesn’t do anything, but we’ll come back to this in the HTML template later. It
is strongly recommended you do not name your custom directives with a prefix ng because that is used
by the Angular framework and we want to avoid conflicts with other existing directives.
I want to note that the directive is loaded in the main Angular module. Open up app.module.ts and
you’ll see it in the declarations section:
@NgModule({
declarations: [
AppComponent,
UseAttributeDirectiveComponent,
CreateAttributeDirectiveComponent,
ReverseDirective
],
// other stuff
})
If you do not add your directive to the @NgModule your app won’t know how to find it and you’ll get
compiler errors.
The first thing we want to do is inject the ElementRef into the directive class:
constructor(private el: ElementRef) { }
Dependency Injection for Directives works just like it does for other components, so we inject the value
into the constructor. This will give the directive class a reference to the component which is was placed
on.
This directive needs to pass in a value of the directive name, and then make changes to the
ElementRef’s styles based on that. To do that we’ll need to define an Input() metadata. The simplest
way to do this:
@Input('appReverse') reversedState: Boolean;
When the directive sees the appReverse directive applied to an HTML tag as an attribute, the value will
be set in the reversedState value inside the directive class. In order to run some code whenever that
value changes I’m going to switch it to a get/set method:
private _reversedState = false;
@Input('appReverse')
get reversedState() {
return this._reversedState;
}
set reversedState(input) {
this._reversedState = input;
this.changeState();
}
The get and set methods are pretty standard. A private variable holds the state. The get method will
return that private variable, and the set method will set it. The set method also runs a changeState()
method, so let’s look at that:
changeState() {
if (this.reversedState) {
this.el.nativeElement.style.backgroundColor = '#000000';
this.el.nativeElement.style.color = '#ffffff';
} else {
this.el.nativeElement.style.backgroundColor = '#ffffff';
this.el.nativeElement.style.color = '#000000';
}
}
This method checks the reversedState value and sets the background and foreground styles. I use
nativeElement property on the ElementRef to drill down into the component and set specific styles. For
the moment, I’m hard coding the color values, but we’ll revisit that in a later sample.
The appReverse directive is placed directly on the button; and it is given the value of reversedState,
which is a Boolean value in the component that we’ll create. The click button is used to call a
reverseState() method that will toggle the reversedState property.
We’ve seen this method, and property, set up identically in the previous sample. The major change is in
how the new styles are applied to the button.
Using Renderer2
If you read the docs on the ElementRef’s nativeElement property, there is a big warning to avoid using
it. It breaks encapsulation by accessing the DOM directly, and can lead to unexpected results in the
application. We’re going to use the Renderer2 component instead. It is designed to give a layer of
abstraction between Angular and the DOM within directives like this. While we’re at it, I’m also going to
switch to using a CSS Class instead of accessing styles directly.
The first step is to inject the Renderer2 into the directive’s reverse.directive.ts file:
constructor(private renderer: Renderer2, private el: ElementRef) { }
The reversedState property remains unchanged, but we will want to modify the changeState() method:
changeState() {
if (this.reversedState) {
this.renderer.removeClass(this.el.nativeElement, 'regular');
this.renderer.addClass(this.el.nativeElement, 'reversed');
} else {
this.renderer.removeClass(this.el.nativeElement, 'reversed');
this.renderer.addClass(this.el.nativeElement, 'regular');
}
}
Instead of drilling down into the nativeElement directly, we’re calling the addClass() and removeClass()
methods on the renderer, and passing in the nativeElement along with the new style. We could have
done the same thing with the previous sample, using addStyle() and removeStyle() methods on the
renderer.
.reversed {
background: #000000;
color: #ffffff;
}
I added one input for the regularCSSClass and one for the reversedCSSClass. Now move to the
changeState() function:
changeState() {
if (this.reversedState) {
this.renderer.removeClass(this.el.nativeElement, this.regularCSSClass);
this.renderer.addClass(this.el.nativeElement, this.reversedCSSClass);
} else {
this.renderer.removeClass(this.el.nativeElement, this.reversedCSSClass);
this.renderer.addClass(this.el.nativeElement, this.regularCSSClass);
}
}
Instead of accessing hard coded values as the CSS Class is added, or removed, from the native element
the variables are added.
Notice how the values have no default styles. To solve that we’ll make sure to call the changeState()
method when ngOnInit() runs, which will be after the property values are set. First implement the
OnInit interface as part of the class definition:
export class ReverseDirective implements OnInit {
This will make sure that the default states are set after the component is initialized.
Angular’s magic under the hood will know the new attributes belong to the directive and will pass in
those values.
The new Angular directive inputs were set up as regular HTML attributes with string values, but we
could set them up as Angular attributes with an Angular expression as the value. In the simplest form,
like this:
<button (click)="reverseState()"
[appReverse]="reversedState"
[regularCSSClass]="'regular'"
[reversedCSSClass]="'reversed'">
Reverse State
</button>
The Angular expression is a literal string, and the regularCSSClass and reversedCSSClass are enclosed in
brackets.
Dispatching Events
A directive can dispatch its own events, just like a component can. Let’s add an event to the directive
that will dispatch whenever the state changes. First, I’ll add a new class:
export class CSSReversedEvent {
addedCSSClass: string;
removedCSSClass: string;
}
I put this class in the reverse.directive.ts file, instead of creating its own class file. It includes one value
for the CSS Class that was removed, and one for the CSS Class that was added.
The first line in the method creates a new instance of the CSSReversedEvent class. The middle of the
method will add the CSS Values onto this new object, depending on which state we are entering. The
final name of the method will dispatch the event.
Open up the create-attribute-directive.html file and add in the stateChange event listener:
<button (click)="reverseState()"
[appReverse]="reversedState"
regularCSSClass="regular"
reversedCSSClass="reversed"
(stateChange)="onStateChange($event)">
Reverse State
</button>
The event listener will call the onStateChange() event inside the create-attribute-
directive.component.ts file:
onStateChange(event) {
console.log(event);
}
Click the button a few times to see the events continue to dispatch:
You should realize now that there is a lot you can do with directives.
.reversed-red {
background: #ff0000;
color: #ffffff;
}
.regular-blue {
background: #ffffff;
color: #0000ff;
}
.reversed-blue {
background: #0000ff;
color: #ffffff;
}
The styles use red and white, or blue and white instead of our usual black and white. Apply them to
HTML elements in create-attribute-directive-component.html:
<p [appReverse]="reversedState" regularCSSClass="regular-red"
reversedCSSClass="reversed-red">
The directive in a p!
</p>
The red style is applied to the p tag and the blue styles are applied to the div. Rerun the code:
And click to switch:
Structural Directives
An attribute directive is one that will add or remove items from the HTML. I’m going to show you how to
use one of the built in Angular directives and then we’ll go into details on how you can create your own.
Open up the app-routing.module.ts file and add a sample3 route to the Routes array:
const routes: Routes = [
{path: 'sample1', component: UseAttributeDirectiveComponent},
{path: 'sample2', component: CreateAttributeDirectiveComponent},
{path: 'sample3', component: UseStructuralDirectiveComponent},
{ path: '**', redirectTo: '/sample1'}
];
This button will toggle on and off the custom directive. It’ll call a method inside the component class,
which we’ll review shortly.
This is just a div we will create, and destroy by toggling the value of textHidden, a value we’ll put inside
the Typescript class. We put the ngIf structural directive. Notice that there is an asterisk before the
directive name. This is Angular shorthand. It is turned into an Angular template:
<ng-template [ngIf]="!textHidden">
<div>
This is hidden text
</div>
</ng-template>
The ngIf is put on the ng-template tag, but the contents remain unchanged. This template is stored
behind the scenes and rendered when placed on the page. If you use any variables, directives, or other
Angular expressions within the ng-template they will be rendered when put on the page.
Let’s move over to the use-structural-directive.component.ts class file. First, add the textHidden value:
textHidden = false;
I’ll get to the directive in a bit, but first let’s create a service to contain user information:
ng generate service services/user-service
We’re going to do a few things to the service. First, we’re going to add some constants to represent
user roles. Next, we’re going to create an active user property. This service will be injected into the
directive and will be used to compare the active user’s roles with the roles for the specified element.
I put these in the service class, but above the metadata. There are four roles:
In a real application I would create a class type for the user object, for this sample I’m keeping it simple
and defining the expected object definition in-line. There is a special reason why I’m implementing this
as a get and set method. I want to dispatch an event whenever the value changes. Add the event
emitter:
userChange: EventEmitter<any> = new EventEmitter();
The templateRef is a reference to the template that this directive will create or delete. The
viewContainer is a reference to the parent container of the directive. The UserServiceService will be
used to reference the roles of the currently active user.
Now, we want to keep track of the appHasAccess value, which will be an array of possible roles:
private _allowedRoles: number[];
@Input('appHasAccess')
get allowedRoles() {
return this._allowedRoles;
}
set allowedRoles(allowedRoles: number[]) {
this._allowedRoles = allowedRoles;
this.onChange();
}
Roles are integers, so I made the allowedRoles property an array of numbers. I use the Input() alias to
use the name, appHasAccess as the directive, but use the name allowedRoles inside the directive code.
There is a method named onChange(), which you’ll use to encapsulate the changes that must take place
when either the activeUser changes in the userService or when the value of the directive changes.
Let’s build the onChange() method. It starts out like all other methods:
private onChange() {
}
If there is no activeUser, then we skip the check. Otherwise we check, leaving allowAccess as false
value. The check makes use of the array some() function, which is shorthand for looping over all the
items in the array and comparing two values. We use one some() inside the other in order to compare
the user’s roles to the allowed roles for this directive’s instance. If at least one value is equal, then the
allowAccess will be set to true.
The rest of the method code creates, or removes, the templateRef on the viewContainer:
if (allowAccess ) {
if (!this.created ) {
this.viewContainer.createEmbeddedView(this.templateRef);
this.created = true;
}
} else {
this.viewContainer.clear();
this.created = false;
}
The viewContainer creates an instance of our template using the templateRef if the user is allowed
access to it. Alternately, if the user does not have access to the code, the viewContainer clear() method
is called to delete the component..
Notice I have a created flag in the directive. Create it now as an instance variable on the class:
created = false;
This will come into play when changing the active user or the directive elements. If the values change,
but the component is already created, we do not want to create it again.
Finally, we want to call this onChange() method when the userChange event is dispatched from the
service. Inside the directive’s constructor, listen for it:
this.userService.userChange.subscribe((value) => {
this.onChange();
});
A click from any one of these will execute a selectUser method inside the component; and each
selection represents a different user with different permissions.
The appHasAcesss directive is put on the li tags with the asterisk. The value passed in is an array with
some number of roles inside it. The directive will know to compare the input values with the values
from the currently activeUser and remove or display the specific list items.
Now look at the create-structural-directive.component.ts file so we can put this all together:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-create-structural-directive',
templateUrl: './create-structural-directive.component.html',
styleUrls: ['./create-structural-directive.component.css']
})
export class CreateStructuralDirectiveComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
This is a currently a standard placeholder component. First, let’s copy the role constants into local
template variables so that they can be accessed in the view:
ADMIN = ADMIN;
AUTHOR = AUTHOR;
DELETER = DELETER;
EDITOR = EDITOR;
Each object in this array coincides with a radio button in the main view. Be sure to inject the
userServiceService into this component so that we can change the activeUser:
constructor(private userService: UserServiceService) { }
When a radio button is selected in the HTML view, this method will be called, which will change the
activeUser in the userService, which will dispatch a userChange event which will cause the directives
onChange() method to run, removing or adding items from the menu list.
This displays more things, including all author links and all editor links. Review the HTML here:
A lot more HTML is added to the page, as relevant based on the code inside the directive. This proves
that are new structural directive is creating, and deleting HTML from the DOM.
Final Thoughts
When building Angular applications my first thought is to always create a component, because that is
the base building block of an Angular application. But, when you want to apply some functionality to
any given component or HTML element, a directive is a really good way to do that.
Chapter 3: Debug Applications from Your Browser
When writing code in any language it is important to understand the tools available to you to help you
debug. This article will talk about some of the ways you can debug your code, first by simply outputting
data to your web browser console, and second by using a JavaScript step-through debugger.
To give you an example of this, I’m going to turn to the login.component.ts file from the LearnWith app.
A user tries to login by clicking a login button. The login button executes the onLogin() method. Add a
line to the method like this:
console.log('onLogin');
The console.log() statement will output some text, “onLogin”, to your browser’s web developer console.
In most browsers you can bring up the web developer console using F12. What this does is show you
that your button is properly hooked up to the method. Sometimes this is the first step you need to verify
when debugging your code.
Next, the method performs some validation to look for errors. The original implementation of the
method would do this:
if(errorFound == true){
alert(this.usernameError + '\n' + this.passwordError);
return;
}
If an error was found, then an alert would be displayed to the user and the processing of the method
would stop with the return. Eventually, the alert was replaced with displaying errors directly on the
HTML form, which is better user experience. However, the alert was a great first step that could be
used to prove that validation was working properly.
After input validation, the onLogin() method is to execute the authenticate() method in the
AuthenticationService object. This method creates a parameter object to pass into the remote service.
You can output the object to the console:
console.log(parameters);
Attempting to login has two console outputs and a single alert. Load the main LearnWith app, and click
the login button without entering a username or password. You’ll see this:
The Chrome developer console will look like this:
The second console.log() statement, displaying the parameters object, never ran because there was an
error detected, and the return statement prevented the method execution from continuing.
If I enter a username and password, then click login again, this is what the console will look like:
You see the onLogin() method, and then the parameter string is output to the console. After that, you
see a few log statements from the login success method. The result object returned from the server is
output to the screen. You see the arrow next to the object; that means you can drill down into the
object’s contents to examine what is there. The object returned from the server has two properties: an
error property and a resultObject property. The embedded resultObject property is another object,
representing the user who logged in. The other two options you see on the screen are both ‘__proto__’.
These relate to JavaScript’s Object-Oriented model, which uses prototypes instead of classes, but is
beyond the scope of this book.
When I started doing JavaScript in the mid-1990s, using alerts was a very common debugging technique.
Unfortunately, alert boxes interrupt the application processing flow and make the user dismiss the alert
box before they can continue to use the application. This is fine if your intent is to provide the user with
feedback that they can’t ignore. For debugging information, that is less than ideal. The console.log()
statements are superior because they do not interrupt the application flow and do not require input
from the user to continue. You could also, theoretically, leave them in for deployment without affecting
the users of the application.
Review the Network Tab
Sometimes you want to review the network calls that pass between your user interface and your server.
Many browsers provide this feature built in as part of their web developer tools, and they are all similar.
I want to take a deeper look at the Chrome tools which I use daily. This is the Chrome network tab when
loading the login page for the LearnWith Task Manager application:
It tells you the file that was loaded; the type of request; the results of the request and what file made
the request; and even what line of the file initiated the request. It also gives you the file size and the
time it took to load the file. Although not obvious in the above screenshot the Waterfall can give you a
good feel for how your page loads:
The red line shows where the page was fully loaded. Before the red line you see the length of time it
took for the specific file to load. After the red line you’ll often see a lot of templates and service calls
made by the Angular libraries, since Angular setup does not start until after all the pages and libraries
were downloaded. Roll over one of the options to see more details:
This tells you what time the request was sent; how long it took to download and how long it waited to
download. This is interesting information if you are trying to debug network issues that may cause long
app load times.
You can use the console to drill into specific details of a request. Just click the file in the network console
and the details are brought up. Here is the request that is used to authenticate the user:
On the right side you see all the details. The form parameters that were sent are in the form data
section. You can also see details on the response:
The preview tab shows the results; in this case it gives you a JSON object you can drill into. The response
tab will give you similar data:
If you are ever having problems with your service calls not working; this is a great way to drill down into
the requests to help diagnose the problem. You can also use this tool to find out if your page is loading
invalid files, or files from the wrong path. Sometimes one typo in a path name will prevent items from
loading and break your whole application.
Use a Step Debugger
I find one of the most useful debugging tools is a step through debugger. Step through debuggers allow
you to add breakpoints to code and to manually execute each line so you can review variables and other
values. Most browsers have a JavaScript debugger built in to the browser as part of their web developer
tools, but for the sake of this article I will focus on the Chrome debugger, because it is the one, I use the
most.
To use the Chrome debugger, first open the web developer tools and then click on the source tab:
The source screen of Chrome developer tools has a tabbed list of files. In the screenshot above, I have
two TypeScript files shown, the login.component.ts and the authentication.service.ts. Even though the
browser won’t run the TypeScript directly, our use of map files allows us to review the TypeScript code.
On the right side of the screen are a bunch of different options, such as watch expressions, breakpoints
and other sections.
To the left of the login.component.ts you see a button that kind of looks like a play button from a tape
deck. Click it to open up the files view:
This panel shows you the location of all the files related to the current script. That includes map files,
source files, compiled JavaScript files. It even shows some libraries that were loaded. The ng:// section
contains a lot of the Bootstrap components and the HTML templates. You can use this browser to open
files not already open. The button on the top right side of the screen can be used to open or close the
panel on the right side of the original screenshot.
The main benefit of a debugger is to be able to stop code execution at certain points in code to review
the code and step through the code manually. The spot where the code stops is called a breakpoint. You
can add a breakpoint by clicking on the line number next to a segment of code:
You see in the screenshot that the breakpoint is now highlighted in blue, and the breakpoint shows up in
the breakpoints section of the right panel. I put the breakpoint in the first line of the onLogin() method.
This method is executed when the use clicks the login button of the login screen of the LearnWith
application.
You can also see scope variables, which are all the variables that are defined, and accessible from the
current method. Here is a more expanded, detailed, version of the scope variables section:
The local variables are defined in the method, and some such as errorFound have not been defined yet.
The Closure section relates to specific functions available to the method. For example, the
authentication_service_1 is the Angular function that created the AuthenticationService object which
was injected into the component. The global section refers to a lot of globally accessible variables.
Above the scope, call stack, watch sections you’ll see a row of buttons that allow you to control how the
debugger works:
Icon Description
Resume: After execution is stopped at a specific breakpoint, this button will continue
execution for the current line.
Step Over: This button continues onto the next line in the current script, ignoring any
other function calls
Step Into: This continues onto the next executing line. If the next line is in another
function, then the code will jump into that function.
Step Out: This button will continue onto the next line of executing code after the current
function call completes. Sometimes I’ve seen this button called “step return” because it
immediately returns the results of the function call back to whatever piece of code asks
for it.
Deactivate Breakpoints: This button is a helper button that will deactivate all
breakpoints. I use this most commonly during an application start up procedure when I
don’t want to step into the debugger until the user is ready to start interacting with the
application.
Pause on Exception: This button tells the debugger to immediately stop execution when
the JavaScript code has an error. This can be helpful when you’re trying to diagnose
errors, or when you come up with errors you didn’t even know existed. This button has
three different states; to pause on no exceptions, to pause on all exceptions, and to
pause on uncaught exceptions.
Most of the buttons in the table should be familiar to you if you’ve used a debugger with other
technologies.
Another important aspect of a step through debugger is the ability to set up watch expressions. The
watch expression section is in the top right of the window tree. In this window you can specify any
expression you wish, and see the results. Here is the watch window with a few watch variables:
I added a simple watch expression, which displays the value of the errorFound variable. Then I added
two conditions, one for errorFound== true and the other for errorFound == false. The watch expression
will show the results of the expression. Click the plus sign on the right of the header bar to create a new
watch expression; then type the expression or variable into the input at the bottom of the watch pane.
You can create expressions of any type you need, but mostly I just enter variable names.
Debugging Angular with Augury
Augury is a chrome browser plugin that allows you to introspect an Angular application. At times I find it
useful to use when debugging Angular applications. This section will tell you bit about it.
Install Augury
You can install Augury, just like any other Chrome Browser Plugin. Go to the Augury listing in the
Chrome web store, click the “Add to Chrome” Button and you’re off. Load an Angular application, such
as the Learn With application you built in the books and you’ll see this:
• Component Tree: The component three shows a list of all components in the current view and
how they relate to each other.
• Router Tree: The router tree will give you information about the current route.
• NgModules: The NgModules tab lists all NgModules in the application and their related
dependencies.
Look at NgModules
Open up the NgModules tab. This is the least interesting tab:
What is good about this one is that you can see all the modules in your applications, as well as their
providers, exports, declarations. This is primarily intended as a reference. It can be helpful if you’re
trying to figure out where that service is coming from, or if you remembered to declare your new
component.
The left side contains a list of all components used in the current view. This drills down and shows the
hierarchy, including the text inputs inside the login component. The right side contains details about the
component. In this case we see that the AppComponent has a single instance variable, title, with a
value of ‘task-manager’. We can click right into the source’s components if we want to open it up in the
source pane.
This is more interesting because it includes the services we injected, such as the router and the
userModel. We can drill into those to find out the properties available to use from the service. It also
includes all the properties inside the component. This is a great way to see the current state of the
component and it can help you debug things especially state related bugs.
I want to show you how complex things can get. Login into the app, and drill down into the
DataTablesComponent:
You can see the full structure of the application’s main screen on the left side including our custom
components, forms, inputs , and select boxes. The right side shows the dependencies in the
DataTableComponent including what services are injected and at what level they are injected. This can
be useful when debugging larger applications.
The main AppComponent contains the router-outlet, and injects the information from all these other
routes into that spot. You can mouse over one of the routes to get more info:
The data property in the popup would refer to a route resolver, something we did not use in the main
book, but is a way to load data or run other code before the view loads.
Making use of Augury can be a helpful debugging tool when you are trying to introspect the state of
your app’s components and to fix the reason why things are going wrong.
Final Thoughts
You should be ready to use these tools to debug your JavaScript application. We discussed using
console.log() or alert to output information to the browser. It showed you how to use the Chrome’s
network tab to view the requests that the browser sends to the server. Finally, it taught you the
concepts behind using Chrome’s JavaScript debugger. The debugger is perhaps the most important tool
when debugging application. There are even ways to integrate browser debuggers into IDEs, such as
IntelliJ; however, that is beyond the scope of this book. I looked at Chrome, but most modern web
browsers have similar debugging features for web developers.
Chapter4: Using Redux with Angular
The JavaScript ecosystem is full of a lot of frameworks and libraries that you can tie together to create
your Application. One of the more popular frameworks is Redux, which is used for managing state.
Redux is most commonly associated with React but ngrx exists as an Angular implementation too. This
chapter will give you an overview of Redux, an example of how to use the library, and then show you
how to use ngrx to implement Redux within an Angular application.
• Actions: Actions are a collection of data that you send to the Store. The object must have a type
value to define the type of action. The action in Redux is similar to the EventEmitter you’ve
probably used in your Angular applications, and are passed to the store using a dispatch()
function.
• Action Creators: Action Creators are functions that return an action. Action Creators do not
dispatch the action, only return it. It is a way to encapsulate your functionality for creating an
action.
• Reducers: Reducers are a function that update the state of the application. It accepts the
current state, and the action you are performing. Reducers should be pure functions, meaning
they do not have any side-affects such as changing the arguments or calling non-pure functions.
The reducer does not change the current state of the application because that would be a side
effect. It only creates, and returns, a new state object.
• State: The state is an object that stores data for your application, such as items in a shopping
cart, the user who logged in, or other information.
• Store: The store is a single object that holds all the application state. It can return the state
using a getState() function. It registers or unregisters listeners using a subscribe() function. And
it updates state via dispatch() function.
As we progress through this chapter, we’ll cover each item in more detail.
1. Start in the top middle. You have a view which is waiting for action. This could be a user
entering data or clicking a button. Technically this doesn’t even have to be a view but could be
any code in your app, such as the result handler of a remote service call.
2. When an action occurs, the view will call the dispatch() method on the store and send in the
action object.
3. The store will, in turn, call the reducer() function. The current state and the new state are
passed in as arguments. The reducer function will do some magic and return the app’s new
state.
4. The store saves the new state.
5. Then the store calls any listener functions. At some point your code must have called
store.subscribe() so it could get notified when something changed.
6. The listener functions run code and update the view. If they need access to the current state
they can call the getState() method on the store.
</body>
</html>
Instead of using NodeJS and Build Script tools, I’m just pulling it out from an Internet based CDN because
it is the simplest way. Create the reducer:
<script>
function reducer(state, action) {
}
</script>
The reducer is the function that will run when actions are dispatched to the store. It takes in the current
state and the action, then runs code to determine what the new state will be. A reducer should not
have any side effects. That means it should not modify the existing state. The first step is to make a
copy of the existing state:
var newState = Object.assign({}, state)
On first run of the application, no existing state will be defined, so we’ll check for that and set some
defaults:
if (typeof state === 'undefined') {
newState = {
helloUser : ''
}
}
The state of the application is just a JavaScript object. I included one property on that object, helloUser
and defaulted it to an empty string. Once we start dispatching events, we’ll revisit this function to
handle those event types. Until then, just return the new state:
return newState;
We’re going to set this up so that when the user types in the input, the text will automatically update in
the span. I’ve seen similar samples used to demonstrate how binding works in Angular.
Let’s switch back to JavaScript and create a function to render the UI:
function renderHello() {
document.getElementById('helloValue').innerHTML =
store.getState().helloUser.toString()
}
The renderHello() function accepts no arguments. It uses the JavaScript document object and
getElementByID() function to retrieve the helloValue span. Then it populates it with the helloUser
string in the state. We retrieve the state from the store using the getState() functionality. Be sure to
call the renderHello() function immediately to render the function in the default state:
renderHello()
Whenever the state changes, we want to run this renderHello() function. We do that with the
subscribe() function on the Redux store:
store.subscribe(renderHello)
Now, when the state changes, the renderHello() function will be called, thus causing the UI to update.
Whenever the username input is modified, we want to dispatch an action to the store so that the
reducer() function runs, which will return the modified state, and then run the renderHello() listener to
modify the UI. This is the code:
document.getElementById('username').addEventListener('keyup', function (e) {
store.dispatch({ type: 'NAME_MODIFIED', value : e.target.value})
})
Without a framework, such as Angular, it is a bit complex to listen for events on elements, but we grab
the username input with getElementByID() and then listen for the keyup event. We use
store.dispatch() to send a NAME_MODIFIED action. By convention, the type of the action is a constant
put in all caps, but here we are hard coding it a bit.
This code is after the undefined check, and before the return statement. It is a switch statement that
checks for the action’s type, and does actions based on that. At this stage in the application, we only
have a single event, but we’ll add one more as we iterate over this code.
As you type your name the text will appear after the hello.
Saying Goodbye
With every hello comes a goodbye. We are going to create a button that will say goodbye to the user,
and then clear out the helloUser value from the store. The purpose of this sample is to add more
objects in the store.
I added a goodbye button, and a span for the goodbyeValue. Back to the JavaScript, handle a click on
the goodbyebtn:
document.getElementById('goodbyebtn')
.addEventListener('click', function (e) {
store.dispatch({ type: 'NAME_LEFT',
value : store.getState().helloUser})
})
This dispatches an action. The type is NAME_LEFT. The value is the current helloUser.
Let’s go back to our reducer function and add some code to handle this new action:
case 'NAME_LEFT':
newState.goodbyeUser = action.value;
newState.helloUser = '';
break;
It is just another case statement in the switch. We set a new value on the state to goodbyeUser and set
the helloUser value to an empty string. When we set the default, near the beginning of the reducer()
method, be sure to initialize the goodbyeUser state property:
if (typeof state === 'undefined') {
newState = {
helloUser : '',
goodbyeUser : ''
}
}
This just uses getElementByID() to get the span for the goodbyeValue. It sets the innerHTML to the
stores goodbyeUser property. Make sure you subscribe:
store.subscribe(renderGoodbye)
This can be simplified with some code tweaks. First, define the initial state:
var initialState = {
helloUser : '',
goodbyeUser : ''
}
In a real-world app, I’d probably create the state as its own class, and pass a new instance of the state
into the method as the default argument. Such details are not needed for this proof of principle.
Let’s start by creating a reducer for the hello portion of this app:
function helloUser(state = '', action){
var newState = state;
switch (action.type) {
case 'NAME_MODIFIED':
newState = action.value;
break;
case 'NAME_LEFT':
newState = '';
break;
default:
}
return newState
}
I created a new function named helloUser(). Notice that this function is named the same name as the
state property. This is done on purpose, and you’ll find out why in the next section. This function has a
similar signature to the reducer. It accepts a state and an action. However, it does not accept the full
state object. We’ll send in the specific value for the helloUser state, which is just one branch of the
main state object. This reducer function only focuses on handling its own state, not other states.
The helloUser() reducer only operates on the helloUser state variable. Notice it defaults the newState
to an empty string in the command line signature. We might do something more complex if we were
using an object or other data type. This reducer has to handle both major actions. If the name is
modified, it sets to the new name. If we said goodbye to the user, then the helloUser resets to an
empty string.
This reducer only needs to operate on the goodbyUser state. As with the helloUser reducer, and as
such only needs to respond to the NAME_LEFT action. In that case, it takes the value from the action
and sets it to the new state. The NAME_MODIFIED state is ignored here.
This automatically returns an object, representing the new state. The helloUser property is given the
results of the helloUser() reducer. The goodbyeUser property is given the results of the goodbyeUser()
reducer. Run the app and you’ll see it working.
Redux will automatically know how to combine these reducers. Try the app again, and you’ll find that it
works.
Using ngrx
The ngrx library is an implementation of Redux that is used for Angular applications. I’ll recreate the
previous sample using Angular and ngrx to show you how it all comes together.
This will setup the new, default, Angular application inside the redux-sample directory:
• NAME_MODIFIED: This action manes the user input is modified, and we change the “Hello
Name” text in the app’s display.
• NAME_LEFT: This action means we say goodbye to the person. The “Goodbye Name” is
changed and the “Hello Name” is deleted.
In our vanilla JavaScript example, we did not create individual classes here the actions, but it makes
more sense to do so in a real application. I put both actions in a single class:
import {Action} from "@ngrx/store";
This reducer mirrors what we previously created in the vanilla JS sample. It accepts two arguments, the
state and the action. The state is set to the initialState value if passed in as null. A copy of the current
state is created, because the reducer will never modify the existing state, it will always return a new
state. Finally, there is a case statement, which is used to change the helloUser and goodbyeUser values.
As part of the import section of the @NgModule metadata, load the StoreModule:
imports: [
BrowserModule,
AppRoutingModule,
StoreModule.forRoot({ hello : reducer}),
],
This uses the forRoot() syntax that is common when loading one module into another. It accepts a
single object, which is our state object. It contains a single property, named ‘hello’ and its value is the
reducer function.
While we’re in here, let’s add Angular’s FormModule. First, import it:
import {FormsModule} from "@angular/forms";
We’ll use this when creating the form to collect the user’s name.
First get rid of the title variable in the AppComponent, then add some imports:
import {select, Store} from "@ngrx/store";
import {Observable} from "rxjs/index";
import {NAME_LEFT, NAME_MODIFIED} from "./actions/name.action";
import {helloGoodByeState} from "./reducers/reducer";
There is a lot to dissect here. First, we bring in the Store and select from the ngrx library. The store is a
service used to access the store. The select is a function used to retrieve an element from the store.
Observable’s should be a known quantity by the time you’re reading this, so there is no need to expand
on that. We also bring in our two actions and the interface for the state. We’ll use all of these in the
code.
Now let’s move to the AppComponent class. Let’s create some variables:
nameInput : string;
The nameInput will be used to collect the user’s input for the first name. We want to say hello to this
person.
goodByeOutput : string;
The goodByeOutput value will be used to store the last person we said goodbye to. It will be an
argument into the NAME_LEFT event.
storeReturn : Observable<any>;
The storeReturn will contain a reference to the store and will be an Observable that we can subscribe to
update our values.
The constructor uses Angular’s dependency injection syntax to inject the Store service into the
component. That gives us access to the store. Then we use select() to get a specific value out of the
store—hello in this case. The hello property references the property we defined when we set up the
reducer in the main NgModule. The select() returns an Observable that we can use in the class, or the
view, to retrieve the store’s current state. We subscribe to it next, using it to update the goodByeOut
value.
This function dispatches the NAME_MODIFIED event, whenever the name input is changed. We use
store.dispatch() to tell the store that the event has changed, and ngrx will call the reducer under the
hood to update the app’s state, which will trigger our observable. I did not create a specialized Action
Creator for this sample but would probably do so in a more advanced setup. The value passed to the
event is the input that the user entered for the name.
This acts similarly to the onNameChange() method. It calls store.dispatch() to tell the store that an
event occurred. The event type is NAME_LEFT, and the value is the goodByeOutput variable. We are
updating goodByeOutput whenever the helloUser variable is updated.
Create the View
Open up the app.component.html file in the app directory. Delete everything in there so you’ll have a
blank file. First, create the input box for collecting the name input from the user:
Enter Your Name:
<input id="name" [(ngModel)]="nameInput" (keyup)="onNameChange()">
<br/><br/>
The ngModel is used to tie the input’s value to the nameInput class variable. And the keyUp event is
used to run the onNameChange() function, which will in turn dispatch an event to the store.
We output the storeReturn using the async pipe. The async pipe is used to handle the Observable in the
view. Since the storeReturn is an object, we use the question mark operator to drill down into the
helloUser property if we the object is returned. Without this we would get a runtime error when
storeReturn is null.
There is nothing special here, when the button is clicked, the onGoodbyeClick() method in the class is
executed. Finally, display the goodbyeUser, as returned from the state:
Goodbye {{(storeReturn | async)?.goodbyeUser}}
<br/><br/>
Remember the input into this is not a full state object, but just one piece ofe the state. Since the
helloUser state is a simple string, this method just checks for the event type and sets the value
according. If we are processing the NAME_MODIFIED event, we set the new name. If we are processing
the NAME_LEFT event, we clear the name by setting it to an empty string.
Let’s do the same for the goodbye piece of the state. Here is the goodByeReducer() function:
export function goodbyeUserReducer(state : string = '', action: NameAction) {
var newState = state;
switch (action.type) {
case 'NAME_LEFT':
newState = action.value;
break;
default:
}
return newState
}
The goodByeUser() state is also a simple string. It only changes for the NAME_LEFT event, where we
give it the value of the event action.
Previously, we gave the single reducer a name, and then introspected the object. Now we are using
multiple reducers, each with its own name. When modifying the view code, we’ll have an easier time
because we’re returning simple values instead of having to introspect an object.
The nameInput and goodByeOutput values are set up identically to the previous sample. The
storeReturn observable is replaced with nameReturn and goodbyeReturn.
The method signature has not changed, and the store is still injected into the component. We use the
store service to select the helloUser value. By subscribing to that we save the goodByeOutput value for
the NAME_LEFT event. The goodbyeReturn value is selected using the goodbyeUser slice of the store.
Let’s turn our attention to the app.component.html file modifications. The name input does not need
to change:
Enter Your Name:
<input id="name" [(ngModel)]="nameInput" (keyup)="onNameChange()">
<br/><br/>
Since the store select is now returning a string value instead of an object, we don’t have to drill down
into it to get our value. We still need to use the async pipe, because nameReturn is still an observable.
The Goodbye button does not need tochange:
<button id="goodbyebtn" (click)="onGoodbyeClick()">GoodBye</button>
<br/><br/>
The changes here are similar to the “Hello User” changes. We now have a single piece of the store,
goodbyeReturn that returns the string value we need. As such we don’t need to introspect the store
object to get the specific value.
Final Thoughts
Having a global store for your application, and a single point of reference for your application’s state is a
cool idea that I’d love to see implemented more in applications. However, at the time of this writing, I
find the documentation on ngrx sorely lacking, and I feel I barely touched on the surface of the library. I
focused this article on the store, but there are other related libraries—just very little information on
what they are or what they’d be used for. I’m not sure I could recommend using it in a project, due to
the lack of tutorials or documentation.
Chapter 5: Standalone Components
Angular 14 introduced the concept of a stand-alone component. A stand-alone component is one that
does not need a module. These are designed to help simplify the boiler plate when building Angular
applications, and to make them more accessible to users.
We’re asking the Angular CLI to generate a component named view1. You’ve seen this before if you got
this far in the series, but let’s dissect each section of the command:
This response is pretty common from generating other components, but you will notice that the main
app.module.ts file is not updated. This is because standalone components do not need to be added to
the module.
Let’s look at the generated component code. First, open the src/app/view1/view1.component.ts file:
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-view1',
standalone: true,
imports: [CommonModule],
templateUrl: './view1.component.html',
styleUrls: ['./view1.component.css']
})
export class View1Component {
}
• selector: This specifies how we’ll use the component in a view. You’re used to this from
traditional components.
• standalone: This is a new property that tells Angular that this component is a standalone
component. The value is true or false. I believe it is false by default. We can also use this new
flag on directives and pipes, in addition to components.
• Imports: This is a new property for components, and it is modeled after the same property on a
module. This specifies the other dependencies that this component has. By default we’re
importing the CommonModule, which contain the fundamentals of the Angular framework.
You may need to import other sub components here that you want to use.
• templateUrls: This is legacy and specifies the HTML template that belongs to this component.
Traditional components already use this property.
• styleUrls: This is an array of related CSS files that belong to this component. This one is also
legacy and you should be familiar with it.
Three other files were created. The view1.component.css will start out as an empty file. The
view1.component.spec.ts is a testing file, and we’ll loop back to it later in this article. The
view.component.html is the template file:
<p>view1 works!</p>
Now, open up the app.component.html file, remove all the default contents, and add it:
<app-view1></app-view1>
What surprised me about the learning is that the main module still exists, and it is required that you
import the component to use it. It looks like the component just moved from the declarations to the
imports section. There is a way to load an application without specifying a main module, which I’ll cover
later in this chapter.
Using Multiple Components
Let’s jump into a sample where multiple standalone components work together. One of my favorite
examples when demonstrating components, is to create one component that modifies a piece of data
and another component that displays it. I’m starting this sample with a brand-new project, creating
using `ng new`.
This component will accept a single input, and display it in the view template.
Let’s look at the component default state. Open the display.component.ts file
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-display',
standalone: true,
imports: [CommonModule],
templateUrl: './display.component.html',
styleUrls: ['./display.component.css']
})
export class DisplayComponent {
}
For simplicity, we’re going to use ngModel to attach an input to a variable, and to do that we need to
import the FormsModule. In the old school way, we’d do that as part of an angular module, but here
we can add it directly to the imports config of the component:
imports: [CommonModule, FormsModule],
Just a single input of the type “text”. It uses ngModel with double binding to keep track of the
component. It also uses the ngModelChange event to make a call to an onValueChange() method.
Switch back to the edit.component.ts file to create the method:
onValueChange() {
this.valueToEditChanged.emit(this.valueToEdit);
}
This method simply dispatches the event, passing in the new value.
We’ve created two components, be sure to import them both into the app.module.ts:
imports: [
BrowserModule,
AppRoutingModule,
DisplayComponent,
EditComponent
],
We’re almost ready. Now we open up the app.component.html file. Delete all its contents, and load in
our two components:
<h1>Edit</h1>
<app-edit (valueToEditChanged)="display.valueToDisplay = $event"></app-edit>
<h1>Display</h1>
<app-display #display ></app-display>
First comes the edit component. It listens to the valueToEditChanged event and runs an in-line
function. The inline function makes a change to the display component, tweaking the valueToDisplay
property. The display component doesn’t have any formal properties set, however I did give it the name
display using the #display syntax.
I’m setting up the component communication in the HTML for simplicity in this sample. Normally I try to
avoid adding any sort of business logic—even simple logic--in the HTML, because it makes things harder
to test.
Go back to your console, run ng serve and take a look at the browser:
The default state has no typed in text, and therefore no display text. Start typing and you should see the
display automatically update:
All good!
Using Providers
Providers, or services as they are sometimes called, are a way to share code across multiple components
in an Angular application. Stand Alone Components support them just as well, and I want to rework the
previous sample to use a provider instead of the input property and output event.
By default, the service is created to be injectable in the root, which means we do not have to set it up in
a module, or at an individual component level. Both traditional components, and standalone
components allow for the provider metadata to be set up at the component level.
The constructor users Angular’s dependency injection to inject the shared service into this component.
Don’t forget the TypeScript import:
import { SharedService } from '../shared.service';
That references the variable we just deleted, so replace it with a reference to the variable inside the
sharedService:
{{sharedService.value}}
onValueChange() {
this.valueToEditChanged.emit(this.valueToEdit);
}
}
We define a property to hold the value to be edited. We have an output event, and a method to deal
with changes. This is no longer needed for this sample, so delete them all. Add a constructor:
constructor(public sharedService: SharedService) {
}
The constructor injects the SharedService using the same dependency injection approach the
DisplayComponent used. And we need the same import:
import {SharedService} from "../shared.service";
We deleted the valueToEdit, so the ngModel reference needs to be changed. Also, we no longer need
the ngModelChange event handler:
<input type="text" [(ngModel)]="sharedService.value">
In this case, we’re listening to the valueToEditChanged event on the EditComponent and using that to
modify the valueToDisplay in the display component. None of this is needed since both components
are sharing a reference to the same service, so we can clean this up:
<h1>Edit</h1>
<app-edit></app-edit>
<h1>Display</h1>
<app-display></app-display>
I know this second iteration of the same example looks like a lot less code, but I always like to caution
people about relying on global services, as they hinder our ability to reuse that component. Reusability
is not always a concern, but you have to be cognizant of the tradeoffs your making with your
architecture decisions.
How to use Embedded Standalone Components
It is very common in a real-world app for one component to be nested inside another. Can we do this
with standalone components? Absolutely! Let’s create a new component that wraps our display and
edit components. For this, I’m going to continue to iterate over the previous sample.
We want to import our other two stand alone components as part of the Component annotation
configuration object:
imports: [CommonModule, DisplayComponent, EditComponent],
Delete all this, and replace it with what we had added to the app.component.html previously:
<h1>Edit</h1>
<app-edit></app-edit>
<h1>Display</h1>
<app-display></app-display>
Now, let’s open the app.component.html file, and you’ll still have the above. Replace it with this:
<app-wrapper></app-wrapper>
Let’s open up the app.module.ts file. Previous we had imported the DisplayComponent, and
EditComponent as part of the module:
imports: [
BrowserModule,
AppRoutingModule,
DisplayComponent,
EditComponent
],
However, we no longer need to do that, since they are only used as children to the
WrapperComponent, so let’s remove those and add the Wrapper:
imports: [
BrowserModule,
AppRoutingModule,
WrapperComponent
],
From here, we should be able to re-run the code and see this working. Point your browser at
localhost:4200 after restarting your dev server:
Start typing and see the updates:
I’m starting to feel a bit guilty about reusing screenshots here, but you’re not paying for printing by the
page, so I think we’re good.
Component Level Services
In the previous example we are still using a shared service between the two components, but the
service is set up at a global level. Open up the shared.service.ts file:
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class SharedService {
value: string = '';
constructor() { }
}
It is the providedIn configuration in the Injectable metadata that tells Angular to load the service
globally. However, the service is only being used by children of the wrapper component, so we don’t
need to define it globally. Let’s define the provider as a provider as part of the wrapper component,
which should share it with all its children—even the standalone components.
Now, jump over to the wrapper.component.ts file. As part of the component metadata, add in a
provider:
@Component({
selector: 'app-wrapper',
standalone: true,
imports: [CommonModule, DisplayComponent, EditComponent],
templateUrl: './wrapper.component.html',
styleUrls: ['./wrapper.component.css'],
providers: [SharedService]
})
In earlier versions of Angular we would have to define the providers at the module level always. I’m not
sure when they allowed for component level providers, but I like it a lot of services which are used to
share data across multiple components that make up a single screen.
Restart your Angular CLI dev server and load it in the browser. Type in the input and you’ll see the
output continue to update, even with this minor code restructure.
Routes and Lazy Loading
My favorite use case of stand-alone components is to lazy load elements through the router. In the past,
if we wanted to lazy load a component or section of an app, we’d have to point it to a module. You’d
have to do something like this:
const routes: Routes = [
{
path: `subsection`,
loadChildren: () => import(`./subsection/subsection.module`).then(
m => m.SubsectionModule
)
}
];
The module would have to load all components, providers, and could even include it’s own router. I
always found this to be a lot of boilerplate code just to implement lazy loading. Now with a standalone
component, we can make it simpler, which I love.
First, let’s create a simple routes that does not include lazy loading. Open up the app-routing.module.ts
file:
const routes: Routes = [
{ path: 'view1', component: WrapperComponent },
{ path: '**', redirectTo: 'view1' }
];
The first route is named view1, and points to WrapperComponent. I also added a catch all route,
distinguished by the two asterisks in the path. The catch all route redirects to the view1.
With this approach, we can remove the component import from the app.module.ts. Instead of this:
imports: [
BrowserModule,
AppRoutingModule,
WrapperComponent
],
You can see the route in the browser at view1. Start typing to make sure it all works:
All good to go; so, we’ve proved that the stand-alone components are working fine with the router. But,
the use of the router here mirrors what we did in the main books, which did not investigate lazy loading
at all. How do we set up lazy loading of the module?
Back to the app-routing.module.ts and replace the view1 route with this:
{
path: `view1`,
loadComponent: () => import(`./wrapper/wrapper.component`).then(
m => m.WrapperComponent
)
},
The syntax is very similar to the lazy loading segment I shared earlier. Instead of a loadChildren()
method, we now use a loadComponent() method. The value is an arrow functions. The arrow function
uses the import command to load in the component. The import returns, surprisingly, a promise. We
resolve the promise with the then() method, and the result handler returns the component.
You can re-run the app right now to see that it is continuing to work, but to see the real power of lazy
loading, let’s create another route and view. Let’s create a new component:
ng generate component view2 --standalone
We’re not going to implement a lot of functionality in the View2Component, only using it to link
between the two respective views. Take a look at the default view2.component.ts:
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-view2',
standalone: true,
imports: [CommonModule],
templateUrl: './view2.component.html',
styleUrls: ['./view2.component.css']
})
export class View2Component {
}
To tell Angular to link between routes, we’ll need to load in the RouterModule as part of the imports
configuration:
imports: [CommonModule, RouterModule],
Open up view2.component.html. Replace all the contents with a link back to View1:
<a [routerLink]="['/view1']">Goto View1</a>
Let’s go back to View1 which is powered by WrapperComponent. First, we’ll need to tell it about the
RouterModule in the component config imports:
imports: [CommonModule, DisplayComponent, EditComponent, RouterModule],
This is the same as what we did in the View2Component. We’ll made a similar addition in the
wrapper.component.html:
<a [routerLink]="['/view2']">Goto View2</a>
I added a link to view2 in front of the other two components in the WrapperComponent.
Now, open up the app-routing.module.ts. I’m going to show you the two cases, and how the browser
interacts differently. First, with no lazy loading setup:
const routes: Routes = [
{
path: 'view1', component: WrapperComponent
},
{
path: 'view2', component: View2Component
},
{ path: '**', redirectTo: 'view1'}
];
We’ve already explored the view1 route and the catch all redirect route. This includes a view2 route,
which loads the View2Component. Now reload the app in the browser and review the network tab:
We load most of the same files. One thing you’ll notice is that main.js is a lot smaller. And there is also a
an additional js file loaded, which corresponds to the WrapperComponent. The end result is that we
have less data loaded on the initial app load—which means your users are using the application much
quicker.
I didn’t clear out the network tab before clicking, but primarily you see View2Component loaded, but
not until we clicked on the link to load that view.
It is beyond the scope of this article to go into the benefits and specifics of lazy loading, but let’s just say
I love this use case for stand-alone components, because it is something that clearly removes a lot of
boilerplate.
Bootstrap an Application without using a Module
The default application for an Angular CLI is still to put everything within a module, but that is no longer
required using standalone components. I’m going to continue iterating over the previous sample, and
this is going to be rough. First delete the app.module.ts and app-routing.module.ts. I’m serious, it’s
okay to do that even if it violates everything you know about building Angular applications. It made me
uncomfortable too.
We want to make this a standalone component. First, add the standalone flag:
@Component({
selector: 'app-root',
standalone: true,
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
We’ll need some imports here too, so import the RouterModule, which is needed to support the router-
outlet in the app.component.html view:
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterModule],
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
Conveniently we do this with the bootstrapApplication() method. You’ll want to import this from the
platform-browser package that is part of Angular:
import {bootstrapApplication} from '@angular/platform-browser';
This is a good start, but you’re probably wondering how we define the routes; which were previous
done in their own module using RouterModule.forRoot({). An alternate option has been provided for
standalone components now, using a provide function, in this case providerRouter(). We can set up
providers as part of a config object in the bootstrapApplication() function:
bootstrapApplication(AppComponent, {
providers: [
provideRouter(routes),
]
});
You can use this providers array to set up other providers, if need be. Be sure to import the
providerRouter() function:
import {provideRouter, Routes} from "@angular/router";
While we’re at it, I imported the Routes type. One thing we’re missing is defining the routes. Previously
they were defined inside the app-routing.module.ts, but with that gone I added them directly to
main.ts:
export const routes: Routes = [
{
path: `view1`,
loadComponent: () => import(`./app/wrapper/wrapper.component`).then(
m => m.WrapperComponent
)
},
{
path: `view2`,
loadComponent: () => import(`./app/view2/view2.component`).then(
m => m.View2Component
)
},
{ path: '**', redirectTo: 'view1' }
];
These routes are copied verbatim, with no changes, from the old routing module.
Now rerun the app with `ng serve` and point your browser to localhost:4200:
Your WrapperComponent loads fine, it’s two sub components are loaded and typing in the input proves
that the output works and the two components communicate through the service.
This whole approach is interesting to me, and I suspect in the near term we’ll see a mix of stand alone
components used against traditional module components, but I’m not sure what the future of Angular
holds.
Testing a Standalone Component
I don’t have a lot of functionality in my components to provide a deep dive into testing. For that, see
the other book that has a full chapter dedicated to that exclusively. But, I did want to share how the
tests for standalone components are set up.
If you’re like me, you notice that the TestBed is imported, which you’ll remember is used to configure
modules. At least right now, testing modules are still use to test standalone components.
These two will work together to provide us with an instance to the component.
The TestBed is used to create a module, and the component is listed in the imports section—not the
declarations. This mirrors what we saw when using standalone components earlier. A component is
created the fixture is grabbed; then from there we get the componentInstance. Finally, we trigger
detectChanges() on the fixture to start the component’s redraw cycle.
This is a simple default test to validate the component’s existence. Finally, we close out the describe
block:
});
If you’ve done testing with Angular Component classes before, very little is new here.
Let’s tackle them one by one. The first error, shown above, is because we converted the AppComponent
to a standalone component, but never modified the tests. Open up app.component.spec.ts and find
where the testing module was configured:
await TestBed.configureTestingModule({
imports: [
RouterTestingModule
],
declarations: [
AppComponent
],
}).compileComponents();
We need to move the AppComponent from the declarations to the imports. And we can delete the
declarations since there is nothing left in it:
await TestBed.configureTestingModule({
imports: [
RouterTestingModule, AppComponent
],
}).compileComponents();
If you think two steps ahead of me, you probably realized we’ll have to do the for the EditComponent.
Open up edit.component.spec.ts and find the same module setup:
await TestBed.configureTestingModule({
imports: [ EditComponent ],
providers: [SharedService]
})
.compileComponents();
And in this file you’ll want to import the SharedService with the rest of the TypeScript imports:
import { SharedService } from '../shared.service';
Rerun the tests, and at least we’re getting fewer errors, but they aren’t gone yet:
This error tells us that the SharedService tests do not know how to find the SharedService provider.
This is because we removed the providedIn root config option on the service class. As such we need to
specify the class as a provider in the testing module. Open up shared.service.spec.ts, find the testing
module creation and add the providers array:
TestBed.configureTestingModule({
providers: [SharedService]
});
One of the fun things with writing code without updating the tests as you go is that you run into a lot of
errors. To fix this one, we’ll need to import the RouterTestingModule into the WrapperComponent’s
test module. Open up wrapper.component.spec.ts:
await TestBed.configureTestingModule({
imports: [ CommonModule, WrapperComponent, RouterTestingModule ]
})
.compileComponents();
Be sure to load the TypeScript imports for both the CommonModule and the RouterTestingModule:
import { CommonModule } from "@angular/common";
import { RouterTestingModule } from '@angular/router/testing';
We’ll need to do the same setup for the View2Component, so open up view2.component.spec.ts:
await TestBed.configureTestingModule({
imports: [ CommonModule, View2Component, RouterTestingModule ]
})
.compileComponents();
This last failure is actually a failing test and not a config issue. Open up the app.component.spec.ts and
find the test:
it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('.content span')?.textContent)
.toContain('New app is running!');
});
This test fails correctly. It checks for values in the rendered template, but we changed those values. You
can delete this test entirely from the file. Now everything should run successfully:
I hope you learned something from this exercise. The important piece is that testing of a standalone
component is currently done through a testing module, but you import the component instead of
declaring it. It sounds a bit non-intuitive, so I wonder if they’ll find a new way to do this in the future.
Final Thoughts
I learned a lot writing this chapter, and I hope you learned a lot reading it. I’m not sure how I feel about
standalone components yet, but I do see them as a one way to make Angular applications easier to
build, especially around lazy loading. I also see a lot of potential using them in libraries, where you want
to optimize a component for reuse.
Afterword
I wrote this series to document my own learning process building HTML5 applications. The main series
focuses on building an application from start to finish. This book focuses on all the surrounding concepts
you’ll probably need to know on the job. I hope you benefited from this book.
If you enjoyed this book, we invite you to post a review on Amazon.com. Reviews help independent
authors like me because Amazon uses the number of reviews as a factor to help us show up in their
search results.
If you want more information, be sure to check out www.learn-with.com. You can test the application
we created in this book, get the source code for each chapter, get the most up to date version of the
books, and browse some of our other titles which build the same app using different technologies.
Check out the other bonus books to this series, along with some of the screencasts we have released.
If you need personal mentoring or have a custom consulting project, we’d love to help out, so please
reach out.
Send me an email at jeffry@dot-com-it.com and tell me how this book helped you become a success.