Skip to content

Fix: Angular Standalone Component Error — Component is Not a Known Element

FixDevs · (Updated: )

Part of:  React & Frontend Errors

Quick Answer

How to fix Angular standalone component errors — imports array, NgModule migration, RouterModule vs RouterLink, CommonModule replacement, and mixing standalone with module-based components.

The Problem

Angular throws a template error for a component that should be available:

Error: 'app-user-card' is not a known element:
1. If 'app-user-card' is an Angular component, then verify that it is included in the '@Component.imports' of this component.
2. If 'app-user-card' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@Component.schemas' of this component to suppress this message.

Or using RouterLink in a standalone component breaks:

Error: Can't bind to 'routerLink' since it isn't a known property of 'a'.

Or a directive from Angular Material doesn’t work in a standalone component:

Error: 'mat-button' is not a known element
// or: Can't bind to 'matFormField' since it isn't a known property

Or migrating from NgModule to standalone components breaks existing functionality:

NullInjectorError: No provider for MatSnackBar!

Why This Happens

Angular’s standalone components work differently from module-based components. In the traditional NgModule system, declaring a component inside a module gave it access to every other component, directive, and pipe that the module imported. Standalone components opt out of that system entirely. They do not belong to any NgModule, so they inherit nothing automatically.

This design is intentional. NgModule’s implicit dependency resolution made it difficult to tree-shake unused code, produced circular dependency issues in large codebases, and required developers to maintain a separate module file for every feature. Standalone components trade convenience for explicitness: every dependency used in the template must be listed in the component’s imports array, including built-in directives like NgIf, NgFor, router directives like RouterLink, and third-party components from libraries like Angular Material.

The most frequent errors come from five gaps. First, omitting a child component from the imports array. Second, forgetting that CommonModule directives (*ngIf, *ngFor, async pipe) are not available by default. Third, importing RouterLink as a standalone directive instead of via RouterModule (or vice versa). Fourth, services that were provided in an NgModule not being available through the root injector. Fifth, mixing standalone and module-based components without properly importing one into the other.

Platform and Environment Differences

Standalone component support, default behavior, and tooling vary across Angular versions, CLI configurations, and monorepo setups.

Angular 14 (standalone preview). Standalone components were introduced as a developer preview in Angular 14. The standalone: true flag was available but not stable. Some features, like lazy loading a standalone component with loadComponent, had incomplete support. If you are on Angular 14 and encounter errors, upgrade to at least Angular 15 where standalone is stable. Angular 14 also requires the standalone: true flag explicitly on every component — there is no way to set it as a default.

Angular 15 (stable standalone). Standalone became stable. bootstrapApplication() was introduced as an alternative to platformBrowserDynamic().bootstrapModule(). However, the Angular CLI’s ng generate component command still generated module-based components by default. You had to pass --standalone to generate a standalone component. This is the version where most teams started migrating.

Angular 16+ (default standalone). Starting with Angular 16, the CLI generates standalone components by default. ng generate component user-list produces a component with standalone: true in the decorator. The ng new command also scaffolds a standalone application with bootstrapApplication() instead of AppModule. If you generate a component in an Angular 16+ project and it does not have standalone: true, check that your Angular CLI version matches your project version with ng version.

Angular 17+ (built-in control flow). Angular 17 introduced @if, @for, and @switch as built-in template syntax that does not require importing NgIf, NgFor, or NgSwitch. This eliminates the most common standalone import mistake. However, the legacy structural directives (*ngIf, *ngFor) still work and still require imports. If you are on Angular 17+ and still using *ngIf, switch to @if to avoid the import requirement entirely.

NgModule vs standalone bootstrap. A project using bootstrapModule(AppModule) in main.ts and a project using bootstrapApplication(AppComponent) have different provider registration patterns. In NgModule bootstrap, providers come from @NgModule({ providers: [...] }). In standalone bootstrap, providers come from the second argument to bootstrapApplication(). Mixing the two patterns (e.g., adding provideRouter() to bootstrapApplication while also having RouterModule.forRoot() in an imported NgModule) causes the router to initialize twice, producing blank routes or duplicate navigation events.

Angular CLI version mismatch. The globally installed @angular/cli and the project-local @angular-devkit/build-angular must be compatible. A global CLI at version 17 generating components in a project that uses Angular 15 dependencies produces components with features (like default standalone or the new control flow syntax) that the project’s compiler does not understand. Run npx ng version (not ng version) to see the project-local version, and compare it with ng version for the global version.

NX monorepo configuration. In an NX workspace, each library and application can use a different Angular version or standalone strategy. NX’s @nx/angular:component generator respects the --standalone flag but also reads from nx.json and project-level generators configuration. If standalone components in one library cannot find components from another library, check that the consuming library imports the component class directly (not via a barrel file that re-exports an NgModule) and that the tsconfig.paths are correctly configured.

Fix 1: Add Components to the imports Array

Every non-HTML element in a standalone component’s template must be imported:

// WRONG — UserCardComponent used in template but not in imports
@Component({
  selector: 'app-user-list',
  standalone: true,
  imports: [],   // Empty — UserCardComponent not available
  template: `
    <app-user-card *ngFor="let user of users" [user]="user" />
  `,
})
export class UserListComponent {
  users = [];
}

// CORRECT — import everything used in the template
import { NgFor } from '@angular/common';
import { UserCardComponent } from './user-card.component';

@Component({
  selector: 'app-user-list',
  standalone: true,
  imports: [
    NgFor,              // For *ngFor
    UserCardComponent,  // For <app-user-card>
  ],
  template: `
    <app-user-card *ngFor="let user of users" [user]="user" />
  `,
})
export class UserListComponent {
  users = [];
}

Common Angular built-ins that need importing:

import {
  NgIf,
  NgFor,
  NgClass,
  NgStyle,
  NgSwitch, NgSwitchCase, NgSwitchDefault,
  AsyncPipe,
  DatePipe,
  DecimalPipe,
  CurrencyPipe,
  UpperCasePipe, LowerCasePipe,
  JsonPipe,
  SlicePipe,
} from '@angular/common';

// Or import all of them at once (larger bundle, but convenient during migration)
import { CommonModule } from '@angular/common';

New control flow syntax (Angular 17+) doesn’t need imports:

// Angular 17+ built-in control flow — no imports needed
@Component({
  standalone: true,
  imports: [UserCardComponent],  // Only UserCardComponent needed
  template: `
    @for (user of users; track user.id) {
      <app-user-card [user]="user" />
    }
    @if (isLoading) {
      <p>Loading...</p>
    }
  `,
})
export class UserListComponent {}

Fix 2: Fix Router Directives in Standalone Components

RouterLink, RouterOutlet, and RouterLinkActive must be explicitly imported:

// WRONG — RouterLink not imported
@Component({
  standalone: true,
  imports: [],
  template: `
    <a routerLink="/home">Home</a>
    <a routerLink="/about">About</a>
  `,
})
export class NavComponent {}

// CORRECT — import individual router directives
import { RouterLink, RouterLinkActive } from '@angular/router';

@Component({
  standalone: true,
  imports: [RouterLink, RouterLinkActive],
  template: `
    <a routerLink="/home" routerLinkActive="active">Home</a>
    <a routerLink="/about" routerLinkActive="active">About</a>
  `,
})
export class NavComponent {}

// Or import RouterModule for all router directives at once
import { RouterModule } from '@angular/router';

@Component({
  standalone: true,
  imports: [RouterModule],
  template: `
    <router-outlet />           <!-- RouterOutlet -->
    <a routerLink="/home">...</a>   <!-- RouterLink -->
  `,
})
export class AppShellComponent {}

Fix 3: Import Angular Material Components

Each Angular Material component must be imported individually in standalone components:

// WRONG — Material components not imported
@Component({
  standalone: true,
  imports: [],
  template: `
    <mat-card>
      <mat-form-field>
        <input matInput placeholder="Name" />
      </mat-form-field>
      <button mat-button>Submit</button>
    </mat-card>
  `,
})
export class ProfileFormComponent {}

// CORRECT — import each Material module used
import { MatCardModule } from '@angular/material/card';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatButtonModule } from '@angular/material/button';

@Component({
  standalone: true,
  imports: [
    MatCardModule,
    MatFormFieldModule,
    MatInputModule,
    MatButtonModule,
  ],
  template: `
    <mat-card>
      <mat-form-field>
        <input matInput placeholder="Name" />
      </mat-form-field>
      <button mat-button>Submit</button>
    </mat-card>
  `,
})
export class ProfileFormComponent {}

Create a shared Material imports file to reduce repetition:

// material.imports.ts — shared Material imports
export const MATERIAL_IMPORTS = [
  MatCardModule,
  MatButtonModule,
  MatFormFieldModule,
  MatInputModule,
  MatTableModule,
  MatDialogModule,
  MatSnackBarModule,
  MatIconModule,
  // ... all Material modules your app uses
] as const;

// In any standalone component
@Component({
  standalone: true,
  imports: [...MATERIAL_IMPORTS, NgIf, RouterLink],
  template: `...`,
})
export class MyComponent {}

Fix 4: Fix Services Not Available in Standalone Components

Services that were provided via NgModule must be moved to root or explicitly provided:

// PROBLEM — service provided only in a NgModule
@NgModule({
  providers: [UserService],   // Only available to this module's components
})
export class UserModule {}

// Standalone component can't use UserService (NullInjectorError)
@Component({ standalone: true })
export class ProfileComponent {
  constructor(private userService: UserService) {}   // NullInjectorError
}

// FIX 1 — make service provided in root
@Injectable({ providedIn: 'root' })
export class UserService { ... }
// Now available everywhere — standalone and module-based

// FIX 2 — provide at the standalone component level
@Component({
  standalone: true,
  providers: [UserService],   // Available to this component and its children
})
export class ProfileComponent {
  constructor(private userService: UserService) {}
}

