Skip to main content

Lab 11: Migration to v21

πŸ“– Resources​

πŸš€ Starter Code​

step-11-album-wholesale-v16-post-compodoc

In this lab, you will explore what Angular's migration to standalone components looks like β€” comparing the v16 NgModule-based architecture with the v21 standalone equivalent provided in the starter. You will understand what ng update and ng generate @angular/core:standalone change, and manually apply the key transformations.

Why not run ng update @angular/core@21 directly?

Angular's CLI update tool only supports updating one major version at a time. Going from v16 to v21 requires five separate runs:

ng update @angular/core@17 @angular/cli@17
ng update @angular/core@18 @angular/cli@18
ng update @angular/core@19 @angular/cli@19
ng update @angular/core@20 @angular/cli@20
ng update @angular/core@21 @angular/cli@21

In a real project you would run each step, fix deprecation warnings, and test before moving on. For this lab, the migrated result is the next starter (step-15-album-wholesale-v21-post-signal-computed) β€” use it to study what changed.

πŸ“ Instructions​

Step 1: Compare the Two Starters​

Open both projects side by side:

  • Before (v16): step-11-album-wholesale-v16-post-compodoc
  • After (v21): step-15-album-wholesale-v21-post-signal-computed

Notice the key structural differences.

Step 2: Understand the Bootstrap Change​

v16 (src/main.ts):

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';

platformBrowserDynamic().bootstrapModule(AppModule);

v21 (src/main.ts):

import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { AppComponent } from './app/app.component';

bootstrapApplication(AppComponent, appConfig);

Step 3: Understand the AppModule β†’ app.config.ts Change​

v16 (src/app/app.module.ts):

@NgModule({
declarations: [AppComponent, AlbumListComponent, ...],
imports: [
BrowserModule,
HttpClientModule,
RouterModule.forRoot(routes),
ReactiveFormsModule,
],
bootstrap: [AppComponent]
})
export class AppModule {}

v21 (src/app/app.config.ts):

export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideHttpClient(),
provideAnimations(),
]
};

Step 4: Understand Standalone Components​

The automated migration runs:

ng generate @angular/core:standalone

This converts each component from:

// v16 β€” declared in a module
@Component({ selector: 'app-album-list', ... })
export class AlbumListComponent { }

To:

// v21 β€” standalone, imports its own dependencies
@Component({
selector: 'app-album-list',
standalone: true,
imports: [MatCard, NgFor, AsyncPipe, ...],
...
})
export class AlbumListComponent { }

Step 5: Understand Functional Guards​

v16 β€” class-based guard:

@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
canActivate(): boolean {
return inject(AuthService).isLoggedIn();
}
}

v21 β€” functional guard:

export const authGuard: CanActivateFn = () => {
return inject(AuthService).isLoggedIn();
};

Step 6: Verify the Migrated Starter Compiles​

cd step-15-album-wholesale-v21-post-signal-computed
npm install
ng build

This is the result of the migration β€” all subsequent labs (12–17) build on top of it.