typescript 带有dayjs和rxjs的简单角度计数器/倒计时组件

带有dayjs和rxjs的简单角度计数器/倒计时组件

counter.component.ts
/**
 * 
 * Example Usage:
 * 
 * # Counter
 * const now = dayjs()
 * <app-counter [autostart]="true" [mode]="forwards" [startTime]="now"></app-counter>
 * 
 * # Countdown
 * const later = dayjs().add(1, 'minute')
 * <app-counter [autostart]="true" [mode]="backwards" [stopTime]="later"></app-counter>
 * 
 */


import { Component, Input, OnDestroy, OnInit } from '@angular/core';
import * as dayjs from 'dayjs';
import { interval, Subject, Subscription } from 'rxjs';
import { takeUntil } from 'rxjs/operators';


@Component({
  selector: 'app-counter',
  template: `{{ timeFormatted }}`,
})
export class CounterComponent implements OnInit, OnDestroy {

  // Forwards-Mode (Counter)
  @Input() forwards: boolean
  @Input() startTime: Date
  public elapsedSeconds: number
  public counterHasStarted: boolean

  // Backwards-Mode (Countdown)
  @Input() backwards: boolean
  @Input() stopTime: Date
  public countdownHasFinished: boolean
  public remainingSeconds: number

  public timeFormatted: string

  @Input() autostart: boolean
  @Input() updateInterval: number = 1000 / 2
  public timeInterval$: Subscription
  private unsubscribe$ = new Subject()


  constructor() { }

  ngOnInit() {
    if (this.autostart) {
      this.start()
    }
  }

  ngOnDestroy() {
    this.stop()
  }


  /**
   * Initializes the Counter / Countdown
   */
  public start() {
    if (this.forwards == this.backwards) {
      console.error("Couldn't start counter as no mode or both modes are set.")
      return
    }
    if (this.forwards && (!this.startTime || !dayjs(this.startTime).isValid())) {
      console.error("Couldn't start counter as mode is 'forwards' but no start-time is provided.")
      return
    }
    if (this.backwards && (!this.stopTime || !dayjs(this.stopTime).isValid())) {
      console.error("Couldn't start counter as mode is 'forwards' but no start-time is provided.")
      return
    }

    // Start Interval
    this.timeInterval$ = interval(this.updateInterval).startWith(0).pipe(
      takeUntil(this.unsubscribe$)
    ).subscribe(_ => {
      this.updateTime()
    })
  }


  /**
   * Stops the Counter / Countdown
   */
  public stop() {
    this.unsubscribe$.next(true)
    this.unsubscribe$.complete()
  }


  /**
   * Updates `timeFormatted` of the Counter / Countdown
   */
  private updateTime() {
    const now = dayjs()

    if (this.forwards) {
      // Start-Time from which the counter gets increased
      const startTime = dayjs(this.startTime)

      this.counterHasStarted = now.isAfter(startTime)
      if (!this.counterHasStarted) {
        this.timeFormatted = '0:00'
        this.elapsedSeconds = 0
        return
      }

      let elapsedTime = dayjs(now.valueOf() - startTime.valueOf())
      elapsedTime = elapsedTime.subtract(dayjs().utcOffset(), 'minute')
      this.elapsedSeconds = now.diff(startTime, 'second')

      const format = elapsedTime.hour() ? 'H:mm:ss' : 'm:ss'
      this.timeFormatted = elapsedTime.format(format)


    } else if (this.backwards) {
      // Stop-Time until which the countdown gets decreased
      const stopTime = dayjs(this.stopTime)

      this.countdownHasFinished = now.isAfter(stopTime)
      if (this.countdownHasFinished) {
        this.timeFormatted = '0:00'
        this.remainingSeconds = 0
        return
      }

      let remainingTime = dayjs(stopTime.valueOf() - now.valueOf())
      remainingTime = remainingTime.subtract(dayjs().utcOffset(), 'minute')
      this.remainingSeconds = stopTime.diff(now, 'second')

      const format = remainingTime.hour() ? 'H:mm:ss' : 'm:ss'
      this.timeFormatted = remainingTime.format(format)
    }
  }

}

typescript 使用几个|在ngFor - > ngFor中的async

