Introduction: Beyond Native Inputs

Welcome back, coding adventurer! In our previous chapters, you’ve become a wizard with Angular Reactive Forms, mastering FormGroup, FormControl, and various built-in validators. You’ve built dynamic forms, handled complex validation, and even started thinking about conditional logic. That’s fantastic!

But what happens when you need a form input that isn’t a simple <input type="text"> or <select>? What if you want to create a fancy star rating component, a custom rich text editor, or a complex date picker that behaves just like a native form control, complete with validation, disabled states, and seamless integration with your FormGroup? This is where Angular’s powerful ControlValueAccessor interface comes into play!

In this chapter, we’re going to unlock the secret to building truly reusable, custom form controls. You’ll learn what ControlValueAccessor is, why it’s a game-changer for complex UI, and how to implement it step-by-step to create your own custom inputs that play perfectly with Angular Reactive Forms. Get ready to level up your form-building skills!

Prerequisites

Before we dive in, make sure you’re comfortable with:

  • Creating Angular projects and components.
  • Working with FormGroup and FormControl.
  • Applying built-in validators like Validators.required.
  • Basic understanding of @Input() and @Output() decorators.

Let’s build something awesome!

Core Concepts: The ControlValueAccessor Superpower

Imagine you have a beautifully designed star rating component. It uses icons, has hover effects, and allows users to click to select a rating. Now, you want to use this component within your Angular form, just like you would use a regular <input> field. You want to bind it to a FormControl, validate its value, and get its value when the form submits. How do you make your custom component “talk” to Angular’s forms system?

That’s precisely the problem ControlValueAccessor solves!

What is ControlValueAccessor? (The “Bridge” Analogy)

Think of ControlValueAccessor as a bridge or an adapter that allows your custom component to communicate with the Angular Forms API. It’s an interface that defines a standard way for a component to:

  1. Receive values from the FormControl (e.g., when you set formControl.setValue(3)).
  2. Report changes back to the FormControl (e.g., when a user clicks a star, your component tells the form, “Hey, my value just changed to 4!”).
  3. Report “touched” state to the FormControl (e.g., when a user interacts with your component and then moves away, your component tells the form, “I’ve been touched!”).
  4. Receive disabled state from the FormControl (e.g., when you call formControl.disable()).

Without this bridge, your custom component would be an isolated island, unable to participate in the powerful Angular Forms ecosystem.

Why Do We Need ControlValueAccessor?

  • Encapsulation & Reusability: Build complex UI widgets once and reuse them across multiple forms and projects. The internal implementation details are hidden from the parent form.
  • Seamless Integration: Your custom component behaves exactly like a native HTML input. You can use formControlName, [formControl], apply validators, and manage its state (valid, dirty, touched, disabled) directly through the FormControl.
  • Abstraction: The parent form doesn’t need to know how your star rating component works internally; it just needs to know it provides a value and responds to changes.
  • Consistency: Ensures all your form controls, whether native or custom, adhere to the same API for interaction with Angular Forms.

Key Methods of the ControlValueAccessor Interface

The ControlValueAccessor interface, part of @angular/forms, requires you to implement a few key methods:

  1. writeValue(obj: any): void

    • Purpose: This method is called by the Angular forms API whenever the FormControl associated with this custom control has its value updated programmatically (e.g., formControl.setValue(5) or formControl.patchValue(...)).
    • Your Job: Take the obj (the new value) and update your component’s internal state and UI to reflect this value.
  2. registerOnChange(fn: any): void

    • Purpose: This method is called by the Angular forms API to register a callback function.
    • Your Job: Store this fn function. Whenever your custom component’s internal value changes (e.g., a user clicks a star), you must call this stored fn with the new value. This notifies the FormControl that its bound value has changed.
  3. registerOnTouched(fn: any): void

    • Purpose: Similar to registerOnChange, this method registers another callback function.
    • Your Job: Store this fn function. Whenever your custom component is “touched” (e.g., a user interacts with it and then blurs away), you must call this stored fn. This notifies the FormControl that it has been touched, which is crucial for validation messages (ng-touched).
  4. setDisabledState?(isDisabled: boolean): void (Optional)

    • Purpose: This method is called when the FormControl associated with your component is programmatically enabled or disabled (e.g., formControl.disable() or formControl.enable()).
    • Your Job: Take the isDisabled boolean and update your component’s UI and internal logic to reflect the disabled state. For instance, you might make the stars unclickable or change their color.

