Components are the fundamental building blocks of Angular applications. Every Angular app has at least one component — the root component — that connects a component tree to the page DOM. If you think of a web page as a LEGO structure, each component is an individual LEGO brick: self-contained, reusable, and snapped together to form the complete UI. A button is a component. A navigation bar is a component. An entire page is a component made of smaller components.What You’ll Learn:
Component anatomy and architecture
Data binding (interpolation, property, event, two-way)
Angular provides four types of data binding. These are the bridges between your TypeScript logic and what users see and interact with on screen. Understanding them is essential — you will use at least one in every single template you write.
┌─────────────────────────────────────────────────────────────────┐│ Data Binding Types │├─────────────────────────────────────────────────────────────────┤│ ││ 1. INTERPOLATION {{ expression }} ││ Component ──────────────────────────► Template ││ Outputs data to the view ││ ││ 2. PROPERTY BINDING [property]="expression" ││ Component ──────────────────────────► Template ││ Binds to DOM properties ││ ││ 3. EVENT BINDING (event)="handler()" ││ Component ◄────────────────────────── Template ││ Responds to user actions ││ ││ 4. TWO-WAY BINDING [(ngModel)]="property" ││ Component ◄─────────────────────────► Template ││ Syncs data both ways ││ │└─────────────────────────────────────────────────────────────────┘
<!-- Binding to native properties --><img [src]="imageUrl" [alt]="imageDescription"><button [disabled]="isLoading">Submit</button><input [value]="username"><div [hidden]="!isVisible">Content</div><!-- Binding to component inputs --><app-user-card [user]="currentUser"></app-user-card><!-- Binding to directive properties --><div [ngClass]="{'active': isActive, 'disabled': isDisabled}"><div [ngStyle]="{'color': textColor, 'font-size': fontSize}">
Property vs Attribute — this trips up almost everyone: An HTML attribute is what you write in your markup (<input value="hello">). A DOM property is the live JavaScript value on the element object (inputEl.value). They start the same but diverge immediately — if the user types in the input, the property changes but the attribute stays as "hello". Angular binds to properties, not attributes. This is why [value] works but you need [attr.colspan] for attributes that have no corresponding DOM property.
<!-- Attribute binding (special syntax for HTML attributes with no DOM property) --><td [attr.colspan]="colSpan"><button [attr.aria-label]="label">
Modern Angular supports signal-based inputs and outputs. This is the recommended approach for new code — it integrates with Angular’s signal-based reactivity system, giving you better performance and a cleaner mental model. The key difference: signal inputs are read by calling them as functions (e.g., user() instead of user), and they are automatically tracked by Angular’s change detection.
import { Component, input, output } from '@angular/core';@Component({ selector: 'app-user-card', standalone: true, template: ` <div class="card"> <h3>{{ user().name }}</h3> <!-- Note: calling as function --> <button (click)="select()">Select</button> </div> `})export class UserCardComponent { // Required signal input user = input.required<User>(); // Optional signal input with default showEmail = input(true); // Signal output selected = output<User>(); select() { this.selected.emit(this.user()); }}
Project content from parent into child component slots. Content projection is Angular’s version of React’s children prop or Vue’s <slot>. It lets you build “wrapper” components (cards, modals, tabs) where the parent decides what goes inside, and the child decides where and how to display it.
Q: Explain the difference between property binding and attribute binding. When would you need attr.colspan instead of just colspan?
Strong Answer: This distinction trips up almost everyone. An HTML attribute is what you write in markup — it is the initial value. A DOM property is the live JavaScript value on the element object. They start the same but immediately diverge. For example, if you write <input value=“hello”> and the user types “world,” the attribute is still “hello” but the property is “world.” Angular binds to DOM properties by default because they reflect the current state.The catch: some HTML attributes have no corresponding DOM property. colspan, rowspan, aria-label, data-* attributes, and SVG attributes fall into this category. When you write [colspan]=“2”, Angular looks for a DOM property called colspan — and it does not exist on the element. So you get a runtime error. You need [attr.colspan]=“2” to tell Angular “set the HTML attribute, not a DOM property.”In practice, I use property binding 95% of the time. I reach for attr. only when dealing with table attributes (colspan, rowspan), ARIA attributes (aria-label, aria-expanded), custom data attributes, and certain SVG properties.Follow-up: What about class and style binding — are those property or attribute bindings?
Answer: Angular provides special shorthand for both. [class.active]=“isActive” toggles a single CSS class, while [ngClass] handles multiple. [style.color]=“myColor” sets a single style property, and [style.font-size.px]=“size” handles units. Under the hood, Angular uses the DOM property API (element.classList, element.style), not setAttribute. This is important because style properties set via the property API are inline styles that override stylesheet rules.
Q: Your component has a list of 10,000 items and users are reporting janky scrolling. You suspect the parent component is re-rendering all child components on every change. How do you diagnose and fix this?
Strong Answer: First, I would diagnose by opening Angular DevTools and running the Profiler during a scroll interaction. This shows exactly which components run change detection and how long each takes. I can also add a console.log in ngDoCheck of the child component to confirm it runs on every cycle.The fix involves multiple layers. First, I ensure the @for loop (or *ngFor) uses track with a unique identifier like item.id. Without tracking, Angular destroys and recreates every DOM element on any list change. Second, I switch the child component to ChangeDetectionStrategy.OnPush, so it only re-renders when its inputs change by reference. Third, if 10,000 items is the real dataset size, I implement virtual scrolling with cdk-virtual-scroll-viewport, which only renders the 20-30 items visible in the viewport. Fourth, I make sure the parent is not creating new object references on every change detection cycle — for example, calling a method in the template that returns a new array each time.The combination of OnPush + track + virtual scrolling can take a list from rendering 10,000 components to rendering only 30, each checked only when their specific data changes. That is a 300x reduction in work.Follow-up: What if the child component needs data that changes frequently but the list itself does not?
Answer: I would separate the frequently-changing data from the list data. For example, if each item has a “last seen” timestamp that updates every second, I would not pass it as an @Input to an OnPush component (since the reference changes constantly). Instead, I would have the child component inject a service that exposes that data as a signal, or use the async pipe with a per-item observable. This way the list itself stays stable while each child independently updates its volatile data.
Q: Compare signal-based inputs (input.required) with decorator-based inputs (@Input). When would you still use @Input in new code?
Strong Answer: Signal inputs are the recommended approach for all new code in Angular 17+. The key advantages: they integrate with Angular’s signal reactivity system, so you can use computed() to derive values from inputs without lifecycle hooks. They are type-safe by default — input.required<User>() will not compile if the parent does not provide it. And they work seamlessly with OnPush change detection because signals automatically notify the framework when they change.The main case where I might still use @Input is when integrating with existing codebases or third-party libraries that expect decorator-based inputs. Also, if you need the transform option with complex logic, @Input transform is slightly more flexible in edge cases. Another scenario: if you are writing a library that needs to support Angular 14-16 consumers, you cannot use signal inputs since they require Angular 17+.In practice, I treat this like the migration from var to let/const in JavaScript — signal inputs are strictly better for new code, but there is no urgent need to rewrite every existing @Input in a codebase.Follow-up: How do you handle the transition from ngOnChanges to signal-based reactivity?
Answer: With decorator inputs, you use ngOnChanges to react to input changes. With signal inputs, you replace that with either computed() for derived values or effect() for side effects. For example, instead of checking changes[‘userId’] in ngOnChanges and fetching data, you write an effect that reads the userId signal input and triggers a fetch. The effect automatically re-runs whenever the input changes. This is cleaner because you do not need to check which input changed or compare previous and current values.