async-ngfor
  <ng-container *ngIf="{
    users: users$ | async,
    adminsList: adminsList$ | async
  } as data">
    <div class="ads-table__content">
      <div *ngFor="let user of data.users" class="users__item">
  
        <div>{{user.userName}}</div>
        <div>{{user.email}}</div>
      
        <select>
          <option value="admin.id" *ngFor="let admin of data.adminsList">{{admin.fullName}}</option>
        </selet>
       
      </div>
    </div>
  </ng-container>

typescript TS装饰器

classDecorators
//If attach decorators to a class, definitely that 1 argument is the constructor

function logged(constructorFn: Function){
	console.log(constructorFn);
}

@logged //prints the constructor of this class
class Person{
	constructor(){
		console.log("Hi");
	}
}

//Alternative - Factory
function logging(value: boolean){
	return value ? logged : null;
}
@logging(true) // Attach the logging results and it returns the logged function which is valid as a decorator as it gets the constructor aguments
class Car{
	
}

//Create a useful decorator - Advanced
function printable(constructorFn: Function){
	constructorFn.prototype.print = function(){
		console.log(this);
	}
}

@printable
class Plant{
	name="Green Plant";
}
const plant = new Plant();
(<any>plant).print():


//Using multiple decorators
@logging(true)
@printable
class Plant{
	name="Green Plant";
}
const plant = new Plant();
(<any>plant).print():
methodDecorators
//Method Decorators
function editable(value: boolean){
	//target depends method is construcotr in static method/class or prototype in instantiate class
	return function(target: any, propName: string, descriptor: PropertyDescriptor){ //look up descriptor
		descriptor.writable = value;
	}
}

class Project{
	projectName: string;
	
	constructor(name: string){
		this.projectName = name;
	}
	
	@editable(false)
	calcBudget(){
		console.log(1000);
	}
}

//Make a decorator to make the method editable or non editable
const project = new Project('Super Project');
project.calcBudget();
project.calcBudget = function(){ //Cant overwrite since decorator editable(false)
	console.log(2000);
}
project.calcBudget();
propertyDecorator
//Property Decorators - Most used for reading, not for writing (reason as bellow)
function overwritable(value: boolean){
	return function(target: any, propName: string): any{
		cosnt newDescriptor: PropertyDescriptor = {
			writable: value
		};
		return newDescriptor;
	}
}
 
class Project{
	@overwritable(false) //constructor will not be able to proceed
	projectName: string;
	
	constructor(name: string){
		this.projectName = name;
	}
	
	@editable(false)
	calcBudget(){
		console.log(1000);
	}
}
 
//Make a decorator to make the property editable or non editable
const project = new Project('Super Project');
console.log(project)
parameterDecorator
//Parameter Decorator
function printInfo(target: any, methodName: string, paramIndex: number){
	console.log("Target: ", target);
	console.log("Method Name: ", methodName);
	console.log("Param Index: ", paramIndex);
}

class Course{
	name: string;
	
	constructor(name:string){
		this.name = name;
	}
	
	printStudentNumbers(mode:string, @printInfo printAll: boolean){
		if(printAll){
			console.log(10000);
		} else{
			console.loh(2000);
		}
	}
}
const course = new Course("Super Course");
course.printStudentNumbers("anything", true;)
course.printStudentNumbers("anything", false;)

typescript TS Generic

generic
//Simple Generic
function echo(data:any){
	return data;
}

console.log(echo("Max").length); //Can put method also
//However, IDE did not show length method which means it did not know that the function returned is a string

//Bottom if put length will show undefined instead of compilation error
console.log(echo(27));
console.log(echo({name:"Max", age: 27}));

//Objective: let TS know that although accept generic data, but can know the type returned from it

//Better Generic
function betterEcho<T>(data: T){ //Makes it a generic function, can be any character
	return data;
}
//Telling TS when using this function, give us the type so that we can use the type in the parameters
console.log(betterEcho("Max").length); //Will show the method
console.log(betterEcho<number>(27)); //Will not have this method and will show the error
console.log(betterEcho({name:"Max", age: 27}));


//Built-in Generics
//1. Arrays
const testResults: Array<number> = [1.94, 2.33];
testResults.push(1); //works
testResults.push("string") //wrong

//2. Assign Generic Type to be an Array
function printAll<T>(args: T[]){
	args.forEach( el => console.log(el));
}
printAll<string>(["Apple", "Banana"])

//Generic Types
const echo2: <T>(data:T) => T = echo;
console.log(echo2<string>("Something"));

//Generic Class
//Problem
class SimpleMath {
	baseValue;
	multiplyValue;
	calculate(){
		return this.baseValue * this.multiplyValue;
	}
}

