© Venkata Keerti Kotaru 2020
V. K. KotaruAngular for Material Designhttps://doi.org/10.1007/978-1-4842-5434-9_5

5. Angular: Data Binding and Change Detection

Venkata Keerti Kotaru1 
(1)
Hyderabad, India
 

A markup language (like HTML) is better suited for building a view. It is declarative and easy to read. As we build web pages in an Angular application, views are built with HTML. Data and logic stay in the TypeScript classes. Traditionally, the JavaScript API (and JQuery) allows you to query and select an HTML element. The JavaScript code might read the value from the selected element or set a value to it. This becomes error prone and convoluted as the application grows in size and complexity.

Angular solves this problem with data binding. Data binding helps bring data in TypeScript code to the view or HTML, and vice versa. This chapter discusses data binding and its approaches. This chapter also covers Angular’s change detection strategy. It is a key Angular feature for data binding. Angular’s new approach (compared to the AngularJS 1.x digest cycle) is at the heart of better-performing modern Angular applications.

Let’s begin with data binding.

Interpolation

Interpolation is a special syntax to show data in an HTML view. By default, Angular templates use the double curly braces ({{ }}) delimiter to embed a variable, an expression, or a value returned by a function among HTML elements. Listing 5-1 and Figure 5-1 show the result of the interpolation sample. It embeds the value of a variable “name” as an <h2> element. A simple addition was interpolated, which evaluates the sum of two numbers and shows the result. Also, a function call shows the value returned from the function.
<!-- Show value of a variable -->
  <h2>{{name}}</h2>
  <!-- Shows sum of two numbers, evaluated -->
  <strong> {{ 1243 + 232 }} </strong>
  <!-- Calls a function and shows the returned value -->
  <div> First appeared in {{getFirstAppearance()}} </div>
Listing 5-1

Data Binding: Interpolation

../images/475625_1_En_5_Chapter/475625_1_En_5_Fig1_HTML.jpg
Figure 5-1

Result of interpolation sample in Listing 5-1

The context of data binding is the TypeScript class component. That is, variables and functions are class-level properties and methods on the component (see the variable “name” and the getFirstAppearance() function in Listing 5-1). It is possible that the values of these variables change on the fly. Data binding automatically updates the value of the view in DOM.

Note

We can override the default interpolation delimiter ( {{ }} ) with a different string value by using the interpolation field on a component decorator.

Property Binding

Property binding allows you to bind a dynamic value to a DOM property. It is easy and natural as we continue to use the HTML element properties that we have always used. For common tasks like enabling or disabling a control, setting a dynamic value for a text field, or setting a dynamic path on an image, you do not need to use any special Angular keywords (see Listing 5-2).
 <!-- disable a dropdown -->
 <select type="text" [disabled]="isOptionsDisabled" >
   <option selected>Blue</option>
   <option>Red</option>
 </select>
 <!-- set a dynamic path on an image -->
 <img [src]="imageUrl" />
 <!-- set a dynamic value to a text field -->
 <input type="text" [value]="textValue">
Listing 5-2

Property Binding

The property is enclosed in square brackets for data binding. That is all it takes to apply property binding. We use the fields defined in the TypeScript class as values. Listing 5-3 has the values that are used: isOptionsDisabled, imageUrl, and textValue. Listing 5-3 defines these variables in the TypeScript file component.
@Component({
  selector: 'app-sample,
  templateUrl: './sample.component.html',
  styleUrls: ['./sample.component.css']
})
export class SampleComponent
{
             isOptionsDisabled: boolean = true;
            imageUrl: string = "/assets/angular-image.png";
             textValue: string = "welcome";
            constructor() {}
}
Listing 5-3

TypeScript Class Component with Fields

You can dynamically change the values of these fields. It enables the drop-down on the fly, changes the image, and sets a new value to the text field.

Alternate Syntax for Property Binding

As an alternative to enclosing a property in square brackets, you may prefix the property name with bind-. For example, you may use bind-disabled instead of [disabled]. There is no difference in how it works. It is just a matter of preference. Some developers may feel one way is more readable than the other. Listing 5-4 uses the bind-disabled syntax instead of [disabled].
 <input type="text" bind-disabled="isTextDisabled" />
Listing 5-4

