Angular tutorial
Angular is one of the first major Model-View-Controller (MVC) frameworks designed to run on browser JavaScript engines. Although web applications have long used the MVC design pattern [1], up until Angular's appearance, the use of MVC in web applications was constrained to server-side programming and languages like Python, PHP, Ruby and Java.
Angular emerged as a solution to Single Page Applications (SPA). The principle of SPA is to unify the functionality of multiple web pages -- dozens or hundreds -- into what's behaviorally a single page, creating a more fluid user experience -- like a desktop application -- instead of transitioning from one web page to another which requires full page refreshes. Inevitably, SPA require that this single page fulfill a lot more duties than an ordinary web page, in a language that's natively understood by web browsers (i.e. JavaScript).
To give you an initial idea of what SPA and Angular must fulfill in terms of additional duties, I'll enlist some of the high-level tasks involved in developing a web application using the MVC design pattern with a server-side language like Python, PHP, Ruby or Java.
- You create a controller to process requests for various web page URLs (e.g. homepage, profile, items).
- Inside a controller you can manipulate or enrich a request's data to satisfy business requirements, as well as interact with an application's models that represent the core data entities (e.g. people, products) for which the application was designed.
- To manipulate/enrich requests sent between controllers/views and controllers/models, you'll import libraries to avoid having to write complex logic from scratch (e.g. parsing text, mathematical calculations). If the manipulation/enrichment process is common enough, you can even export your own library for use in multiple controller locations or in different projects.
- Once a controller is done manipulating or enriching a request's data for a given URL it communicates the changes to either (or both) a view and model. Views are boilerplate files with HTML and placeholders that get substituted with data, where as models are data structures designed to maintain an application's entities. It's worth pointing out that in server-side MVC frameworks, models are typically database (DB) tables, where as in client-side MVC frameworks like Angular, models are typically JavaScript Object Notation (JSON) data strctures.
The terms may vary a little depending on the server-side technology, but this last sequence is what happens to a greater extent on all server bound web applications that use the MVC design pattern. Now envision this same sequence of events all taking place on a browser, this is what Angular helps you achieve and is a process that's illustrated in figure 10-1.
Figure 10-1. Angular architecture
You can see in figure 10-1 that once an Angular application is developed and placed on a server -- ready to be sent to an end user -- its first step consists of being delivered to a browser. Once delivered, an Angular application fulfills all MVC functionality directly on a browser without needing to contact the server again. Once the initial functionality of an Angular application is exhausted, an Angular application can make additional requests to the server to load new functionalities.
Angular: Its languages and versions
Because Angular is a framework, it's an amalgamation of logic and processes put together to achieve the end result illustrated in figure 10-1: a client-side MVC framework. And therein lies the conundrum for frameworks like Angular designed to run entirely on the client-side, namely browser JavaScript engines. On the one hand you want to leverage what a majority of browsers support and most developers know about, so as not to alienate people with a new programming language. But on the other hand you know the old programming language (i.e. JavaScript) comes with so many limitations and flaws -- as described in earlier chapters -- you need a new programming language to create more solid functionality for something as elaborate as a client-side MVC framework.
It turns out, Angular took the same approach taken by other projects confronted by JavaScript limitations, like those for running JavaScript outside of a browser. Although Angular applications end up being 100% JavaScript compatible -- as illustrated in figure 10-1 -- Angular is built entirely in TypeScript, which is one of many languages that produce JavaScript. With this approach, JavaScript purists can use Angular with JavaScript as they know it and those looking for more powerful and simpler approaches that JavaScript can't handle well (e.g. types, static checking, testing, modules) can use Angular with a more powerful language like TypeScript, that at the end of the day gets converted to JavaScript.
Although the Angular framework is developed in TypeScript and therefore 100% JavaScript compatible, there's also an Angular spin-off named AngularDart[2]. AngularDart is designed to create client-side MVC applications, but unlike its close relative built with TypeScript, AngularDart is built with the Dart language[3]. Dart is an object-oriented, client-side optimized language to build mobile, desktop, as well as server-side applications, with support for compilation to machine code and inclusively transpilation to JavaScript (i.e. convert Dart to JavaScript), however, because of the complete paradigm shift of using Dart vs. JavaScript/TypeScript for application development -- hence the need for an Angular/Dart spin-off -- I won't go into greater detail about Dart or AngularDart.
Angular uses a rather complex versioning system, that when combined with the history of Angular can make it difficult to grasp, so let's begin with a little bit of history. Angular was conceived as AngularJS, with the JS standing for JavaScript, since Angular was initially written in JavaScript. The second major version of Angular was re-written in TypeScript and with it the JS in Angular was dropped to simply Angular. In addition, shortly after this second major Angular release based on TypeScript, Angular was also spun out into a separate AngularDart framework based on the Dart language.
Therefore, AngularJS represents the first major Angular version and is no longer under active development, with its last feature release -- version 1.7 -- made in 2018 and set to only receive critical long term support through its end of life on June 30, 2021. AngularDart is a completely separate framework based on the Dart language with its own versioning scheme. Where as Angular, the TypeScript version of the framework, represents the Angular framework as it's known today and is the focus of this tutorial.
Angular has very aggressive releases with major versions scheduled every six months, with each version having 6 months of active support -- during which regularly-scheduled updates and patches are released -- and 12 months of long-term support (LTS) -- during which only critical fixes and security patches are released. In addition, Angular also ensures backward compatibility between two major versions of Angular, which means if you're using Angular 7 you can upgrade to Angular 8 without fear you'll break core components designed for Angular 7.
At the time of this writing Angular 8 is the latest release, so any steps outlined from this point on are based on the use of Angular 8.
Angular: Its deployment process
As you learned in this past section, Angular is written in TypeScript to facilitate the development of the framework itself and application development in general, all of which takes us to a central concern when developing TypeScript applications: deployment.
Because TypeScript is not a language that's natively understood by client-side browsers, it must be converted into JavaScript, which is a natively supported language by browsers through JavaScript engines. This creates two options to deploy Angular applications which are illustrated in figure 10-2.
Figure 10-2. Angular deployment architecture
At the center of figure 10-2, you can see that one way or another Angular development begins with TypeScript. However, once development is done, Angular TypeScript logic must either be transpiled into JavaScript or deployed 'as is'. The issue with deploying Angular TypeScript logic 'as is' -- as shown to the left-hand side of figure 10-2 -- is that it eventually requires a transpilation step into JavaScript, a task that ends up being delegated to end user browser JavaScript engines. Although deploying Angular TypeScript logic 'as is' is easier, it's an atypical process because it's inefficient and irregular, requiring end users to download a transpiler and perform the process themselves, in addition to being difficult to ensure all end user browsers are modern enough to support a TypeScript transpilation process. The right-hand side of figure 10-2 illustrates the more typical Angular TypeScript deployment process, which consists of performing the TypeScript transpilation process as part of the Angular development process. Although this last process of performing TypeScript transpilation as part of the Angular development process is more involved, requiring additional tools and work, it's generally the preferred route because it's more efficient and clean, ensuring end user browsers only need to work with plain JavaScript.
To make what I assume is your first time using Angular as digestible as possible, I'll split this tutorial into two major parts covering the ways in which you can deploy Angular: an entirely browser-based approach only recommended for developing and testing applications -- illustrated to the left-hand side of figure 10-2 -- as well as a build-based approach intended to deliver optimized applications for production environments -- illustrated to the right-hand side of figure 10-2. In addition, each section is further subdivided into Angular's UI (User Interface) component functionalities and Angular's services functionalities.
Part I: Angular running TypeScript directly on the browser
Listing 1 illustrates a self-contained web page with Angular's UI (User Interface) component functionalities which is what I'll use to start this discussion. Listing 2 illustrates another Angular application that builds on the one from listing 1, but illustrates Angular's services functionalities. By keeping things in two separate Angular applications, I hope there's a smaller probability you'll feel overwhelmed or frustrated trying to understand Angular's various concepts.
In addition, listing 3 shows the equivalent web page with jQuery so you can easily compare the differences -- note this jQuery example is the same one used as a baseline to introduce previous modern JavaScript frameworks like React.
Listing 1 - Angular web page with UI component functionality (Part I)
Listing 2 - Angular web page with services functionality (Part II)
Listing 3 - jQuery web page with main feature set
If you skim through the index.html
page in listing 1, you can see the web page structure is almost empty. With the exception of a lot of <script>
elements to import resources, there's a JavaScript inline System.
snippet -- which is part of the SystemJS module loader -- and the page's <body>
tag that just has the <banner-app>Loading...</banner-app>
element which is an Angular UI component.
SystemJS and Typescript
A module loader such as SystemJS has two purposes in Angular. The first reason is an Angular application has the potential to use dozens of JavaScript files/modules, so you're better off using a dedicated library to manage more than a couple of JavaScript files/modules. The second and more important reason for a module loader is it helps browsers load files/modules that aren't in JavaScript. Aren't in JavaScript ?! What do you mean ?!.
In a prior section I mentioned why we would use the TypeScript language -- a superset of JavaScript -- to create Angular applications. The thing with TypeScript is browsers don't know anything about it. TypeScript may be similar to JavaScript and get converted to JavaScript, but browsers don't know that. Therefore you need a way to convert TypeScript files/modules into JavaScript so browsers understand their meaning. This conversion process -- or as it's technically known transpiling -- is done by a module loader.
Near the top of the index.html
page in listing 1 you can see we import the system.js
library -- which gives us access to System.
calls -- as well as the typescript.js
library, which is tasked with converting TypeScript into JavaScript.
Listing 4 - SystemJS configuration
|
Moving to the SystemJS calls in listing 4 -- which are part of the index.html
page in listing 1 -- you can see the System.config
statement defines transpiler: 'typescript'
which configures the module loader to transpile files/modules from TypeScript. The additional System.config
arguments configure on which packages the transpiler is used -- in this case, the app
folder/package uses the transpiler with the default .ts
extension -- and transpiler options are set with the typescriptOptions
variable.
The System.import
statement is used to load a module onto the page. In this case, 'app/main'
indicates the app
folder/package and the main
file/module. And because the app
package is defined with a default .ts
extension in System.config
, it means the loader will look for the TypeScript file app/main.ts
, relative to index.html
. The final .then(...)
statement is a JavaScript promise that works to send all errors associated with the preceding module (i.e. 'app/main'
) to the console for debugging purposes.
I should mention doing the transpiling process on an end user's browser is not an efficient practice. The transpiling process -- particularly for production applications -- should be done as part of the development process. However, because you need to walk before you run, we'll let the end user's browser deal with transpiling for now. We've got a lot more to cover, but you just learned how to use a module loader to transpile TypeScript into JavaScript in a browser and how to load modules onto a page.
Initializing (a.k.a. bootstraping) Angular
You just read how the index.html
page in listing 1 uses the System.import('app/main')
to load the app/main.ts
module. Listing 5 illustrates the contents of the app/main.ts
file.
Listing 5 - Angular bootstrap of UI component
|
The first line in listing 5 imports the bootstrap
function which is used to initialize Angular UI components. Notice the bootstrap
function is imported with the syntax from 'angular2/platform/browser'
which is a module provided by Angular's core library (i.e. <script src="...angular2.min.js">
). The second line in listing 5 imports the Banner
class which is the actual Angular UI component. Notice the Banner
class is imported with the syntax from './banner.component'
, where the leading ./
indicates a relative path to the current file (i.e. app/main.ts
). This means there must be a file named app/banner.components.ts
with the Banner
UI component -- note the .ts
extension which is expected for all TypeScript modules under the app
package.
Finally, the third and last line in listing 5 bootstrap(Banner)
uses the two imports to say: Initialize/bootstrap the Banner
UI component.
The Angular initialization or bootstraping process in listing 5 is one of the simplest you can have. As an Angular application grows, this type of file in listing 5 inevitably grows to bootstrap more UI components, as well as incorporate other general facilities required by an Angular application. However, this process is the same for all Angular applications. You bootstrap Angular UI components as a way to add them to a web page and the UI components interact with other classes that provide services and business logic.
Now that you know Angular applications are always initialized by bootstraping UI components, lets take a look at an Angular UI component.
Angular UI component structure
An Angular UI component is declared as a class and decorated with the @Component
annotation to tell Angular it's a UI component. You use the class to define a component's fields & methods and the @Component
annotation to configure a component's behaviors.
In the past section Angular bootstraped the Banner
UI component located in the app/banner.components.ts
file, listing 6 illustrates this file that shows the structure of an Angular UI component.
Listing 6 - Angular UI component
|
Listing 6 starts with another import
statement for Component
which comes from Angular's core library and is used to decorate the class (i.e. mark it as an Angular UI component). Also notice the component class is preceded with the export
keyword to make it available to other modules. It's worth mentioning you should really get used to using import
and export
statements, they're a recurring practice that helps keep namespace and scope/visibility in check -- much like other languages (e.g. Java,PHP,Python,Ruby) require you to use similar import
and export
statements for the same purpose.
Now look at the Banner
class and its two fields: message
-- that will hold a display message -- and isBannerVisibile
-- that will control the visibility of an HTML element. More importantly notice the field syntax <field_name>: <data_type> = <field_value>
. This is TypeScript syntax to enforce type checked fields, something that's not possible with plain JavaScript. While you can keep using plain JavaScript (e.g. message = "Angular 101!";
) -- because TypeScript is a JavaScript superset, plain JavaScript is equally valid -- type checking is one of the many programming techniques that's useful to avoid potential bugs.
In addition, the Banner
class has the handleClick
method, which is invoked when a click is made on an HTML element in the Angular UI component. All this last method does is change the value of the isTheBannerVisible
field to its opposite boolean (e.g. if isTheBannerVisible
is false
, a call to handleClick
changes the isTheBannerVisible
field value to true
, and viceversa).
Next, lets analyze the @Component
annotation. Notice the @Component
annotation is declared right above the Banner
class, yes there are a lot of statements in curly braces {}
, but the @Component
's closing parenthesis ()
ends above the Banner
class, which tells Angular the class is a UI Component. The statements between curly braces {}
of @Component
though are very important.
The selector
field tells Angular on what web page element it should generate the UI component, which in this case corresponds to 'banner-app'
. If you go back and see the index.html
page in listing 1, you'll see precisely <banner-app>
is the only element on the page. So this means Angular will swap out the original <banner-app>Loading...</banner-app>
element on the page and replace it with the component's template:
value.
The first thing to note about the template
value in listing 6 is that it's mostly HTML markup, after all it will end up on a web page. The second thing to notice is the content is enclosed by backquotes or backticks (i.e. `
, the character on the same key as the tilde ~
, next to the 1/!
on the keyboard) which is an important syntax to abide by, otherwise a component won't work. The HTML markup has a few things that have special meaning in Angular and I'll describe next.
The <button>
element uses (click)="handleClick()"
. Angular treats parenthesis ()
in templates as events, so (click)
is Angular's syntax to hook into an HTML element's click event. In this case, when a click is made on the <button>
element, it will trigger the component's handleClick()
method, which as described previously changes the component's isTheBannerVisible
field.
To access a component's field value in templates, Angular uses double curly braces {{}}
. In listing 6 you can see the Click to {{isTheBannerVisible ? 'hide' : 'show'}} {{message}}
statement. The {{isTheBannerVisible ? 'hide' : 'show'}}
snippet is a ternary operator that literally outputs hide
or show
depending on the component's isTheBannerVisible
field value. The {{message}}
snippet simply outputs the value of the component's message
field value.
The last <div>
element in listing 6 uses [hidden]="!isTheBannerVisible"
. Angular uses square brackets []
to define directives, so [hidden]
tells Angular to apply the hidden
directive -- which is a handy way to show/hide an HTML element -- to the <div>
element. In this case, [hidden]="!isTheBannerVisible"
indicates the <div>
element should be hidden when the component's isTheBannerVisible
negated value (i.e. !isTheBannerVisible
) is true
. Since isTheBannerVisible
starts with true
, its negated value is false
, so the <div>
element starts off visible. But because the component's handleClick()
method -- assigned to the <button>
element -- changes the isTheBannerVisible
field value each time a click is made on the <button>
element, Angular hides and shows the <div>
thanks to the [hidden]
directive.
And we're done....with part I. Go back and check out the example in listing 1 once more to ensure you understand everything we've done up to this point, otherwise you might get lost in the upcoming sections as I'll build on what we've done up to this point.
Angular templates
The Angular UI component in listing 6 uses the template: ` `
statement to declare a UI component's markup. While this is workable, it isn't a good practice to mix a component's functional logic with its markup. Angular UI components also support the templateUrl
variable to define a UI component's markup in a separate file.
For example, you can use the templateUrl:'app/banner.template.html'
statement to tell Angular to load a component's markup from the app/banner.template.html
. Note the templateUrl
value is relative to the location of the page where the component is loaded (e.g. index.html) and not where a component is declared (e.g. app/banner.component.ts). So if a component is loaded on the index.html
page, it means the component's markup page should be located in app/banner.template.html
relative to index.html
.
In listing 2 you can see an Angular application that uses the templateUrl
technique, based on the same component in listing 6.
Angular dependency injection
Dependency injection plays a big role in Angular, it's one of those concepts you really need to grasp to understand Angular's potential. Dependency injection is rooted in the world of OOP (Object Orientated Programming) and is used as a mechanism to instantiate objects that are related to one another. Dependency injection needs to be built from the ground-up into a framework, so even if you have experience with OOP, unless you've used a framework designed with dependency injection (e.g. Spring for Java, PHP-DI for PHP) you've probably never used it. But now that you know Angular uses dependency injection, let's get to the details.
Dependency injection is easiest to understand with the Hollywood principle: "Don't call us, we'll call you". Translated to OOP parlance, it means you don't need to do the instantiation of a related object to use it, the instantiation is done for you. In OOP you typically do object instantiation with the new
keyword, however, this has a number of drawbacks particularly when there's a potential to work with dozens or hundreds of classes like an Angular application.
Directly instantiating a class with a syntax like new DataService()
in another class leads to maintenance problems. A target class that needs a DataService
instance only cares about getting an instance to do its work. But by using new DataService()
you're forcing a target class to care about how to create a DataService
instance, if one day the DataService()
constructor changes, the target class also needs to be updated. With dependency injection this isn't an issue, an instance is injected into a target class and the target class never knows or cares how an instance is created.
Another issue with instantiating a class with a syntax like new DataService()
is a new instance of the object is created every single time. The problem in this scenario boils down to re-usability. If you want to re-use an object instance multiple times (e.g. because it's a heavyweight process) you can't do it using new
every time. With dependency injection this also isn't an issue, an instance can be re-used because it's injected into a target class and the target class doesn't know where or how it was created.
Now let me expand on dependency injection further with a series of class interactions that use dependency injection and the same set without dependency injection.
- Window needs Frame
- Window creates Frame
- Window calls Frame
- Frame needs Glass
- Frame creates Glass
- Frame calls Glass
- Glass needs Glue
- Glass creates Glue
- Glass calls Glue
- Glue needs Brush
- ....
Non dependency injection
- Window needs Frame, which needs Glass, which needs Glue, which needs Brush
- Window creates Brush
- Window creates Glue and gives it Brush
- Window creates Glass and gives it Glue
- Window creates Frame and gives it Glass
- Window calls Frame
- Frame calls Glass
- Glass calls Glue
- Glue calls Brush
With dependency injection
Notice how the creation or instantiation process for the dependency injection sequence is inverted, so instead of multiple "parent creates child" a "parent first creates its great-great grandchild, then its great grandchild,etc". This is why dependency injection is cataloged as a type of inversion of control, since the control to create instances is inverted. This inverted process works by having the dependencies of each object injected as they're needed, instead of having to manually create the dependencies, hence the term dependency injection or the Hollywood principle: "Don't call us, we'll call you".
By now you might be saying to yourself this sounds awfully complicated, conceptually it might sound like it, but in practice -- which I'll get to in a minute -- it isn't, particularly because most of the work is done by the underlying framework.
Because Angular applications are built entirely with classes -- some are UI component classes and others are service classes for things like data and validation -- even the most basic Angular applications end up with dependencies between classes. Managing these dependencies via injection is a pretty natural and comfortable fit, particularly when you can have dependencies three or four levels deep (e.g. a UI component container class, that depends on a UI component table class, that further depends on a UI component cell class). The more classes and dependencies an Angular application has, the more you'll appreciate dependency injection instantiation.
Angular services and dependency injection in practice
Turning our attention back to the Angular UI component in listing 6, you see its data is hard-coded (i.e. the component's message
field is assigned the "Angular 101!"
value directly in the class). This isn't too realistic, because in the real world a component is more likely to get its data from a separate service and it also isn't good practice to mix data with a UI component.
Next, let's create an Angular service to provide data to the UI component we've worked with up to this point. For the service I'll use dependency injection -- which I described in the previous section -- I'll also create the service to get data from a constant in a separate file and an AJAX service, as well as use a JavaScript promise and observable. Listing 7 illustrates the Angular service, which is also in the app/banner.service.ts
file from listing 2.
Listing 7 - Angular service
|
The Angular service in listing 7 is declared as a class and decorated with the @Injectable()
annotation. The @Injectable()
annotation tells Angular the class can be used for dependency injection (e.g. BannerService
can be injected into a UI component). Note class BannerService
is preceded with the export
keyword to make it accessible to other modules.
Now take a look at the constructor (private http: Http) {}
statement in listing 7, it's the class constructor that's called when an instance of the BannerService
is created. But do you know what the argument private http: Http
means ? It represents an instance of Angular's Http
class -- used for things like AJAX calls -- that's assigned the http
reference. This is dependency injection at work. You didn't need to create an Http
object instance manually or anything, all that was needed was for the class constructor to use an argument with a reference to an @Injectable
class -- like Angular's Http
class which is marked as @Injectable
or any other service class marked with @Injectable
. Angular takes care of all the other details, and in this case a BannerService
instance gets access to an Http
instance through the this.http
reference.
If you look at the getMessageViaAjax()
method in listing 7 you can see it uses a JavaScript observable, but more importantly the method has immediate access to make an AJAX call using the this.http
reference injected by the constructor.
The getMessage()
method in listing 7 uses a JavaScript promise and returns the value of the BANNER_MESSAGE
constant. If you look toward the top of listing 7 you can see the BANNER_MESSAGE
constant is imported through the import {BANNER_MESSAGE} from './mock-banner';
statement. In this case, the mock-banner.ts
file contains the single statement export var BANNER_MESSAGE: string = 'Angular 101!';
which you can confirm in listing 2. This last last method isn't as interesting as the getMessageViaAjax()
method, but still illustrates how you can get data from a constant in another application file.
Angular providers, component initialization and dependency injection in practice again
Now that we have an Angular service from the last section, it's time to integrate it into an Angular UI component. Listing 8 shows a modified version of the Angular UI component we created in listing 6, but this version uses the Angular service from listing 7.
Listing 8 - Angular UI with service
|
The first important change made to the component in listing 8 is the providers: [BannerService,HTTP_PROVIDERS]
statement inside @Component
. This gives the Angular UI component access to the BannerService
class created in the past section, as well as the HTTP_PROVIDERS
class used to make things like AJAX calls -- note that even though the component doesn't make an AJAX call directly, the component makes use of the service's getMessageViaAjax()
method which does make the AJAX call and due to the JavaScript observable used for this purpose, the component also requires access to HTTP_PROVIDERS
.
Next, comes the component's constructor(private _bannerService: BannerService) { }
statement. This should be familiar from the past section that described Angular dependency injection. In this case, the statement injects an instance of BannerService
-- made accessible due to the providers:
variable -- and makes it available through the _bannerService
reference to the rest of the component class.
Once the component class has access to the service instance, it's possible to call methods that belong to the service with the reference this._bannerService
. It's worth mentioning the syntax used in the component's getMessage()
method uses JavaScript promise syntax -- because the service method it calls also uses a JavaScript promise -- where as the component's handleAjaxButtonClick()
method uses JavaScript observable syntax -- because the service method it calls also uses a JavaScript observable.
Finally, the other important change in listing 8 is how the message
field gets its initial value. Because the message
field value is now provided by the Angular service, it's declared empty as message: string;
in the class. But notice how and when the service is called to populate the message
field.
The UI component class in listing 8 now uses the implements OnInit
syntax in order to provide custom initialization logic via the ngOnInit()
method. If you look for the ngOnInit()
method in listing 8, you can see it calls the component's getMessage()
method which is the method that makes the actual call to the Angular service and assigns the initial value to the message
field. So why not simply call the getMessage()
method in the component's constructor
and avoid all this OnInit
stuff ? constructor
is a TypeScript method for class field initialization and ngOnInit
is an Angular UI component life-cycle method that runs after a component's data-bound properties have been initialized. Because you're attempting to set a component's field value, the service call should be made after a component has been created, which is when ngOnInit
is triggered. The component's constructor
method should only be used for class field initialization (i.e. non-data related) which is why a component's dependency injection process is done in this method.