Chapter 4. Getting Started with Angular Components and Directives
By this point, we're already familiar with the core building blocks that Angular provides for the development of single-page applications and the relations between them. However, we've touched only the surface by introducing the general idea behind Angular's concepts and the basic syntax used for their definition. In this chapter, we'll take a deep dive into Angular's components and directives.
In the following sections, we will cover these topics:
Enforced separation of concerns of the building blocks that Angular provides for developing applications.
The appropriate use of directives or components when interacting with the DOM.
Built-in directives and developing custom ones.
An in-depth look at components and their templates.
Content projection.
View children versus content children.
The component's life cycle.
Using template references.
Configuring Angular's change detection.
The "Hello world!" application in Angular
Now, let's build our first "Hello world!" application in Angular. In order to get everything up and running as easy and quickly as possible, for our first application, we will use the ECMAScript 5 syntax with the transpiled bundle of Angular. First, create the index.html
file with the following content:
<!-- ch4/es5/hello-world/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<script src="https://unpkg.com/zone.js@0.6.25/dist/zone.js"></script>
<script src="https://unpkg.com/reflect-metadata@0.1.3/Reflect.js"></script>
<script src="https://unpkg.com/rxjs@5.0.1/bundles/Rx.js"></script>
<script src="https://unpkg.com/@angular/core@2.2.0/bundles/core.umd.js"></script>
<script src="https://unpkg.com/@angular...
Although we already have an Angular application running, we can do much better! We didn't use any package manager or module loader. We spent all of
Chapter 3, TypeScript Crash Course, talking about TypeScript; however, we didn't write a single line of it in the preceding application. Although it is not required that you use TypeScript with Angular, it's more convenient to take advantage of all the bonuses that static typing provides. By using TypeScript, we can also use the Ahead-of-Time compilation in Angular.
Setting up our environment
The core team of Angular developed a brand new CLI tool for Angular, which allows us to bootstrap our applications with a few commands. Although we will introduce it in the final chapter, by then, in order to boost our learning experience, we will use the code located at
https://github.com/mgechev/getting-started-with-angular. This repository includes all the examples in this book, in one big application. It has all the required dependencies...
Playing with Angular and TypeScript
Now, let's play around with the files we already have. Navigate to the app/ch4/ts/hello-world
directory inside getting-started-with-angular
. Then, open app.ts
and replace its content with the following snippet:
// ch4/ts/hello-world/app.ts
import {Component, NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
@Component({
selector: 'my-app',
templateUrl: './app.html'
})
class App {
target: string;
constructor() {
this.target = 'world';
}
}
@NgModule({
declarations: [App],
imports: [BrowserModule],
bootstrap: [App],
})
class AppModule {}
platformBrowserDynamic().bootstrapModule(AppModule);
Let's take a look at the code line by line:
import {Component, NgModule} from '@angular/core';
import {BrowserModule...
We have already built our simple "Hello world!" app. Now, let's start building something that is closer to a real-life application. By the end of this section, we'll have a simple application that lists a number of items we need to do and greets us at the header of the page.
Let's start by developing our app
component. The two modifications from the preceding example that we need to make are renaming the target
property to name
and adding a list of todos
to the component's controller definition:
// ch4/ts/ng-for/detailed-syntax/app.ts
import {Component, NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
@Component({
selector: 'app',
templateUrl: './app.html',
})
class App {
todos: string[];
name: string;
constructor() {
this.name = 'John';
this.todos = ['Buy milk...
Improved semantics of the directives syntax
In
Chapter 1, Get Going with Angular, we mentioned the opportunity for improved tooling in Angular. A big issue in AngularJS is the different ways in which we can use directives. This requires an understanding of the attribute values, which can be literals, expressions, callbacks, or a microsyntax. Starting with Angular 2, this problem is eliminated by introducing a few simple conventions that are built into the framework:
In the first line, the propertyName
attribute accepts a string literal as a value. Angular will not process the attribute's value any further; it will use it the way it is set in the template.
The second syntax, [propertyName]="expression"
, gives a hint to Angular that the value of the attributes should be handled as an expression. When Angular finds an attribute surrounded by brackets, it will interpret the expression in the context of the component associated...
Defining Angular directives
Now that we've built a simple Angular component, let's continue our journey by understanding the Angular directives.
Using Angular directives, we can apply different behavioral or structural changes over the DOM. In this example, we will build a simple tooltip directive.
In contrast to components, directives do not have views and templates. Another core difference between these two concepts is that the given HTML element may have only a single component but multiple directives on it. In other words, directives augment the elements compared to components that are the actual elements in our views.
Angular's official style guide's recommendation is to use directives as attributes, prefixed with a namespace. Keeping this in mind, we will use the tooltip directive in the following way:
<div saTooltip="Hello world!"></div>
In the preceding snippet, we use the tooltip directive over the div
element. As a namespace, its selector uses the sa
string.
Creating custom Angular components
Now, let's build a simple to-do application in order to demonstrate the syntax to define components further.
Our to-do items will have the following format:
interface Todo {
completed: boolean;
label: string;
}
Let's start by importing everything we will need:
import {Component, NgModule, ViewEncapsulation} from '@angular/core';
//...
Now, let's declare the component and the metadata associated with it:
@Component({
selector: 'todo-app',
templateUrl: './app.html',
styles: [
`ul li {
list-style: none;
}
.completed {
text-decoration: line-through;
}`
],
encapsulation: ViewEncapsulation.Emulated
})
Here, we specify that the selector of the Todo
component will be the todo-app
element. Later, we add the template URL, which points to the app.html
file. After that, we use the styles
property; this is the first time...
Explaining Angular's content projection
Content projection is an important concept when developing user interfaces. It allows us to project pieces of content into different places of the user interface of our application. Web Components solve this problem with the content
element. In AngularJS, it is implemented with the infamous transclusion.
Angular is inspired by modern Web standards, especially Web Components, which led to the adoption of some of the methods of content projection used there. In this section, we'll look at them in the context of Angular using the ng-content
directive.
Basic content projection in Angular
Let's suppose we're building a component called fancy-button
. This component will use the standard HTML button element and add some extra behavior to it. Here is the definition of the fancy-button
component:
@Component({
selector: 'fancy-button',
template: '<button>Click me</button>'
})
class FancyButton { ... }
Inside of the ...
Hooking into the component's life cycle
Components in Angular have a well-defined life cycle, which allows us to hook into different phases of it and have further control over our application. We can do this by implementing specific methods in the component's controller. In order to be more explicit, thanks to the expressiveness of TypeScript, we can implement different interfaces associated with the life cycle's phases. Each of these interfaces has a single method, which is associated with the phase itself.
Although code written with explicit interface implementation will have better semantics, since Angular supports ES5 as well, within the component we can simply define methods with the same names as the life cycle hooks (but this time, prefixed with ng
) and take advantage of duck typing.
The following diagram shows all the phases we can hook into:
Figure 10
Let's take a look at the different life cycle hooks:
Defining generic views with TemplateRef
We are already familiar with the concepts of inputs, content children, and view children, and we also know when we can get a reference to them in the component's life cycle. Now, we will combine them and introduce a new concept-TemplateRef
.
Let's take a step back and take a look at the last to-do application we developed earlier in this chapter. In the following screenshot, you can see what its UI looks like:
Figure 11
If we take a look at its implementation in ch4/ts/inputs-outputs/app.ts
, we'll see that the template used to render the individual to-do items is defined inside the template of the entire to-do application.
What if we want to use a different layout to render the to-do items? We can do this by creating another component called Todo
, which encapsulates the responsibility of rendering them. Then, we can define separate Todo
components for the different layouts we want to support. This way, we need to have n different components for n different...
Understanding and enhancing the change detection
We have already briefly described the change detection mechanism of the framework. We said that compared to AngularJS, where it runs in the context of the "scope", in Angular 2 and later versions, it runs in the context of the individual components. Another concept we mentioned is the zones, which basically intercept all the asynchronous calls that we make using the browser APIs and provide execution context for the change detection mechanism of the framework. Zones fix the annoying problem that we have in AngularJS, where when we use APIs outside of Angular, we needed to explicitly invoke the digest
loop.
In
Chapter 1, Get Going with Angular and Chapter 2, The Building Blocks of an Angular Application, we discussed that the code that performs change detection over our components is being generated, either runtime (Just-in-Time) or as part of our build process (Ahead-of-Time). AoT compilation works great for environments with strict CSP (Content...
In this chapter, we went through the core building blocks of an Angular application: directives and components. We built a couple of sample components, which showed us the syntax to be used for the definition of these fundamental concepts. We also described the life cycle of each directive and the core set of features the given directive and component have. As the next step, we saw how we can enhance the performance of our application using the OnPush
change detection strategy with an immutable data.
The next chapter is completely dedicated to the Angular services and the dependency injection mechanism of the framework. We will take a look at how we can define and instantiate custom injectors and how we can take advantage of the dependency injection mechanism in our directives and components.