The NG_VALUE_ACCESSOR Token

How does Angular know that your component is a ControlValueAccessor? You tell it by providing your component using the NG_VALUE_ACCESSOR injection token in your component’s providers array.

// Inside your @Component decorator's providers array
providers: [
  {
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => StarRatingComponent), // Your component class
    multi: true // Essential for registering multiple CVA's if needed, but always for custom controls
  }
]
  • provide: NG_VALUE_ACCESSOR: This is the injection token Angular looks for.
  • useExisting: forwardRef(() => StarRatingComponent): This tells Angular to use an existing instance of StarRatingComponent as the ControlValueAccessor. forwardRef is necessary because StarRatingComponent isn’t fully defined yet when the providers array is evaluated, preventing a circular dependency.
  • multi: true: This is crucial. It tells Angular that NG_VALUE_ACCESSOR can have multiple providers (e.g., if you had multiple custom controls in different places). Without multi: true, your custom control would override any other ControlValueAccessor registered in the application.

Phew! That’s a lot of theory, but understanding these concepts makes the implementation much clearer. Ready to build our star rating component? Let’s go!

Step-by-Step Implementation: Building a Star Rating Component

We’ll create a simple star rating component that displays 5 stars. Users can click on a star to set a rating, and this rating will be seamlessly integrated into our FormGroup.

Step 1: Set Up Your Angular Project

If you don’t have an Angular project ready, let’s quickly create one. We’ll use Angular CLI version 18 (as of 2025-12-05).

# Check Angular CLI version (should be 18.x.x)
ng version

# If you need to update:
# npm uninstall -g @angular/cli
# npm cache clean --force
# npm install -g @angular/cli@latest

# Create a new project (using NgModule for this guide for simplicity)
ng new custom-cva-app --no-standalone --routing false --style css --skip-tests
cd custom-cva-app

Now, let’s generate our custom star rating component:

ng generate component star-rating

Next, we need to ensure our app.module.ts imports ReactiveFormsModule so we can use Reactive Forms.

Open src/app/app.module.ts:

// src/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule } from '@angular/forms'; // <-- Import this!

import { AppComponent } from './app.component';
import { StarRatingComponent } from './star-rating/star-rating.component';

