Angular 22.2: new features for developers
ℹ️ Draft: Angular 22.2 is currently available as a pre-release. This post will be updated when new features are added.
The development of Angular 22.2 just started. I will update this pages with the changes and the examples.
Until the new release you can refer to:

and

Changes until today (September 10, 2026)
WebMCP improved features
Angular 22 introduced the support to WebMCP (as experimental). This features helps AI Agents to use your web application, creating web AI-aware apps.
Annotations interface
We can now add metadata to MCP tools and declare them trustablereadOnlyHint?: boolean; this tells the AI agent that the tool wont modify the state (no changes in the UI)
readOnlyHint?: boolean; this tells if the tool will return untrusted content.
Example:
declareExperimentalWebMcpTool({
name: 'getUsers',
description: 'Returns users',
inputSchema: {
type: 'object',
properties: {},
},
annotations: {
readOnlyHint: true,
untrustedContentHint: false,
},
execute: () => {
return userService.getUsers();
},
});New @boundary error handling
This is a big change, you can now catch rendering errors.
Declarative way:
@boundary {
<user-profile />
} @error (let error = $error) {
<p>{{ error.message }}</p>
<button (click)="$retry()">Retry</button>
}Programmatic:
viewContainerRef.createComponent(UserProfileComponent, {
onError: (error, details) => {
console.error(error);
},
});Here you can find the definition of BoundaryError.
Injector directly from a query
feat(core): allow reading Injector from a view or content query
From the description: `{read: Injector}` now returns the node injector of the matched element, so a component can resolve tokens as they are seen from that element. This is useful when wrapping third-party components that project templates and expect directives inside those templates to inject the host component.
Now you can read an Injector directly from a query.
Until 22.2 you needed a workaround.
@Directive({
selector: '[injectorRef]',
})
export class InjectorRefDirective {
readonly injector = inject(Injector);
}
.html
<div #box injectorRef></div>
.ts
injectorRef = viewChild(InjectorRefDirective);
boxInjector = computed(
() => this.injectorRef()?.injector
);Now you can get the Injector of a specific element directly from a query.
boxInjector = viewChild('box', { read: Injector })and use it to resolve things like services, InjectionToken, directives/providers attached to that element and objects visible from that node, e.g.:
const element = injector?.get(ElementRef);
Route resource
Router Resources
feat(router): expose router resources in public API
With the new Route Resources we can use a resource()instead of a resolver to fetch the data, reducing the complexity of the code. Additionally, we can define blocking and not blocking routing and .reload() the data without navigating to the route again.
Until now:
export const userResolver: ResolveFn<User> = (route) => {
return inject(UserService).getUser(route.paramMap.get('id')!);
};
const routes: Routes = [
{
path: 'users/:id',
component: UserProfile,
resolve: {
user: userResolver,
},
},
];With Router Resources:
const routes: Routes = [
{
path: 'users/:id',
component: UserProfile,
resources: (ctx) => ({
user: resource({
params: () => ctx.params()['id'],
loader: ({ params: id }) =>
inject(UserService).getUser(id),
}),
}),
},
];Testing Directives
feat(core): add utility for testing directives
If you create tests for your UI and you create directives, this change can simplify your code. Now you can test a directive without creating a temporary host component using TestBed.createDirective
Your directive:
@Directive({
selector: '[highlight]',
host: { class: 'highlighted' },
})
class HighlightDirective {}Until now:
@Component({
template: `<div highlight></div>`,
imports: [HighlightDirective],
})
class TestHost {}
const fixture = TestBed.createComponent(TestHost);
fixture.detectChanges();With the change:
const fixture = TestBed.createDirective(
HighlightDirective,
{ tagName: 'div' }
);
fixture.detectChanges();
expect(fixture.directiveInstance)
.toBeInstanceOf(HighlightDirective);Component only members are now accessible to the template
feat(compiler): allow template to access private props
The component's own template is allowed to access the component's private members.
export class AppComponent {
private title = signal('Dashboard');
private refresh() {
// only called from the template
}
}
<h1>{{ title() }}</h1>
<button (click)="refresh()">Refresh</button>Previously titleand refresh where not accessible in the view.
RedirectCommand in Guards and Resolvers
feat(router): allow throwing RedirectCommand to trigger redirects
Angular guards and resolvers can now throw RedirectCommand to redirect immediately, making redirects from deeply nested logic cleaner and avoiding RedirectCommand or UrlTree pollution in function return types.
The Angular team extended RedirectCommand with Error: export class RedirectCommand extends Error
const guard: CanActivateFn = () => {
const router = inject(Router);
if (!isLoggedIn()) {
throw new RedirectCommand(
router.parseUrl('/login')
);
}
return true;
};Previously we could not throw the Redirect command:
return new RedirectCommand(
router.parseUrl('/login')
);Other changes
- allow permanent hidden fields in signal forms : Allow the hidden utility function to be called without a configuration object to make fields permanently hidden. https://github.com/angular/angular/commit/d5e8b1ef7a02c84d4fd70a6b4d748ead9ff815bf
- improved the
deferredImportscheck to avoid that components are inside the wrong@deferblock: feat(compiler-cli): scope type-checking of keyed defer blocks - aria injection tokens to support custom subclasses: https://github.com/angular/components/commit/8d3da9ea36db6123ea0fcb44aaac18c6c91c7639


