Saturday, February 25, 2017

fun features in TypeScript!

On the 13th of this moth I saw Chander Dhall speak on "TypeScript and Angular 2" at the Austin .NET User Group. The first half of the talk was just on TypeScript and he said some things I had not heard before! What is below will return two alerts. The first has 13 in it and the second has undefined in it.

class Product {
   public id: number;
   constructor(id: number) {
      alert(id);
   }
}
let product = new Product(13);
alert(product.id);

 
 

Tuples are for beating the Thunderdome problem. When you start bolting stuff onto a tuple it has to be one of the predefined types already there, but beyond that you may just bolt stuff on. Lists may be cast to tuples. The following will throw three alerts saying iOS then Android then Windows at the end.

let list = ["iOS", "Android", "Windows"];
let [first, second, third] = list;
alert(first);
alert(second);
alert(third);

 
 

This throws the same three alerts.

let list = ["iOS", "Android", "Windows"];
let [first, ...rest] = list;
alert(first);
alert(rest[0]);
alert(rest[1]);

 
 

Suck out the parameters from a type out to variables! The following throws two alerts that say first 123 and then: Product Model 123

let product = {
   id: 123,
   address: {},
   model: "Product Model 123"
}
let { id, model } = product;
alert(id);
alert(model);

 
 

This throws two alerts with Joshua and Cazton being the values:

function setDefault(obj: { empName: string, companyName?: string }) {
   let { empName, companyName = "Cazton" } = obj;
   alert(empName);
   alert(companyName);
}
setDefault({empName:"Joshua"});

 
 

This does the same thing more or less, but now the second alert will say Whatever.

function setDefault(obj: { empName: string, companyName?: string }) {
   let { empName, companyName = "Cazton" } = obj;
   alert(empName);
   alert(companyName);
}
setDefault({empName:"Joshua", companyName: "Whatever"});

 
 

This allows for the value handed in to be one of two things.

