Home Web Development Architecting Angular Applications with Redux, RxJS, and NgRx

Architecting Angular Applications with Redux, RxJS, and NgRx

books-svg-icon Book
eBook $39.99 $27.98
Print $48.99
Subscription $15.99 $10 p/m for three months
$10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
BUY NOW $10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
eBook $39.99 $27.98
Print $48.99
Subscription $15.99 $10 p/m for three months
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
  1. Free Chapter
    Quick Look Back at Data Services for Simple Apps
About this book
Managing the state of large-scale web applications is a highly challenging task with the need to align different components, backends, and web workers harmoniously. When it comes to Angular, you can use NgRx, which combines the simplicity of Redux with the reactive programming power of RxJS to build your application architecture, making your code elegant and easy to reason about, debug, and test. In this book, we start by looking at the different ways of architecting Angular applications and some of the patterns that are involved in it. This will be followed by a discussion on one-way data flow, the Flux pattern, and the origin of Redux. The book introduces you to declarative programming or, more precisely, functional programming and talks about its advantages. We then move on to the reactive programming paradigm. Reactive programming is a concept heavily used in Angular and is at the core of NgRx. Later, we look at RxJS, as a library and master it. We thoroughly describe how Redux works and how to implement it from scratch. The two last chapters of the book cover everything NgRx has to offer in terms of core functionality and supporting libraries, including how to build a micro implementation of NgRx. This book will empower you to not only use Redux and NgRx to the fullest, but also feel confident in building your own version, should you need it.
Publication date:
March 2018
Publisher
Packt
Pages
364
ISBN
9781787122406

 

Quick Look Back at Data Services for Simple Apps

Welcome to the first chapter of this book. You have hopefully picked up this book because you have experienced issues setting up the architecture of your Angular application. Your application has grown and in that process you slowly feel your are losing track of what your application knows at a given point, what we call the state of the application. There might be other issues, such as parts of your application not being in agreement with what they know. An update that happened in one part may not have been applied to some other part and you scratch your head, thinking should it be this hard and is there a better answer?

It's entirely possible you are just picking up this book as you have heard about NgRx as the way to structure your application and you are curious and want to know more.

Regardless of which motivation drives you to read this book, this book is about learning to structure your application and learning how to set up and communicate your application's state, and its changes, in a way that all parts of your application are in agreement on what is happening. The underlying architectural pattern for NgRx is Redux, which constrains data to live in only one place and ensures data is flowing in only one direction. We will have a chance to cover Redux in more depth in a dedicated chapter in this book.

To get to a point where we have learned to master NgRx, we first need to pick up some paradigms and patterns along the way. We need to build a good foundation of knowledge. A good foundation consists of learning concepts such as Functional Reactive Programming (FRP), the architectural pattern Flux, and a new and exciting way of thinking about async concepts, Observables.

So why are these relevant for our learning journey of mastering NgRx? The Flux pattern has a lot in common with Redux and it is its shortcomings that led to Redux being created. NgRx itself is implemented using RxJS, which promotes a functional reactive style of programming. So you see, the foundations we are about to explore all help us grasp the theory and motivations behind NgRx.

In this chapter, we lay the foundations for the book by discussing the well-known Model-View-Controller (MVC) pattern. To verify we understand the MVC pattern, we use the Angular framework to make things easier. As interesting as it is to talk about architecture, if you don't see it applied to something real, it might be hard to grasp.

We continue diving into an application workflow in Angular and its Dependency Injection machinery. Before concluding the chapter, we will also have a look at how to fetch data through an API because, after all, that is where the data comes from and should flow to.

In this chapter, we will:

  • Describe the building blocks of the MVC pattern
  • Describe MVC in Angular and the core constructs that help support it
  • Review the HTTP service and how to deal with Ajax
 

Model-View-Controller – the pattern we all know

Regardless of whether you have been a programmer for a year or 20 years, you have almost certainly encountered the MVC pattern in some way, shape, or form. The pattern itself, MVC, consists of three interconnected parts: model, view, and controller. More important than knowing all its parts is knowing what problem it solves. It solves the problem of separation of concerns by decoupling view logic, data logic, and business logic. The MVC pattern has given rise to, among others:

  • Model-View-Adapter (MVA)
  • Model-View-Presenter (MVP)
  • Model-View-ViewModel (MVVM)

Cohesion and coupling – establishing a common language

Without a pattern like MVC, your code could turn out to be hard to maintain as it could have low cohesion and high coupling. Those are fancy words, so what do we mean? Cohesion is about focus and what the class should do. The lower the cohesion, the more different things are performed by a class and therefore it has no clear intention of what it should perform.

The following code shows what happens when a class has low cohesion; it does a lot more than storing data about an invoice, such as being able to log to a file or talk to a database:

Invoice
details
total
date
validate()
print()
log()
saveToDatabase()

Now we have introduced new dedicated classes and moved methods out of the Invoice class to make sure that each and every class now has high cohesion, that is, is more focused on doing one thing well. We therefore now have the classes Invoice, Printer, Logger, and InvoiceRepository:

Invoice
details
total
date
validate()

Printer
print(document)

Logger
log()

InvoiceRepository
saveToDatabase(invoice)

The point I am trying to make here is that a class should only do one thing well. This is illustrated by the unfocused Invoice class being split into four different classes that each do only one focused thing well.

So that deals with cohesion/focus. What about coupling? Coupling is about how strongly connected a software element is to another software element. Ultimately, the higher the coupling, the harder/more tedious it is to change. Let's look at the following example of high coupling written in Java:

// cohesion-and-coupling/invoice-system.java

class Printer {
print(Invoice invoice) {
String total ="";
total += invoice.getTitle();
total += invoice.getDetails();
total += invoice.getDate();
//print 'total'
}
}

