The TypeScript you actually need for Angular, from types and interfaces to generics, strict null checks and the decorators Angular relies on.

Bishal Biswas
Self Learner
Why Angular insists on TypeScript
Angular is written in TypeScript and expects you to use it. That is not decoration. The template compiler type checks your templates against your component classes, so a typo in a binding is a build error rather than a blank space on the page.
You do not need advanced TypeScript. You need about a dozen things, and they are all here.
Basic types
let name: string = 'Asha';
let age: number = 30;
let active: boolean = true;
let tags: string[] = ['admin', 'editor'];
let anything: unknown = fetchSomething();
// usually you can leave the type out, it is inferred
let city = 'Delhi'; // string
let counts = [1, 2, 3]; // number[]
Write the type when it is not obvious from the value. Leave it out when it is. Annotating everything makes code noisier without making it safer.
Interfaces and types
An interface describes the shape of an object. This is the single most useful TypeScript feature in Angular, because it describes your API responses.
export interface User {
id: number;
name: string;
email: string;
role: 'admin' | 'editor' | 'viewer'; // only these three strings
lastLogin?: Date; // optional
readonly createdAt: Date; // cannot be reassigned
}
type does almost the same job and can do more:
type Status = 'idle' | 'loading' | 'error';
type Id = string | number;
type UserSummary = Pick<User, 'id' | 'name'>;
type UserDraft = Omit<User, 'id' | 'createdAt'>;
type MaybeUser = User | null;
Use interface for object shapes you might extend, and type for unions and derived types. In practice either is fine, so pick one and be consistent.
Union types are the useful trick
A union says “one of these”. Angular code uses them constantly for state.
type LoadState =
| { status: 'loading' }
| { status: 'error'; message: string }
| { status: 'ready'; users: User[] };
function render(state: LoadState) {
switch (state.status) {
case 'loading':
return 'Please wait';
case 'error':
return state.message; // TypeScript knows message exists here
case 'ready':
return `${state.users.length} users`;
}
}
This is a discriminated union. Checking state.status narrows the type, so the compiler knows which properties are available in each branch. It makes impossible states impossible.
Strict null checks
Angular projects enable strict mode by default, and the most important part is that null and undefined are not assignable to everything.
let name: string = null; // error under strict mode
let name: string | null = null; // fine
That forces you to handle the missing case:
function greet(user: User | null): string {
if (user === null) return 'Hello, stranger';
return `Hello, ${user.name}`;
}
// or shorter
const display = user?.name ?? 'Anonymous';
?. returns undefined instead of throwing when the left side is null. ?? supplies a fallback when the left side is null or undefined, but not when it is 0 or '', which is why it beats ||.
const count = 0;
console.log(count || 10); // 10, probably not what you wanted
console.log(count ?? 10); // 0, correct
The non-null assertion
! tells the compiler “trust me, this is not null”. It turns off a safety check, so it needs a reason.
const element = document.getElementById('app')!; // I know it exists
In Angular you will see it on properties assigned by the framework rather than the constructor. Prefer ? and ?? where you can, and reserve ! for cases where you genuinely know better than the compiler.
Generics
A generic is a type that takes a type. You will use them far more often than you write them.
// using them: extremely common
const users = signal<User[]>([]);
this.http.get<User[]>('/api/users');
const cache = new Map<string, User>();
// writing one: less common, but not hard
function first<T>(items: T[]): T | undefined {
return items[0];
}
const firstUser = first(users()); // T is inferred as User
<User[]> on the HTTP call is the important one. Without it the response is Object and you get no help at all. With it, every property is checked.
Classes and access modifiers
Angular components and services are classes.
export class UserProfile {
public name = ''; // default, visible everywhere
private token = ''; // this class only
protected role = 'viewer'; // this class and subclasses, and templates
readonly id = 1; // cannot be reassigned after construction
}
protected matters in Angular. A template can read public and protected members but not private ones. Modern Angular style marks template-only members protected, which keeps the public surface of the class honest.
@Component({ template: `{{ label }}` })
export class Badge {
protected readonly label = 'New'; // template can read it, other code cannot
}
Decorators
A decorator is the @Something syntax that attaches metadata to a class or member. Angular uses them for its core building blocks.
@Component({ ... }) // this class is a component
@Injectable({ ... }) // this class can be injected
@Directive({ ... }) // this class is a directive
@Pipe({ ... }) // this class is a pipe
You almost never write your own. You just apply Angular’s.
Note that modern Angular has moved away from decorators for members. @Input() and @Output() are being replaced by the input() and output() functions, which give better types and work with signals.
Type narrowing in templates
The template compiler narrows types too, which is why @if blocks are safer than they look.
@Component({
template: `
@if (user(); as u) {
<p>{{ u.name }}</p> <!-- u is User, not User | null -->
} @else {
<p>Not signed in</p>
}
`,
})
export class Profile {
readonly user = signal<User | null>(null);
}
The as u binding captures the narrowed value, so the compiler knows it is not null inside the block.
Configuring strictness
{
"compilerOptions": {
"strict": true,
"noImplicitReturns": true,
"noUnusedLocals": true
},
"angularCompilerOptions": {
"strictTemplates": true,
"typeCheckHostBindings": true
}
}
strictTemplates is the one worth protecting. It type checks bindings in your templates, catching a whole class of runtime bugs at build time. New projects enable it. Do not turn it off to silence errors.
Quick recap
- Angular type checks templates, so TypeScript is doing real work for you.
- Interfaces describe API shapes, and unions describe the states something can be in.
- Discriminated unions make impossible states impossible.
- Strict null checks force you to handle missing values, with
?.and??. - Use
??rather than||so0and empty strings survive. - Pass a generic to
http.get<User[]>()or you get no type safety at all. - Mark template-only members
protected, and keepstrictTemplateson.
// Interfaces describe the shape of your data, especially API responses
export interface User {
id: number;
name: string;
role: 'admin' | 'editor' | 'viewer'; // a union of allowed strings
lastLogin?: Date; // optional
readonly createdAt: Date;
}
// Derived types save you repeating yourself
type UserSummary = Pick<User, 'id' | 'name'>;
type UserDraft = Omit<User, 'id' | 'createdAt'>;
// A discriminated union makes impossible states impossible
type LoadState =
| { status: 'loading' }
| { status: 'error'; message: string }
| { status: 'ready'; users: User[] };
function describe(state: LoadState): string {
switch (state.status) {
case 'loading': return 'Please wait';
case 'error': return state.message; // narrowed: message exists
case 'ready': return `${state.users.length} users`;
}
}
// Strict null checks force you to handle the missing case
function greet(user: User | null): string {
return user ? `Hello, ${user.name}` : 'Hello, stranger';
}
const display = (u: User | null) => u?.name ?? 'Anonymous';
// ?? beats || because 0 and '' survive
console.log(0 || 10); // 10, probably wrong
console.log(0 ?? 10); // 0, correct
// Pass the generic or you get no type safety from HTTP at all
// this.http.get<User[]>('/api/users')
// Access modifiers: templates can read public and protected, never private
export class Badge {
protected readonly label = 'New'; // template only, not public API
private readonly token = 'secret'; // invisible to the template
}
Was this explanation helpful?
Your feedback helps us improve the content for everyone.
Thank you for your feedback!
We appreciate you helping us improve.
What could we improve? (Optional)
Read aloud
Ready