@NgModule({
  declarations: [
    AppComponent,
    StarRatingComponent
  ],
  imports: [
    BrowserModule,
    ReactiveFormsModule // <-- Add to imports array!
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Great! Our project is set up.

Step 2: Design the StarRating Component Template and Basic Logic

First, let’s make our StarRatingComponent display some stars and manage its internal rating.

Open src/app/star-rating/star-rating.component.ts:

// src/app/star-rating/star-rating.component.ts
import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-star-rating',
  templateUrl: './star-rating.component.html',
  styleUrls: ['./star-rating.component.css']
})
export class StarRatingComponent {
  // We'll add this later for the ControlValueAccessor
  // onChange = (value: any) => {};
  // onTouched = () => {};

  @Input() maxStars: number = 5; // How many stars to display
  rating: number = 0; // The currently selected rating
  hoverRating: number = 0; // For visual hover effect

  // Generate an array of numbers from 1 to maxStars for *ngFor
  get stars(): number[] {
    return Array(this.maxStars).fill(0).map((x, i) => i + 1);
  }

  // When a star is clicked
  rate(star: number): void {
    this.rating = star;
    // We will call onChange here later
    // this.onChange(this.rating);
    // this.onTouched(); // Also call onTouched
  }

  // When mouse enters a star
  hover(star: number): void {
    this.hoverRating = star;
  }

  // When mouse leaves the star area
  leave(): void {
    this.hoverRating = 0;
  }

  // Helper to determine if a star should be "filled" (active)
  isStarActive(star: number): boolean {
    return star <= (this.hoverRating || this.rating);
  }
}

Now, let’s create the template for our stars.

Open src/app/star-rating/star-rating.component.html:

<!-- src/app/star-rating/star-rating.component.html -->
<div class="star-rating-container"
     (mouseleave)="leave()">
  <span *ngFor="let star of stars"
        class="star"
        [class.active]="isStarActive(star)"
        (click)="rate(star)"
        (mouseenter)="hover(star)">
    &#9733; <!-- Unicode star character -->
  </span>
</div>

And some basic styling to make them look like stars.

Open src/app/star-rating/star-rating.component.css:

/* src/app/star-rating/star-rating.component.css */
.star-rating-container {
  display: inline-block;
  font-size: 2em; /* Make stars larger */
  cursor: pointer;
}

.star {
  color: #ccc; /* Default grey */
  transition: color 0.1s ease-in-out;
}

.star.active {
  color: gold; /* Active stars are gold */
}

/* Optional: Add a disabled style later */
.star-rating-container.disabled .star {
  color: #eee;
  cursor: not-allowed;
}
.star-rating-container.disabled .star.active {
  color: #ddd;
}

At this point, you can add <app-star-rating></app-star-rating> to app.component.html and run ng serve to see your stars. They’ll be interactive, but they won’t communicate with any form yet.

Step 3: Implement ControlValueAccessor Interface

This is where the magic happens! We’ll make our StarRatingComponent conform to the ControlValueAccessor interface.

Open src/app/star-rating/star-rating.component.ts again and update it:

// src/app/star-rating/star-rating.component.ts
import { Component, Input, forwardRef } from '@angular/core'; // <-- Import forwardRef
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; // <-- Import these!

@Component({
  selector: 'app-star-rating',
  templateUrl: './star-rating.component.html',
  styleUrls: ['./star-rating.component.css'],
  // This is the crucial part: tell Angular this component is a ControlValueAccessor
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => StarRatingComponent), // Reference to this component
      multi: true // Essential for custom form controls
    }
  ]
})
// Implement the ControlValueAccessor interface
export class StarRatingComponent implements ControlValueAccessor { // <-- Implement CVA
  @Input() maxStars: number = 5;
  rating: number = 0;
  hoverRating: number = 0;
  isDisabled: boolean = false; // Internal state for disabled

  // These are placeholders for the functions Angular will provide
  // We'll call them when our internal value changes or the control is touched
  private onChange = (value: any) => {};
  private onTouched = () => {};

  get stars(): number[] {
    return Array(this.maxStars).fill(0).map((x, i) => i + 1);
  }

  rate(star: number): void {
    if (this.isDisabled) { // Prevent rating if disabled
      return;
    }
    this.rating = star;
    this.onChange(this.rating); // <-- Notify Angular Forms that the value changed!
    this.onTouched(); // <-- Notify Angular Forms that the control was touched!
  }

  hover(star: number): void {
    if (this.isDisabled) {
      return;
    }
    this.hoverRating = star;
  }

  leave(): void {
    if (this.isDisabled) {
      return;
    }
    this.hoverRating = 0;
  }

  isStarActive(star: number): boolean {
    return star <= (this.hoverRating || this.rating);
  }

  // --- ControlValueAccessor methods ---

  // Called by the forms API to write a value to the component
  writeValue(obj: any): void {
    // Make sure obj is a valid number, otherwise default to 0
    if (typeof obj === 'number' && obj >= 0 && obj <= this.maxStars) {
      this.rating = obj;
    } else {
      this.rating = 0; // Default or handle invalid input
    }
  }

  // Called by the forms API to register a callback function
  // Your component should call this function whenever its value changes
  registerOnChange(fn: any): void {
    this.onChange = fn;
  }

  // Called by the forms API to register a callback function
  // Your component should call this function whenever it's "touched" (e.g., on blur)
  registerOnTouched(fn: any): void {
    this.onTouched = fn;
  }

  // Optional: Called by the forms API to set the disabled state
  setDisabledState?(isDisabled: boolean): void {
    this.isDisabled = isDisabled;
    // You might want to update the UI to reflect the disabled state
    // We'll add a class to the host element for this.
  }
}

