Sep 30, 2020

Create an Angular demo project

When learning Angular or any other framework or we have an idea, we want to quickly create a proof of concept. One way is quickly to quickly go to a online editor, such stackblitz, codepen, and start coding a demo project, then show case to other people of your code. But the problem is that, we quickly forget about the url of the projects, the more we create and the more we forget. So I built a project to serve as container of all demo projects. Here is what you need to do if you want to create new demo.

  1. Create a component, name it as demo-[name], such as demo-hero. Put your demo code in the component.
  2. Register the component in the demo-list.ts, like below. That is it.
//demo-list.ts
import { DemoHelloComponent } from "./demo/demo-hello/demo-hello.component";
import { DemoByeComponent } from "./demo/demo-bye/demo-bye.component";
import { DemoHeroComponent } from "./demo/demo-hero/demo-hero.component";

export const demoList = {
  DemoHelloComponent,
  DemoByeComponent,
  DemoHeroComponent
};

Here it is preview of the project

Pretty simple, right? Here is how I do that.

  • Step 1 Create the DemoService(demo.service.ts) which list all the component you want to demo.
  • Step 2. Create a component(demo.component.ts) menu link to all the component in list
  • Step 3. Create route (app.module.ts) like demo/:id and link to the each component

If you interested in the code, you can navigate in the project

Angular component field initialization

In my previous post, I talked about Class field initialization in typescript. How about field initialization of angular component, is it different? Angular component is defined an typescript class. It should follow same logics. However, Angular component has life cycle. The ngOnInit is special method that angular will run angular first displays the data-bound properties and sets the directive or component's input properties. So if your initialization code depends on these data-bound proprties and input properties, you can not run them in constructor. So the following code doesn't work

@Component({
  //..
})
class MyComponent {

  //angular specific
  @Input() firstName: string;
  @Input() lastName: string;

  fullName: string;

  constructor() {
    //this does not work
    this.fullName = this.firstName + ',' + this.lastName;
  }
}

You have to run this code in the ngOnInit method like below.

@Component({
  //..
})
class MyComponent {

  //angular specific
  @Input() firstName: string;
  @Input() lastName: string;

  fullName: string;

  ngOnInit() {
    this.fullName = this.firstName + ',' + this.lastName;
  }

}

But some people take it too far. Since it is safter to run in ngOnInit method, why not always put all initialize code in this method. And So the following initialization code become very popular, even the initialization code has no dependency of angular data-bound proprties and input properties.

@Component({
  //..
})
class ProductComponent {

  displayCode$: Observable<boolean>;
  errorMessage$: Observable<string>;
  products$: Observable<Product[]>;
  selectedProduct$: Observable<Product>;

  constructor(private store: Store) { }

  ngOnInit(): void {
    this.store.dispatch(ProductPageActions.loadProducts());
    this.products$ = this.store.select(getProducts);
    this.errorMessage$ = this.store.select(getError);
    this.selectedProduct$ = this.store.select(getCurrentProduct);
    this.displayCode$ = this.store.select(getShowProductCode);
  }
}

So If we understand this, can initialize the code like the following. It saves your effor of explicit typing by using typescript type inference. and it also improves code readability.

@Component({
  //..
})
class ProductComponent {

  products$ = this.store.select(getProducts);
  errorMessage$ = this.store.select(getError);
  selectedProduct$ = this.store.select(getCurrentProduct);
  displayCode$ = this.store.select(getShowProductCode);

  constructor(private store: Store) { }
}

Please your library user with typescript generic

In typescript, generic is a tool that api/library author to please its consumer. What does this means.

For example, as library developer, I want to create a higher order function to modify a an existing function, so that I can log the entering and exiting of the function 's execution. The following function does the job

function log (fn) {
  const name = fn.name;
  return function (...args) {
    console.log(`entering ${name}`);
    const rtn = fn(...args);
    console.log(`leaving ${name}`);
  };
}

function add(a:number, b: number) {
  return a + b;
}

add(1, 2);
add('1', '2'); //warning
//
const logAdd = log(add); //type information is lost
logAdd(1, 2);
logAdd('1', '2'); //no warning

When consuming the log function, the modified function run as expected, but it lost the signature of the original function, and it cannot type checking like the original function.

It is pretty easy to solve the problem by adding generic to the log function.

function log<T extends (...args) => any>(fn: T) {
  const name = fn.name;
  return function (...args) {
    console.log(`entering ${name}`);
    const rtn = fn(...args);
    console.log(`leaving ${name}`);
  } as T;
}

function add(a:number, b: number) {
  return a + b;
}

add(1, 2);
add('1', '2'); //warning
//
const logAdd = log(add); //type information is retained.
logAdd(1, 2);
logAdd('1', '2'); //warning

Now the modified function has all the type information of the original function. The way it works is that typescript infer the type of input argument, and pass along the type along.

Class field initialization in typescript

In typescript class, you can define your field initialization in multiple ways. Here is one typical way to do that.

class BadPerson {

  fullName: string;
  age: number;
  created: Date;

  constructor(public firstName: string, public lastName: string) {
    this.fullName = this.firstName + ',' + this.lastName;
    this.age = 18;
    this.created = new Date();
    //
    //other imperative initailization goes to bottom
  }
}

//typescript will compile to javascript like 
class BadPerson {
    constructor(firstName, lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.fullName = this.firstName + ',' + this.lastName;
        this.age = 18;
        this.created = new Date();
        //
        //other imperative initailization goes to bottom
    }
}

The problem here is that you have to explicitly have specify the types of each field, and you initialize them in different place from where they are declared. The better way to initialize class fileds is like the following.

class GoodPerson {

  fullName = this.firstName + ',' + this.lastName;
  age = 18;
  created = new Date();

  constructor(public firstName: string, public lastName: string) {
    //
    //other imperative initailization goes to bottom
  }
}
  
// typescript will compile to javascript like
class GoodPerson {
    constructor(firstName, lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.fullName = this.firstName + ',' + this.lastName;
        this.age = 18;
        this.created = new Date();
        //
        //other imperative initailization goes to bottom
    }
}

Now the types of each fields are inferred correctly from the initialization, and you don't need to waste time to declare types from them. Secondly, you put initiaization and declaration in the same place, so the readability is much improved. Thirdly, the generated code is identical, so the logics is the same. It is true that field initialization happen before the code inside class constructor (from the code you can see that). So people believe that the shortcut field initialization in the constructor parameter is also initialized after normal field initialization, so if field initialization depends on the constructor parameter, they are not accessible. So people think they need to initialize those field inside the constructor. But from the compiled code, we can see that shortcut field initialization happens first, and they are accessible to other field initialization.