// FIX 3 — provide in bootstrapApplication (for root-level services)
// main.ts
bootstrapApplication(AppComponent, {
  providers: [
    UserService,
    MatSnackBar,          // For Material services
    provideRouter(routes),
    provideHttpClient(),
    provideAnimations(),
  ],
});

Fix 5: Bootstrap a Standalone Application

Setting up a fully standalone Angular application:

// main.ts — standalone bootstrap (no AppModule)
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { provideRouter } from '@angular/router';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { provideAnimations } from '@angular/platform-browser/animations';
import { routes } from './app/app.routes';
import { authInterceptor } from './app/auth.interceptor';

bootstrapApplication(AppComponent, {
  providers: [
    provideRouter(routes),
    provideHttpClient(
      withInterceptors([authInterceptor]),
    ),
    provideAnimations(),
    // Feature-specific providers
    importProvidersFrom(MatSnackBarModule),
  ],
}).catch(console.error);

// app.routes.ts — lazy loading standalone components
export const routes: Routes = [
  {
    path: '',
    loadComponent: () =>
      import('./home/home.component').then(m => m.HomeComponent),
  },
  {
    path: 'users',
    loadComponent: () =>
      import('./users/user-list.component').then(m => m.UserListComponent),
  },
  {
    path: 'users/:id',
    loadComponent: () =>
      import('./users/user-detail.component').then(m => m.UserDetailComponent),
  },
];

Fix 6: Mix Standalone and NgModule Components

During migration, standalone and module-based components can coexist:

// Use a standalone component inside an NgModule
@NgModule({
  declarations: [OldComponent],
  imports: [
    StandaloneComponent,   // Import standalone component into NgModule
  ],
})
export class FeatureModule {}

// Use a module-based component inside a standalone component
@Component({
  standalone: true,
  imports: [
    NonStandaloneModule,   // Import entire NgModule into standalone component
    // All components/directives exported by NonStandaloneModule become available
  ],
  template: `<app-legacy-component />`,
})
export class NewStandaloneComponent {}

Mark a component as standalone:

// Before (module-based)
@Component({
  selector: 'app-user-card',
  template: `<div>{{ user.name }}</div>`,
})
export class UserCardComponent {}

// @NgModule declares it:
@NgModule({
  declarations: [UserCardComponent],
  exports: [UserCardComponent],
})
export class SharedModule {}

// After (standalone)
@Component({
  selector: 'app-user-card',
  standalone: true,   // Add this flag
  imports: [],        // Add needed imports
  template: `<div>{{ user.name }}</div>`,
})
export class UserCardComponent {}

// Remove from NgModule declarations — add to imports instead
@NgModule({
  imports: [UserCardComponent],    // From declarations to imports
  exports: [UserCardComponent],    // Keep if other modules need it
})
export class SharedModule {}

Fix 7: Run Angular’s Standalone Migration Schematic

Angular provides an automated migration tool:

# Migrate the entire project to standalone (Angular 15.2+)
ng generate @angular/core:standalone

# Options:
# --mode=convert-to-standalone  — convert components, directives, pipes
# --mode=prune-ng-modules       — remove unnecessary NgModules
# --mode=standalone-bootstrap   — switch to bootstrapApplication()

# Run each step separately for safety
ng generate @angular/core:standalone --mode=convert-to-standalone
# Review changes, test, then:
ng generate @angular/core:standalone --mode=prune-ng-modules
# Review changes, test, then:
ng generate @angular/core:standalone --mode=standalone-bootstrap

Still Not Working?

CUSTOM_ELEMENTS_SCHEMA — if you’re using actual Web Components (not Angular components) in your template, add CUSTOM_ELEMENTS_SCHEMA to suppress the “unknown element” error:

import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';

@Component({
  standalone: true,
  schemas: [CUSTOM_ELEMENTS_SCHEMA],   // Allow unknown HTML elements
  template: `<my-web-component></my-web-component>`,
})
export class MyComponent {}

Peer dependency issues with Angular Material — Angular Material 15+ has standalone support. Earlier versions require importProvidersFrom(MatModule) in providers. Verify your Material version matches Angular version.

provideRouter vs RouterModule.forRoot — in standalone bootstrap, use provideRouter(routes). In NgModule bootstrap, use RouterModule.forRoot(routes). Mixing these causes router to not initialize.

Circular import between standalone components — two standalone components that import each other create a TypeScript circular dependency. Break the cycle by lazy-loading one of them or extracting shared logic into a third component that both import.

ng build succeeds but ng serve fails — the Angular compiler’s template checking is stricter in JIT mode (ng serve) than in AOT mode (ng build). A missing import may not cause a build error but will cause a runtime template error during development. Always test with ng serve during development.

Test harness missing standalone importsTestBed.configureTestingModule for standalone components must use imports: [ComponentUnderTest], not declarations. If you put a standalone component in declarations, Angular throws “Component is part of the declarations of 2 modules.”

For related Angular issues, see Fix: Angular HTTP Interceptor Not Working, Fix: Angular Signals Not Updating, Fix: Angular NullInjectorError, and Fix: Angular Change Detection Not Working.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles