Hone logo
Hone
Problems

Angular Signals: Creating and Managing Input Signals

This challenge focuses on leveraging Angular's Signals feature to manage dynamic data within components. You will create a component that accepts data via an input signal and demonstrates how to update and react to changes in that signal. This is crucial for building reactive and efficient Angular applications.

Problem Description

You need to create an Angular component called UserProfileCardComponent that displays user information. This component should receive user data through an input signal. The component should then render this data and allow for a mechanism to update the user's name, demonstrating how signal inputs can be modified and how the component automatically reacts to these changes.

Key Requirements:

  1. Input Signal: The UserProfileCardComponent must accept user data (an object with at least id and name properties) via a signal input.
  2. Display User Data: The component should display the id and name of the user passed through the input signal.
  3. Update Mechanism: Implement a button within the UserProfileCardComponent that, when clicked, updates the user's name. This update should propagate through the signal input and cause the displayed name to change.
  4. Parent Component Integration: Create a parent component (e.g., AppComponent) that uses UserProfileCardComponent, passes an initial user object to its signal input, and provides a way to trigger the name update from the parent.

Expected Behavior:

  • When the UserProfileCardComponent is initialized with user data, it should display the correct ID and Name.
  • Clicking the "Update Name" button within UserProfileCardComponent should change the displayed name to a new value.
  • The parent component should be able to trigger this name update from its own UI or logic.

Edge Cases:

  • Handling initial null or undefined input signal: While not strictly required for this basic challenge, consider how your component might behave if the initial input signal is null or undefined (though for this problem, we'll assume valid initial data).

Examples

Example 1: Initial Display

Parent Component (app.component.html):

<app-user-profile-card [userData]="userSignal"></app-user-profile-card>
<button (click)="changeUserNameInParent()">Change Name from Parent</button>

Parent Component (app.component.ts):

import { Component, signal, Signal } from '@angular/core';
import { UserProfileCardComponent } from './user-profile-card/user-profile-card.component';

interface User {
  id: number;
  name: string;
}

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [UserProfileCardComponent],
  templateUrl: './app.component.html',
})
export class AppComponent {
  userSignal = signal<User>({ id: 1, name: 'Alice' });

  changeUserNameInParent() {
    // This will trigger the update in the child component
    this.userSignal.update(user => ({ ...user, name: 'Bob' }));
  }
}

Child Component (user-profile-card.component.html):

<div *ngIf="userData()">
  <h2>User ID: {{ userData().id }}</h2>
  <p>Name: {{ userData().name }}</p>
  <button (click)="updateUserName()">Update Name</button>
</div>

Child Component (user-profile-card.component.ts):

import { Component, input, Signal } from '@angular/core';
import { CommonModule } from '@angular/common';

interface User {
  id: number;
  name: string;
}

@Component({
  selector: 'app-user-profile-card',
  standalone: true,
  imports: [CommonModule],
  templateUrl: './user-profile-card.component.html',
})
export class UserProfileCardComponent {
  userData = input<User>();

  updateUserName() {
    // We need a way to modify the signal that's passed IN.
    // For this example, let's assume we can update it from within.
    // In a real-world scenario, you might have a more complex update logic or service.

    // IMPORTANT: Signal inputs are read-only from the perspective of the child component
    // modifying the *exact same signal object* passed from the parent.
    // To demonstrate a change, we need to signal the parent to update, or create a local copy if needed.
    // For this challenge, we'll simulate an update by calling a parent method.
    // Let's adjust the example to show the parent triggering the change.
  }
}

Explanation (Initial Display): The AppComponent creates a signal named userSignal with initial data. This userSignal is passed to the userData input signal of UserProfileCardComponent. The UserProfileCardComponent then reads and displays the id and name from this signal.

Example 2: Name Update (Triggered from Parent)

Parent Component (app.component.html): (Same as Example 1)

<app-user-profile-card [userData]="userSignal"></app-user-profile-card>
<button (click)="changeUserNameInParent()">Change Name from Parent</button>

Parent Component (app.component.ts): (Same as Example 1)

import { Component, signal, Signal } from '@angular/core';
import { UserProfileCardComponent } from './user-profile-card/user-profile-card.component';

interface User {
  id: number;
  name: string;
}

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [UserProfileCardComponent],
  templateUrl: './app.component.html',
})
export class AppComponent {
  userSignal = signal<User>({ id: 1, name: 'Alice' });

  changeUserNameInParent() {
    this.userSignal.update(user => ({ ...user, name: 'Bob' }));
  }
}

Child Component (user-profile-card.component.html):

<div *ngIf="userData()">
  <h2>User ID: {{ userData().id }}</h2>
  <p>Name: {{ userData().name }}</p>
  <!-- Removed the internal update button as we'll trigger from parent -->
</div>

Child Component (user-profile-card.component.ts):

import { Component, input, Signal } from '@angular/core';
import { CommonModule } from '@angular/common';

interface User {
  id: number;
  name: string;
}

@Component({
  selector: 'app-user-profile-card',
  standalone: true,
  imports: [CommonModule],
  templateUrl: './user-profile-card.component.html',
})
export class UserProfileCardComponent {
  userData = input<User>(); // This is a read-only signal input
}

Explanation (Name Update): When the "Change Name from Parent" button is clicked in AppComponent, changeUserNameInParent() is called. This method updates the userSignal in the parent. Because userSignal is passed as an input to UserProfileCardComponent, Angular's change detection mechanism will automatically detect the change in the userData signal input. The UserProfileCardComponent will then re-render, displaying the new name "Bob".

Constraints

  • Angular Version: Must be compatible with Angular v16 or later (for Signals support).
  • Language: TypeScript.
  • Component Structure: Use standalone components for both parent and child components.
  • Signal Usage: You must use Angular's signal and input APIs for managing the user data. Do not use @Input() decorators for this specific data.
  • Immutability: When updating the user object, ensure you create a new object to maintain immutability, especially when using signal.update().

Notes

  • Signal inputs in Angular are read-only by default within the child component. The child cannot directly modify the signal passed from the parent. Any changes to the data must originate from the parent component updating its own signal.
  • The input() function creates a signal-based input property. You can access its value by calling it like a function: userData().
  • When updating a signal that holds an object, use the update() method of the signal and create a new object with the desired modifications. This ensures reactivity.
Loading editor...
typescript