// File: courses.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'courses',
template: `
<h2>{{ title }}</h2>
<img src = "{{ imageUrl }}" />
`
})
export class CoursesComponent {
title = "List of courses";
imageUrl = "http://lorempixel.com/400/200";
}
property binding
in which a property is binded to a TypeScript class// Interpolation
<h2>{{title}}</h2>
src
to a field of our component imageUrl
// Property Binding
<h2 [textContent] = "title"></h2>
Another exmaple:
/
for self-closing tag e.g. <img>// Interpolation
<img src="{{imageUrl}}" />
// Property Binding
<img [src]="imageUrl" />
Property binding
works only one way: from component to the DOM. Any changes in the DOM are NOT reflected back in a component// Attribute Binding
[colspan] = "colSpan"
import { Component } from '@angular/core';
@Component({
selector: 'courses',
template: `
<img alt="img" [src] = "imageUrl" />
<table>
<tr>
<td [colspan]="colSpan"></td>
</tr>
</table>
`
})
export class CoursesComponent {
imageUrl = "";
colSpan = 2;
}
Upon compilation, note the error in the console:
`Can't bind to 'colspan' since it isn't a known property of 'td'
In order to understand this error, first you need to understand the difference between DOM (Document Object Model) and HTML
DOM
is a model of objects that represent the structure of a document. It's essentially a tree of objects in memory.
HTML
is a markup language that we use to represent DOM
in text. When your browser parses an HTML document, it creates a tree of objects in memory that they refer to as a DOM.
We can also create this tree of objects programmatically using vanilla JavaScript. We don't neccessarily need HTML but HTML is far simpler.
Most of the attributes of HTML elements have a one-to-one mapping to properties of DOM objects. There are however a few exceptions. For example, we have HTML attributes that don't have a respresentation in the DOM e.g. The DOM object does not have a property called colspan
. That's why we can the error.
On the other hand, we have properties in DOM that do not have a representation in HTML e.g. HTML does not have an attribute called textContent
<h1 [textContent]="title"></h1>
attr.
which tells Angular that we're targeting the colspan
attribute of a HTML element<td [attr.colspan]="colSpan"></td>
Open the terminal and type npm install bootstrap@3.3.7 --save
bootstrap
and store it into node_modules
folder--save
will add bootstrap as a dependency in package.json
The meaning of the line "bootstrap":"^3.3.7"
^
: we can use the most recent "major" version e.g. 3.4, 3.5, 3.9 but if there is a newer version, like version 4 or 5, don't install thatBy listing all the dependencies in package.json
, anyone who checks out this source code from a repository can simply go to the terminal and type npm install
. NPM looks at the package.json
and downloads all the dependencies to the machine
angular-cli.json
, add the following:"styles": [
"../node_modules/bootstrap/dist/css/bootstrap.min.css",
"styles.css"
],
src/style.css
@import
and add the path of bootstrap.css
relative from node_modules
folder@import "~bootstrap/dist/css/bootstrap.css";
courses.component.ts
and add a <button>:import { Component } from '@angular/core';
@Component({
selector: 'courses',
template: `
<button class="btn btn-primary">Save</button>
`
})
export class CoursesComponent {
}
style.css
and add the following:body {
padding: 20px;
}
isActive
field to DOM property class.active
:// Class Binding
[class.active] = "isActive"
// File: courses.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'courses',
template: `
<button class="btn btn-primary" [class.active]="isActive">Save</button>
`
})
export class CoursesComponent {
isActive = true;
}
isActive
is true
, the target class will be added to the elementisActive
is false
and class.active
exists in the element, the target class will be removed// Style Binding
<button [style.backgroundColor]="isActive? 'blue': 'white'">Save</button>
// File: courses.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'courses',
template: `
<button [style.backgroundColor]="isActive? 'blue': 'white'">Save</button>
`
})
export class CoursesComponent {
isActive = false;
}
style
object in DOM:
()
instead of squared boxes []
// Event Binding
<button (click)="onSave()">Save</button>
event object
that was raised in the event handler. For example with mouse movements, the event object will tell us the x and y poisitions$event
to represent a DOM event
import { Component } from '@angular/core';
@Component({
selector: 'courses',
template: `
<button (click)="onSave($event)">Save</button>
`
})
export class CoursesComponent {
isActive = false;
onSave($event){
console.log("Button was clicked", $event);
}
}
import { Component } from '@angular/core';
@Component({
selector: 'courses',
template: `
<div (click)="onDivClicked()">
<button (click)="onSave($event)">Save</button>
</div>
`
})
export class CoursesComponent {
isActive = false;
onDivClicked(){
console.log("div was clicked");
}
onSave($event){
console.log("Button was clicked", $event);
}
}
event bubbling
by using the standard method in vanilla JavaScript:// Stop Event Bubbling
$event.stopPropagation();
@Component({
selector: 'courses',
template: `
<div (click)="onDivClicked()">
<button (click)="onSave($event)">Save</button>
</div>
`
})
export class CoursesComponent {
isActive = false;
onDivClicked(){
console.log("div was clicked");
}
onSave($event){
$event.stopPropagation();
console.log("Button was clicked", $event);
}
}
// Vanilla JS
@Component({
selector: 'courses',
template: `
<input (keyup)="onKeyUp($event)" />
`
})
export class CoursesComponent {
onKeyUp($event){
if ($event.keyCode == 13) console.log("Enter was pressed");
}
}
In Angular, we have a better way to implement the exact same feature by applying a filter when handling an event
// Angular version
@Component({
selector: 'courses',
template: `
<input (keyup.enter)="onKeyUp()" />
`
})
export class CoursesComponent {
onKeyUp(){
console.log("Enter was pressed");
}
}
// Vanilla JS
// Get value of input
@Component({
selector: 'courses',
template: `
<input (keyup.enter)="onKeyUp($event)" />
`
})
export class CoursesComponent {
onKeyUp($event){
console.log($event.target.value);
}
}
Instead of passing the event object around, in Angular, we can declare a variable in our template that references this input field
#[variable name]
// Angular Template variable
// Get value of input
@Component({
selector: 'courses',
template: `
<input #email (keyup.enter)="onKeyUp(email.value)" />
`
})
export class CoursesComponent {
onKeyUp(email){
console.log(email);
}
}
email
in the previous section is a technique used in procedural programming
which is not recommendedemail
in classonKeyUp()
don't need parameteremail
property represents dataonKeyUp()
represents behavior behind the viewtemplate
property represents HTML markupme@example.com
. However, after we changed the address to me@domain.com
, in the console, we still got me@example.com
! Why?property binding
, the direction of binding is from the component to the view
banana in a box [()]
for implementing two-way bindingngModel
ngModel
is something that Angular adds to the DOM object// Two-way Binding
<input [(ngModel)]="email" (keyup.enter)="onKeyUp()" />
import { Component } from '@angular/core';
@Component({
selector: 'courses',
template: `
<input [(ngModel)]="email" (keyup.enter)="onKeyUp()" />
`
})
export class CoursesComponent {
email = "me@example.com";
onKeyUp(){
console.log(this.email);
}
}
ngModel
is defined in one of the models called forms
and by default, it's not imported in your applicationapp.module.ts
and add the FormsModule
// Import Forms module
import { FormsModule } from '@angular/forms';
FormsModule
in our main AppModuleif you miss this step, you will get an error Can't bind to 'ngModel' since it isn't a known property of 'input'
in the console
Back to the page, we change input to me@domain.com
and examine the console. This time, we got me@domain.com
|
to format data@Component({
selector: 'courses',
template: `
{{course.title | uppercase | lowercase}} <br/>
{{course.students | number}} <br/>
{{course.rating | number: '1.2-2'}} <br/>
{{course.price | currency: 'AUD': false: '3.2-2'}} <br/>
{{course.price | currency: 'AUD': '3.2-2'}} <br/>
{{course.releaseDate | date: 'shortDate'}} <br/>
`
})
export class CoursesComponent {
course = {
title: "The Complete Angular Course",
rating: 4.9745,
students: 30123,
price: 190.95,
releaseDate: new Date(2016, 3, 1)
}
}
Pipe
decorator function and PipeTransform
interface that shapes of all pipes in AngularPipeTransform
interface@Pipe()
decorator functionimport { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'summary'
})
export class SummaryPipe implements PipeTransform {
transform(value: string, limit?: number){
if (!value)
return null;
let actualLimit = (limit)? limit: 50;
return value.substr(0, actualLimit) + ' ...';
}
}
app.module.ts
and add SummaryPipe
in @NgModule
// File: courses.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'courses',
template: `
{{text | summary:10 }}
`
})
export class CoursesComponent {
text = `Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.`;
}