class Invoice {
String title;
String details;
int total;
Date date;
public String getTitle() { return this.title; }
public String getDetails() { return this.details; }
public String getDate() { return this.date; }
}

public class Program {
private Printer printer = new Printer();
public void run(ArrayList list) {
for(int i=0; i< list.length; i++) {
Object item = list.getItem(i);
if(item instanceof Invoice) {
Invoice invoice = (Invoice) item;
printer.print(invoice);
}
}
}

public static void main(String [] args) {
ArrayList list = new ArrayList();
list.add(new Invoice());
Program program = new Program();
program.run( list );
}
}

There are multiple problems with this code, especially if you aim to change the code in any way. Let's say we wanted to print an email as well. It is tempting to think we would need an Email class and need to add another print() method override to the Printer class. We would also need to add branching logic to the Program class. Furthermore, testing the Program class cannot be achieved without causing a side-effect: calling the run() method would cause an actual call to a printer. The way we tend to work with tests nowadays is to run our tests every time the code changes, which it might do quite a lot as we are developing our program. We might end up with thousands of printed papers just developing our code. For that reason, we need to isolate ourselves from side effects when developing code and tests. What we want to test at the end of the day is that our code behaves correctly, not that the physical printer seems to work.

In the following code, we see an example of high coupling. We add another type, Email. The purpose of doing that is to see the effects of doing so, which is that we need to add code to several places at once. Having to do so is a sign of a code smell. The fewer changes you need to make, the better it usually is:

// cohesion-and-coupling/invoice-systemII.java

class Email {
String from;
String to;
String subject;
String body;
String getSubject() { return this.subject; }
String getFrom() { return this.from; }
String getTo() { return this.to; }
String getBody() { return this.body; }
}


class Invoice {
String title;
String details;
int total;
Date date;
String getTitle(){ return this.title; }
String getDetails() { return this.details; }
Date getDate() { return this.date; }
}

class Printer {
print(Invoice invoice) {
String total ="";
total += invoice.getTitle();
total += invoice.getDetails();
total += invoice.getDate();
//print 'total'
}

print(Email email) {
String total ="";
total += email.getSubject();
total += email.getFrom();
total += email.getTo();
total += email.getBody();
}
}

class Program {
private Printer printer = new Printer();
run(ArrayList list) {
for(int i=0; i< list.length; i++) {
Object item = list.getItem(i);
if(item instanceof Invoice) {
Invoice invoice = (Invoice) item;
printer.print( invoice );
} else if( item instanceof Email ) {
Email email = (Email) item;
printer.print( email );
}
}
}

public static void main(String [] args) {
ArrayList list = new ArrayList();
list.add( new Invoice() );
list.add( new Email() );
Program program = new Program();
program.run( list );
}
}

So let's rearrange the code a bit:

// cohesion-and-coupling/invoice-systemIII.java

class Email implements IPrintable {
String from;
String to;
String subject;
String body;
String getSubject() { return this.subject; }
String getFrom() { return this.from; }
String getTo() { return this.to; }
String getBody() { return this.body; }
public String getContent() {
String total = "";
total += email.getSubject();
total += email.getFrom();
total += email.getFrom();
total += email.getBody();
return total;
}
}

class Invoice implements IPrintable {
String title;
String details;
int total;
Date date;
String getTitle() { return this.title; }
String getDetails() { return this.details; }
String getDate() { return this.date; }
public
String getContent() {
String total = "";
total += invoice.getTitle();
total += invoice.getDetails();
total += invoice.getDate();
return total;
}
}

interface IPrintable {
String getContent();
}


interface IPrinter {
print(IPrintable printable);
}

class Printer implements IPrinter {
print( IPrintable printable ) {
String content = printable.getContent();
// print content
}
}

class Program {
private IPrinter printer;
public
Program(IPrinter printer) {
this.printer = printer;
}

run(ArrayList<IPrintable> list) {
for(int i=0; i< list.length; i++) {
IPrintable item = list.getItem(i);
printer.print(item);
}
}

public static void main(String [] args) {
ArrayList<IPrintable> list = new ArrayList<IPrintable>();
Printer printer = new Printer();
list.add(new Invoice());
list.add(new Email());
Program program = new Program(printer);
}
}

At this point, we have made our program open to extension. How can we say that, you ask? Clearly, we have removed the printer methods from printer. We also removed the switch logic from the method run in the Program class. We have also added the abstraction IPrintable, which makes anything printable responsible for telling a printer what the printable content is.

You can clearly see how we went from high coupling to low coupling when we introduced the types Document and Note. The only change they cause is themselves being added and implementing the IPrintable interface. Nothing else has to change. Success!

// invoice-systemIV.java

class Document implements IPrintable {
String title;
String body;

String getContent() {
return this.title + this.body;
}
}

class Note implements IPrintable {
String message;

String getContent() {
return this.message;
}
}

// everything else stays the same

// adding the new types to the list
class Program {
public static void main(String[] args) {
list.add(new Note());
list.add(new Document());
}
}

OK, so to sum up our changes:

  • We added the IPrintable interface
  • We simplified/removed the branching logic in the Program.run() method
  • We made each printable class implement IPrintable
  • We added some code at the end of the previous snippet to demonstrate how easy it would be to add new types
  • We injected an IPrinter through the Program class constructor to ensure that we can easily test the Program class

In particular note that we did not need to change any logic in either Printer or Program, when adding the Document and Note types. The only thing we needed to do was add Document and Notes as classes and ensure they implemented the IPrintable interface. To put emphasis on this, any addition to a program should not lead to an overall system change in the code.

Let's reiterate the last bullet of adding IPrinter. Testability is a very good measurement to see whether your code has low coupling. If you depend on abstractions rather than actual classes, you are able to easily switch out one concrete class for another, while maintaining high-level behavior.

