Wednesday, June 17, 2020

Property & Event Binding




Our goal in this lecture is to hide the punchline of our jokes and only reveal them when the user clicks a button.

Learning Outcomes

  • How to use the hidden DOM property to hide/show elements.

  • Difference between DOM properties and HTML attributes.

  • How to perform input property binding with []

  • How to perform output event binding with ()

Hiding and Showing Elements

We can hide any element by adding an attribute called hidden to the element in HTML, so we could hide the punchline like so:

HTML
Copy<p class="card-text" hidden>{{joke.punchline}}</p>

This is a core feature of HTML, not some functionality provided by Angular.

We want to add the hidden property to the element by default and then remove it when the user clicks a button.

So we add the following markup:

HTML
Copy<p class="card-text" [hidden]="true">{{joke.punchline}}</p>

Specifically we added the markup [hidden]="true" and again it works, the element is hidden.

A few things:

  1. We wrapped the attribute with a [], more on that later.

  2. We made the attribute equal to true, if we made it equal to false it shows the element.

We say we have bound the value true to the property called hidden.

Important

This is called Input Property Binding and it’s a very important concept in Angular.

HTML Attribute vs DOM Property

The distinction between an HTML attribute and a DOM property is important in understanding binding in Angular.

HTML is a set of written instructions for how to display a web page.

The browser reads the HTML and creates something called a DOM, a Document Object Model. This is the manifestation of those HTML instructions in memory.

Changing the HTML doesn’t automatically update the webpage unless the user refreshes the browser, changing the DOM however instantly updates the webpage.

There is mostly a 1-to-1 mapping between the names and values of HTML attributes and their equivalent DOM properties, but not always.

The hidden HTML attribute is a good example, it only needs to exist on an HTML element to instruct the browser to hide the element.

So hidden="true" hides the element but confusingly so does hidden="false" in HTML we just need to add hidden to hide the element.

The DOM representation of the hidden attribute is a property also called hidden, which if set to true hides the element and false shows the element.

Angular doesn’t manipulate HTML attributes, it manipulates DOM properties because the DOM is what actually gets displayed.

So when we write [hidden] we are manipulating the DOM property and not the HTML attribute.

This is why the above is called Input Property Binding and not Input Attribute Binding.

Input Property Binding

Looking back at our use of the hidden property:

HTML
Copy<p class="card-text" [hidden]="true">{{joke.punchline}}</p>

The target inside [] is the name of the property. In the example above the target is the hidden DOM property.

The text to the right of = is JavaScript code that gets executed and the resulting value is assigned to the target.

Tip

true is still JavaScript code which if executed returns true.

So in summary, we are binding to the DOM property hidden and setting it to true so the element is hidden.

Tip

In other parts of the web you’ll see this referred to as just property binding. However to distinguish it from the other type of binding in Angular I like to call this input property binding.

We can only use this type of binding to change the value of the target. We can’t use it to get notified when the target’s value changes, to do that we need to use something called Output Event Binding, more on that soon.

Let’s add a property called hide on each joke and set it to true, like so:

JSON
Copy[
  {
    setup: "What did the cheese say when it looked in the mirror?",
    punchline: "Hello-Me (Halloumi)",
    hide: true
  },
  {
    setup: "What kind of cheese do you use to disguise a small horse?",
    punchline: "Mask-a-pony (Mascarpone)",
    hide: true
  },
  {
    setup: "A kid threw a lump of cheddar at me",
    punchline: "I thought ‘That’s not very mature’",
    hide: true
  },
]

Now we can set the hidden input property to joke.hide in the template, like so:

HTML
Copy<div class="card card-block"
     *ngFor="let joke of jokes">
  <h4 class="card-title">{{joke.setup}}</h4>
  <p class="card-text"
     [hidden]="joke.hide">{{joke.punchline}}</p>
</div>

Output Event Binding

We want to show or hide the punchline when a user clicks a button, so let’s add a button with the label Tell Me to the bottom of each card, like so:

HTML
Copy<div class="card card-block"
     *ngFor="let joke of jokes">
  <h4 class="card-title">{{joke.setup}}</h4>
  <p class="card-text"
     [hidden]="joke.hide">{{joke.punchline}}</p>
  <a class="btn btn-primary">Tell Me</a>
</div>

We want to set joke.hide to false when the user clicks the button, and then back to true again when they click the button a second time.

To have Angular call some code every time someone clicks on the button we add some special markup to our button:

HTML
Copy<a class="btn btn-primary"
   (click)="joke.hide = !joke.hide">Tell Me
</a>

We have some new syntax with (). The target inside the () is an event we want to listen for, we are listening for the click event.

The text to the right of = is some JavaScript which will be called every time a click event occurs.