const simpleMath = new SimpleMath();
simpleMath.baseValue = 10; //if change to "something", no compilation error
simpleMath.multiplyValue = 20;
console.log(simpleMath.calculate());

//Solution - change to generic class
class SimpleMath<T> {
	baseValue: T;
	multiplyValue: T;
	calculate(): number{
		return +this.baseValue * +this.multiplyValue; //explicitly cast the value "+"
	}
}

const simpleMath = new SimpleMath();
simpleMath.baseValue = 10; //if changed to "something", no compilation error
simpleMath.multiplyValue = 20;
console.log(simpleMath.calculate());

//Solution 2
class SimpleMath<T extends number | string> { //set a costraint and then add option of string and if given "10", will cast to Number
	baseValue: T;
	multiplyValue: T;
	calculate(): number{
		return +this.baseValue * +this.multiplyValue; //explicitly cast the value "+"
	}
}
const simpleMath = new SimpleMath<number>();
morethanOneGeneric
class SimpleMath<T extends number | string, U extends number | string> { 
	baseValue: T;
	multiplyValue: U;
	calculate(): number{
		return +this.baseValue * +this.multiplyValue;
	}
}
const simpleMath = new SimpleMath<string, number>();
simpleMath.baseValue = "10";
simpleMath.multiplyValue = 20;
console.log(simpleMath.calculate());
 

typescript TS接口继承

interfaceInheritance
interface NamedPerson{
	firstName: string;
	age?: number;
	[propName: string]: any;
	greet(lastName: string): void;
}

interface AgedPerson extends NamedPerson{
	age: number;
}

const oldPerson: AgedPerson = {
	age: 27,
	firstName: "Max",
	greet(lastName: string){
		console.log("Hello");
	}
}

typescript 带有函数类型的TS接口

interfaceFunc
interface DoubleValueFunc{
	(number1: number, number2:number):number;
}

let myDoubleFunction: DoubleValueFunc;
myDoubleFunction = function(a: number, b:number){
	return (a + b )*2;
}

console.log(myDoubleFunction(10, 20)); //60

typescript TS InterfaceWithClass

interfaceWithClass
interface NamedPerson{
	firstName: string;
	age?: number;
	[propName: string]: any;
	greet(lastName: string): void;
}

class Person implemenets NamedPerson {
	firstName: string;
	greet(lastName: string){
		console.log("Hi, I am" + this.firstName + lastName)
	}
}

const myPerson = new Person();
myPerson.greet("Anything");

typescript TS接口

interface
//When have function which needs to know what it uses has certain property or method, then we need to use interface

function greet(person:any){
	console.log("Hello" + person.name);
}

const person ={
	firstName: "Max",
	age: 27
}

greet(person); //will give "Hello undefined"

//Answer
function greet(person:{name: string}){
	console.log("Hello" + person.name);
}

//Answer 2
interface NamedPerson{
	name: string
	age?: number //THE QUESTION MARK MEANS OPTIONAL ARGUMENT
}

function greet(person: NamedPerson}){
	console.log("Hello" + person.name);
}
property
//Setting up dynamic property if unsure of the property name during creation of the interface
interface NamedPerson {
	firstName: string;
	age?: number;
	[propName: string]: any //Property is type string and the value can be any type
}

const person ={
	firstName: "Max",
	hobbies: ["Cooking", "Sports"]
}
method
interface NamedPerson{
	firstName: string;
	age?: number;
	[propName: string]: any;
	greet(lastName: string): void;
}

const person: NamedPerson = {
	firstName: "Max",
	hobbies: ["Cooking", "Sports"],
	greet(lastName: string){
		console.log(this.firstName)
	}
}

typescript TypeScript Private Constructos和Singletons

privateConstructors
class OnlyOne {
	private static instance: OnlyOne;
	
	private constructor(public name: string){}
	
	static getInstance(){
		if(!OnlyOne.instance){
			OnlyOne.instance = new OnlyOne("The Only One");
		}
		return OnlyOne.instance;
	}
}

let wrong = new OnlyOne('The Only One');
let right = OnlyOne.getInstance();
readOnly
class OnlyOne {
	private static instance: OnlyOne;
	
	private constructor(public readonly name: string){}
	
	static getInstance(){
		if(!OnlyOne.instance){
			OnlyOne.instance = new OnlyOne("The Only One");
		}
		return OnlyOne.instance;
	}
}
 
let wrong = new OnlyOne('The Only One');