Another reason for switching Printer to IPrinter is so that we remove side effects from the program when we test our code. Side effects are when we talk to files, mutate states, or talk over the network for example. Testing the Program class means we want to get rid of a side effect such as actual printing and have it call something fake, or we would have a large stack of papers every time we run our tests. So to instantiate our Program class for the purposes of testing, we would write something like this instead:

// cohesion-and-coupling/invoice-systemV.java

class FakePrinter implements IPrinter {
print(IPrintable printable) { System.out.println("printing"); }
}

class Program {
FakePrinter fakePrinter;
Program(FakePrinter fakePrinter) {
this.fakePrinter = fakePrinter;
}

public static void main(String[] args) {
ArrayList<IPrintable> list = new ArrayList<IPrintable>();
Printer printer = new FakePrinter();
list.add(new Invoice());
list.add(new Email());
Program program = new Program(printer);
}
}

What we see from this code is how we shift from instantiating the Printer class (which prints to a real printer) to the Program class using an instance of FakePrinter. In a testing scenario, this is exactly what you would do, if wanting to test the Program class. What you most likely care about is the print() method being called with the correct arguments.

OK, so this was a pretty long way of expressing what low coupling is about. It is, however, important to establish what crucial terms such as coupling and cohesion are, especially when talking about patterns.

Explaining the components of MVC

Back to the MVC pattern. Using said pattern means we get high cohesion and low coupling; this is due to code being split into different layers with different responsibilities. View logic belongs in views, controller logic in controllers, and model logic in models.

The model

This is the crucial part of the application. This does not rely on any specific user interface but more defines the domain in which you operate. Rules, logic, and data live here.

The view

This can be anything from a native app view to a bar chart, or even a web page. The point is that it ultimately displays data from the model. There can be different views displaying the same thing, but depending on for whom they are designed, they might look different. An admin might see a totally different view than a user for the same information.

The controller

This is really the spider in the web. It is able to take input from the view or from the data and turn it into commands.

Interactions – the behavior between the components

All these three mentioned components act in different ways when talking to each other. A model stores data it is being given from the controller based on commands. A view changes its appearance based on changes happening in the model. A controller can send a command to the model based on a user interaction. One such example is a user deciding to browse between page-based records. A new set of data will need to be retrieved based on the new visual position.

These two basic flows are what mostly happens in an application-based on MVC:

  • User interaction: Controller sends command to Model => Model changes => View is updated
  • View asks for data: Controller sends command to Model => Model is created/changed => View is updated

MVC summary

A lot can be said about MVC and its many variants, but let's be content with what we have for now by summarizing the properties of the pattern that we identified:

  • Low coupling
  • High cohesion, separating presentation concerns from the model
  • Simultaneous development is possible; due to the existence of many layers, people can work in parallel on a task
  • Ease of change; because of how things are separated, adding future concepts or making alterations becomes easier
 

An MVC flow in Angular

Let's look at the following problems and how we solve them in Angular:

  • Creating and rendering model data to the screen
  • Learning how the MVC pattern maps to the Angular framework
  • Learning how we can structure an Angular application in different building blocks
  • Fetching data/persisting data

The model

The model in Angular is a plain class, as we are using TypeScript. It can look like the following code:

// mvc/MvcExample/src/app/product.model.ts

export class Product {
constructor(
private id: number,
private title: string,
private description: string,
private created: Date
) {}

method() {}

anotherMethod() {}
}

It is a plain TypeScript file, or rather an ES2015 module, not to be confused with an Angular module. We will discuss in the next main section what an Angular module is, in terms of setup and how it is consumed. For now, remember the model is a simple thing.

The component – a controller and a building block

In the context of MVC, the component is the V and C, the view and the controller. The component allows you to define either a separate template file or an inline template. The template is the view part.

The controller in this context is a component class file that handles user interactions and also fetches the necessary data for the template to display.

Components have come to be a central concept for a lot of frameworks that are popular today, such as React, Vue.js, and Polymer. A component can take inputs, which are either data or methods. It consists of a piece of code and an HTML template, which render interesting data, living on the component. A component in Angular consists of three major parts:

  • A decorator function
  • A class
  • A template

A component consists of a controller class and a template. It can play two different roles in an Angular application: either it can be the responder to the route or it can serve as a building block. In the first case, Angular will instantiate it when a new route happens and respond with that component. In the latter case, the component is created directly by existing as a child component within another component.

We will explain next what we meant by the previous paragraph.

First responder to a route

As mentioned, a component can be used as a responder to a route. So let's say the application routes to the /products route as a result of a user interaction, or programmatically. Angular's way of dealing with this is to associate the /products route with a component. With the help of a component's class and HTML markup, we are able to produce a piece of HTML containing our markup and data rendered together. Pointing out a component as a responder to a route, is done when defining the so-called route map, like so:

// example of what routing might look like

export const appRoutes: Routes = [
{
path: '',
component: HomeComponent
},
{
path: 'payments',
component: ProductsComponent,
data: { title: 'Products' }
}
]

Essentially, a route is defined as an object with path properties, pointing out our route, and a component property pointing to the responding component. We can attach other properties to the route, such as data, to give the responding components some initial data to render.

Used as a building block

Using a component as a building block means it will be part of another component's template. Essentially, it will be seen as that component's child. This line of thinking is quite natural and means that we can think of our application as a hierarchical tree of components. A component in Angular consists of a controller class and a template as we have mentioned previously. A typical component looks like so:

// an example component

@Component({
selector: 'example-component'
})
export class ExampleComponent {}

The @Component decorator function adds metadata to the class. This instructs Angular on how to create the component so that Angular can place the component in the DOM. This enables you to use it as a responder to a route or as your own custom element. The property selector is what decides what your component should be called, if used as a custom element. Example usage looks like the following:

// an example container component
@Component({
selector: `
{{ title }}
<example-component>
`
})
export class ContainerComponent {
title ="container component";
}

The fact that components can be used this way makes it easy to think about an app as consisting of a hierarchical tree of components. A Todo application could therefore look like the following:

AppComponent
TodoList
TodoItem
TodoItem
TodoItem
...

Let's start to create this app, starting with the AppComponent. As this is the topmost component, it is also referred to as the root component. The AppComponent should render the TodoListComponent in its own template, like so:

// mvc/MvcExample/src/app/app.component.ts

import { Component } from "@angular/core";

@Component({
selector: "app-root",
template: `
<todo-list></todo-list>
`,
styleUrls: ["./app.component.css"]
})
export class AppComponent {
title = "app";
}

The next step is defining the TodoListComponent and knowing that it should be able to render a number of TodoItemComponent instances within its template. The size of a list is usually unknown. This is exactly what the structural directive *ngFor is for. So that is what we will utilize in the following code as we define the TodoListComponent:

// mvc/MvcExample/src/app/todo-list.component.ts

import { Component } from "@angular/core";

@Component({
selector: "todo-list",
template: `
<h1>{{title}}</h1> <custom></custom>
<div *ngFor="let todo of todos">
<todo-item [todo]="todo" ></todo-item>
</div
>
` . // the view
})
export class TodoListComponent { // the controller class
title: string;
todos = [{
title: "todo1"
},{
title: "todo1"
}]
}

Here, we can see that we render out a list of todo items by looping out the todos array in the template, like so:

<div *ngFor="let todo of todos">
<todo-item [todo]="todo" ></todo-item>
</div>

We can see in the preceding code that we are rendering out the todo-item selector, which points to a TodoItemComponent that we are yet to define. Worth noting is how we pass it a todo object and assign it to an input property on the TodoItemComponent. The definition for said component is as follows:

// mvc/MvcExample/src/app/todo-item.component.ts

import { Component, Input } from "@angular/core";
@Component({
selector: "todo-item",
template: `<h1>{{todo.title}}</h1>`
})
export class TodoItemComponent {
@Input() todo;
}

Reasoning about which components should exist as part of which other components is something you are going to dedicate a lot of time to.

Components from an architectural standpoint

You are encouraged to create a lot of components in your Angular application. With the former section's example of creating a todo list application it was tempting to create an application that just consisted of one component, the AppComponent. This would have meant that one component would have been responsible for a ton of things, such as displaying todo items, saving said items, removing them and so on. Components are meant to be used to solve one thing well. That's why we created a TodoItemComponent which only job in life was to display a todo item. Same thing goes for the TodoListComponent. It should only care about displaying a list, nothing else. The more you split down your applications into small and focused areas the better.

NgModule – our new facade (and some other bits)

So far, we have talked about components in terms of them being dedicated to solving one task well. However, there are other constructs that can be used in Angular, such as pipes, directives, and services. A lot of our components will find themselves belonging to a common theme, such as products or user management and so on. When we realize what constructs belong to the same theme, we also realize that some of these constructs are constructs we want to use elsewhere in the application. Conversely, some constructs are only meant to be used in the context of the mentioned theme. To protect the latter constructs from unintended use, we would like to group them in a facade-like way and put a protective layer between the constructs and the rest of the application. The way to do that in pure ES2015 modules is to create a facade file, in which public constructs are exported and others are not, like so:

// an old facade file, index.ts

import { MyComponent } from 'my.component';
import { MyService } from 'my.service';

export MyComponent;
export MyService;

Imagine we have a directory consisting of the following files:

/my
MyComponent.ts
MyService.ts
MyOtherService.ts
index.ts

The intent of creating a facade file here is to ensure there is only one place from where you import all the constructs you need. In this case that would be the index.ts file. A consumer of the preceding directory would do the following:

// consumer.ts

import * as my from './my';
let component = new my.MyComponent();
let service = new MyService();

MyOtherService is not being exposed by the index.ts file though, so attempting to access it like we do in consumer.ts would lead to an error. You could theoretically specify the full path to the construct but you are supposed to be using the barrel. Barrels are usually meant to be used to easily access your constructs without having to write import statements that are five miles long, like so:

// index.ts
import { Service } from '../../../path-to-service';
import { AnotherService } from '../../path-to-other-service';
export Service;
export AnotherService;

// consumer.ts

// the long and tedious way
import { Service } from '../../../path-to-service';
import { AnotherService } from '../../path-to-other-service';

// the easier way using a barrel
import * as barrel from './index';
let service = new barrel.Service();
let anotherService = new barrel.AnotherService();

As you can see that barrel, index.ts is the one that is responsible for knowing where all your constructs are located. This also means that were you to move files around, changing directories for certain constructs, the barrel file is the only one where updating the paths to these constructs is needed.

The Angular way of dealing with this is to use Angular modules. An Angular module looks like the following:

// mvc/MvcExample/src/app/my/my.module.ts

import { NgModule } from "@angular/core";
import { MyComponent } from "./my.component";
import { MyPipe } from "./my.pipe";

@NgModule({
imports: [],
exports: [MyComponent],
declarations: [MyComponent, MyPipe],
providers: []
})
export class MyModule {}

The effect of putting MyComponent and MyPipe into the declarations property of the module is so that these components can be freely used within MyModule. For example, you can use MyPipe within the MyComponent template. However, if you want to use MyComponent outside of this module, in a component belonging to another module, you will need to export it. We do that by placing it in the array belonging to the exports property:

exports: [MyComponent]

