A TypeScript interface defines the expected shape of an object — genuinely valuable in Angular for typing API responses, component inputs, and service method signatures, though it's worth understanding exactly what it can and can't do compared to a class.
Defining a basic interface
interface Product {
id: number;
name: string;
price: number;
inStock: boolean;
}
Using it to type a service's return value
@Injectable({ providedIn: 'root' })
export class ProductService {
constructor(private http: HttpClient) {}
getProducts(): Observable {
return this.http.get('https://api.example.com/products');
}
}
Typing the HTTP response as Product[] gives autocomplete and compile-time checking on every property accessed on the returned data — a typo like product.nmae is caught immediately by the TypeScript compiler rather than surfacing as a confusing undefined at runtime.
Optional properties
interface Product {
id: number;
name: string;
price: number;
description?: string; // optional
}
The ? marks a property as optional — accessing it without first checking for its presence (product.description?.length, using the optional chaining operator) is necessary since TypeScript correctly treats it as possibly undefined.
Extending one interface from another
interface BaseEntity {
id: number;
createdAt: Date;
}
interface Product extends BaseEntity {
name: string;
price: number;
}
A Product now requires everything BaseEntity defines plus its own additional fields — genuinely useful for a set of related types (like every entity coming from the same API) that all share a common base shape.
Using an interface to type a component's @Input
@Component({
selector: 'app-product-card',
template: `{{ product.name }}`
})
export class ProductCardComponent {
@Input() product!: Product;
}
The critical distinction: an interface has zero runtime presence
interface Product {
id: number;
name: string;
}
console.log(typeof Product); // ERROR — Product doesn't exist at runtime at all
An interface exists purely at compile time for TypeScript's own type-checking purposes — it's completely erased from the compiled JavaScript output, which is exactly why it can never carry actual behavior (a method with real logic, a constructor) the way a class can; attempting to reference it at runtime (like checking instanceof Product) is simply not possible.
When a class is the better choice instead of an interface
class Product {
constructor(public id: number, public name: string, public price: number) {}
get formattedPrice(): string {
return `$${this.price.toFixed(2)}`;
}
}
A class is the right choice once the type needs actual behavior attached to it (a computed getter, a method, a constructor with default values or validation logic) — an interface alone can only ever describe shape, never carry this kind of runtime functionality.
Type aliases as a closely related alternative
type ProductStatus = 'active' | 'discontinued' | 'out-of-stock';
interface Product {
id: number;
status: ProductStatus;
}
A type alias can express things an interface cannot, like this union of specific string literals — interfaces and type aliases overlap significantly for plain object shapes, and the choice between them for that specific case is largely a team/style convention rather than a meaningful technical difference.