Skip to content

A Guide to (Typed) Reactive Forms in Angular - Part III (Creating Custom Form Controls)

So far in the series, we have learned the basics of Angular Reactive forms and created some neat logic to construct and display dynamic forms. But our work is still not done yet. Whether we just want to make our controls look good and enhance them with some markup, or whether we need a more complex control than a simple textarea, input or checkbox, we'll either need to use a component library such as Angular Material Components or get familiar with the ControlValueAccessor interface.

Angular Material, by the way, uses ControlValueAccessor in its components and I recommend looking into the source code if you want to learn some advanced use cases (I have borrowed a lot of their ideas in the past). In this post, however, we will build a basic custom control from scratch.

A common requirement for a component that cannot be satisfied by using standard HTML markup I came across in many projects is having a searchable combobox. So let's build one. We will start by creating a new Angular component and we can do that with a handy ng cli command:

  ng generate component form-fields/combobox

Then we'll implement displaying data passed in the form of our FormField class we have defined earlier in a list and allowing for filtering and selecting the options:

// combobox.component.ts
import { Component, ElementRef, Input, ViewChild } from '@angular/core';
import { FormField } from '../../forms.model';

@Component({
  selector: 'app-combobox',
  templateUrl: './combobox.component.html',
  styleUrls: ['./combobox.component.scss'],
})
export class ComboboxComponent {
  private filteredOptions?: (string | number)[];

  // a simple way to generate a "unique" id for each component
  // in production, you should rather use a library like uuid
  public id = String(Date.now() + Math.random());

  @ViewChild('input')
  public input?: ElementRef<HTMLInputElement>;

  public selectedOption = '';

  public listboxOpen = false;

  @Input()
  public formFieldConfig!: FormField<string | number>;

  public get options(): (string | number)[] {
    return this.filteredOptions || this.formFieldConfig.options || [];
  }

  public get label(): string {
    return this.formFieldConfig.label;
  }

  public toggleListbox(): void {
    this.listboxOpen = !this.listboxOpen;
    if (this.listboxOpen) {
      this.input?.nativeElement.focus();
    }
  }

  public closeListbox(event: FocusEvent): void {
    // timeout is needed to prevent the list box from closing when clicking on an option
    setTimeout(() => {
      this.listboxOpen = false;
    }, 150);
  }

  public filterOptions(filter: string): void {
    this.filteredOptions = this.formFieldConfig.options?.filter((option) => {
      return option.toString().toLowerCase().includes(filter.toLowerCase());
    });
  }

  public selectOption(option: string | number): void {
    this.selectedOption = option.toString();
    this.listboxOpen = false;
  }
}
<!-- combobox.component.html -->
<label for="combobox-input-{{ id }}">{{ label }}</label>
<div class="combobox">
  <div class="group">
    <input
      #input
      type="text"
      role="combobox"
      (focus)="listboxOpen = true"
      (blur)="closeListbox($event)"
      (input)="filterOptions(input.value)"
      [value]="selectedOption"
      id="combobox-input-{{ id }}"
    />
    <button type="button" tabindex="-1" (click)="toggleListbox()">
      &#9660;
    </button>
  </div>
  <ul *ngIf="listboxOpen" role="listbox">
    <li
      *ngFor="let option of options"
      role="option"
      class="option-for-{{ id }}"
      (click)="selectOption(option)"
    >
      {{ option }}
    </li>
  </ul>
</div>

Note: For the sake of brevity, we will not be implementing keyboard navigation and aria labels. I strongly suggest referring to W3C WAI patterns to get guidelines on the markup and behavior of an accessible combo box.

While our component now looks and behaves like a combo box, it's not a form control yet and is not connected with the Angular forms API. That's where the aforementioned ControlValueAccessor comes into play along with the NG_VALUE_ACCESSOR provider. Let's import them first, update the @Component decorator to provide the value accessor, and declare that our component is going to implement the interface:

import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

@Component({
  selector: 'app-combobox',
  templateUrl: './combobox.component.html',
  styleUrls: ['./combobox.component.scss'],
  providers: [
    {
      // provide the value accessor
      provide: NG_VALUE_ACCESSOR,
      // for our combobox component
      useExisting: ComboboxComponent,
      // and we don't want to override previously provided value accessors
      // we want to provide an additional one under the same "NG_VALUE_ACCESSOR" token instead
      multi: true,
    },
  ],
})
export class ComboboxComponent implements ControlValueAccessor {

Now, the component should complain about a few missing methods that we need to satisfy the ControlValueAccessor interface:

  • A writeValue method that is called whenever the form control value is updated from the forms API (e.g. with patchValue()).
  • A registerOnChange method, which registers a callback function for when the value is changed from the UI.
  • A registerOnTouched method that registers a callback function that marks the control when it's been interacted with by the user (typically called in a blur handler).
  • An optional setDisabledState method that is called when we change the form control disabled state-

Our (pretty standard) implementation will look like the following:

  private onChanged!: Function;
  private onTouched!: Function;

  public disabled = false;

  // This will write the value to the view if the form control is updated from outside.
  public writeValue(value: any) {
    this.value = value;
  }

  // Register a callback function that is called when the control's value changes in the UI.
  public registerOnChange(onChanged: Function) {
    this.onChanged = onChanged;
  }

  // Register a callback function that is called by the forms API on initialization to update the form model on blur.
  public registerOnTouched(onTouched: Function) {
    this.onTouched = onTouched;
  }

  public setDisabledState(isDisabled: boolean): void {
    this.disabled = isDisabled;
  }

  public setDisabledState(isDisabled: boolean): void {
    this.disabled = isDisabled;
  }

We don't have to update the template a lot, but we can add [disabled]="disabled" attribute on our button and input to disable the interactive UI elements if the provided form control was disabled. The rest of the work can be done in the component's TypeScript code. We'll call this.onTouched() in our closeListbox method, and create a value setter that updates our internal value and also notifies the model about the value change:

  public set value(val: string | number) {
    this.selectedOption = val.toString();
    this.onChanged && this.onChanged(this.selectedOption);
    this.onTouched && this.onTouched();
  }

You can check out the full implementation on StackBlitz.

Conclusion

In this series, we've explored the powerful features of Angular reactive forms, including creating and managing dynamic typed forms. We also demonstrated how to use the ControlValueAccessor interface to create custom form controls, such as a searchable combo box. This knowledge will enable you to design complex and dynamic forms in your Angular applications.

While the examples provided here are basic, they serve as a solid foundation for building more advanced form controls and implementing validation, accessibility, and other features that are essential for a seamless user experience. By mastering Angular reactive forms and custom form controls, you'll be able to create versatile and maintainable forms in your web applications. If you want to further explore the topic and prefer a form of a video, you can check out an episode of JavaScript Marathon by my amazing colleague Chris.

Happy coding!

This Dot Labs is a development consultancy that is trusted by top industry companies, including Stripe, Xero, Wikimedia, Docusign, and Twilio. This Dot takes a hands-on approach by providing tailored development strategies to help you approach your most pressing challenges with clarity and confidence. Whether it's bridging the gap between business and technology or modernizing legacy systems, you’ll find a breadth of experience and knowledge you need. Check out how This Dot Labs can empower your tech journey.

You might also like

A Guide to (Typed) Reactive Forms in Angular - Part II (Building Dynamic Superforms) cover image

A Guide to (Typed) Reactive Forms in Angular - Part II (Building Dynamic Superforms)

In the first blog post of the series, we learned about Angular reactive forms and the data structures behind them. When developing real-world applications, however, you often need to leverage dynamic forms, as writing boilerplate for every form and its specific cases can be tedious and time-consuming. In certain situations, it may even be necessary to retrieve information from an API to construct the forms. In this post, we will go over a convenient abstraction we can create to build dynamic and adaptable forms without repeating boilerplate. The trick is to create a "view model" for our data and use a service to transform that data into a reactive form. I was first introduced to this approach by my friend and former teammate Thomas Duft, and I've been using it ever since. The approach outlined in the linked article worked great with untyped forms, but since now we can get our forms strictly typed, we'll want to upgrade it. And here is where it gets a bit tricky. Remember how I mentioned you shouldn't predeclare your form groups earlier? If you want to recursively create a form from a config, you just have to. And it's a dynamic form, so you cannot easily type it. To solve the issue, I devised a trick inspired by a "Super Form" suggested by Bobby Galli. Assuming we will have interfaces defined for our data, using this approach, we can create dynamic type-safe forms. First, we'll create types for our form config: `TypeScript // this will be our ViewModel for configuring a FormGroup export class FormSection | FormField | (FormSection | FormField)[]; } = any > { public title?: string; public fields: T; constructor(section: { title?: string; fields: T; }) { this.title = section.title; this.fields = section.fields; } } // Let's define some editor types we'll be using in the templates later export type FormEditor = | 'textInput' | 'passwordInput' | 'textarea' | 'checkbox' | 'select'; // And this will be a ViewModel for our FormControls export class FormField { public value: T; public editor: FormEditor; public validators: Validators; public label: string; public required: boolean; public options?: T[]; constructor(field: { value: T; editor: FormEditor; validators: Validators; label: string; required: boolean; options?: T[]; }) { this.value = field.value; this.editor = field.editor; this.validators = field.validators; this.label = field.label; this.required = field.required; this.options = field.options; } } ` And then we'll create some type mappings: `TypeScript // We will use this type mapping to properly declare our form group export type ControlsOf> = { [K in keyof T]: T[K] extends Array ? FormArray> : T[K] extends Record ? FormGroup> : FormControl; }; // We will use this type mapping to type our form config export type ConfigOf = { [K in keyof T]: T[K] extends (infer U)[] ? U extends Record ? FormSection>[] : FormField[] : T[K] extends Record ? FormSection> : FormField; }; ` And now we can use our types in a service that will take care of creating nested dynamic forms: `TypeScript import { Injectable } from '@angular/core'; import { AbstractControl, FormArray, FormControl, FormGroup, } from '@angular/forms'; import { ConfigOf, ControlsOf, FormField, FormSection } from './forms.model'; @Injectable({ providedIn: 'root', }) export class FormsService { public createFormGroup>( section: FormSection> ): FormGroup> { // we need to create an empty FormGroup first, so we can add FormControls recursively const group = new FormGroup({}); Object.keys(section.fields).forEach((key: any) => { const field = section.fields[key]; if (Array.isArray(field)) { group.addControl(key, this.createFormArray(field)); } else { if (field instanceof FormSection) { group.addControl(key, this.createFormGroup(field)); } else { group.addControl(key, new FormControl(field.value, field.validators)); } } }); // and we need to cast the group to the correct type before returning return group as unknown as FormGroup>; } public createFormArray>( fields: unknown[] ): FormArray> { const array: FormArray> = new FormArray( [] ) as unknown as FormArray>; fields.forEach((field) => { if (field instanceof FormSection) { array.push(this.createFormGroup(field)); } else { const { value, validators } = field as FormField; array.push(new FormControl(value, validators)); } }); return array as unknown as FormArray>; } } ` And that's it. Now we can use our FormService` to create forms. Let's say we have the following User model: `TypeScript export type User = { email: string; name: string; } ` We can create a form for this user from config in the following way: `TypeScript const userFormConfig = new FormSection>({ title: 'User Form', fields: { email: new FormField({ value: '', validators: [Validators.required, Validators.email], label: 'Email', editor: 'textInput', required: true, }), name: new FormField({ value: '', validators: [Validators.required], label: 'Name', editor: 'textInput', required: true, }) } }); const userForm = this.formsService.createFormGroup(userFormConfig); ` If we would check the type of userForm.value` now, we would see that it's correctly inferred as: `TypeScript Partial ` Outputting the Dynamic Forms To display the dynamic forms, we can write a simple component that takes the FormSection` or `FormField` as an `Input()` along with our `FormGroup` and displays the form recursively. We can use a setter to assign either field` or `section` property when the view model is passed into the component, so we can conveniently use them in our template. Our form component's TypeScript code will look something like this: `TypeScript import { Component, Input } from '@angular/core'; import { FormField, FormSection } from '../forms.model'; import { FormArray, FormGroup } from '@angular/forms'; @Component({ selector: 'app-form', templateUrl: './form.component.html', styleUrls: ['./form.component.scss'], }) export class FormComponent { private fieldConfig?: FormField; private sectionConfig?: FormSection; private arrayConfig?: (FormSection | FormField)[]; private sectionFieldsArray?: [string, FormField][]; @Input() public set config( config: | FormField | FormSection | (FormSection | FormField)[] ) { this.fieldConfig = config instanceof FormField ? config : undefined; this.arrayConfig = Array.isArray(config) ? config : undefined; this.sectionConfig = config instanceof FormSection ? config : undefined; this.sectionFieldsArray = Object.entries(this.sectionConfig?.fields || {}); } public get sectionFields(): [string, FormField][] { return this.sectionFieldsArray || []; } public get field(): FormField | undefined { return this.fieldConfig; } public get section(): FormSection | undefined { return this.sectionConfig; } public get array(): (FormSection | FormField)[] | undefined { return this.arrayConfig; } ngAfterViewInit() { console.log(this.arrayConfig); } @Input() public key!: string; @Input() public group!: FormGroup; public get sectionGroup(): FormGroup { return this.group.get(this.key) as FormGroup; } public get formArray(): FormArray { return this.group.get(this.key) as FormArray; } } ` And our template will reference a new form component for each section field in case we have passed in a FormSection` and it will have a switch case to display the correct control in case a `FormField` has been passed in: `HTML {{ field.label }} {{ section.title }} ` That way, we can display the whole form just by referencing one component, such as `HTML ` Check out an example on StackBlitz. In the next (and last) post of the series, we will learn about building custom Form Controls....

How to Create Standalone Components in Angular cover image

How to Create Standalone Components in Angular

Angular has become one of the most popular frameworks to build web applications today. One of the key features of the framework is its component-based architecture, which allows best practices like modularity and reusability. Each Angular component consists of a template, a TypeScript class and metadata. In this blog post, we will dive deeper into standalone components, and we will explore the anatomy of an application based on them. For the demo application, we will create a card-like component which can be used to render blog posts in a web application. Prerequisites You'll need to have installed the following tools in your local environment: The latest LTS version of Node.js is recommended. Either NPM or Yarn as a package manager. The Angular CLI tool(Command-line interface for Angular). Initialize the Project Let's create a project from scratch using the Angular CLI tool: `bash ng new demo-angular-standalone-components --routing --prefix corp --style css --skip-tests ` This command will initialize a base project using some configuration options: `--routing`. It will create a routing module. `--prefix corp`. It defines a prefix to be applied to the selectors for created components(`corp` in this case). The default value is `app`. `--style css`. The file extension for the styling files. `--skip-tests`. Disable the generation of testing files for the new project. If you pay attention to the generated files and directories, you'll see an initial project structure including the main application module and component: `txt |- src/ |- app/ |- app.module.ts |- app-routing.module.ts |- app.component.ts ` Creating Standalone Components First, let's create the custom button to be used as part of the Card component later. Run the following command on your terminal: `bash ng generate component button --inline-template --standalone ` It will create the files for the component. The --standalone` option will generate the component as _standalone_. Let's update the button.component.ts` file using the content below. `ts import { Component } from '@angular/core'; import { CommonModule } from '@angular/common'; @Component({ selector: 'corp-button', standalone: true, imports: [CommonModule], template: , styleUrls: ['./button.component.css'] }) export class ButtonComponent { } ` Pay attention to this component since it's marked as standalone: true`. Starting with Angular v15: components, directives, and pipes can be marked as standalone by using the flag standalone`. When a class is marked as standalone_, it does not need to be declared as part of an `NgModule`. Otherwise, the Angular compiler will report an error. Also, imports` can be used to reference the dependencies. > The imports property specifies the standalone component's template dependencies — those directives, components, and pipes that can be used within its template. Standalone components can import other standalone components, directives, and pipes as well as existing NgModules. Next, let's create the following components: card-title`, `card-content`, `card-actions` and `card`. This can be done at once using the next commands. `bash ng generate component card-title --inline-template --standalone ng generate component card-content --inline-template --standalone ng generate component card-actions --inline-template --standalone ng generate component card --inline-template --standalone ` On card-title.component.ts` file, update the content as follows: `ts //card-title.component.ts import { Component } from '@angular/core'; import { CommonModule } from '@angular/common'; @Component({ selector: 'corp-card-title', standalone: true, imports: [CommonModule], template: , styleUrls: ['./card-title.component.css'] }) export class CardTitleComponent { } ` Next, update the card-content.component.ts` file: `ts //card-content.component.ts import { Component } from '@angular/core'; import { CommonModule } from '@angular/common'; @Component({ selector: 'corp-card-content', standalone: true, imports: [CommonModule], template: , styleUrls: ['./card-content.component.css'] }) export class CardContentComponent { } ` The card-actions.component.ts` file should have the content below: `ts // card-actions.component.ts import { Component } from '@angular/core'; import { CommonModule } from '@angular/common'; @Component({ selector: 'corp-card-actions', standalone: true, imports: [CommonModule], template: , styleUrls: ['./card-actions.component.css'] }) export class CardActionsComponent { } ` Finally, the card.component.ts` file should be defined as follows: `ts //card.component.ts import { Component } from '@angular/core'; import { CommonModule } from '@angular/common'; @Component({ selector: 'corp-card', standalone: true, imports: [CommonModule], template: , styleUrls: ['./card.component.css'] }) export class CardComponent { } ` Using Standalone Components Once all Standalone components are created, we can use them without the need to define an NgModule`. Let's update the app.component.ts` file as follows: `ts // app.component.ts import { Component } from '@angular/core'; import { ButtonComponent } from './button/button.component'; import { CardComponent } from './card/card.component'; import { CardTitleComponent } from './card-title/card-title.component'; import { CardContentComponent } from './card-content/card-content.component'; import { CardActionsComponent } from './card-actions/card-actions.component'; @Component({ selector: 'corp-root', standalone: true, imports: [ ButtonComponent, CardComponent, CardTitleComponent, CardContentComponent, CardActionsComponent ], templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { title = 'demo-angular-standalone-components'; } ` Again, this Angular component is set as standalone: true` and the `imports` section specifies the other components created as dependencies. Then, we can update the `app.component.html` file and use all the standalone components. `html Latest Posts Introduction to Angular Angular is a component-based framework for building scalable web applications. View Introduction to TypeScript TypeScript is a strongly typed programming language that builds on JavaScript, providing better tooling at any scale. View ` This may not work yet, since we need to configure the Angular application and get rid of the autogenerated module defined under the app.module.ts` file. Bootstrapping the Application Angular provides a new API to use a standalone component as the application's root component. Let's update the main.ts` file with the following content: `ts // main.ts import { bootstrapApplication } from '@angular/platform-browser'; import { AppComponent } from './app/app.component'; bootstrapApplication(AppComponent); ` As you can see, we have removed the previous bootstrapModule` call and the `bootstrapApplication` function will render the standalone component as the application's root component. You can find more information about it here. Live Demo and Source Code Want to play around with this code? Just open the Stackblitz editor or the preview mode in fullscreen. Conclusion We’re now ready to use and configure the Angular components as standalone and provide a simplified way to build Angular applications from scratch. Do not forget that you can mark directives and pipes as standalone: true`. If you love Angular as much as we do, you can start building today using one of our starter.dev kits or by looking at their related showcases for more examples....

Content Projection in Front-end JavaScript Frameworks cover image

Content Projection in Front-end JavaScript Frameworks

Content Projection in Front-end Frameworks Initially, I wanted to write a nice comprehensive guide on ng-content` and content projection in Angular. But then I found out that my colleague Linda already wrote a wonderful article that covers the topic very well, so I thought about it a little and realized that pretty much every front-end framework has some implementation of the concept. So I decided to write a short article about content projection in general and show how it is implemented in some of the most popular front-end frameworks. What is Content Projection? Some of you may also remember it by the term "transclusion", a bit of a tongue-twister from the Angular.js days. Content projection is a way to take markup (HTML, components...) and slot it right into another component. It's like the Swiss Army knife in our coding toolbox, helping us create reusable components that can fit anywhere. Let's imagine a card component - something you see on pretty much every website these days. It can have a header, a body, maybe some image, and a footer. Now, what you put inside that card can change based on what you need. Maybe today it's a user profile, tomorrow it's a product description, and the day after, it's a blog post. The layout stays the same, but the content? That's entirely up to you, and that's the magic of content projection. Using this approach can help us reduce redundancy in our code and keep it DRY. It also ensures a more consistent user experience. It is, however, important to remember that content projection is not a silver bullet. It can be overused, and it can make your code harder to read and maintain. So, as with everything, use it wisely. How Does it Work Content projection is essentially about two main players: the receiving component (where the content is projected into) and the projecting component (which provides the content to be projected). The receiving component has specific placeholders, often denoted as slots or named slots in frameworks like Vue.js or Web Components. These slots serve as 'parking spaces' for the content from the projecting component. In general, we distinguish two types of content projection: single-slot and multi-slot. Single-slot content projection is when you have only one placeholder for the content. Multi-slot content projection is when you have multiple placeholders for the content. The placeholders can be named or unnamed. Named placeholders are used when you want to project different content into different placeholders. When you're defining the receiving component, you usually specify these slots without defining what will go into them. It's akin to laying out an empty stage for a play, with specific spots designated for props, but not specifying what those props will be. This gives you the flexibility to later decide and alter what content will 'perform' in those spaces. Now, when it comes to the projecting component, that's where the magic happens. Here, you'll take advantage of the slots that the receiving component provides, filling them with the specific content you want to project. For example, you might have a card component with a 'header' slot. When you use this card component elsewhere in your application, you can decide what gets projected into that 'header' slot. It could be a title for one instance of the card and an image for another. Now, let's see the concepts different frameworks use to allow content projection, starting with the Web Components way. The Web Components Way (Slots) As I mentioned before, a very popular way to implement content projection is with slots`. The "slot" approach is not only used in Web Components, but also in Vue.js, Svelte, or Qwik. In Web Components, slots are defined using the ` tag. Let's illustrate how slots` are used in Web Components with an example of a card component with three slots: header, body, and footer. The content that will be projected into these slots will be defined when the component is used. The component will look like this: `html :host { display: block; border: 1px solid #ddd; border-radius: 4px; padding: 20px; margin-bottom: 20px; } ::slotted([slot="header"]) { font-weight: bold; margin-bottom: 10px; } ::slotted([slot="body"]) { margin-bottom: 10px; } ::slotted([slot="footer"]) { text-align: right; color: gray; } class CardComponent extends HTMLElement { constructor() { super(); const template = document.getElementById("card-template"); const templateContent = template.content; this.attachShadow({ mode: "open" }).appendChild( templateContent.cloneNode(true) ); } } customElements.define("card-component", CardComponent); ` This receiving component could be used in a projecting component like this: `html Card Title This is the main content of the card. Click me ` You can check out this example on StackBlitz. Vue.js As mentioned above, Vue.js and other frameworks also use the slot` concept. Let's see how it's implemented in Vue.js. First, the receiving component: `html Default body content here export default { name: "CardComponent", }; .card { border: 1px solid #ddd; border-radius: 4px; padding: 20px; margin-bottom: 20px; } .card-header { font-weight: bold; margin-bottom: 10px; } .card-body { margin-bottom: 10px; } .card-footer { text-align: right; color: gray; } ` And then, the projecting component: `html Card Title This is the main content of the card. Click me import CardComponent from "./CardComponent.vue"; export default { components: { CardComponent, }, }; ` Pretty much the only difference for you as a developer is that you use the v-slot` directive instead of the `slot` attribute. Similarly, Qwik for example uses a `q:slot` attribute. You can check out the Vue.js example on StackBlitz too! The Angular Way (ng-content) In Angular, content projection is implemented using the ` tag. It is a tag that is used to mark the place where the content will be projected. It can be used in two ways: - As a tag with a select` attribute, which is used to select the content that will be projected. The value of the `select` attribute is a CSS selector that is used to select the content that will be projected. If the `select` attribute is not present, all content will be projected. - As a tag without a select` attribute, which will project all content. Let's have look at an example of a receiving card component in Angular: `TypeScript import { Component } from '@angular/core'; @Component({ selector: 'app-card', standalone: true, template: , styles: [ .card { border: 1px solid #ddd; border-radius: 4px; padding: 20px; margin-bottom: 20px; }, .card-header { font-weight: bold; margin-bottom: 10px; }, .card-body { margin-bottom: 10px; }, .card-footer { text-align: right; color: gray; }, ], }) export class CardComponent {} ` And then, the projecting component can be used like this: `html Card Title This is the main content of the card. Click me ` You can check out this example on StackBlitz. As you can see, the Angular way is not very different from the Web Components way. It uses the ` tag instead of the `` tag and a `select` directive instead of the `name` (or `q-name`) attribute, but the principles are very much the same. The React Way (children) So far, we covered the "standard" way of content projection. In React, however, it is not so straightforward as it does not have a concept of "slots" or "content projection". We can still achieve something similar using the children` prop. It is a prop that is used to pass children to a component. It can be used in two ways: - As a prop with a function as a value, which is used to select the content that will be projected. The function will be called for each child and it should return a React element. If the function returns null` or `undefined`, the child will not be rendered. This way allows us to have "multiple content projection". - As a prop without a function as a value, which will project all content. Let's see an example of a receiving card component in React: `jsx import as React from "react"; const styles = { card: { border: "1px solid #ddd", borderRadius: "4px", padding: "20px", marginBottom: "20px", }, header: { fontWeight: "bold", marginBottom: "10px", }, body: { marginBottom: "10px", }, footer: { textAlign: "right", color: "gray", }, }; const CardComponent = ({ header, children, footer }) => { return ( {header} {children} {footer} ); }; export default CardComponent; ` And then, we can project content like this: `jsx import React from "react"; import CardComponent from "./CardComponent"; const App = () => { return ( Card Title} footer={Click me} > This is the main content of the card. ); }; export default App; ` In this example, anything you pass as a header prop will be rendered in the header of the card component, and anything you pass as a footer prop will be rendered in the footer. Any children of the CardComponent will be rendered in the body. Here's the StackBlitz to play with! Conclusion As we've seen, content projection is a powerful and handy concept widely adopted by front-end frameworks to enable reusability, component composition, and dynamic content management. The implementation may vary between different libraries, such as Angular's `, Vue's `slot`s, or React's `children` prop. Nevertheless, the underlying principle remains the same: providing a way to inject custom content into predefined placeholders within a component, thereby extending its flexibility and reusability. We've taken a look at how this is achieved in Angular, Vue.js, Web Components, and even in React. But remember, just like with any tool or skill, it's important to think things through before you start implementing this in your app or library. It might be tempting to start throwing this into every component you build, but it's always worth taking a step back and considering whether it's the right tool for the job. Always aim for code that's not just flexible, but also easy to read, maintain, and efficient to run....

Nuxt DevTools v1.0: Redefining the Developer Experience Beyond Conventional Tools cover image

Nuxt DevTools v1.0: Redefining the Developer Experience Beyond Conventional Tools

In the ever-evolving world of web development, Nuxt.js has taken a monumental leap with the launch of Nuxt DevTools v1.0. More than just a set of tools, it's a game-changer—a faithful companion for developers. This groundbreaking release, available for all Nuxt projects and being defaulted from Nuxt v3.8 onwards, marks the beginning of a new era in developer tools. It's designed to simplify our development journey, offering unparalleled transparency, performance, and ease of use. Join me as we explore how Nuxt DevTools v1.0 is set to revolutionize our workflow, making development faster and more efficient than ever. What makes Nuxt DevTools so unique? Alright, let's start delving into the features that make this tool so amazing and unique. There are a lot, so buckle up! In-App DevTools The first thing that caught my attention is that breaking away from traditional browser extensions, Nuxt DevTools v1.0 is seamlessly integrated within your Nuxt app. This ensures universal compatibility across browsers and devices, offering a more stable and consistent development experience. This setup also means the tools are readily available in the app, making your work more efficient. It's a smart move from the usual browser extensions, making it a notable highlight. To use it you just need to press Shift + Option + D` (macOS) or `Shift + Alt + D` (Windows): With simple keystrokes, the Nuxt DevTools v1.0 springs to life directly within your app, ready for action. This integration eliminates the need to toggle between windows or panels, keeping your workflow streamlined and focused. The tools are not only easily accessible but also intelligently designed to enhance your productivity. Pages, Components, and Componsables View The Pages, Components, and Composables View in Nuxt DevTools v1.0 are a clear roadmap for your app. They help you understand how your app is built by simply showing its structure. It's like having a map that makes sense of your app's layout, making the complex parts of your code easier to understand. This is really helpful for new developers learning about the app and experienced developers working on big projects. Pages View lists all your app's pages, making it easier to move around and see how your site is structured. What's impressive is the live update capability. As you explore the DevTools, you can see the changes happening in real-time, giving you instant feedback on your app's behavior. Components View is like a detailed map of all the parts (components) your app uses, showing you how they connect and depend on each other. This helps you keep everything organized, especially in big projects. You can inspect components, change layouts, see their references, and filter them. By showcasing all the auto-imported composables, Nuxt DevTools provides a clear overview of the composables in use, including their source files. This feature brings much-needed clarity to managing composables within large projects. You can also see short descriptions and documentation links in some of them. Together, these features give you a clear picture of your app's layout and workings, simplifying navigation and management. Modules and Static Assets Management This aspect of the DevTools revolutionizes module management. It displays all registered modules, documentation, and repository links, making it easy to discover and install new modules from the community! This makes managing and expanding your app's capabilities more straightforward than ever. On the other hand, handling static assets like images and videos becomes a breeze. The tool allows you to preview and integrate these assets effortlessly within the DevTools environment. These features significantly enhance the ease and efficiency of managing your app's dynamic and static elements. The Runtime Config and Payload Editor The Runtime Config and Payload Editor in Nuxt DevTools make working with your app's settings and data straightforward. The Runtime Config lets you play with different configuration settings in real time, like adjusting settings on the fly and seeing the effects immediately. This is great for fine-tuning your app without guesswork. The Payload Editor is all about managing the data your app handles, especially data passed from server to client. It's like having a direct view and control over the data your app uses and displays. This tool is handy for seeing how changes in data impact your app, making it easier to understand and debug data-related issues. Open Graph Preview The Open Graph Preview in Nuxt DevTools is a feature I find incredibly handy and a real time-saver. It lets you see how your app will appear when shared on social media platforms. This tool is crucial for SEO and social media presence, as it previews the Open Graph tags (like images and descriptions) used when your app is shared. No more deploying first to check if everything looks right – you can now tweak and get instant feedback within the DevTools. This feature not only streamlines the process of optimizing for social media but also ensures your app makes the best possible first impression online. Timeline The Timeline feature in Nuxt DevTools is another standout tool. It lets you track when and how each part of your app (like composables) is called. This is different from typical performance tools because it focuses on the high-level aspects of your app, like navigation events and composable calls, giving you a more practical view of your app's operation. It's particularly useful for understanding the sequence and impact of events and actions in your app, making it easier to spot issues and optimize performance. This timeline view brings a new level of clarity to monitoring your app's behavior in real-time. Production Build Analyzer The Production Build Analyzer feature in Nuxt DevTools v1.0 is like a health check for your app. It looks at your app's final build and shows you how to make it better and faster. Think of it as a doctor for your app, pointing out areas that need improvement and helping you optimize performance. API Playground The API Playground in Nuxt DevTools v1.0 is like a sandbox where you can play and experiment with your app's APIs. It's a space where you can easily test and try out different things without affecting your main app. This makes it a great tool for trying out new ideas or checking how changes might work. Some other cool features Another amazing aspect of Nuxt DevTools is the embedded full-featured VS Code. It's like having your favorite code editor inside the DevTools, with all its powerful features and extensions. It's incredibly convenient for making quick edits or tweaks to your code. Then there's the Component Inspector. Think of it as your code's detective tool. It lets you easily pinpoint and understand which parts of your code are behind specific elements on your page. This makes identifying and editing components a breeze. And remember customization! Nuxt DevTools lets you tweak its UI to suit your style. This means you can set up the tools just how you like them, making your development environment more comfortable and tailored to your preferences. Conclusion In summary, Nuxt DevTools v1.0 marks a revolutionary step in web development, offering a comprehensive suite of features that elevate the entire development process. Features like live updates, easy navigation, and a user-friendly interface enrich the development experience. Each tool within Nuxt DevTools v1.0 is thoughtfully designed to simplify and enhance how developers build and manage their applications. In essence, Nuxt DevTools v1.0 is more than just a toolkit; it's a transformative companion for developers seeking to build high-quality web applications more efficiently and effectively. It represents the future of web development tools, setting new standards in developer experience and productivity....