Angular takes the concept of a module way beyond grouping. Some instructions in our NgModule are meant for the compiler so that it knows how to assemble the components. Some other instructions we give it are meant for the Dependency Injection tree. Think of the Angular module as a configuration point, but also as the place where you logically divide up your application in to cohesive blocks of code.

On the object sent to the @NgModule decorator, there are properties you can set that have different meanings. The most important properties are:

  • The declarations property is an array that states what belongs to our module
  • The imports property is an array that states what other Angular modules we are dependent on; it could be basic Angular directives or common functionality that we want to use inside of our module
  • The exports property is an array stating what should be made available for any module importing this module; MyComponent is made public whereas MyPipe would become private for this module only
  • The providers property is an array stating what services should be injectable into constructs belonging to this module, that is, to constructs that are listed in the declarations array
 

Using ES2015 modules

So far, we have mentioned that models are just plain classes. An ES2015 module is just one file. Within that file lives both public and private constructs. Things that are private are only visible within that file. Things that are public can be used outside said file. In Angular, Es2015 modules aren't used only for models but for all imaginable constructs such as components, Directives, Pipes, Services, and so on. This is because ES2015 modules are an answer to how we split our project into smaller parts, which provides us with the following benefits:

  • Many small files makes it easier to parallelize the work you do and have many developers work on it at the same time
  • The ability to hide data by, making some parts of your application public and some other private
  • Code reuse
  • Better maintainability

We have to remember what web development used to look like to understand these statements. When the web was young our JavaScript code more often than not consisted of one file. That quickly became a huge mess. There have been different techniques over the years to find a way to split up our app into many small files. Many small files have made it easier to maintain and also to get a good overview of what is going on, among many other benefits. There have been other issues though. As all these small files had to be stitched back together before being shipped with the app, a process called bundling, we suddenly had one giant file where functions and variables could by mistake affect each other due to naming collisions. A way to attack that problem is to deal with something called information hiding. This to ensure the variables and functions we created are only visible to certain other constructs. There are multiple ways, of course, to address this issue. ES2015 has a private by default way about them. Everything declared in an ES2015 is private by default unless you explicitly export it, thereby making it publicly accessible to other modules that import the aforementioned module.

So how does this connect to the previous statements? Any module system really allows us to maintain visibility in our project as it grows with us. The alternative is one file which is complete chaos. As for several developers working at the same time, any way of logically dividing up the app makes it easier to divide up the workstreams between developers.

Consuming a module

In ES2015, we use the import and from keywords to import one or several constructs like so:

import { SomeConstruct } from './module';

The imported file looks like this:

export let SomeConstruct = 5;

The basic operations involved, working with ES2015 modules, can be summarized as follows:

  • Define a module and write the business logic of the module
  • Export the constructs you want to make public
  • Consume said module with an import keyword from a consumer file

Of course there is a bit more to it than that, so let's look at what else you can do in the next subsection.

An Angular example

We have been using ES2015 imports extensively throughout this chapter already, but let's emphasize when that was. As mentioned, all constructs used ES2015 modules, models, services, components, and modules. For the module, this looked like this:

import { NgModule } from '@angular/core';

@NgModule({
declarations: [],
imports: [],
exports: [],
providers: []
})
export class FeatureModule {}

Here, we see that we import the functionality we need and we end up exporting this class, thereby making it available for other constructs to consume. It's the same thing with modules, like so:

import { Component } from '@angular/core';

@Component({
selector: 'example'
})
export class ExampleComponent {}

The pipe, directive, and filter all follow the same pattern of importing what they need and exporting themselves to be included as part of an NgModule.

Multiple exports

So far, we have only shown how to export one construct. It is possible to export multiple things from one module by adding an export keyword next to all constructs that you wish to export, like so:

export class Math {
add() {}
subtract() {}
}

export const PI = 3.14

Essentially, for everything you want to make public you need to add an export keyword at the start of it. There is an alternate syntax, where instead of adding an export keyword to every construct, we can instead define within curly brackets what constructs should be exported. It looks like this:

class Math {
add() {}
subtract() {}
}

const PI = 3.14

export {
Math, PI
}

Whether you put export in front of every construct or you place them all in an export {}, then end result is the same, it's just a matter of taste which one to use. To consume constructs from this module, we would type:

import { Math, PI } from './module';

Here, we have the option of specifying what we want to import. In the previous example, we have opted to export both Math and PI, but we could be content with only exporting Math, for example; it is up to us.

The default import/export

So far, we have been very explicit with what we import and what we export. We can, however, create a so-called default export, which looks somewhat different to consume:

export default class Player {
attack() {}
move() {}
}

export const PI = 3.13;

To consume this, we can write the following:

import Player from './module';
import { PI } from './module'

Note especially the first row where we no longer use the curly brackets, {}, to import a specific construct. We just use a name that we make up. In the second row, we have to name it correctly as PI, but in the first row we can choose the name. The player points to what we exported as default, that is, the Player class. As you can see, we can still use the normal curly brackets, {}, to import specific constructs if we want to.

Renaming imports

Sometimes we may get a collision, with constructs being named the same. We could have this happening:

import { productService } from './module1/service'
import { productService } from './module2/service'; // name collision

This is a situation we need to resolve. We can resolve it using the as keyword, like so:

import { productService as m1_productService }
import { productService as m2_productService }

Thanks to the as keyword, the compiler now has no problem differentiating what is what.

The service

We started this main section talking about how ES2015 modules are for all constructs in Angular. This section is about services, and services are no different when it comes to using ES2015 modules. Services we use should be declared in a separate file. If we intend to use a service, we need to import it. It needs to be imported for different reasons though, depending on what type of service it is. Services can be of two types:

  • Services without dependencies
  • Services with dependencies

Service without dependencies

A service without dependencies is a service whose constructor is empty:

export Service {
constructor(){}
getData() {}
}

To use it, you simply type:

import { Service } from './service'
let service = new Service();
service.getData();

Any module that consumes this service will get their own copy of the code, with this kind of code. If you, however, want consumers to share a common instance, you change the service module definition slightly to this:

class Service {
constructor() {}
getData() {}
}
const service = new Service();
export default service;

Here, we export an instance of the service rather than the service declaration.

Service with dependencies

A service with dependencies has dependencies in the constructor that we need help resolving. Without this resolution process, we can't create the service. Such a service may look like this:

export class Service {
constructor(
Logger logger: Logger,
repository:Repository
) {}
}

In this code, our service has two dependencies. Upon constructing a service, we need one Logger instance and one Repository instance. It would be entirely possible for us to find the Logger instance and Repository instance by typing something like this:

import { Service } from './service'
import logger from './logger';
import { Repository } from './repository';

// create the service
let service = new Service( logger, new Repository() )

This is absolutely possible to do. However, the code is a bit tedious to write every time I want a service instance. When you start to have 100s of classes with deep object dependencies, a DI system quickly pays off.

This is one thing a Dependency Injection library helps you with, even if it is not the main motivator behind its existence. The main motivator for a DI system is to create loose coupling between different parts of the system and rely on contracts rather than concrete implementations. Take our example with the service. There are two things a DI can help us with:

  • Switch out one concrete implementation for another
  • Easily test our construct

To show what I mean, let's first assume Logger and Repository are interfaces. Interfaces may be implemented differently by different concrete classes, like so:

import { Service } from './service'
import logger from './logger';
import { Repository } from './repository';

class FileLogger implements Logger {
log(message: string) {
// write to a file
}
}

class ConsoleLogger implements Logger {
log(message: string) {
console.log('message', message);
}
}

// create the service
let service = new Service( new FileLogger(), new Repository() )

This code shows how easy it is to switch out the implementation of Logger by just choosing FileLogger over ConsoleLogger or vice versa. The test case is also made a lot easier if you only rely on dependencies coming from the outside, so that everything can therefore be mocked.

 

Dependency Injection

Essentially, when we ask for a construct instance, we want help constructing it. A DI system can act in one of two ways when asked to resolve an instance:

  • Transient mode: The dependency is always created anew
  • Singleton mode: The dependency is reused

Angular only creates singletons though which means every time we ask for a dependency it will only be created once and we will be given an already existing dependency if we are not the first construct to ask for that dependency.

The default behavior of any DI framework is to use the default constructor on a class and create an instance from a class. If that class has dependencies, then it has to resolve those first. Imagine we have the following case:

export class Logger { }

export class Service {
constructor(logger: Logger) { }
}

The DI framework would crawl the chain of dependencies, find the construct that does not have any dependencies, and instantiate that first. Then it would crawl upwards and finally resolve the construct you asked for. So with this code:

import { Service } from './service';

export class ExampleComponent {
constructor(srv: Service) { }
}

The DI framework would:

  • Instantiate the logger first
  • Instantiate the service second
  • Instantiate the component third

Dependency Injection in Angular using providers

So far we have only discussed Dependency Injection in general, but Angular has some constructs, or decorators, to ensure that Dependency Injection does its job. First imagine a simple scenario, a service with no dependencies:

export class SimpleService {}

If a component exists that requires an instance of the service, like so:

@Component({
selector: 'component'
})
export class ExampleComponent {
constructor(srv: Service) {}
}

The Angular Dependency Injection system comes in and attempts to resolve it. Because the service has no dependencies, the solution is as simple as instantiating Service, and Angular does this for us. However, we need to tell Angular about this construct for the DI machinery to work. The thing that needs to know this is called a provider. Both Angular modules and components have access to a providers array that we can add the Service construct to. A word on this though. Since the arrival of Angular modules, the recommendation is to not use the providers array for components. The below paragraphs are merely there to inform you how providers for components work.

This will ensure that a Service instance is being created and injected at the right place, when asked for. Let's tell an Angular module about a service construct:

import { Service } from "./Service";

@NgModule({
providers: [Service]
})
export class FeatureModule{}

This is usually enough to make it work. You can, however, register the Service construct with the component class instead. It looks identical:

@Component({
providers: [Service]
})
export ExampleComponent {}

This has a different effect though. You will tell the DI machinery about this construct and it will be able to resolve it. There is a limitation, however. It will only be able to resolve it for this component and all its view child components. Some may see this as a way of limiting what components can see what services and therefore see it as a feature. Let me explain that by showing when the DI machinery can figure out our provided service:

Everybody's parent – it works: Here, we can see that as long as the component highest up declares Service as a provider, all the following components are able to inject Service:

AppComponent // Service added here, Can resolve Service
TodosComponent // Can resolve Service
TodoComponent // Can resolve Service

Let's exemplify this with some code:

// example code on how DI for works for Component providers, there is no file for it
// app.component.ts
@Component({
providers: [Service] // < - provided,
template : `<todos></todos>`
})
export class AppComponent {}

// todos.component.ts
@Component({
template : `<todo></todo>`,
selector: 'todos'
})
export class TodosComponent {
// this works
constructor(private service: Service) {}
}

// todo.component.ts
@Component({
selector: 'todo',
template: `todo component `
})
export class TodoComponent {
// this works
constructor(private service: Service) {}
}

TodosComponent – will work for its children but not higher up: Here, we provide Service one level down, to TodosComponent. This makes Service available to the child components of TodosComponent but AppComponent, its parent, misses out:

AppComponent // Does not know about Service
TodosComponent // Service added here, Can resolve Service
TodoComponent // Can resolve Service

Let's try to show this in code:

// this is example code on how it works, there is no file for it
// app.component.ts
@Component({
selector: 'app',
template: `<todos></todos>`
})
export class AppComponent {
// does NOT work, only TodosComponent and below knows about Service
constructor(private service: Service) {}
}

