Angular + Typescript Notes


Ultimate Course

Typescript: A type checking wrapper on top of javascript

Property binding

both of them are updated dynamically.

[name]=”<expression>”

name=”<plain string> , or {{interpolation}} as string”

Event binding

<input (input)="onChange($event)">

Two way binding

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

<input [(ngModel)="name"] > // internally, angular registers an event handler and update name for you.

Template Ref

<input #inputElem>  // This is a template ref, the id for it is inputElem
<button (click)="onClick(inputElem.value)">click me</button> // pass value of inputElem to the function

Rendering flow

ngIf, * syntax and <ng-template>

<div *ngIf="<expression>">

is equal to

<ng-template [ngIf]="name.length > 0">
  <p>searching for {{name}}...</p>
</ng-template>

The “*” is a syntax sugar.

ngFor

<div *ngFor="let item of items; let i = index;"> {{i}} : {{item}}</div>

*ngFor is a syntax sugar, and the code block above is equivalent to

<ng-template ngFor let-i="index" let-item [ngForOf]="items">
  <div> {{i}} : {{item}} </div>
</ng-template>

ngFor is a structure directive

You can also use ngFor on a single layer of an element.

<div *ngFor="let item of items" [name]="item"></div>

ngClass and className bindings

[class.className]=”expression” and [ngClass]=”{ ‘className’: expression, ‘anotherClassName’: anotherExpression}” can both add className to the element, but ngClass can add multiple classNames to it.

<div [class.checked-in]="isCheckedIn"> // "checked-in" class is added to the element if "isCheckedIn" is evaluated to true

<div [ngClass]=" { 'checked-in': isCheckedIn, 'checked-out': 'isCheckedOut'}"> // 'checked-in' is added to the element if 'isCheckedIn' is evaluated to true; 'checked-out' is added to the element if isCheckedOut is evaluated to true. 

ngStyle and style bindings

<div [style.background]="'#2ecc71'"> // bind the color hex code to style.background. becareful, since the content in side "" is expression. so '' is needed to wrap the string.

<div [ngStyle]="{background: checkedIn? '#2ecc71': 'ffffff'}"> // use ngStyle to add multiple styles to it.

Pipes for data transformation

pipes can be used to transform the data right before rendering.

{{dateInSeconds | date: 'yMMM'}}  {{ name | json}}

There are bunch of built in pipes available here. And you can create custom pipes to transform the data for rendering

Safe navigation

Use ? operator to avoid null pointer exception.

children?.length // '?' is used as safe navigator. If children is null or undefined, the whole expression is finished, otherwise access 'length' member of children. 

let children? : string[];
length = children?.length || 0; // return children.length if children exists, otherwise return 0.

Use ?? for null checking and return default value

const yourName = name??'default_name' ;
// is equal to 
const yourName = name == null? 'default_name': name; 

Component architecture and feature modules

Dumb and smart component

Dumb/presentational component just renders the UI via @input, and emit events via @output

Smart component communicates with services, and render child components.

One way data flow

The event emits up, the data flows down.

<div [data]="companies" (change)="handleChange()">

Ng-template with context

<ng-container *ngTemplateOutlet="child;context:ctx> // ctx is an context object
<ng-template #child let-node></ng-template> // node = object assigned by $implicit

-----
.ts file
ctx = {
 $implicit = object;
 // other keys 
}

Immutable state changes

remove elements from the array

names = names.filter(name => name.id !== id); // remove names whose id is id

edit elements in an array.

names = names.map(name => {
  if (name.id === event.id) {
    name = Object.assign({}, name, event) // the name object is merged with the event object. when conflict property value happens,  the property value in event object prevails.
  }
  return name;
})

Service, HTTP and Observation

// service file
export class PassengerService {
  constructor(){}
  getPassengers(): Passengers[] {
    return [];
  }
}

// module file
import: [],
export: [],
declaration: [],
providers: [PassengerService] // make the service available in the current module. Make it available for injection.

// component file
// dependency injection
constructor(private passengerService: PassengerService) {}

Injectable annotation

// PersonService.ts
import {HttpClient} from '@angular/common/http';
import {Injectable} from '@angular/core';

@Injectable() // This means this service could use inject dependency (HttpClient)
export class PersonService {
  constructor(private http: HttpClient){}
}

