From 2bc6210524272d3e16e42a45a314d6c5acbe4565 Mon Sep 17 00:00:00 2001 From: reedanj Date: Wed, 18 Mar 2026 20:08:14 +0000 Subject: [PATCH 1/3] feat: update user component to use input signal --- .../43-signal-input/src/app/user.component.ts | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/apps/signal/43-signal-input/src/app/user.component.ts b/apps/signal/43-signal-input/src/app/user.component.ts index 553042ebb..5fd3a3c66 100644 --- a/apps/signal/43-signal-input/src/app/user.component.ts +++ b/apps/signal/43-signal-input/src/app/user.component.ts @@ -2,8 +2,9 @@ import { TitleCasePipe } from '@angular/common'; import { ChangeDetectionStrategy, Component, - Input, - OnChanges, + computed, + input, + numberAttribute, } from '@angular/core'; type Category = 'Youth' | 'Junior' | 'Open' | 'Senior'; @@ -18,23 +19,18 @@ const ageToCategory = (age: number): Category => { selector: 'app-user', imports: [TitleCasePipe], template: ` - {{ fullName | titlecase }} plays tennis in the {{ category }} category!! + {{ fullName() | titlecase }} plays tennis in the {{ category() }} category!! `, host: { class: 'text-xl text-green-800', }, changeDetection: ChangeDetectionStrategy.OnPush, }) -export class UserComponent implements OnChanges { - @Input({ required: true }) name!: string; - @Input() lastName?: string; - @Input() age?: string; +export class UserComponent { + readonly name = input.required(); + readonly lastName = input(); + readonly age = input(0, { transform: numberAttribute }); - fullName = ''; - category: Category = 'Junior'; - - ngOnChanges(): void { - this.fullName = `${this.name} ${this.lastName ?? ''}`; - this.category = ageToCategory(Number(this.age)); - } + fullName = computed(() => `${this.name()} ${this.lastName() ?? ''}`); + category = computed(() => ageToCategory(this.age())); } From 64283fccfde8e90cd6d7946dcf48e32bf4e33679 Mon Sep 17 00:00:00 2001 From: reedanj Date: Sun, 22 Mar 2026 14:46:55 +0000 Subject: [PATCH 2/3] feat: first iteration to fix bug --- apps/signal/50-bug-in-effect/src/app/app.component.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/signal/50-bug-in-effect/src/app/app.component.ts b/apps/signal/50-bug-in-effect/src/app/app.component.ts index ec6ba09b0..8af13d98d 100644 --- a/apps/signal/50-bug-in-effect/src/app/app.component.ts +++ b/apps/signal/50-bug-in-effect/src/app/app.component.ts @@ -3,6 +3,7 @@ import { Component, effect, model, + signal, } from '@angular/core'; import { FormsModule } from '@angular/forms'; @@ -39,14 +40,20 @@ export class AppComponent { ram = model(false); gpu = model(false); + noOfModelsSelected = signal(0); + constructor() { /* Explain for your junior team mate why this bug occurs ... */ effect(() => { - if (this.drive() || this.ram() || this.gpu()) { + const models = [this.drive(), this.ram(), this.gpu()]; + const currentNoSelected = models.filter((model) => model).length; + const shouldFireAlert = currentNoSelected > this.noOfModelsSelected(); + if (shouldFireAlert) { alert('Price increased!'); } + this.noOfModelsSelected.set(currentNoSelected); }); } } From 474a73dc95f3427abf44f0cb6ef2da18390dcb9f Mon Sep 17 00:00:00 2001 From: reedanj Date: Sat, 28 Mar 2026 20:25:58 +0000 Subject: [PATCH 3/3] feat: use closure --- .../50-bug-in-effect/src/app/app.component.ts | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/apps/signal/50-bug-in-effect/src/app/app.component.ts b/apps/signal/50-bug-in-effect/src/app/app.component.ts index 8af13d98d..f67f582e0 100644 --- a/apps/signal/50-bug-in-effect/src/app/app.component.ts +++ b/apps/signal/50-bug-in-effect/src/app/app.component.ts @@ -1,6 +1,7 @@ import { ChangeDetectionStrategy, Component, + computed, effect, model, signal, @@ -36,24 +37,28 @@ import { FormsModule } from '@angular/forms'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class AppComponent { - drive = model(false); - ram = model(false); - gpu = model(false); + protected readonly drive = model(false); + protected readonly ram = model(false); + protected readonly gpu = model(false); - noOfModelsSelected = signal(0); + private readonly currentNoSelected = signal(0); + + private readonly noOfModelsSelected = computed( + () => + [this.drive(), this.ram(), this.gpu()].filter((model) => model).length, + ); + + private prev = this.currentNoSelected(); constructor() { - /* - Explain for your junior team mate why this bug occurs ... - */ effect(() => { - const models = [this.drive(), this.ram(), this.gpu()]; - const currentNoSelected = models.filter((model) => model).length; - const shouldFireAlert = currentNoSelected > this.noOfModelsSelected(); + const valueNow = this.noOfModelsSelected(); + const shouldFireAlert = valueNow > this.prev; if (shouldFireAlert) { alert('Price increased!'); } - this.noOfModelsSelected.set(currentNoSelected); + this.prev = valueNow; + this.currentNoSelected.set(valueNow); }); } }