// todos.component.ts
@Component({
selector: 'todos',
template: `<todo></todo>`
providers: [Service]
})
export class TodosComponent {
// this works
constructor(private service: Service) {}
}

// todo.component.ts
@Component({
selector: 'todo',
template: `a todo`
})
export class TodoComponent {
// this works
constructor(private service: Service) {}
}

We can see here that adding our Service to a component's providers array has limitations. Adding it to an Angular module is the sure way to ensure it can be resolved by all constructs residing inside of that array. This is not all though. Adding our Service to an Angular module's providers array ensures it is accessible throughout our entire application. How is that possible, you ask? It has to do with the module system itself. Imagine we have the following Angular modules in our application:

AppModule
SharedModule

For it to be possible to use our SharedModule, we need to import it into AppModule by adding it to the imports array of AppModule, like so:

//app.module.ts

@NgModule({
imports: [ SharedModule ],
providers: [ AppService ]
})
export class AppModule{}

We know this has the effect of pulling all constructs from the exports array in SharedModule, but this will also concatenate the providers array from SharedModule to that of AppModule. Imagine SharedModule looking something like this:

//shared.module.ts

@NgModule({
providers : [ SharedService ]
})
export class SharedModule {}

After the import has taken place, the combined providers array now contains:

  • AppService
  • SharedService

So the rule of thumb here is if you want to expose a service to your application, then put it in the Angular module's providers array. If you want to limit access to the service, then place it into a component's providers array. Then, you will ensure it can only be reached by that component and its view children.

Up next, let's talk about cases when you want to override the injection.

Overriding an existing construct

There are cases when you want to override the default resolution of your construct. You can do so at the module level, but also at the component level. What you do is simply express which construct you are overriding and with which other construct. It looks like this:

@Component({
providers: [
{ provide: Service, useClass : FakeService }
]
})

The provide is our known construct and useClass is what it should point to instead. Let's imagine we implemented our Service like so:

export class Service {
no: number = 0;
constructor() {}
}

And we added the following override to a component:

@Component({
providers: [{ provide : Service, useClass: FakeService }]
})

The FakeService class has the following implementation:

export class FakeService {
set no(value) {
// do nothing
}

get no() {
return 99;
}
}

Now the component and all its view child components will always get FakeService when asking for the Service construct.

Overriding at runtime

There is a way to decide what to inject for/into a construct at runtime. So far, we have been very explicit about when to override, but we can do this with a bit of logic added to it by using the useFactory keyword. It works like the following:

let factory = () => {
if(condition) {
return new FakeService();
} else {
return new Service();
}
}

@Component({
providers : [
{ provide : Service, useFactory : factory }
]
})

This factory can in itself have dependencies; we specify those dependencies with the deps keyword like so:

let factory = (auth:AuthService, logger: Logger) => {
if(condition) {
return new FakeService();
} else {
return new Service();
}
}

@Component({
providers : [
{ provide : Service, useFactory : factory,
deps: [AuthService, Logger] }
]
})

Here, we highlighted the condition variable, which is a Boolean. There can be a ton of reasons why we would want to be able to switch the implementation. One good case is when the endpoint don't exist yet and we want to ensure it calls our FakeService instead. Another reason could be that we are in testing mode and by just changing this one variable we can make all our services rely on a fake version of themselves.

Overriding constants

Not everything, though, is a class that needs to be resolved; sometimes it is a constant. For those cases, instead of using useClass, we can use useValue, like so:

providers: [ { provide: 'a-string-token', useValue: 12345678 } ]

This is not really a class type, so you can't write this in a constructor:

constructor(a-string-token) . // will not compile

That wouldn't compile. What we can do instead is to use the @Inject decorator in the following way:

constructor( @Inject('a-string-token') token) // token will have value 12345678

The useValue is no different from useClass when it comes to how to override it. The difference is of course that we need to type useValue in our instruction to override rather than useClass.

Resolving your dependencies with @Injectable

We took a little deep dive into DI in the previous section, but almost forgot about a very important decorator, @Injectable. @Injectable is not strictly mandatory to use for services in general. However, if that service has dependencies, then it needs to be used. Failure to decorate a service with @Injectable that has dependencies leads to an error where the compiler complains that it doesn't know how to construct the mentioned service. Let's look at a case where we need to use the @Injectable decorator:

import { Injectable } from '@angular/core';

@Injectable()
export class Service {
constructor(logger:Logger) {}
}

In this case, Angular's DI machinery will look up Logger and inject it into the Service constructor. So, providing we have done this:

providers: [Service, Logger]

In a component or module, it should work. Remember, when in doubt, add @Injectable to your service if it has dependencies in the constructor or will have in the near future. If your service lacks the @Injectable keyword and you try to inject it into a component's constructor, then it will throw an error and your component will not be created.

This section set out to explain how DI works from a general standpoint and how it works in Angular. For the latter, it covered how to register constructs to work with Angular's DI machinery, but also how to override it. It is clear that the DI machinery is quite sophisticated. It can be scoped to the application level, by adding constructs to the providers array of Angular modules, but also to the component level and its view children. The main reason for describing the DI machinery was to teach you the possibilities of it, so you know how to best use it to your advantage when you define the architecture of your app.

 

Fetching and persisting data with HTTP – introducing services with Observables

So far, we have gone through a data flow where the component is our view to the outside world, but also the controller. The component uses a service to get the data, but also to persist it. The data, however, has up until this point lived in the service and that's not a very likely place for it to reside. Almost certainly, that data should be fetched and persisted to an endpoint. That endpoint is an exposed URL to a backend system published somewhere on the internet. We can use HTTP to reach said endpoint. Angular has created a wrapper on top of the vanilla way of fetching data through HTTP. The wrapper is a class that wraps the functionality of an object called XmlHttpRequest. The Angular wrapper class is called the HttpClient service.