Property Binding with bind-property Name

Note

The “bind-” syntax is not often used. Enclosing a property in square brackets is a popular approach. The alternate syntax is described for information only.

Notice that the direction of data binding is one way. It goes from the component class to the HTML template. As values change in the component, the view is updated. Various events, such as server-side API calls and DOM (Document Object Model) events could cause the values to change. Data binding ensures updating the view.

With property binding, the binding target is an element’s DOM properties; not the HTML elements’ attributes. Please note that the DOM’s objects are different from HTML elements. The latter are created in the HTML web page when it loads. DOM properties are initialized by the HTML elements and attributes. The DOM element properties can change while using the web page.

In many cases, attribute names match property names, but there are exceptions, and Angular property bindings depend on DOM properties.

Consider a colspan example with an attribute on <td> in an HTML table. The property is named colSpan (with an uppercase S). Listing 5-5 shows the results in an error. It attempts to use the attribute name with a lowercase s. Please note that spanTwoCells is a variable defined in the TypeScript class. The value is set to an arbitrary number.
    <td [colspan]="spanTwoCells">2.b</td>
Listing 5-5

Incorrect Usage of an Attribute in Property Binding

Note the following error.
compiler.js:2427 Uncaught Error: Template parse errors:
Can't bind to 'colspan' since it isn't a known property of 'td'.
To fix the problem, use the property name with an uppercase S.
    <td [colSpan]="spanTwoCells">2.b</td>
Listing 5-6

Property Binding with colSpan Property Name

There could be scenarios in which attribute names need to be used. Use attribute binding to bind an element attribute with a variable. Prefix the attribute name with attr. In Listing 5-7, the attribute name with colspan (lowercase s) does not return an error.
    <td [attr.colspan]="spanTwoCells">2.b</td>
Listing 5-7

Attribute Binding

Class Binding (CSS classes)

Class binding allows you to apply a CSS class on an element on the fly. Typically, CSS classes define the various style aspects of an element, including the font, foreground/background colors, border styles, and so forth.

Use data binding to apply one or more CSS classes on an element. It also provides the ability to change the CSS class, and hence, change the styles of an element.

When using class binding, we combine all the CSS classes into a single string that is separated by spaces. In Listing 5-8, like property binding, the “class” element property is enclosed in square brackets. A myCssClass variable holds a list of CSS classes to apply on the <div> element.
 <div [class]="myCssClass">
   Welcome to data binding
 </div>
Listing 5-8

Class Binding Sample

The declaration of the myCssClass class variable is in Listing 5-9. The show-border and show-bg-color CSS classes are in a single string separated by a space.
 myCssClass: string = "show-border show-bg-color"
Listing 5-9

Class with CSS String on a Variable

We could add more CSS classes or remove an existing CSS class from this string. This would change the look and feel of the element.

Editing a string is convoluted and error prone, however. There is an alternate approach to changing CSS classes on the fly: use boolean variables that can be toggled to turn a CSS class on or off. When true, the CSS class is applied (and vice versa). Listing 5-10 has boolean variables declared in the component class. These values are initialized. The value can change dynamically based on user interaction, service calls, or any other criteria.
 showBorder: boolean = false;
 showBgColor: boolean = true;
 applyMargin: boolean = true;
Listing 5-10

Boolean Variables that Control to Show or Hide a CSS Class on an Element

In the template, use the [class.class-name] syntax to bind with the boolean variables (see Listing 5-11).
<div [class.show-bg-color]="showBgColor" [class.show-border]="showBorder" [class.apply-margin]="applyMargin">
  <!-- Content goes here -->
 </div>
Listing 5-11

Bind Boolean Variables with Class Binding

Figure 5-2 shows that when the value of showBorder is false, showBgColor and applyMargin are true.
../images/475625_1_En_5_Chapter/475625_1_En_5_Fig2_HTML.jpg
Figure 5-2

A div with CSS classes applied with class binding

ngClass Directive

In addition to the class binding described in the previous section, Angular provides an easier and better way to apply a CSS class on the fly. The framework provides an out-of-box directive—ng-class—with ready-made functionality to easily update CSS classes. It allows encapsulating class names in a TypeScript object. In Listing 5-12, the individual class names are put together in an object.
 cssClasses: {[key: string]: boolean} = {
   'show-border': false,
   'show-bg-color': true,
   'apply-margin': true
 }
Listing 5-12

An Object Encapsulating CSS Classes for Data Binding

Note the data type for the cssClasses variable, {[key: string]: Boolean}. Let’s decode the anonymous TypeScript type defined in this sample. On the outset, it is an object with key/value pairs. The key is a string. The values in Listing 5-13 (show-border, show-bg-color, etc.) use a hyphen in the key name. A typical JSON object does not use a hyphen in the key name or field name. Considering that we are defining a CSS class, it is a general practice to use a hyphen in CSS class names.

We toggle each key (which is nothing but the CSS class name) as true or false. The values can change on the fly. With the data binding applied by the directive, the CSS class is reflected in the view.

Note

A directive in Angular enables adding a custom behavior to the HTML markup and elements. I discuss directives in an upcoming chapter.

The usage of the cssClasses variable in the HTML template is shown in Listing 5-13. It is an attribute directive that is used similar to property data binding.
 <div [ngClass]="cssClasses">
        <!-- content goes here -->
 </div>
Listing 5-13

Template Using the ngClass

Style Binding

Style binding allows you to change the style of an element on the fly. While class binding works on CSS classes, which encapsulates multiple CSS styles into a single CSS class, style binding helps with in-line styles (see Listing 5-14).
<div [style.font-size.pt]="fontSize" [style.color]="fontColor">{{name}}</div>
Listing 5-14

Style Binding

Style bindings are prefixed with the word style. In Listing 5-15, fontSize and fontColor are the fields in the TypeScript class component; the values can be changed on the fly (see Listing 5-15).
 fontSize: number = 16;
 fontColor: string = "red";
Listing 5-15

Styles Values Defined in the Component

Note

font-size is followed by a unit as well. In Listing 5-15, it is pt (points). You can use other measurements, like % or px.

It is a common practice to change the values of a style based on a condition. We can do it in the template or in the component file. Listing 5-15 applies red text if the hero’s weight is more than 180.
 <div [style.font-size.pt]="fontSize" [style.color]="heroWeight > 180 ? 'red' : 'black'">{{name}}</div>
Figure 5-3 shows the result. The inline styles are applied in the developer tools.
../images/475625_1_En_5_Chapter/475625_1_En_5_Fig3_HTML.jpg
Figure 5-3

Result of style binding

ngStyle Directive

Angular provides another easy and better way to apply CSS styles on the fly. Use the ngStyle directive to encapsulate all style values into an object. Listing 5-16 is in the TypeScript class component. The styles object is created with all the necessary inline styles. The values are conditional and can change and update the style in the element.
 styles: {[key: string]: string} = {
   'font-size.pt': this.fontSize,
   'color': this.heroWeight > 180 ? 'red': 'black',
   'font-weight': this.heroWeight > 180 ? 'bold': 'normal',
   'text-decoration': this.heroWeight > 180 ? 'underline': 'none'
 }
Listing 5-16

Style Object For Inline Styles

Listing 5-17 is the template using the ngStyles directive.
   <div [ngStyle]="styles">{{name}}</div>
Listing 5-17

ngStyle Directive for Inline Styles

Figure 5-4 shows the result. Refer to the developer tools with the desired styles added inline.
../images/475625_1_En_5_Chapter/475625_1_En_5_Fig4_HTML.jpg
Figure 5-4

Result with ngStyles

Event Binding

When a user changes the values of a text field, clicks a button, or submits a form, TypeScript code needs to capture the user’s actions and data input. Event binding is used for this. Each action, key stroke, and button click generates events. The direction of the binding is from the view or the HTML template to the TypeScript file component. It goes in the opposite direction of property bindings.

We bind events on an element with a function in the TypeScript file component. The context of the binding remains in the TypeScript class component (like property binding).

Angular captures the event emitted by the element and creates an $event object. Since there are many types of events, raised by various controls, the structure of an $event object differs. Depending on the event, we can capture the necessary information from the object.

