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
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()orMonitor.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’sstreammodule.
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 firstngOnChanges; 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();