joke.hide = !joke.hide toggles the value of joke.hide, so if it’s false clicking the button will change it to true, if it’s true clicking it will change it to false.

We can just as easily make the expression to the right of = call a function on our component instead, like so:

HTML
Copy<a class="btn btn-primary"
   (click)="toggle(joke)">Tell Me
</a>

Now when the button gets clicked it calls the toggle(…​) function on the JokeListComponent and passes it the joke object the card is bound to, like so:

JavaScript
Copytoggle(joke) {
  joke.hide = !joke.hide;
}

Now when we click the button, we set the show property to true which then unhides the element.

Summary

The way to think about these two different ways of binding is in terms of inputs and outputs.

  • With the [] we are binding to an input of a Component.

  • With the () we are binding to an output of a Component.

This is what we call one-way data binding, since data only flows one way, either into or out of a component.

It is possible to simulate two-way data binding in Angular and we’ll cover that in a later section on Forms.

Listing

Listing 1. src/main.ts
TypeScript
Copyimport {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {NgModule}      from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {Component} from '@angular/core';

@Component({
	selector: 'joke-list',
	template: `
<div class="card card-block"
     *ngFor="let joke of jokes">
	<h4 class="card-title">{{joke.setup}}</h4>
	<p class="card-text"
	   [hidden]="joke.hide">{{joke.punchline}}</p>
	<a class="btn btn-warning" (click)="toggle(joke)">Tell Me</a>
</div>
  `
})
class JokeListComponent {
	jokes: Object[];

	constructor() {
		this.jokes = [
			{
				setup: "What did the cheese say when it looked in the mirror?",
				punchline: "Hello-Me (Halloumi)",
				hide: true
			},
			{
				setup: "What kind of cheese do you use to disguise a small horse?",
				punchline: "Mask-a-pony (Mascarpone)",
				hide: true
			},
			{
				setup: "A kid threw a lump of cheddar at me",
				punchline: "I thought ‘That’s not very mature’",
				hide: true
			},
		];
	}

	toggle(joke) {
		joke.hide = !joke.hide;
	}
}

@NgModule({
	imports: [BrowserModule],
	declarations: [JokeListComponent],
	bootstrap: [JokeListComponent]
})
export class AppModule {
}

platformBrowserDynamic().bootstrapModule(AppModule);

Game changing Features of Angular v6

With the release of Angular 6 several amazing new features were introduced. These changes synchronizes the major version number of the angular framework, angular CLI and Angular Material + CDK. With an exception to a small change in RxJS, there are no major breaking changes in this version of Angular. Let us examine these new features in greater detail.

ng add

ng add <package>

is a new CLI command that helps to seamlessly add new capabilities to your applications. This command does two things: first it will use the package manager to download new dependencies then it will run an installation script in that package.

For example installing and setting up Angular Material use to require quite a few steps, but now it can all be done simple with one command:

ng add @angular/material

Now adding things like Progressive web apps, Service workers and Angular elements to your existing Angular application is relatively easier.

ng update

ng update <package>

is another new CLI command that is used to update your packages. It analyzes your package.json files and recommends updates to your application. Therefore helping you adopt the right version of dependencies and keep your dependencies in sync. It applies what is known as schematics under the hood to not only do the npm install but keep your project up to date.

For example running a command such as

ng update @angular\core

will update all of the Angular framework packages as well as RxJS and TypeScript, and will run any schematics available on these packages to keep you up to date.

You will also get all of the latest packages in the packageGroup of @angular\core. You can find the packageGroup in the package.json file of @angular\core located in node_modules\@angular\core\package.json:

React LogoReact Logo
UPGRADE YOUR JS
Go from vanilla JavaScript 👉 React

Animations

Previously animations where imported from @angular\core now these can be imported from the @animations\animations package. With this new implementation of animations, there is no longer a need for web animations polyfill which are enabled through the src/polyfills.ts file. This file typically incorporates the mandatory and many of the optional polyfills as JavaScript import statements. Now with version 6 of Angular you can remove this polyfill from your application and save approximately 47KB of bundle size.

Registering Providers

Previously if you had a service that you want to make use of, you would register it with the injector in a particular module. Now however, we can do this in the service itself using the providedIn property in the injectable decorator.

Before

@NgModule({
  ...
  providers: [MyService]
})
export class AppModule {}

After

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

@Injectable({
  providedIn: 'root',
})
export class MyService {
  constructor() { }
}

The providedIn property here, tells Angular that the root injector is responsible for creating an instance of the service. Services that are provided this way are automatically made available to the entire application and don't need to be listed in any particular module.

ngModelChange

This affects the way you may track an input value in your application. Previously ngModelChange was emitted before it's underlying control was updated, like so:

<input [(ngModel)]="name" (ngModelChange)="onChange($event)">

onChange(value) {
    console.log(value)
}

This is fine if you pass value directly through the $event keyword. However if you have a handler for the ngModelChange event, you will get the old value instead of the updated value. For example:

<input #modelDir="ngModel" [(ngModel)]="name" (ngModelChange)="onChange(modelDir)">

onChange(ngModel: NgModel) {
    console.log(ngModel.value);       // would log old value, not updated value
}

In Angular 6 though, this would always give the updated new value.

Angular Material + CDK

Angular Material is a project from the Angular team with the aim of creating a set of high quality user interface components based on Google's Material design specification. CDK stands for Component Dev Kit. The main purpose for CDK is to provide tooling for developers to create there own custom components without having to re-invent the wheel for common interaction patterns and behaviours.

CDK is now a standalone library which is broken up into multiple sub-packages each with an individual responsibility or single purpose. Some of these sub-packages include the a11y sub-package for things having to do with accessibility like focus management, keyboard interaction, screen reader users e.t.c. Another is the overlay sub-package that deals with floating panels or pop-ups in your application. The table sub-package is exclusively responsible for rendering rows of cells without any opinions on style. So it is not tied to any particular design system, however @angular/material:material-table is based on top of this with some extra CSS and specific behavior for sorting, pagination that are specific to that design system.

With the release of v6, the CDK now includes a new tree component for displaying hierarchical data. As explained in the official release blog post “following patterns from the data-table component, the CDK houses the core tree directives, with Angular Material offering the same experience with Material Design styles on top.”

In addition new badge and bottom-sheet components where also introduced. The former helps display helpful information such as unread item counts while the latter represent a special type of mobile-centric dialogs that come up from the bottom of the viewport, commonly used to present a list of options following an action.

Futhermore, @angular/cdk/overlay package now includes new positioning logic or flexible positioning that helps make pop-ups that intelligently remain on-screen in all situations.

Angular Material Starter Components

This version of Angular also includes 3 new starter components. You can see this in action after first running ng add @angular/material

Material Sidenav

Running the command ng generate @angular/material:material-nav --name=my-nav generates a starter template including a toolbar with the app name and a side navigation. It is also responsive

Material Dashboard

There is now a template for a dashboard which contains a dynamic grid list of cards. To get started with the material dashboard run the command

ng generate @angular/material:material-dashboard --name=my-dashboard

Material Data Table

Finally we also have a template for a data table pre-configured with a data source for sorting and pagination. You can see this by running the command

ng generate @angular/material:material-table --name=my-table

Angular Element

This new feature takes Angular components and bootstraps them as custom elements. Custom elements is a web standard for defining new HTML elements in a framework-agnostic way.

In order to keep track of all available custom elements the browser maintains a registry in which every elements needs to be registered first. In this registry the name of the tag is mapped to the JavaScript class which controls the behavior and the output of that element.

The Angular Elements functionality is available with the package @angular/elements package which exports a createCustomElement() API that provides a bridge from Angular's component interface and change detection functionality to the built-in DOM API. This replaces the need to manually bootstrap Angular components found in static html content.

RxJS

This version of Angular now depends on RxJS 6. RxJS 6 introduces some breaking changes but a new package, rxjs-compat can be installed alongside RxJS 6 to provide a backward compatibility. You can install rxjs-compat by running npm install --save rxjs-compat. With this extra library, your old code should continue to work just fine and you can smoothly update your project.

RxJS 6 introduces two important changes; A change in package structure and replacing chainable operators with pipeable operators. The change in package structure made the bundle smaller and import easier. You therefore need to update your import statements and how you use operators.

Before

import { Observable } from 'rxjs/Observable';
//operators
import { map } from 'rxjs/add/operator/map';

After

import { Observable } from 'rxjs';
//operators
import { map } from 'rxjs/operators';

With pipeable operators usablity was improved and bundle size was reduced. This also lead to some operators been renamed.

Before

myObs
  .do(console.log)
  .map(x => x * 2)
  .subscribe(x => {
    console.log('Value is', x);
  });

After

// after
myObs
  .pipe(
    tap(console.log),
    map(x => x * 2)
  )
  .subscribe(x => {
    console.log('Value is', x);
  });

Upgrading to Angular 6.0

The Angular team has put together a tool that makes it easy to migrate your Angular installation to the latest version.

Conclusion

Angular has seen a huge growth and adoption in the last year. With the release of Angular 6 this growth is expected to continue as the project becomes better and more exciting to use. What do you think about this latest release? Drop a comment below.

Free hosting web sites and features -2024

  Interesting  summary about hosting and their offers. I still host my web site https://talash.azurewebsites.net with zero cost on Azure as ...