let right = OnlyOne.getInstance();
console.log(right.name) //Can access since it is a public property
right.name = 'Something else' //Able to change therefore

//TO ONLY SET THE PUBLIC AT THE CONSTRUCTOR ONLY AND NOT OTHER PLACE
//other than using just the getter keyword and do not specify the setter
//TS offer another easier way

//OR
class OnlyOne {
	private static instance: OnlyOne;
	public readonly name:string;
	
	private constructor(name: string){
		this.name = name;
	}
	
	static getInstance(){
		if(!OnlyOne.instance){
			OnlyOne.instance = new OnlyOne("The Only One");
		}
		return OnlyOne.instance;
	}
}

typescript TypeScript ES6类

马克西米利安

class
//public: make property available to anyone who want to access it (default: public)
//private: accessible from this class or any object that is instantiated with this class
//protected: same like private, additionally, accessible from any object/classes inherited from the class that contain the protected property

class Person {
	public name: string;
	private type: string;
	protected age: number;
	
	constructor(name:string){
		this.name = name;
	}
}

//OR

class Person {
	private type: string;
	protected age: number;
	
	constructor(public name:string, public username:string){}
}
let person= new Person('Alex', "alexwck");
console.log(person);
console.log(person.name, person.username)
classMethods
//How we access private and protected
class Person {
	private type: string;
	protected age: number = 26;
	
	printAge() {
		console.log(this.age);
	}
	
	//If want to set the private or protected type property from the outside
	setType(type: string){
		this.type = type;
		console.log(this.type);
	}
}
let person= new Person();
person.printAge();
person.setType("Cool guy")


//Method also can be private or protected
class Person {
	private type: string;
	protected age: number = 26;
	
	printAge() {
		console.log(this.age);
		this.setType("Old Guy");
	}
	
	private setType(type: string){
		this.type = type;
		console.log(this.type);
	}
}
let person= new Person();
person.printAge();
// person.setType("Cool guy") //Will not work anymore since method is private
//Error will say `Property 'setType' is private and only accessible within class 'Person'`
inheritance
class Person {
	public name: string;
	private type: string;
	protected age: number = 26;
	
	constructor(name:string, public username:string){
		this.name = name;
	}
	
	printAge() {
		console.log(this.age);
		this.setType("Old Guy");
	}

	private setType(type: string){
		this.type = type;
		console.log(this.type);
	}
}

//Object want to inherit all properties and methods from Person but want to set name default as Alex. However, setType() method and type property will not be inherited in child class due to private
class Alex extends Person{
	name = "Alex"
}
//let person= new Alex(); // Fail to compile since the parent constructor expects 2 arguments
let person = new Alex("Anna", "alexwck");

//OR
class Alex extends Person{
	constructor(username: string){
		super("Alex", username); //need to if defined own construcotr in child class
		this.age = 31; //able to overwrite since age is protected
	}
}
let person = new Alex("alexwck");
getters_setters
class Person{
  private _firstName: string = "Default";

	//This is property accessible from the outside, not a method
  get firstName(){
    return this._firstName;
  }

  set firstName(value: string){
    if (value.length > 3){
      this._firstName = value;
    } else{
      this._firstName = "Default";
    }
  }
}

let person = new Person();
console.log(person.firstName);
person.firstName="Ai";
console.log(person.firstName);
person.firstName = "Alex";
console.log(person.firstName);
staticPropertiesMethods
//Only way to access this is to instantiate an object from this class
class Helpers {
	PI: number = 3.14;
}

//Solution
class Helpers {
	static PI: number = 3.14; //May use this property without instantiate the class
	
	static calcCircumference(diameter: number): number{
		return this.PI * diameter;
	}
}
console.log(2 * Helpers.PI);
console.log(Helpers.calcCircumference(10));
abstractClass
// Abstract classes cannot be instatianted directly, can only be instantiated through inheritance (NO FUNCTION BODY, ONLY HOW FUNCTION TYPE LOOKS LIKE)

// Purpose: Only require set up, but the child class will do the logic such as in the case of abstract METHOD

abstract class Project{
	public projectName: string = "Default";
	public budget: number
	
	abstract changeName(name: string): void;
	
	calcBudget(){
		return this.budget*2;
	}
}

class ITProject extends Project{
	changeName(name:string):void{
		this.projectName = name
	}
}

let project = new ITProject();
console.log(project)
project.changeName = "Cool"
console.log(project)