Skip to content

A Guide to (Typed) Reactive Forms in Angular - Part I (The Basics)

When building a simple form with Angular, such as a login form, you might choose a template-driven approach, which is defined through directives in the template and requires minimal boilerplate. A barebone login form using a template-driven approach could look like the following:

<!-- login.component.html -->
<form name="form" (ngSubmit)="myAuthenticationService.login(credentials)">
    <label for="email">E-mail</label>
    <input type="email" id="email" [(ngModel)]="credentials.email" required email />
    <label for="password">Password</label>
        <input type="password" id="password" [(ngModel)]="credentials.password" required />
    <button type="submit">Login!</button>
</form>
// login.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html'
})

export class LoginComponent implements OnInit {
  public credentials = {
    email: '',
    password: ''
  };

  constructor(public myAuthenticationService: MyAuthenticationService) { }
}

However, when working on a user input-heavy application requiring complex validation, dynamic fields, or a variety of different forms, the template-driven approach may prove insufficient. This is where reactive forms come into play.

Reactive forms employ a reactive approach, in which the form is defined using a set of form controls and form groups. Form data and validation logic are managed in the component class, which updates the view as the user interacts with the form fields. This approach requires more boilerplate but offers greater explicitness and flexibility.

In this three-part blog series, we will dive into the reactive forms data structures, learn how to build dynamic super forms, and how to create custom form controls.

In this first post, we will familiarize ourselves with the three data structures from the @angular/forms module:

FormControl

The FormControl class in Angular represents a single form control element, such as an input, select, or textarea. It is used to track the value, validation status, and user interactions of a single form control. To create an instance of a form control, the FormControl class has a constructor that takes an optional initial value, which sets the starting value of the form control. Additionally, the class has various methods and properties that allow you to interact with and control the form control, such as setting its value, disabling it, or subscribing to value changes.

As of Angular version 14, the FormControl class has been updated to include support for typed reactive forms - a feature the Angular community has been wanting for a while. This means that it is now a generic class that allows you to specify the type of value that the form control will work with using the type parameter <TValue>. By default, TValue is set to any, so if you don't specify a type, the form control will function as an untyped control.

If you have ever updated your Angular project with ng cli to version 14 or above, you could have also seen an UntypedFormControl class. The reason for having a UntypedFormControl class is to support incremental migration to typed forms. It also allows you to enable types for your form controls after automatic migration.

Here is an example of how you may initialize a FormControl in your component.

import { FormControl } from '@angular/forms';

const nameControl = new FormControl<string>("John Doe");

Our form control, in this case, will work with string values and have a default value of "John Doe".

If you want to see the full implementation of the FormControl class, you can check out the Angular docs!

FormGroup

A FormGroup is a class used to group several FormControl instances together. It allows you to create complex forms by organizing multiple form controls into a single object. The FormGroup class also provides a way to track the overall validation status of the group of form controls, as well as the value of the group as a whole.

A FormGroup instance can be created by passing in a collection of FormControl instances as the group's controls. The group's controls can be accessed by their names, just like the controls in the group.

As an example, we can rewrite the login form presented earlier to use reactive forms and group our two form controls together in a FormGroup instance:

// login.component.ts
import { FormControl, FormGroup, Validators } from '@angular/forms';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html'
})

export class LoginComponent implements OnInit {
  public form = new FormGroup({
    email: new FormControl<string>('', [Validators.required, Validators.email]),
    password: new FormControl<string>('', [Validators.required]),
  });

  constructor(public myAuthenticationService: MyAuthenticationService) { }

  public login() {

    // if you hover over "email" and "password" in your IDE, you should see their type is inferred
    console.log({
      email: this.form.value.email,
      password: this.form.value.password
    });

    this.myAuthenticationService.login(this.form.value);
  }

}
<!-- login.component.html -->
<form name="form" (ngSubmit)="login()" [formGroup]="form">
    <label for="email">E-mail</label>
    <input type="email" id="email" formControlName="email" />
    <label for="password">Password</label>
        <input type="password" id="password" formControlName="password" />
    <button type="submit">Login!</button>
</form>