// module.ts
import {HttpClientModule} from '@angular/common/http';

@NgModule({
  imports:      [ BrowserModule, FormsModule, HttpClientModule ],
})

Http data fetch with Observerables

Http put, delete with immutable state

Headers and requestOptions

// PersonService.ts
const PASSENGER_API: string = '/api/passengers';
@Injectable() // This means this service could use inject dependency (HttpClient)
export class PersonService {
  constructor(private http: HttpClient){}

  getPassengers(): Observable<Passenger[]> {
    return this.http.get(PASSENGER_API)
        .map((response: Response) => {
           return response.json();
         })
  }

  updatePassenger(passenger: Passenger): Observable<Passenger> {
    let headers = new Headers({
      'Content-type': 'application/json'
    });
    let options = new RequestOptions({headers: headers});
    return this.http.put(`${PASSENGER_API}\${passenger.id}`, passenger, options)
           .map((response: Response) => {
             return response.json();
           })
  }

  removePassenger(passenger: Passenger): Observable<Passenger> {
    return this.http.delete(`${PASSENGER_API}\${passenger.id}`)
           .map((response: Response) => {
             return response.json();
           })
  }
}

// component.ts
constructor(private passengerService: PassengerService){}
ngOnInit() {
  this.passengerService.getPassengers().subscribe((data: Passenger[]) => {
    this.passengers = data;
  })
}

handleEdit(event: Passenger) {
  this.passengerService.edit(event).subscribe((data: Passenger) => {
    this.passengers = this.passengers.map((passenger: Passenger) => {
    if(passenger.id === event.id) {
      passenger = Object.assign({}, passenger, event);
    }
  return passenger;
  })
})
}

handleRemove(event: Passenger) {
  this.passengerService.remove(event).subscribe((data: Passenger) => 
  { // data is not used here, just as a placeholder.
  this.passenger = this.passenger.filter((passener: Passenger) => {
    return passenger.id !== event.id;
  })
})
}

Http promises alternative

toPromise() operator could map the observable to promise.

Observable throw error handling

// service.ts

return this.http.get(PASSENGER_API).map((response : Response) => response.json()).catch((error: any) => Observable.throw(error.json()));

// caller.ts
this.passengerService.getPassenger().subscribe((data: Passenger[]) => handle_data, (error: Error) => handle_error);

Angular Template Driven Form

set up form container component

set up form stateless component: container passes input value to stateless component.

ngForm and ngModel, radio button, checkBox

// stateless component
template=`
<from #form="ngForm"> // activate ngForm directive, assign the model to template reference variable 'form'
  // input box
  <input ngModel type="number" name="id"> // ngModel: bind the input to the form object, with a key named 'id' 
  // radio button
  <label><input ngModel (ngModelChange)="toggleCheckIn($event)" name="isCheckedIn" type="radio" [value]="true">Yes</label>
  <label><input ngModel (ngModelChange)="toggleCheckIn($event)" name="isCheckedIn" type="radio" [value]="false">No</label>
  // checkbox
  <label><input ngModel (ngModelChange)="toggleCheckIn($event)" name="isCheckedIn" type="checkbox">Check in</label>
  // options 
  <select name="baggage" [ngModel]="detail?.baggage">
    <option *ngFor="let item of baggage" 
             [value]="item.key">{{item.value}}</option>
    [selected]="item.key === detail?.baggage" // pre-select an option based on the input. 
  // You can also use [ngValue]="item.key" to replace [value] and [selected]
  </select>
</form>

{{ form.value | json }} // {'id': xxx}
`
export class PassengerFormComponent {
  @Input()
  detail: Passenger;

  toggleCheckIn(checkedIn: boolean) {
     detail.checkedInDate = Date.now();   
  }
}

Form validation

<form #form="ngForm">
  <input #fullname="ngModel" required>
  <div *ngIf="fullname.errors?.required && fullname.dirty"> Fullname is required</div>
  {{ fullname.errors | json}} // {"required": true}
</form>
{{form.valid | json}} // boolean
{{form.invalid | json}} // boolean

Form submit

@Output()
update: EventEmitter<Passenger> = new EventEmitter<Passenger>();

<form (ngSubmit)="handleSubmit(form.value, form.valid)" #form="ngForm">

  <button type="submit" [disabled]="form.invalid"></button>
</form>

handleSubmit(passenger: Passenger, isValid: boolean) {
  if (isvalid) {
    this.update.emit(passenger);
  }
}

Component routing

Base href

<head>
  <base href="/"> // Important to include it in the index.html
</hread>

// module.ts
import {RouterModule} from '@angular/router'

404 handling, routerLink, routerLinkActive

//app.module.ts

const routes: Routes = [
  { path: '', component: HomeComponent, pathMatch: 'full'},
  { path: '*', component: NotFountComponent}
];

@NgModule({
  declarations: [],
  imports: [],
  bootstrap: [AppComponent]
})

// NotFoundComponent.ts
@Component({
  selector: 'not-found'
  template: `<div> Not Found</div>`
})

// app.component.ts

template: `
  <div class='app'>
<nav>
    <a routerLink='/' routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">Home</a> // routerLinkActive: add 'active' class to the link if it's active.
    <router-outlet></router-outlet>
  </div>
`</nav>

// .scss

a {
  &.active {
    color: #b690f1;
  }
}

For multiple elements in nav, you can also use an ngFor to initialize the <a> links.

In a child module, use RouterModule.forChild() to register the router module for child.

Children route, Router params

const route: Routes = [
{
  path: 'passengers',
  children: [
    {path: '', component: PassengerDashboardComponent}, // maps to '/passengers'
    {path: ':id', component: PassengerViewComponent}, // maps to '/passengers/123423' any number
  ]
}
]

Route and ActivatedRoute

template: `
  <button (click)="goBack()">Go Back</button>
`

constructor(private router: Router, private route: ActivatedRoute)

ngOnInit(){
  this.route.params.switchMap((data: Params) => this.passengerService.getPassenger(data.id)).subscribe((passenger: Passenger) => {this.data = passenger;})
}
// switchMap - listen to an observerable, and switch to return to another observable. The previous observable is cancelled once the value is returned. 
goBack(){
  this.router.navigate(['/passengers']);
}

viewPassenger(event: Passenger){
  this.router.navigate(['/passengers', event.id]); // /passengers/:id
}

Hash location strategy: the parts after ‘#’ symbol in the url never sends to the server.

Thus the parts after the ‘#’ symbol could be used to record the client state. like anchor in the page, etc.

Redirect to

const routes: Routes = [
  {path: '', redirectTo: '/passengers'}
] // the '' page would be redirected to the '/passengers' page

Angular Pro Course

Content Projection with <ng-content>

the content in the selector tag could be projected to the child component html using <ng-content>.

<auth-form (submitted)="login($event)")>
  <h3>Login</h3>
  <button type="submit">login</button>
</auth-form>
<auth-form (submitted="signUp($event)")>
  <h3>Sign Up</h3>
  <button type="submit">join us</button>
</auth-form>

// auth-form.ts

template: `
  <ng-content select="h3"></ng-content>
  <form>
   <ng-content select="button"></ng-content> // select is a query selector, it can also select a class by using '.class-name'
  </form>
`

Content projection can also bind a component element, rather than basic HTML elements.

Measure the Web App performance

The Web Apps should aim for refreshing the page at 60fps. That is, finish a rendering cycle within 16ms. The javascript should be finish within 10ms, thus the browser has 6ms to do the housekeeping works.

https://developers.google.com/web/fundamentals/performance/rendering

The full pixel pipeline

browser渲染一个页面分五步:

Javascript: 运行javascript,包括更新变量值,更新DOM,更新variable和DOM element里的binding。

Style:计算每个element应该attach to哪个css class

Layout:计算每个element在页面中的位置

Paint:把每个element的所有pixel画出来。

Composite:paint过程中,实际上element被画在了不同的layer上。composite这步就是把不同layer的画整合到一个layer的screen上。包括谁应该覆盖谁,谁在上面谁在下面。

实际rendering中,并不是每个步骤都一定会被执行。

The full pixel pipeline
case 1. 所有阶段都被执行一遍
The  pixel pipeline without layout.
case 2. layout不用执行。比如element的layout不用改。可能只改了UI string,换了背景颜色等。
The pixel pipeline without layout or paint.
case 3. layout和paint都不用改,也即画面不需要update。这种cycle最节省资源。

Udacity的课程,如何优化Web App的performance

https://www.udacity.com/course/browser-rendering-optimization–ud860

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.