Angular Revisited 2025
Introduction
Been a while since I touched Angular except for when I put my loader into WASM. So this page is just to add any changes I have found
CommonModule
The structural directives, e.g. NgFor NgIf not can be import in the component. I guessing the is because of SSR so now we do this
@Component({
selector: 'app-chess-board',
imports: [NgFor, NgClass, NgIf],
templateUrl: './chess-board.component.html',
styleUrl: './chess-board.component.css'
})
export class ChessBoardComponent {
...
Assests
These now live in the public directory. I wonder if the is something to copy them from there old haught of src.
Modules now Providers
There is no app.modules.ts anymore but I believe you can make one. The new way is to edit the app.config.ts. We don't import modules but providers which do the same job.
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes),
provideHttpClient()]
};
Reminder on how EventEmitters Work
This screws with my dyslexia so I will write it down again. In component containing the event emitter we have this. When you click in the child component you fire an event
@Component({
selector: 'app-search-input-text-button',
imports: [],
templateUrl: './search-input-text-button.html',
styleUrl: './search-input-text-button.css'
})
export class SearchInputTextButton {
@Input() active: boolean = false
@Output() activeChange = new EventEmitter<boolean>();
handleLocalClick(): void {
const newState = !this.active;
this.activeChange.emit(newState);
}
}
In the parent component we have
<div class="flex"
<app-search-input-text-button (activeChange)=handleSearchButtonClick($event) />
</div>
Which is then defined in the code
@Component({
selector: 'app-header-layout',
imports: [SearchInputText, SearchInputTextButton],
templateUrl: './header-layout.html',
styleUrl: './header-layout.css'
})
export class HeaderLayout {
handleSearchButtonClick(activeChange: boolean): void {
// this.showMenu = false;
}
}
Angular 16
Signals
I think the purpose is really the same purpose as useState in react.
What Signals Solve
Wrapping variables in values in signals means they are updating dynamically.
let a = 2
let b = 3
let c = a + b
b = 8
console.log(c) // 5
With signals this is not the case
let a = signal(2)
let b = signal(3)
let c = computed(() => a() + b());
b.set(8)
console.log(c) // 10
Computed Signal
Like above we can compute value of a signal based of other signals
Effect
This is used when you want to do something when a dependent signal changes. E.g.
effect(() => console.log(this.selectedVehicle()))
We can declare an effect
e = effect(() => console.log(this.selectedVehicle()))
They are said to be useful for
- Logging
- External APIs (not RxJs)
Update/Mutate Signal
We can update a signal (Using existing current value with update
let a = signal(2)
a.update(qty => qty * 2)
If a variable is an type we can change a property with mutate
this.selectedVehicle.mutate(v =>
v.price = v.price + (v.price * .20))
Angular 17
Required
My fav change. How I hated managing this. We can not just a required: true on inputs
@Input({required: true}) menuItem!: MenuItem
@ViewChild and @ViewChildren
This is like ref in React and allows you to get a reference to the component. E.g.
export class AppComponent {
@ViewChild(ChildComponent, {static: true}) child?: ChildComponent
increment() {
this.child?.increment()
}
}
But it is best to use @output to achieve this. We can also reference and element as well as a component. E.g.
export class AppComponent {
@ViewChild('button', {static: true}) buttonRef?: ElementRef<HtmlButtonElement>
If we want to know all of the components of a type on child page we can use @ViewChild with a QueryList. This allows us to filter all of the elements/components
export class AppComponent {
@ViewChildren(ChildComponent) children?: QueryList<ChildComponent>
Control Flow
This must be a great idea as I did even notice when I started using them. All self explanatory but here they are
Switch (ngSwitch)
@switch (color) {
@case ("red") {
<div>Red</div>
}
@case ("blue") {
<div>Blue</div>
}
}
For (ngFor)
@for (color of colors; track color) {
<li>{{ color }}</li>
}
Defer
Defer is a way to not download a component in a view straight away. And example might be
<h1>Bibble Academy</h1>
<div>Content</div>
<div>Content</div>
<div>Content</div>
<div>Content</div>
<div>Content</div>
<div>Content</div>
<div>Content</div>
<div>Content</div>
<div>Content</div>
<div>Content</div>
<div>Content</div>
@defer (on idle) {
<app-child></app-child>
}
This will mean the child will be loaded after the main content and therefore the user will see the main data faster. This also means that the javascript won't be downloaded for the deferred component either. on idle is the default and can be ommitted
We can do this on viewport which means when in view (I think). For this we need to specify a place holder.
...
<div>Content</div>
<div>Content</div>
@defer (on idle) {
<app-child></app-child>
}
@placehoder {
<div>Placeholder</div>
}
Other options, like on hover, on time, on immediate and on interaction. We can also use conditions like when. We can add prefetch to decide when we download the code. Full code for the defer is
@defer (on idle) {
<app-child></app-child>
}
@loading (after 150ms; minimum 150ms) {
<div>Loading</div>
}
@placehoder {
<div>Placeholder</div>
}
@error {
<div>Error</div>
}
SSR
So this was quite a surprise as it did not really require me to do much. Basically you make a project with the flag
ng new my_great_project --ssr
And then you just need to tweak the app.config and app.config.server and you are away. I am sure there is more to learn but that did it for me. For the server you need withFetch presumably to stop if using their client.
const serverConfig: ApplicationConfig = {
providers: [
provideServerRendering(withRoutes(serverRoutes)),
provideHttpClient(withFetch()),
]
};
With the advantages of SSR maybe NextJS might not be my go to framework anymore. I did wonder why though we need the httpClient in the app.config too.
If (ngIf)
@if (showHello) {
<h2>Hello</h2>
}
@else if (showGoodbye) {
<h2>Goodbye</h2>
}
@else {
<h2>See you later</h2>
}
Angular 18
Zone.js
Not seeing a lot happening with 18. It seems to be more about making Angular not depend on zone.js as much. Performance at scale, maintenance and cost of loading are reason for doing this. Signals are there new way going forward. This includes there new change detection mode Hybrid which supports both zones and signals.
Enhanced Hydration Support
This seems to be an update to the devTools to help with visualising hydration. Hydration now works for Material too.
Angular 19
LinkedSignal
This is a new part of signals in Angular. It is meant to react to a source signal. There jargon from the video is linkedSignal() is a Writeable signal that can react to changes in a source signal and reset based on a computation.
selectedProduct = signal<Product|null>(null)
price = computed(() => this.selectProduct()?.price ?? 0)
// All good so far but we want the quantity to be 1 when they
// choice a new selected product and this is where linkedSignal comes in
quantity = linkedSignal({
source: this.selectedProduct,
computation:() => 1
})
total = computed(() => this,quantity() * this.price())
Incremental Hydration
You can not hydrate incrementally when using defer.