back to index

Angular and .NET Notes

C# threading, JS vs C# GC, TypeScript type tricks, and the Angular lifecycle and DI.

~/posts/angular-dotnet-notes $ cat post.md

/ LANG EN / 中文
/ THEME / /

C#

Threading

class Program
{
    static void Main(string[] args)
    {
        Thread t1 = new Thread(new ThreadStart(WithOut));
        Thread t2 = new Thread(new ParameterizedThreadStart(With));
        t1.IsBackground = true;
        t2.IsBackground = true;
        t1.Start();
        t2.Start("Hello");
    }

    public static void WithOut()
    {
        // Run
    }

    public static void With(object data)
    {
        string str = data as string; // "Hello"
    }
}

Sleep vs Wait

  • Sleep is time-based — no other thread is needed to wake it up, it resumes when the timer expires. Can be called from anywhere.
    • Sleep does not release object ownership or locks. Sleeping inside a synchronized method or block leaves other threads blocked.
  • Wait voluntarily gives up ownership and goes into a wait state. Must be called from a synchronized method or block.
    • Wait releases its lock.
    • Wake-up: Monitor.Pulse() or Monitor.PulseAll().

Thread pool

Creating and destroying threads costs. A thread pool keeps a set of threads alive in the background and queues new tasks onto them — avoiding the repeated setup/teardown.

class Program
{
    static void Main(string[] args)
    {
        ThreadPool.QueueUserWorkItem(TestMethod, "Hello");
        Console.ReadKey();
    }

    public static void TestMethod(object data)
    {
        string str = data as string;
        Console.WriteLine(str);
    }
}

Garbage collection

JavaScript

Reachability-based: starting from the roots, scan downward; everything unreachable is collectable. Compared to reference counting, this:

  • handles mutually-referencing objects, and
  • collects an entire unreachable “island” of objects in one pass without per-object tests.

The downside is having to start from scratch each scan. Standard optimizations:

  • Generational collection — objects are assigned a generation; older generations are collected less frequently.
  • Incremental collection — split the scan into chunks spread across runtime gaps.
  • Idle-time collection — schedule scans during idle periods.

C# has one optimization JS doesn’t: parallel collection.

C#

Reference tracking: when no references remain, the object becomes eligible. The GC runs when it sees fit.

GC can’t collect unmanaged resources — file handles, native pointers, etc. These typically implement IDisposable, paired with using for deterministic cleanup.

Force collection: GC.Collect().

Recipe for unmanaged resources:

  • Inherit IDisposable.
  • Implement Dispose() — release managed and unmanaged resources, and suppress finalization.
  • Implement a finalizer as a safety net to release unmanaged resources.

TypeScript

Function overloads

Like Java — declare the same function multiple times with different signatures:

on(event: "close", listener: () => void): this;
on(event: "data", listener: (chunk: any) => void): this;
on(event: "end", listener: () => void): this;

Self-referential types

Writing type T = ... T ... outright will be rejected. But if the self-reference is gated through a generic parameter, it works, because the generic is compiled before it’s expanded.

Constraining key/value lookup

const func = (obj, key) => obj[key].toString();

let x = { a: 1, b: 2, c: 3, d: 4 };
getProperty(x, "a"); // OK
getProperty(x, "m"); // should error

Use K extends keyof T to bind key to the actual key set of obj:

const getProperty = <T, K extends keyof T>(obj: T, key: K): T[K] => obj[key];

Typed on by event name

In a .d.ts, you can stack overloads:

on(event: "close", listener: () => void): this;
on(event: "data", listener: (chunk: any) => void): this;
on(event: "end", listener: () => void): this;
on(event: "readable", listener: () => void): this;
on(event: "error", listener: (err: Error) => void): this;
on(event: string | symbol, listener: (...args: any[]) => void): this;

Example from @types/node’s stream module.

A more modern version uses extends, keyof, and A[T]:

type Listeners = {
    close: () => void;
    data: (chunk: any) => void;
    // ...
};

const on = <T extends keyof Listeners>(event: T, listener: Listeners[T]) => {
    // ...
};

Angular 2+

Component / directive lifecycle

For both components and directives:

  • ngOnChanges — fires when Angular sets or resets data-bound input properties.
  • ngOnInit — runs after the first ngOnChanges; the usual place to fetch template data from a backend.
  • ngDoCheck — runs on every change-detection pass; use it to respond manually when Angular’s view of state changes.
  • ngOnDestroy — cleanup before destruction — unsubscribe, detach handlers, etc.

Directive-only:

  • ngAfterContentInit — projected content initialized.
  • ngAfterContentChecked — after Angular checks projected content bindings.
  • ngAfterViewInit — after Angular creates the component’s view.
  • ngAfterViewChecked — after Angular checks the component’s view bindings.

Order:

ngOnChanges → ngOnInit → ngDoCheck → ngAfterContentInit
→ ngAfterContentChecked → ngAfterViewInit → ngAfterViewChecked → ngOnDestroy

Dependency injection

  • Mark services with @Injectable().
  • Mark components with @Component.

DI is inversion of control under another name: app code and concrete implementations are decoupled, so as long as the interface holds, you can swap implementations freely. It also makes testing easier.

For providers, useClass vs useExisting: useExisting reuses an existing instance (singleton-style); useClass constructs a fresh one each time.

Change detection

Default mode is top-down: when a component changes, every descendant is checked. That’s Default.

In OnPush, when a component’s @Input doesn’t change, the entire subtree’s check is skipped.

changeDetection: ChangeDetectionStrategy.OnPush;

detach and reattach let you take a component out of the change-detection queue and reattach it later. Useful for components that change rapidly but want to control when the view updates.

constructor(private ref: ChangeDetectorRef) {}

// detach
this.ref.detach();
// ...work...
// reattach
this.ref.reattach();
back to index