Notice we have to specify a formGroup and a formControlName to map the markup to our reactive form. You could also use a formControl directive instead of formControlName, and provide the FormControl instance directly.

FormArray

As the name suggests, similar to FormGroup, a FormArray is a class used to group form controls, but is used to group them in a collection rather than a group.

In most cases, you will default to using a FormGroup but a FormArray may come in handy when you find yourself in a highly dynamic situation where you don't know the number of form controls and their names up front.

One use case where it makes sense to resort to using FormArray is when you allow users to add to a list and define some values inside of that list. Let's take a TODO app as an example:

import { Component } from '@angular/core';
import { FormArray, FormControl, FormGroup } from '@angular/forms';

@Component({
  selector: 'app-todo-list',
  template: `
    <form [formGroup]="todoForm">
      <div formArrayName="todos">
        <div *ngFor="let todo of todos.controls; let i = index">
          <input [formControlName]="i" placeholder="Enter TODO name" />
        </div>
      </div>
      <button (click)="addTodo()">Add TODO</button>
    </form>
  `,
})
export class TodoListComponent {
  public todos = new FormArray<FormControl<string | null>>([]);

  public todoForm = new FormGroup({
    todos: this.todos,
  });

  addTodo() {
    this.todoForm.controls['todos'].push(new FormControl<string>(''));
  }
}

In both of the examples provided, we instantiate FormGroup directly. However, some developers prefer to pre-declare the form group and assign it within the ngOnInit method. This is usually done as follows:

// login.component.ts
import { FormControl, FormGroup, Validators } from '@angular/forms';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html'
})

export class LoginComponent implements OnInit {
  // predeclare the form group
  public form: FormGroup;

  constructor(public myAuthenticationService: MyAuthenticationService) { }

  ngOnInit() {
    // assign in ngOnInit
    this.form = new FormGroup({
      email: new FormControl<string>('', [Validators.required, Validators.email]),
      password: new FormControl<string>('', [Validators.required]),
    });
  }

  public login() {
    // no type inference :(
    console.log(this.form.value.email);
  }

}

If you try the above example in your IDE, you'll notice that the type of this.form.value is no longer inferred, and you won't get autocompletion for methods such as patchValue. This is because the FormGroup type defaults to FormGroup<any>. To get the right types, you can either assign the form group directly or explicitly declare the generics like so:

public form: FormGroup<{
  email: FormControl<string>,
  password: FormControl<string>,
}>;

However, explicitly typing all your forms like this can be inconvenient and I would advise you to avoid pre-declaring your FormGroups if you can help it.

In the next blog post, we will learn a way to construct dynamic super forms with minimal boilerplate.

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

Angular 17: Continuing the Renaissance cover image

Angular 17: Continuing the Renaissance