In Listing 5-18, there is a text field. We will capture an input event in the text field. To capture the input event, enclose the event name in braces. We can invoke a handler function defined in the TypeScript class component. This function typically processes the event. For this example, we will print the event object.
--contents in HTML template file---
<input type="text" (input)="handleEvent($event)" />
--TypeScript event handler---
 handleEvent(event) {
   console.log("$event object", event);
   console.log(`event type`, event.type);
   console.log("Value on the event target element", event.target.value);
 }
Listing 5-18

Input Event and Handler

Note

For binding, an event can be enclosed in braces or prefixed with on-eventName. (click)=”handlerName()” is the same as on-click=”handlerName”.

There are three console.log statements printing the event object.
  • The first statement prints the complete event object.

  • The second statement prints the event type information, which is different for the various types of events that HTML elements can generate.

  • The third statement prints the value of the element, which is useful for input controls like text fields, drop-downs, and so forth. On the other hand, elements like buttons do not change values often.

We access the value by using the value field in the target object. The target is on the event object raised by the element.

Figure 5-5 shows the result.
../images/475625_1_En_5_Chapter/475625_1_En_5_Fig5_HTML.jpg
Figure 5-5

Event handler output for input text field

The event type is “input” and the value is the data keyed into the text field.

Let’s test this handler function on various other events. Listing 5-19 and Figure 5-6 show a drop-down change event.
   <select type="text" (change)="handleEvent($event)">
     <option selected>Blue</option>
     <option>Red</option>
   </select>
Listing 5-19

Drop-down with Change Event

../images/475625_1_En_5_Chapter/475625_1_En_5_Fig6_HTML.jpg
Figure 5-6

Event handler output for drop-down

The event type is “change” and the value is the selected value in the drop-down. Listing 5-20 and Figure 5-7 show a button click event.
   <button (click)="handleEvent($event)">Click me</button>
Listing 5-20

Button with Click Event

../images/475625_1_En_5_Chapter/475625_1_En_5_Fig7_HTML.jpg
Figure 5-7

Event handler output for button

Typically, the event type is “click” and the value of the button is not significant.

The event handler in this example is for demonstration purposes. A typical handler might update the model object associated with the component. The model object may eventually be sent to a server API for insert or update operations in a database.

Property binding and interpolation provide values to the view (HTML template) from the model object (component fields).

Event binding captures user input in the view or HTML template. It provides values to the model object (component fields) from the view (HTML template).

So far, data binding has been one way—either toward the view from the model object or toward the model object from the view.

In Listing 5-21, we combine the two and update the value captured in a text field to a variable using the event binding. It shows the value in an element using the property binding.
 <div class="apply-margin">
   <input type="text" (input)="superheroName=$event.target.value;" />
   |
   <strong [innerText]="superheroName"></strong>
 </div>
Listing 5-21

Combine Property Binding and Event Binding

We captured the input value of a text field using $event.target.value to a variable called superheroName. We used the innerText property binding to show the input field value in an element. It updates the label when the input event is raised, which is every keystroke. Figure 5-8 shows the result.
../images/475625_1_En_5_Chapter/475625_1_En_5_Fig8_HTML.jpg
Figure 5-8

Result of combining property binding with event binding

Two-Way Data Binding

Two-way data binding simplifies updating model objects as the user changes the view; and updating the view as model objects change; hence, it is called two-way.

For simplifying two-way data binding, Angular provides a directive—ngModel, which comes out of the box with FormsModule in @angular/forms. Follow these next steps to implement two-way data binding.
  1. 1.

    Import the forms module.

    We have been building superhero components as part of the superheros Material Design module. Import the forms module in it, so that we can use ngModel for two-way data binding. Make the changes shown in Listing 5-22 in superheroes-material-design.module.ts.

    Notice that we imported FormsModule from @angular/forms. Next, we added it to the imports array on the @NgModule decorator of the TypeScript class.

     
import { FormsModule } from '@angular/forms';
@NgModule({
 declarations: [
   // For brevit, removed code
 ],
 imports: [
   // For brevit, removed code
   FormsModule
 ],
 })
export class SuperheroesMaterialDesignModule { }
Listing 5-22

Import Forms Module for ngModel

  1. 2.
    Use ngModel in the component for two-way data binding. As described in the prevoius section, two-way data binding is about combining
    1. a.

      Property binding, which is used with syntax enclosing the variable in square brackets and

       
    2. b.

      Event binding, which is used with syntax enclosing the variable in round brackets.

       
     

