Let us list out the most important changes and new features in the minor versions Angular 17.2
Signal Inputs
Signal Inputs arrived in Angular 17.1. They fulfill the same tasks as the @Input
decorator: Property Binding.
A Signal Input is a simple function named input()
. The new Property Binding syntax looks like this:
// @Input-Style (old)
class HolidayComponent {
@Input() username = '';
@Input({required: true}) holiday: Holiday | undefined;
}
// Signal Input (new)
class HolidayComponent {
username = input(''); // Signal<string>
holiday = input<Holiday>(); // Signal<Holiday | undefined>
}
The property is of type Signal
, which makes it reactive by nature. Instead of ngOnChanges()
and ngOnInit()
, we can consume changes with effect()
or computed()
.
The second addition is the additional required()
function. It fixes the issue with @Input({required: true})
, which always results in the union type including undefined
and the actual type:
// required input.
// Here, We dont need the including "undefined Type" anymore, like with the old syntax
class HolidayComponent {
username = input(''); // Signal<string>
holiday = input.required<Holiday>(); // Signal<Holiday>
}
@Component({
tempate: `<app-holiday [username]="username" [holiday]="holiday" />`
})
class HolidayContainerComponent {
username = 'Konrad Weber';
holiday = createHoliday();
}
As we can see in the example above, the input()
function returns a signal, that can be used in the template or in computed values (which would be the modern equivalent of ngOnChanges
).
@Component({
standalone: true,
selector: 'ns-pony',
template: `
@if (ponyModel(); as ponyModel) {
<figure>
<img [src]="imageUrl()" [alt]="ponyModel.name" />
<figcaption></figcaption>
</figure>
}
`
})
export class PonyComponent {
ponyModel = input<PonyModel>();
imageUrl = computed(() => `assets/pony-${this.ponyModel()!.color}.gif`);
we can also use the signal as the source of an observable, to trigger an action when the input changes. For example, to fetch data from a server:
export class PonyComponent {
ponyService = inject(PonyService);
ponyId = input.required<number>();
// entity fetched from the server every time the ponyId changes
ponyModel = toSignal(toObservable(this.ponyId)
.pipe(switchMap(id => this.ponyService.get(id))));
imageUrl = computed(() => `assets/pony-${this.ponyModel()!.color}.gif`);
Model signal inputs
In the latest update of Angular 17.2, a new feature was introduced: model inputs. These inputs are built on writable signals and establish an input/output pair that enables two-way bindings.
In the example provided below, the signals in both components always have the same value, and we can increase this value by pressing on of the buttons
// First Component
@Component({
selector: 'app-counter',
standalone: true,
template: `<button (click)="increase()">Counter's button: {{ value() }}</button>`,
})
export class CounterComponent {
value = model.required<number>();
increase() {
this.value.update((x) => x + 1);
}
}
// 2nd Component - Here we use the first component as the child-component
@Component({
selector: 'app-wrapper',
standalone: true,
imports: [CounterComponent],
template: `<app-counter [(value)]="count" />
<button (click)="increase()">Wrapper's button: {{ count() }}</button>`
})
export class WrapperComponent {
count = signal(0);
increase() {
this.count.update((x) => x + 1);
}
}
We can also bind an input element’s value to a writable signal by two-way data binding, using the ‘banana in the box’ syntax [(ngModel)]
:
@Component({
selector: 'app-root',
standalone: true,
imports: [
FormsModule,
],
template: `
<textarea
[(ngModel)]="promptValue"
></textarea>`
})
export class AppComponent {
promptValue = signal('');
}
View queries and component queries as signals
With this improvement, we can query elements from the component’s template as signals: there are new viewChild()
, viewChildren()
, contentChild()
and contentChildren()
functions that return Signals.
PS: These are signal based versions of the @viewChild
, @viewChildren
, @contentChild
and @contentChildren
decorators:
@Component({
selector: 'app-vc-query-as-signal',
standalone: true,
template: `
<button (click)="show()">Show</button>
@if(visible()) {
<div #id1>Hi!</div>
}`,
})
class VcQueryAsSignalComponent {
visible = signal(false);
divEl = viewChild<ElementRef<HTMLDivElement>>('id1'); // ?
effectRef = effect(() => {
console.log(this.divEl());
});
show() {
this.visible.set(true);
}
}
// First message on the console: undefined
// The user clicks on the button
// Second message on the console: _ElementRef {nativeElement: div}
Angular CLI: clearScreen option support
Angular can clear the screen before each re-build. we can enable this feature in angular.json
, by setting the clearScreen
builder option to true
(it’s false
by default):
// angular.json
{
"projects": {
"ng172": {
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
// ? clear the screen before each re-build
"clearScreen": true,
// ...
Angular CLI: ‘define’ option for declaring global identifiers
The application
builder supports the define
option for declaring global identifiers. As these identifiers declared in angular.json
, not in a .ts
support, we can declare it for typescript using a declare const
statement in src/types.d.ts
.
PS: We can use these identifiers as an alternate to the environment files in the future.
// angular.json
{
"projects": {
"ng172": {
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"define": {
// the value must have a valid JSON syntax ?
"CONSTANT_IN_ANGULAR_JSON": "{ 'text': 'This constant is defined in angular.json', 'number': 1 }"
},
// ...
// src/types.d.ts
declare const CONSTANT_IN_ANGULAR_JSON: { text: string; number: number };
// component.ts
@Component({
template: `
Text: {{ CONSTANT_IN_ANGULAR_JSON.text }},
Number:{{ CONSTANT_IN_ANGULAR_JSON.number }}`,
})
export class GlobalIdentifierComponent {
CONSTANT_IN_ANGULAR_JSON = CONSTANT_IN_ANGULAR_JSON;
}
Reference:
https://dev.to/this-is-angular/