Angular 17: A New Era November 8th marked a significant milestone in the world of Angular with the release of Angular 17. This wasn't just any ordinary update; it was a leap forward, signifying a new chapter for the popular framework. But what made this release truly stand out was the unveiling of Angular's revamped website, complete with a fresh brand identity and a new logo. This significant transformation represents the evolving nature of Angular, aligning with the modern demands of web development. To commemorate this launch, we also hosted a release afterparty, where we went deep into its new features with Minko Gechev from the Angular core team, and Google Developer Experts (GDEs) Brandon Roberts, Deborah Kurata, and Enea Jahollari. But what exactly are these notable new features in the latest version? Let's dive in and explore. The Angular Renaissance Angular has been undergoing a significant revival, often referred to as Angular's renaissance, a term coined by Sarah Drasner, the Director of Engineering at Google, earlier this year. This revival has been particularly evident in its recent versions. The Angular team has worked hard to introduce many new improvements, focusing on signal-based reactivity, hydration, server-side rendering, standalone components, and migrating to esbuild and Vite for a better and faster developer experience. This latest release, in particular, marks many of these features as production-ready. Standalone Components About a year ago, Angular began a journey toward modernity with the introduction of standalone components. This move significantly enhanced the developer experience, making Angular more contemporary and user-friendly. In Angular's context, a standalone component is a self-sufficient, reusable code unit that combines logic, data, and user interface elements. What sets these components apart is their independence from Angular's NgModule system, meaning they do not rely on it for configuration or dependencies. By setting a standalone: true` flag, you no longer need to embed your component in an NgModule and you can bootstrap directly off that component: `typescript // ./app/app.component.ts @Component({ selector: 'app', template: 'hello', standalone: true }) export class AppComponent {} // ./main.ts import { bootstrapApplication } from '@angular/platform-browser'; import { AppComponent } from './app/app.component'; bootstrapApplication(AppComponent).catch(e => console.error(e)); ` Compared to the NgModules way of adding components, as shown below, you can immediately see how standalone components make things much simpler. `ts // ./app/app.component.ts import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], }) export class AppComponent { title = 'CodeSandbox'; } // ./app/app.module.ts import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { AppComponent } from './app.component'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { } // .main.ts import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; platformBrowserDynamic() .bootstrapModule(AppModule) .catch((err) => console.error(err)); ` In this latest release, the Angular CLI now defaults to generating standalone components, directives, and pipes. This default setting underscores the shift towards a standalone-centric development approach in Angular. New Syntax for Enhanced Control Flow Angular 17 introduces a new syntax for control flow, replacing traditional structural directives like ngIf` or `ngFor`, which have been part of Angular since version 2. This new syntax is designed for fine-grained change detection and eventual zone-less operation when Angular completely migrates to signals. It's more streamlined and performance-efficient, making handling conditional or list content in templates easier. The @if` block replaces `*ngIf` for expressing conditional parts of the UI. `ts @if (a > b) { {{a}} is greater than {{b}} } @else if (b > a) { {{a}} is less than {{b}} } @else { {{a}} is equal to {{b}} } ` The @switch` block replaces `ngSwitch`, offering benefits such as not requiring a container element to hold the condition expression or each conditional template. It also supports template type-checking, including type narrowing within each branch. ```ts @switch (condition) { @case (caseA) { Case A. } @case (caseB) { Case B. } @default { Default case. } } ``` The @for` block replaces `*ngFor` for iteration and presents several differences compared to its structural directive predecessor, `ngFor`. For example, the tracking expression (calculating keys corresponding to object identities) is mandatory but offers better ergonomics. Additionally, it supports `@empty` blocks. `ts @for (item of items; track item.id) { {{ item.name }} } ` Defer Block for Lazy Loading Angular 17 introduces the @defer` block, a dramatically improving lazy loading of content within Angular applications. Within the `@defer` block framework, several sub-blocks are designed to elegantly manage different phases of the deferred loading process. The main content within the `@defer` block is the segment designated for lazy loading. Initially, this content is not rendered, becoming visible only when specific triggers are activated or conditions are met, and after the required dependencies have been loaded. By default, the trigger for a `@defer` block is the browser reaching an idle state. For instance, take the following block: it delays the loading of the calendar-imp` component until it comes into the viewport. Until that happens, a placeholder is shown. This placeholder displays a loading message when the `calendar-imp` component begins to load, and an error message if, for some reason, the component fails to load. `ts @defer (on viewport) { } @placeholder { Calendar placeholder } @loading { Loading calendar } @error { Error loading calendar } ` The on` keyword supports a wide a variety of other conditions, such as: - idle` (when the browser has reached an idle state) - interaction` (when the user interacts with a specified element) - hover` (when the mouse has hovered over a trigger area) - timer(x)` (triggers after a specified duration) - immediate` (triggers the deferred load immediately) The second option of configuring when deferring happens is by using the when` keyword. For example: `ts @defer (when isVisible) { } ` Server-Side Rendering (SSR) Angular 17 has made server-side rendering (SSR) much more straightforward. Now, a --ssr` option is included in the `ng new` command, removing the need for additional setup or configurations. When creating a new project with the `ng new` command, the CLI inquires if SSR should be enabled. As of version 17, the default response is set to 'No'. However, for version 18 and beyond, the plan is to enable SSR by default in newly generated applications. If you prefer to start with SSR right away, you can do so by initializing your project with the `--ssr` flag: `shell ng new --ssr ` For adding SSR to an already existing project, utilize the ng add` command of the Angular CLI: `shell ng add @angular/ssr ` Hydration In Angular 17, the process of hydration, which is essential for reviving a server-side rendered application on the client-side, has reached a stable, production-ready status. Hydration involves reusing the DOM structures rendered on the server, preserving the application's state, and transferring data retrieved from the server, among other crucial tasks. This functionality is automatically activated when server-side rendering (SSR) is used. It offers a more efficient approach than the previous method, where the server-rendered tree was completely replaced, often causing visible UI flickers. Such re-rendering can adversely affect Core Web Vitals, including Largest Contentful Paint (LCP), leading to layout shifts. By enabling hydration, Angular 17 allows for the reuse of the existing DOM, effectively preventing these flickers. Support for View Transitions The new View Transitions API, supported by some browsers, is now integrated into the Angular router. This feature, which must be activated using the withViewTransitions` function, allows for CSS-based animations during route transitions, adding a layer of visual appeal to applications. To use it, first you need to import withViewTransitions`: `ts import { provideRouter, withViewTransitions } from '@angular/router'; ` Then, you need to add it to the provideRouter` configuration: `ts bootstrapApplication(AppComponent, { providers: [ provideRouter(routes, withViewTransitions()) ] }) ` Other Notable Changes - Angular 17 has stabilized signals, initially introduced in Angular 16, providing a new method for state management in Angular apps. - Angular 17 no longer supports Node 16. The minimal Node version required is now 18.13. - TypeScript version 5.2 is the least supported version starting from this release of Angular. - The @Component` decorator now supports a `styleUrl` attribute. This allows for specifying a single stylesheet path as a string, simplifying the process of linking a component to a specific style sheet. Previously, even for a single stylesheet, an array was required under `styleUrls`. Conclusion With the launch of Angular 17, the Angular Renaissance is now in full swing. This release has garnered such positive feedback that developers are showing renewed interest in the framework and are looking forward to leveraging it in upcoming projects. However, it's important to note that it might take some time for IDEs to adapt to the new templating syntax fully. While this transition is underway, rest assured that you can still write perfectly valid code using the old templating syntax, as all the changes in Angular 17 are backward compatible. Looking ahead, the future of Angular appears brighter than ever, and we can't wait to see what the next release has in store!...

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....