Fetching data with the HTTP service

There is more than one way to communicate over HTTP. One way is using the XmlHttpRequest object, but that is a quite cumbersome and low-level way of doing it. Another way is to use the new fetch API, which you can read more about here: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API.

Angular has its own abstraction, the HTTP service, which can be found in the HTTPModule. To use it, simply import the HttpModule:

import { HttpClientModule } from '@angular/common/http';

@NgModule({
imports: [HttpClientModule]
})

Then, inject the HttpClient service where you want to use it, like so:

import { HttpClient } from '@angular/common/http';

@Component({
selector: 'consumer',
template: ``
})
export class ConsumerComponent {
constructor(private http:HttpClient) {}
}

At this point, we are ready to use it. Let's see a quick overview of what methods this HTTP service has:

  • get('url', <optional options param>) fetches the data for us
  • post('url', payload,<optional options param>) creates a resource
  • put('url', payload,<optional options param>) updates a resource
  • delete('url',<optional options param>) removes a resource
  • request is a raw request where you can configure exactly what call you want to make, what headers you want to add, and so on

When we use http.get() we get a construct back called an Observable. An Observable is just like the Promise, an asynchronous concept that enables us to attach callbacks to when the data arrives some time in the future, as well as attaching callbacks to an error when an error occurs. The RxJS implementation of the Observable comes packed with a number of operators that help us transform the data and interact with other Observables. One such operator is called toPromise() and enables us to convert an Observable to a Promise. With this, we can make HTTP calls in two different ways, or flavors. The first way is where we use the toPromise() operator and convert our Observable to a Promise, and the other is using our Observable and dealing with the data that way.

A typical call comes in two different flavors:

  • Using promises
// converting an Observable to a Promise using toPromise()
http
.get('url')
.toPromise()
.then(x => x.data)
.then(data => console.log('our data'))
.catch(error => console.error('some error happened', error));

This version feels familiar. If you need to brush up on Promises, have a look at the following link before continuing: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise. We recognize the .then() method as the method that is called when the data arrives and the .catch() method that is called when something goes wrong with our request. This is what we expect when, dealing with promises.

  • Using RxJS
// calling http.get() and gets an Observable back
http
.get('url')
.map( x => x.data )
.subscribe( data => console.log('our data', data))
.catch( error => console.error('some error happened', error))

The second version looks different. Here, we are using the .map() method in much the same way as we used the .then() method. This statement needs some explanation. Let's have a look at the promise flavor code one more time and highlight what we are saying:

http
.get('url')
.toPromise()
.then(x => x.data)
.then(data => console.log('our data'))
.catch(error => console.error('some error happened', error));

The highlighted portion is the method that is called when the data first arrives from the service. What we do inside of this call is to create a projection of the data, like so:

.then(x => x.data)

The subsequent call to then() just deals with printing the data to the console:

.then(data => console.log('our data'))

Let's now have a look at how the RxJS version differs by highlighting the projection part and the part where we print out our result:

http
.get('url')
.map( x => x.data )
.subscribe( data => console.log('our data', data) )
.catch( error => console.error('some error happened', error) )

The first line of our highlighted portion of the code indicates our projection:

.map( x => x.data )

The call to subscribe is where we print our data, like so:

.subscribe( data => console.log('our data', data) )

When we use http.get(), we get a construct back called an Observable. An Observable is just like the Promise, an asynchronous concept that enables us to attach callbacks to when the data arrives some time in the future, as well as attaching callbacks to when an error happens.

The Observable is part of a library called RxJS and this is what is powering the HttpClient service. It is a powerful library meant for more than just a simple request/response pattern. We will spend future chapters exploring the RxJS library further and discover what a powerful paradigm the Observable really is, what other important concepts it brings, and the fact that it isn't really only about working with HTTP anymore, but all async concepts.

 

Summary

We started this chapter by trying to explain how important it was to get a good foundation in application architecture in general, and for that reason we had a look at the MVC pattern. We then continued describing how the MVC pattern was somewhat used in Angular, even though it was called MVW, model view whatever. We did this to understand that the Angular framework consists of a lot of constructs that help us organize our application in a way that makes it easy to extend, maintain, and parallelize the work.

Angular brought a lot of new things to it though, such as ES2015 modules, which attempted to solve the problem of how to split up the code in a manageable way. After that, we argued that although ES2015 modules were great, there was a lot of ceremony attached to them when it came to creating complex objects. To help relieve us of that ceremony, we described how Angular Dependency Injection could be the solution to said problem. In reality, you will use ES2015 to import your constructs. What Angular DI helps us with is creating the dependencies needed for our constructs.

Lastly, we tied the knot of explaining the MVC pattern by simply stating that data doesn't really live permanently, in either the model, the controller, or the view, but can be retrieved and persisted by talking to an endpoint, reachable through HTTP. We concluded the chapter by describing how the Angular 4.x HTTP service can help us with just that.

All of this is interesting from an educational standpoint. It doesn't describe the elephant in the room, how do we manage our data when things gets complicated? The concerns we have to deal with are:

  • Bidirectional data flow
  • Lack of predictability (a change can lead to cascading changes)
  • Spread out state (there is no one source of truth and our components can sit on a state that is partially updated)

Let's keep these concerns in mind as we move on to Chapter 2, 1.21 Gigawatt – The Flux Pattern Explained.

Latest Reviews (14 reviews total)
Excelente información!!!!
Informative - keeps me reading it.
clear coverage of the material
Architecting Angular Applications with Redux, RxJS, and NgRx
Unlock this book and the full library FREE for 7 days
Start now