After my previous guides, Master Angular 17 Study guide and Master Angular 17.1 and 17.2 Study guide, the Angular team has now shipped 17.3.
🎯Changes and new features
This post covers the key updates and additions, along with resources to help you understand how the new Angular features work:
-
output()API—for emitting output from a component -
outputFromObservable()—converts an observable into a component'soutput() -
outputToObservable()—turns a component's output into an observable -
hostAttributeToken()—creates a token for injecting static attributes from the host node - Support for TypeScript 5.4
📌New output() API
Official documentation: output
Pull request: Finalizing output() and outputFromObservable() APIs
The output() API, released in Angular 17.3, is intended for emitting component output. It pairs well with the input() and model() APIs and provides full type safety:
@Component({
selector: 'app-output',
standalone: true,
template: `
<button (click)="onClick.emit()">Button</button>
<input #in type="text" (keyup)="onChange.emit(in.value)" />
`,
})
export class OutputComponent {
onClick = output(); // 👈 OutputEmitterRef<void>
onChange = output<string>(); // 👈 OutputEmitterRef<string>
}
@Component({
selector: 'app-output-wrapper',
standalone: true,
imports: [OutputComponent],
template: ` <app-output (onClick)="log('onClick')" (onChange)="log('onChange', $event)" /> `,
})
export class OutputWrapperComponent {
log(t1: string, t2: string = '') { console.log(t1, t2); }
}
// after you click on the button, then type 'test' into the input field,
// the messages on the console are:
// onClick
// onChange t
// onChange te
// onChange tes
// onChange test
📌outputFromObservable() and outputToObservable helper functions
See the official documentation: outputFromObservable, outputToObservable
PR: Finalizing output() and outputFromObservable() APIs
Beyond the fresh output() API, Angular 17.3 ships outputFromObservable(), which lets you take an observable and turn it into an output() for your component:
@Component({
selector: 'app-output2',
standalone: true,
template: `<button (click)="onClick$.next('click2')">Button</button>`,
})
export class Output2Component {
onClick$ = new BehaviorSubject(''); // 👈 BehaviorSubject<string>
onClick = outputFromObservable(this.onClick$); // 👈 OutputRef<string>
}
@Component({
selector: 'app-output-wrapper2',
standalone: true,
imports: [Output2Component],
template: `<app-output2 (onClick)="log('onClick', $event)" />`,
})
export class OutputWrapper2Component {
log(t1: string, t2: string = '') { console.log(t1, t2); }
}
// after you click on the button, the message on the console is:
// onClick click2
Alongside that, outputToObservable() serves as a utility for turning a component’s output into an observable stream:
@Component({
selector: 'app-output3',
standalone: true,
template: `<button (click)="onClick.emit()">Button</button>`,
})
export class Output3Component {
onClick = output(); // 👈 OutputEmitterRef<void>
}
@Component({
selector: 'app-output-wrapper3',
standalone: true,
imports: [Output3Component],
template: `<app-output3/>`, // 👈 no (onClick)="..." here!
})
export class OutputWrapper3Component implements OnInit {
childComponent = viewChild(Output3Component);
destroyRef = inject(DestroyRef);
ngOnInit(): void {
const childComponent = this.childComponent();
if (childComponent) {
const onClick$ = outputToObservable(childComponent.onClick) // 👈
.pipe(takeUntilDestroyed(this.destroyRef));
onClick$.subscribe(() => console.log('onClick'));
}
}
}
// after you click on the button, the message on the console is:
// onClick
📌HostAttributeToken() class
Documentation: HostAttributeToken
PR: feat(core): add API to inject attributes on the host node
With HostAttributeToken(), you can generate a token for injecting static host-node attributes. This mirrors constructor(@Attribute('value') type: string) in behavior, yet it leverages the modern inject() function instead of the @Attribute decorator.
@Component({
selector: 'app-hat',
standalone: true,
template: `<div>{{ value }}</div>
<div>{{ value2 }}</div>`,
})
export class HatComponent {
// 👇 required, we get a DI error if the attribute is not specified
value = inject(new HostAttributeToken('value'));
// 👇 optional attribute
value2 = inject(new HostAttributeToken('value2'),
{ optional: true }) || 'Default value';
}
@Component({
selector: 'app-hat-wrapper',
standalone: true,
imports: [HatComponent],
// we don't specify the optional 'value2' attribute,
// 👇 so its value is 'Default value'
template: `<app-hat value="Hello" />`,
})
export class HatWrapperComponent {}
For a deeper dive into this functionality, Netanel Basal’s write-up offers a more detailed explanation.
📌Typescript 5.4 support
The corresponding PR is feat(core): support TypeScript 5.4.
In his release announcement, Daniel Rosenwasser called out the most notable additions that ship with TypeScript 5.4:
- Narrowing preserved in closures after the final assignment
- Introduction of the
NoInferutility type -
Object.groupByandMap.groupBy require()now works with--moduleResolution bundlerand--module preserve- Validation for import attributes and assertions
- A quick fix to insert missing parameters
- Auto-imports for subpath imports
👨💻About the author
I’m Gergely Szerovay, serving as a frontend development chapter lead. Angular is both a professional focus and a personal passion—I’m constantly absorbing the latest in articles, podcasts, and conference talks.
To share what I discover, I launched the Angular Addict Newsletter, delivering a monthly roundup of the best resources I stumble upon. Whether you’re just starting out or a long-time enthusiast, there’s something valuable for you in every issue.
Beyond the newsletter, Angular Addicts serves as my curated publication of the most insightful materials. If you’re interested in contributing as a writer, just drop me a note.
Let’s dive into Angular together—subscribe now 🔥
For more Angular insights, connect with me on Substack, Medium, Dev.to, Twitter, or LinkedIn.