function unionTypes(value: number | number[]) {

 
 

interface IDivide {
   (n: number, d: number): number;
}

...is like...

let divide : (n: number, d: number) => number;

 
 

The following was given as an example of how to do generics, but I wonder if this is going to work given how the very first example above behaves.

class Model{
   model: T;
   constructor(model: T) {
   }
}

 
 

Getting into Angular2 another thing I learned that was new had to do with the distinction between Observable, Promise, and Callback. To illuminate let's explain what Observable is firstly, revamping the app I describe here and here by making, to begin with, a "models" folder in the "app" folder with two new, badly named objects in it. ;) The first is in a file dubbed "outer.model.ts" and it looks like this:

import {Observable} from 'rxjs/observable';
import {Inner} from './inner.model';
export class Outer {
   isbn: string;
   characters: Array<string>;
   contentDrugInFromOtherEndpoints: Array<Observable<Inner>>;
   contentKeptInFromOtherEndpoints: Array<Inner>;
}

 
 

The other is "inner.model.ts" and it looks like this:

export class Inner {
   name: string;
}

 
 

The models above will be used to catch calls that come back from an API. Outer has some extra state bolted on to it too. It'll make sense. Patience! Next make a "services" folder in the "app" folder and put "api-service.service.ts" in it. It will hold the following code:

import {Injectable} from '@angular/core';
import {Http, Response} from '@angular/http';
import {Outer} from '../models/outer.model';
import {Inner} from '../models/inner.model';
import {Observable} from 'rxjs/observable';
import {ApiContract} from '../contracts/api-contract.contract';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/toPromise';
@Injectable()
export class ApiService implements ApiContract {
   public api:string;
   constructor(private http: Http) {
      this.api = "http://www.anapioficeandfire.com/api/books";
   }
   public getOuter(): Observable<Outer[]>{
      return this.http.get(this.api).map((response: Response) => <Outer[]>response.json());
   }
   public getInner(url: string): Observable<Inner> {
      return this.http.get(url).map((response: Response) => <Inner>response.json());
   }
}

 
 

Alright, I'm going to use the dependency injection trick at the providers for my template so therefore I also have to have an inheritance trick for my service above. It needs to be upcastable to something like an interface that just holds signatures for its methods. In going through a learning curve it turned out that you cannot make a contract for a service that is an interface and get it to play nicely with the "providers dependency injection thing" but instead the class you are to use needs to implement a class you are to use for a contract. Note that I used the word implement. Typically in inheritance in TypeScript a class implements and interface and a class extends another class, but herein, in this wackiness, a class must implement a class. This means that a line reading super(); at the first line inside of the constructor of the service needs to be removed as not only is it no longer mandatory, it won't compile. You'll notice this as one of the big changes when you switch implements to extends in these scenarios. Just below is the last of the four new files. Make a "contracts" folder and put "api-contract.contract.ts" inside of it holding the following code.

import {Outer} from '../models/outer.model';
import {Inner} from '../models/inner.model';
import {Observable} from 'rxjs/observable';
export class ApiContract {
   constructor() {}
   public getOuter: () => Observable<Outer[]>;
   public getInner: (url: string) => Observable<Inner>;
}

 
 

Our original starting-point-component.ts needs love anew:

import { Component, Input } from '@angular/core';
import {Http, Headers} from '@angular/http';
import {Observable} from 'rxjs/observable';
import {Outer} from '../models/outer.model';
import {Inner} from '../models/inner.model';
import {ApiContract} from '../contracts/api-contract.contract';
import {ApiService} from '../services/api-service.service';
@Component({
   selector: 'entry-point',
   templateUrl: '/Partial/SomethingErOther',
   providers: [{provide: ApiContract, useClass: ApiService}]
})
export class StartingPoint {
   private welcome: string;
   private outers: Observable<Outer[]>;
   constructor(private apiContract: ApiContract) {
   this.welcome = "Hey, look at this!";
   this.outers = apiContract.getOuter();
   }
   act(outer: Outer) {
      if (!outer.contentKeptInFromOtherEndpoints) {
         outer.contentKeptInFromOtherEndpoints = new Array<Inner>();
         let counter = 0;
         while (counter < outer.characters.length){
            outer.contentKeptInFromOtherEndpoints.push(new Inner())
            counter++;
         }
      }
      outer.characters.forEach(url => {
         let inner = this.apiContract.getInner(url);
         if (!outer.contentDrugInFromOtherEndpoints) {
            outer.contentDrugInFromOtherEndpoints = new Array<Observable<Inner>>();
         }
         outer.contentDrugInFromOtherEndpoints.push(inner);
      });
   }
   react(inner: Inner, outer: Outer, counter: number){
      if(outer.contentKeptInFromOtherEndpoints[counter]){
         if (!outer.contentKeptInFromOtherEndpoints[counter].name){
            if (inner && inner.name){
               outer.contentKeptInFromOtherEndpoints[counter] = inner;
            }
         }
      }
   }
}

 
 

Let's make the HTML for the view starting-point-component.ts end up using look like this:

<ol>
   {{welcome}}
   <li *ngFor="let outer of outers | async">
      {{outer.isbn}}
      <ng-container *ngIf="!outer.contentDrugInFromOtherEndpoints">
         <a style="cursor:pointer; color:blue" (click)="act(outer)">more?</a>
      </ng-container>
      <ng-container *ngIf="outer.contentDrugInFromOtherEndpoints">
         <ng-container *ngFor="let inner of outer.contentDrugInFromOtherEndpoints;
               let i = index;">
            {{react(inner | async, outer, i)}}
         </ng-container>
      </ng-container>
      <ul *ngIf="outer.contentDrugInFromOtherEndpoints">
         <li *ngFor="let inner of outer.contentKeptInFromOtherEndpoints">
            {{inner.name}}
         </li>
      </ul>
   </li>
</ol>

 
 

Alright, this all works. There is a way to use Promise<Outer[]> instead of Observable<Outer[]> above (with a little bit different syntax) and Promise is native to Angular 2 and does not require you to use rxjs as Observable does. Also you can just use Callback by which I mean plain Jane JavaScript asynchronous implementations that have existed since the beginning of AJAX. TypeScript 2 supports the Promise API so you can do its .done stuff, so why go to Observable at all and loop in rxjs? Observable is good for talking to a web socket more so than an API endpoint, where there is a buffer. You can get back a collection one item at a time in a progressing stream and then manhandle/repurpose/use the array asynchronously as it fills in. One last thing on Angular 2: An $event from a keyup event may be caught as a KeyboardEvent at a TypeScript method and then cast to a HTMLInputElement if desired.

No comments:

Post a Comment