1. Component Decorator Options
You've seen @Component a few times already; this week you learn it
properly. Three options account for almost everything you'll configure day to day:
import { Component } from '@angular/core';
@Component({
selector: 'app-rating-stars',
templateUrl: './rating-stars.html',
styleUrl: './rating-stars.css',
})
export class RatingStars {}
selector— the HTML tag this component is used as, e.g.<app-rating-stars>templateUrl/template— an external file, or an inline string for very small componentsstyleUrl/styleUrls— one or more stylesheets scoped to just this component (more on that scoping in Week 7)
Small, purely presentational components are often written with an inline template instead of a separate file — a judgment call you'll get a feel for as you write more of them.
2. Interpolation, Property & Event Binding
Three binding syntaxes cover almost every template you'll write:
<!-- Interpolation: render a value as text -->
<h2>{{ product.name }}</h2>
<!-- Property binding: set a DOM/component property -->
<img [src]="product.imageUrl" [alt]="product.name" />
<app-badge [count]="cartItemCount" />
<!-- Event binding: call a method when something happens -->
<button (click)="addToCart(product)">Add to cart</button>
<!-- Class and style bindings -->
<div [class.out-of-stock]="product.stock === 0"
[style.opacity]="product.stock === 0 ? 0.5 : 1">
{{ product.name }}
</div>
[src]="..." sets the DOM property, not the HTML attribute — that distinction rarely matters, except for a handful of attributes (like custom data-* ones) where you need [attr.data-id]="..." instead. Angular defaults to property binding because it's faster and type-checked.
3. input(), output() & model()
These three functions are how a component receives data, emits events, and supports
two-way binding — the signal-based replacement for the older
@Input()/@Output() decorators.
import { Component, input, output, model } from '@angular/core';
@Component({
selector: 'app-rating-stars',
templateUrl: './rating-stars.html',
})
export class RatingStars {
// Required input -- consumer MUST provide a value
max = input.required<number>();
// Optional input with a default
readOnly = input(false);
// Output: an event this component emits
rated = output<number>();
// model(): two-way bindable signal -- both an input and an output, paired
value = model(0);
select(stars: number) {
if (this.readOnly()) return;
this.value.set(stars);
this.rated.emit(stars);
}
}
<app-rating-stars
[max]="5"
[(value)]="productRating"
(rated)="onRated($event)"
/>
[(value)] is Angular's "banana in a box" syntax — shorthand for binding
[value] and listening to (valueChange) at once.
model() generates that valueChange output for you
automatically; you never write it by hand.
4. Template Reference Variables
A #name on any element gives you a handle to it — the DOM element, or the
component/directive instance if there is one — usable anywhere else in that same
template.
<input #searchBox type="text" (keyup.enter)="search(searchBox.value)" />
<button (click)="search(searchBox.value)">Search</button>
This is a lightweight escape hatch for simple cases — reading an input's current value
without wiring up a full FormControl (which you'll learn properly in Week
12). For anything beyond "read this one value," reactive forms are the better tool.
5. ng-template & ng-container
ng-container groups elements — for a binding, or just for organization —
without adding an extra element to the rendered DOM. It's invisible at
runtime.
<ng-container [ngTemplateOutlet]="header"></ng-container>
<!-- No wrapping <div> in the rendered output -- useful when you need
a binding target but a real DOM element would break your CSS layout -->
ng-template defines a block of markup that isn't rendered by
default — something else decides when (or whether) to render it. You'll meet it
again this way in Week 11, where @defer uses a similar "placeholder content"
idea for lazy-loaded UI:
@for (item of items(); track item.id) {
<li>{{ item.name }}</li>
} @empty {
<ng-template #noItems>
<li>No items yet.</li>
</ng-template>
}
In practice, you'll write @if/@for's own @empty
and @else blocks (Week 6) far more often than a raw ng-template
— but recognizing it matters, because libraries and older codebases still use it directly.
6. Lifecycle Hooks & Signal-Based Alternatives
Angular calls specific methods on your component class at specific moments. The three you'll use most:
ngOnInit()— once, after the component's inputs are first setngOnChanges(changes)— whenever an@Input()-decorated property changesngOnDestroy()— right before the component is removed, for cleanup (unsubscribing, clearing timers)
import { Component, OnInit, OnDestroy } from '@angular/core';
export class SearchBox implements OnInit, OnDestroy {
private intervalId?: ReturnType<typeof setInterval>;
ngOnInit() {
this.intervalId = setInterval(() => this.refresh(), 30_000);
}
ngOnDestroy() {
clearInterval(this.intervalId);
}
refresh() { /* ... */ }
}
With signals, ngOnChanges specifically is often unnecessary —
effect() (Week 5) reacts to a signal input changing directly, without the
boilerplate of comparing an old and new value yourself:
import { Component, input, effect } from '@angular/core';
export class SearchBox {
query = input('');
constructor() {
effect(() => {
const q = this.query(); // re-runs automatically whenever `query` changes
console.log('Query changed to', q);
});
}
}
ngOnInit and ngOnDestroy remain useful and aren't going
anywhere — it's specifically ngOnChanges that signals make largely
redundant.
7. Hands-on Exercise
Build a reusable rating-stars component
Turn the code from Section 3 into a fully working, reusable component.
Requirements:
- Create
RatingStarswith a requiredmaxinput, areadOnlyinput defaulting tofalse, a two-wayvalueviamodel(), and aratedoutput. - In the template, render
maxstar icons (plain★/☆characters are fine) and highlight however many match the currentvalue. - Clicking a star updates
valueand emitsrated— unlessreadOnlyistrue, in which case clicks do nothing. - In a parent component, bind
[(value)]to a signal and display the live value next to the component, proving the two-way binding works both directions (also try setting the parent's signal programmatically and confirm the stars update). - Render a second,
readOnlyinstance of the same component to prove one component handles both modes.
To render "max stars, some filled," an @for loop over an array built with Array.from({ length: this.max() }) works well — you'll get proper practice with the new control-flow syntax a couple weeks early.
8. Knowledge Check
Four quick questions. Expand each to check your answer.
Q1
What does [(value)]="productRating" actually expand to?
What does [(value)]="productRating" actually expand to?
[value]="productRating" plus (valueChange)="productRating = $event". It's shorthand for a property binding and an event binding to a conventionally-named xChange output at once — model() is what generates that paired output automatically.
Q2
When would you need [attr.data-id]="..." instead of a plain property binding?
When would you need [attr.data-id]="..." instead of a plain property binding?
Whenever there's no matching DOM property to bind to — custom data-* attributes, ARIA attributes, and SVG attributes are the common cases. Angular's default [x]="..." binds a DOM property; [attr.x]="..." explicitly sets the HTML attribute instead.
Q3
What's the key difference between ng-container and ng-template?
What's the key difference between ng-container and ng-template?
ng-container renders its contents immediately, just without adding a wrapping DOM element. ng-template's contents render not at all by default — something else (a structural directive, @defer, a template outlet) has to explicitly decide to render them.
Q4
Why does effect() often replace the need for ngOnChanges, but not ngOnInit or ngOnDestroy?
Why does effect() often replace the need for ngOnChanges, but not ngOnInit or ngOnDestroy?
ngOnChanges exists specifically to react to input changes — which effect() does automatically and more precisely, by tracking exactly which signals it reads. ngOnInit (run setup once) and ngOnDestroy (run cleanup once) aren't about reacting to changing values at all, so an effect isn't a substitute for either.