Now, let’s modify the star-rating.component.html and star-rating.component.css to visually reflect the disabled state.

Update src/app/star-rating/star-rating.component.html:

<!-- src/app/star-rating/star-rating.component.html -->
<!-- Use ngClass to add 'disabled' class if isDisabled is true -->
<div class="star-rating-container"
     [class.disabled]="isDisabled"
     (mouseleave)="leave()">
  <span *ngFor="let star of stars"
        class="star"
        [class.active]="isStarActive(star)"
        (click)="rate(star)"
        (mouseenter)="hover(star)">
    &#9733;
  </span>
</div>

The CSS we added earlier will now take effect when isDisabled is true.

Step 4: Integrate StarRating into AppComponent

Now that our StarRatingComponent is a proper ControlValueAccessor, we can use it just like any other form control!

Open src/app/app.component.ts:

// src/app/app.component.ts
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; // <-- Import FormBuilder & Validators

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  title = 'Custom Control Value Accessor';
  feedbackForm!: FormGroup; // Use definite assignment assertion

  constructor(private fb: FormBuilder) {}

  ngOnInit(): void {
    this.feedbackForm = this.fb.group({
      name: ['', Validators.required],
      email: ['', [Validators.required, Validators.email]],
      // Our custom star rating control!
      rating: [0, [Validators.required, Validators.min(1)]], // Default to 0, but require at least 1 star
      comments: ['']
    });

    // Optional: Watch for value changes in the form
    this.feedbackForm.valueChanges.subscribe(value => {
      console.log('Form Value Changed:', value);
    });
  }

  onSubmit(): void {
    if (this.feedbackForm.valid) {
      console.log('Form Submitted!', this.feedbackForm.value);
      alert('Form Submitted! Check console for data.');
    } else {
      console.log('Form is invalid. Please check fields.');
      alert('Please fill out all required fields correctly.');
      // Mark all fields as touched to display validation messages
      this.feedbackForm.markAllAsTouched();
    }
  }

  // Example of programmatically setting a value
  setRatingTo(value: number): void {
    this.feedbackForm.get('rating')?.setValue(value);
  }

  // Example of programmatically disabling/enabling
  toggleRatingDisabled(): void {
    const ratingControl = this.feedbackForm.get('rating');
    if (ratingControl?.disabled) {
      ratingControl.enable();
    } else {
      ratingControl?.disable();
    }
  }
}

Now, let’s update src/app/app.component.html to include our form and the app-star-rating component.

<!-- src/app/app.component.html -->
<div class="container">
  <h1>{{ title }}</h1>

  <form [formGroup]="feedbackForm" (ngSubmit)="onSubmit()">
    <div class="form-group">
      <label for="name">Your Name:</label>
      <input type="text" id="name" formControlName="name" class="form-control">
      <div *ngIf="feedbackForm.get('name')?.invalid && (feedbackForm.get('name')?.dirty || feedbackForm.get('name')?.touched)"
           class="alert alert-danger">
        Name is required.
      </div>
    </div>

    <div class="form-group">
      <label for="email">Email:</label>
      <input type="email" id="email" formControlName="email" class="form-control">
      <div *ngIf="feedbackForm.get('email')?.invalid && (feedbackForm.get('email')?.dirty || feedbackForm.get('email')?.touched)"
           class="alert alert-danger">
        <span *ngIf="feedbackForm.get('email')?.errors?.['required']">Email is required.</span>
        <span *ngIf="feedbackForm.get('email')?.errors?.['email']">Please enter a valid email.</span>
      </div>
    </div>

    <div class="form-group">
      <label>Overall Rating:</label>
      <!-- Our custom star rating component, bound with formControlName! -->
      <app-star-rating formControlName="rating" [maxStars]="5"></app-star-rating>
      <div *ngIf="feedbackForm.get('rating')?.invalid && (feedbackForm.get('rating')?.dirty || feedbackForm.get('rating')?.touched)"
           class="alert alert-danger">
        Please provide a rating (at least 1 star).
      </div>
    </div>

    <div class="form-group">
      <label for="comments">Comments:</label>
      <textarea id="comments" formControlName="comments" class="form-control"></textarea>
    </div>

    <button type="submit" [disabled]="feedbackForm.invalid" class="btn btn-primary">Submit Feedback</button>

    <hr>
    <h3>Form Actions:</h3>
    <button type="button" (click)="setRatingTo(3)" class="btn btn-secondary mr-2">Set Rating to 3</button>
    <button type="button" (click)="toggleRatingDisabled()" class="btn btn-secondary">Toggle Rating Disabled</button>

    <pre>Form Status: {{ feedbackForm.status }}</pre>
    <pre>Form Value: {{ feedbackForm.value | json }}</pre>
  </form>
