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/

By Shabazz

Software Engineer, MCSD, Web developer & Angular specialist

Leave a Reply

Your email address will not be published. Required fields are marked *