The ngModel directive enables you to combine the two with the directive name in the middle: [(ngModel)]. It is colloquially referred to as banana in a box.

Consider the following changes to the input text field in the Superhero profile component (see Listing 5-23).
<input type="text" [(ngModel)]="superheroName"/>
Listing 5-23

ngModel for Two-Way Data Binding on a Text Field

Use the superheroName variable (model variable) on a different element in the view with the interpolation or property binding. As changes are made to the text field, the value is reflected in the other element (see Listing 5-24).
<!-- Interpolation -->
<strong> {{superheroName}} </strong>
Listing 5-24

Show the ngModel value with Interpolation or Property Binding

OR
<!-- property data binding -->
<strong [innerText]="superheroName"></strong>

The ngModel directive internally raises an ngModelChange event every time there is a change to the text field. The event sets a value on the model object. Angular adds the directive in the input elements.

Change Detection

So far, you have seen data binding and its approaches. You know that Angular updates the view from the model and vice versa. A model refers to a field or an object in the TypeScript class component. A model is an object representation (of data) for the view.

Change detection plays an important role in data binding. There may be changes to the data due to various reasons, including
  • User interactions: : Edit the fields and perform actions using controls such as buttons, swipes, and so forth.

  • Ajax calls : XHR and fetch calls that retrieve data from a remote server using RESTful API calls.

  • JavaScript API : Timeouts, like setTimeout and setInterval.

The change detection process identifies a change and synchronizes the view and model.

Angular uses Zone.js in the change detection process. It is a JavaScript library included with Angular. It provides the execution context for change detection. Facilitated by Zone.js, Angular taps into the browser API to identify a change. As the user interacts with the view, or an Ajax call returns data, the change detection occurs for each component.

As described in Chapter 4, components are organized as a tree. Angular begins with a root component on a primary Angular module, which bootstraps the application. There are child components in it, and the child components have more components. Figure 5-9 is a hypothetical example demonstrating a component tree. It depicts a root component that has three components facilitating page navigation, left navigation, tabs, and a toolbar. The tabs component has two child components to add and show superheroes. The toolbar component has a single header component.
../images/475625_1_En_5_Chapter/475625_1_En_5_Fig9_HTML.jpg
Figure 5-9

Component tree and change detector with each component

Angular creates a change detector for each component. The change detector is not generic for the whole application, but rather created specifically for each component, which makes the change detection process faster. The change detector identifies changes to the template expression and updates the view and model objects. Figure 5-9 depicts a separate change detector in each component.

Angular emphasizes a unidirectional data flow from the parent to the child components through input attributes. When a change is detected, it is propagated to all child components, which is further sent down to the other nodes.

Detecting change is a simple process of comparing a component’s previous input with the new input. If a change is identified, in the complete tree under the component, verify and synchronize the template expressions with the model.

Change Detection Strategy

A change detection strategy is applied at the component level. We can specify the strategy as part of a component decorator. It affects the component and its children. Angular provides two change detection strategies: the default and OnPush.

A change could be caused by user interaction, such as the click of a button or API calls. The change is propagated from parent to child components. Often change is identified with an input parameter value change.

In Figure 5-10, AppComponent has two child components: Toolbar and SuperheroProfile. AppComponent includes a form that moves a superhero from one city to another. When the change is identified in AppComponent, and the user clicks the Go button, the change needs to propagate to the SuperheroProfile component so that the city name in the profile is updated.
../images/475625_1_En_5_Chapter/475625_1_En_5_Fig10_HTML.jpg
Figure 5-10

Change detection sample

Default Change Detection Strategy

As the name suggests, Default Change Detection Strategy is the default strategy on a component and its children. In Figure 5-10, the SuperheroProfile component has the input attributes shown in Listing 5-25. The property data binding and fields in AppComponent are shown in Listing 5-25.
<app-superhero-profile
[superpowers]="superpowers"
[address]="address"
[name]="name"
[firstAppearance]="year"
[lives-in]="country">
</app-superhero-profile>
Listing 5-25

Input Attributes on SuperheroProfile Component