</div>

Let’s add some minimal global styling to src/styles.css for better readability of the form.

/* src/styles.css */
body {
  font-family: Arial, sans-serif;
  padding: 20px;
  background-color: #f4f4f4;
}

.container {
  max-width: 600px;
  margin: 20px auto;
  padding: 20px;
  background-color: #fff;
  border-radius: 8px;
  box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}

.form-group {
  margin-bottom: 15px;
}

.form-group label {
  display: block;
  margin-bottom: 5px;
  font-weight: bold;
}

.form-control {
  width: 100%;
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 4px;
  box-sizing: border-box; /* Ensures padding doesn't expand width */
}

.alert {
  padding: 8px;
  margin-top: 5px;
  border-radius: 4px;
  color: #a94442;
  background-color: #f2dede;
  border: 1px solid #ebccd1;
}

.btn {
  padding: 10px 15px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 16px;
  margin-right: 10px;
}

.btn-primary {
  background-color: #007bff;
  color: white;
}

.btn-primary:disabled {
  background-color: #a0c9ed;
  cursor: not-allowed;
}

.btn-secondary {
  background-color: #6c757d;
  color: white;
}

pre {
  background-color: #eee;
  padding: 10px;
  border-radius: 4px;
  white-space: pre-wrap;
  word-wrap: break-word;
}

Now, run ng serve and open your browser to http://localhost:4200.

Observe:

  • You can now click on the stars to set a rating.
  • The Form Value display below the form updates immediately as you click stars.
  • If you try to submit without selecting a star, the rating field will be invalid due to Validators.min(1), and the error message will show.
  • Clicking the “Set Rating to 3” button will programmatically update the star rating component to 3 stars.
  • Clicking “Toggle Rating Disabled” will disable/enable the star rating, and you’ll see the visual change and inability to click stars when disabled.

Isn’t that neat? Your custom component now behaves exactly like a native input, thanks to ControlValueAccessor!

Switching from Template-Driven Forms (A Quick Note)

While this chapter focuses on Reactive Forms, it’s important to note that ControlValueAccessor is the underlying mechanism that allows both Reactive and Template-Driven forms to interact with custom components.

If you were migrating from a Template-Driven approach for a custom component, the ControlValueAccessor implementation within the custom component itself would remain largely the same. The difference would be in the parent component’s template:

Template-Driven (using ngModel):

<app-star-rating name="myRating" [(ngModel)]="myRatingValue" required></app-star-rating>

Here, ngModel would interact with the ControlValueAccessor to read/write values and manage validation. The required attribute would also be picked up by the forms system.

The core benefit of ControlValueAccessor is that it makes your custom component agnostic to the form type used by the parent, allowing for maximum reusability.

Mini-Challenge: Half-Star Ratings

You’ve built a solid star rating component. Now, let’s push it a bit further!

Challenge: Modify the StarRatingComponent to allow for half-star ratings.

This means:

  1. When a user clicks on the left half of a star, it should set a .5 rating (e.g., 2.5 stars).
  2. When a user clicks on the right half of a star, it should set a whole number rating (e.g., 3.0 stars).
  3. The visual representation should also reflect half stars (e.g., a half-filled star).

Hint:

  • You’ll need to modify the rate(star: number) method to also consider the event object (MouseEvent) to determine where the click occurred relative to the star’s width.
  • You might need to adjust isStarActive and possibly the stars array or how you render the stars (e.g., using a different character or background-image for half stars, or two <span>s per star).
  • Consider how to represent half stars in your rating property (e.g., 2.5).