Being a CTO at Any Level: A Discussion with Kathy Keating, Co-Founder of CTO Levels cover image

Being a CTO at Any Level: A Discussion with Kathy Keating, Co-Founder of CTO Levels

In this episode of the engineering leadership series, Kathy Keating, co-founder of CTO Levels and CTO Advisor, shares her insights on the role of a CTO and the challenges they face. She begins by discussing her own journey as a technologist and her experience in technology leadership roles, including founding companies and having a recent exit. According to Kathy, the primary responsibility of a CTO is to deliver the technology that aligns with the company's business needs. However, she highlights a concerning statistic that 50% of CTOs have a tenure of less than two years, often due to a lack of understanding and mismatched expectations. She emphasizes the importance of building trust quickly in order to succeed in this role. One of the main challenges CTOs face is transitioning from being a technologist to a leader. Kathy stresses the significance of developing effective communication habits to bridge this gap. She suggests that CTOs create a playbook of best practices to enhance their communication skills and join communities of other CTOs to learn from their experiences. Matching the right CTO to the stage of a company is another crucial aspect discussed in the episode. Kathy explains that different stages of a company require different types of CTOs, and it is essential to find the right fit. To navigate these challenges, Kathy advises CTOs to build a support system of advisors and coaches who can provide guidance and help them overcome obstacles. Additionally, she encourages CTOs to be aware of their own preferences and strengths, as self-awareness can greatly contribute to their success. In conclusion, this podcast episode sheds light on the technical aspects of being a CTO and the challenges they face. Kathy Keating's insights provide valuable guidance for CTOs to build trust, develop effective communication habits, match their skills to the company's stage, and create a support system for their professional growth. By understanding these key technical aspects, CTOs can enhance their leadership skills and contribute to the success of their organizations....