Listing 5-26 features the name, address, and superpowers attributes declared in the component class.
export class AppComponent {
 title: string = "Superheroes";
 name: string = "Chhota Bheem";
 firstAppearance: number = 2008;
 superpowers: Array<string> = [
   "Can fly",
   "Strength and Stamina",
   "Kids love him"
 ];
 address: AddressType = {
   firstLine: "Road # 4",
   city: "Bengaluru"
 };
// Remaining code removed for brevity
}
Listing 5-26

Class Fields That Are Input Attributes to the Superhero Profile Component

As mentioned, a separate change detector is generated for each component. When an event that can cause change is raised, it compares input attributes, the previous value, and the new value. If there is a difference, the change is identified and the component template is updated.

For the SuperheroProfile component, each of the input attribute values— superpowers, address, name, firstAppearance, and lives-in—are compared.

With the default change detection strategy, for example, name and firstAppeared is compared between previous and new value. The value types are a string and a number, respectively. If there is a value change, a simple === comparison identifies the change; however, it is different with reference types, for example, an address. If we update the address object, the equals (===) comparison will fail. The reference comparison checks the memory address of the object. We updated the same object, and hence, no change is identified. Here, the address is a mutable type object.

Note

There are also immutable type objects that do not change their value. We need to create a new object at a different memory location to update a value of these types.

Hence, Angular has to compare all the fields in the object. In a default change detection strategy, that’s exactly what is done by a component-specific change detector; however, it compares fields if they are part of the template in the superhero profile component. In the previous example, the address field, the city field, and the firstLine variable values are compared.

When the user changes the value in the AppComponent’s text field, the new values are propagated to the SuperheroProfile component.

This approach is costly because the code has to compare child objects and fields, but it is effective in identifying the change.

Note

Overall, the Angular’s component-specific change detector is efficient and fast compared to the AngularJS 1.x digest cycle performance.

Listing 5-27 explicitly states the default change detection strategy. It is not required to be explicitly specified because it is the default.
@Component({
 selector: 'app-superhero-profile',
 templateUrl: './superhero-profile.component.html',
 changeDetection: ChangeDetectionStrategy.Default
})
export class SuperheroProfileComponent implements OnInit, OnChanges {
// Removed rest of the code for brevity
Listing 5-27

Default Change Detection Strategy

OnPush Change Detection Strategy

OnPush compares values by reference. It does not compare individual fields on reference types; hence, it is faster than the default approach.

In Listing 5-28, the address object is compared by reference. The two address fields (firstLine and city) are not compared, even though they are part of the template in the SuperheroProfile component.

If the OnPush strategy is used (for better performance), we need to use immutable objects. As mentioned, immutable objects can never be mutated or updated. A new instance is created every time there is a change. As a consequence, a new instance has a new memory location. This ensures that the reference comparison identifies the change, and it is done faster because individual fields do not need to be compared.

Listing 5-28 is the OnPush change detection strategy.
@Component({
 selector: 'app-superhero-profile',
 templateUrl: './superhero-profile.component.html',
 changeDetection: ChangeDetectionStrategy.OnPush
})
export class SuperheroProfileComponent implements OnInit, OnChanges {
// Removed rest of the code for brevity
Listing 5-28

OnPush Change Detection Strategy

Conclusion

This chapter covered Angular’s data binding and change detection features. It began with an explanation of data binding and how it eases web application development.

Interpolation and property binding help show the values of TypeScript component fields in the template. It works toward the template or view, away from the model.

Event binding allows changes made by the user to reflect in model objects. The latter are defined in TypeScript component classes. These are the fields and objects defined with the component.

CSS class binding and style binding combine the power of data binding with web page style features.

Change detection is crucial for performant and fluid web UI applications. Two strategies of change detection were discussed: default and on-push.

Exercise

Create a form that allows the user to input the following information about a dinosaur.
  • Dinosaur name

  • Description

  • Family drop-down menu: the values are Herrerasauridae, Ceratopsidae, Tyrannosauridae, and Plateosauridae

  • Submit button

The user should see the dinosaur information in a separate section of the page. The section could be to the side of or below the form. Use interpolation to show the dinosaur values in this section.

Use ngModel and two-way data binding to persist the form’s values in a model object.

When the Submit button is clicked, the console logs the values provided by the user.

References

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.105.124