What to observe/learn: This challenge will deepen your understanding of how writeValue and onChange interact with a component’s internal state, and how to handle more granular user input within a custom form control. It also pushes you to think about visual representation for complex data.

Take your time, experiment, and don’t be afraid to search for ideas on how others have implemented half-star ratings visually!

Common Pitfalls & Troubleshooting

Even with clear steps, working with ControlValueAccessor can have its quirks. Here are a few common issues and how to resolve them:

  1. Forgetting NG_VALUE_ACCESSOR Provider:

    • Symptom: Your custom component renders, but formControlName or [formControl] doesn’t seem to bind to it. The FormControl’s value never changes when you interact with your component, and setValue() calls from the parent don’t update your component.
    • Reason: Angular doesn’t know your component is designed to be a form control.
    • Solution: Ensure you have the providers array correctly set up in your @Component decorator, including NG_VALUE_ACCESSOR, useExisting, forwardRef, and multi: true.
  2. Not Calling onChange or onTouched:

    • Symptom: Your component’s UI updates, but the FormControl’s value in the parent form (feedbackForm.value) doesn’t change. Validation messages might not appear correctly (ng-dirty or ng-touched classes are missing).
    • Reason: You’ve updated your component’s internal state, but haven’t told Angular’s forms system about it.
    • Solution: Make sure you call this.onChange(newValue) whenever your component’s value changes due to user interaction, and this.onTouched() when the user has interacted with the component (e.g., on click or blur events).
  3. Circular Dependency with forwardRef:

    • Symptom: You might get an error like “Cannot access ‘StarRatingComponent’ before initialization” or a similar circular dependency error during compilation.
    • Reason: When providers are defined, Angular tries to resolve StarRatingComponent before StarRatingComponent itself is fully defined, leading to a loop.
    • Solution: Always wrap the reference to your component in forwardRef(() => YourComponentClass) within the useExisting property of the NG_VALUE_ACCESSOR provider. We already did this, but it’s a common mistake to forget.
  4. Improper Handling of setDisabledState:

    • Symptom: Calling formControl.disable() or formControl.enable() works on other inputs, but your custom component doesn’t visually change or prevent user interaction.
    • Reason: You’ve implemented setDisabledState but haven’t hooked it up to your component’s internal logic or template.
    • Solution:
      • Ensure your setDisabledState method updates an internal isDisabled property.
      • Use this isDisabled property in your template (e.g., [class.disabled]="isDisabled") to apply visual styles.
      • Add conditional logic in your event handlers (like rate() or hover()) to prevent actions when this.isDisabled is true.

By carefully checking these points, you can debug most ControlValueAccessor related issues. The console is your best friend here!

Summary

You’ve just mastered a powerful technique in Angular forms! Let’s recap what we’ve covered:

  • ControlValueAccessor as a Bridge: It allows your custom components to seamlessly integrate with Angular’s Reactive (and Template-Driven) Forms API, behaving just like native HTML inputs.
  • Key Methods: You now understand the roles of writeValue, registerOnChange, registerOnTouched, and the optional setDisabledState. These methods are the contract between your component and the forms system.
  • NG_VALUE_ACCESSOR Token: This special injection token, along with useExisting, forwardRef, and multi: true, is how you tell Angular that your component is a ControlValueAccessor.
  • Practical Application: You successfully built a reusable StarRatingComponent that can be bound to a FormControl, accepts values programmatically, reports changes, handles validation, and responds to disabled states.

Building custom form controls with ControlValueAccessor is a fundamental skill for creating robust, maintainable, and highly reusable Angular applications. It empowers you to encapsulate complex UI logic while keeping your forms clean and declarative.

What’s Next?

In the next chapters, we’ll continue our deep dive into advanced Angular forms scenarios, including:

  • Dynamic Forms: Building forms where fields can be added, removed, or changed based on user input or external data.
  • Custom Async Validators: Creating validators that perform asynchronous operations (like checking if a username is available on a server).
  • Form Arrays: Managing lists of form controls, perfect for scenarios like adding multiple items to an order.

Keep coding, keep exploring, and keep building amazing things with Angular!