Skip to content

Form Handling using RxJS and TypeScript

RxJS and TypeScript are becoming more and more popular these days. It's feasible to use both technologies alone or even as part of any JavaScript library or framework.

It's an interesting fact to know that the RxJS project is using TypeScript actively and it helped to find bugs when the library was migrating from JavaScript.

In this article, we'll see a Reactive approach to handle HTML forms using TypeScript and RxJS.

HTML Forms

HTML forms are widely used in Web Applications nowadays. They are essential every time you want to register a user, send a message through a contact form, or collect any data from your users. At the same time, the HTML vocabulary can help you to define a Form with simple syntax:

<form>
   Add HTML Form Controls here: text input, checkboxes, etc
</form>

An HTML Form Control element can be used to receive an event or collect data within the HTML form context.

A simple Login Form

Let's get started with a simple form:

login-form-rxjs-typescript

This may be a simple form. However, there are different ways to handle it from a developer perspective.

Form Definition

Let's consider the following login form implementation:

<div class="container">
  <form class="form">
    <div class="form-group">
      <label for="email">Email address</label>
      <input type="email" class="form-control" id="email" />
    </div>
    <div class="form-group">
      <label for="password">Password</label>
      <input type="password" class="form-control" id="password" />
    </div>
    <button id="submit" type="button" class="btn btn-primary">Submit</button>
  </form>
</div>

The previous code uses Bootstrap, which is a powerful CSS framework that contains several ready-to-use styles.

The TypeScript Model

TypeScript is all about good and safe typing. Let's be sure to define an Interface for our data model:

// user.ts
export interface User {
  email?: string;
  password?: string;
}

Right after that, we can define a new variable to "contain" our User model:

let userModel: User = {};

The DOM Elements

Since we have our data model defined, it is time to define the variables that allow us to access the DOM elements:

// DOM Elements - declaration
let buttonElement: HTMLButtonElement;
let emailInput: HTMLInputElement;
let passwordInput: HTMLInputElement;

These variables are ready to have a reference to the HTML Form controls we defined before in the markup. Pay attention to the types for each variable.

At some point in the code it will be necessary to obtain the elements of the DOM:

// DOM elements - assignation
buttonElement = document.getElementById("submit") as HTMLButtonElement;
emailInput = document.getElementById("email") as HTMLInputElement;
passwordInput = document.getElementById("password") as HTMLInputElement;

In the JavaScript world, we can use the getElementById method to get the Element associated with the given id property. You may use the querySelector() method as an alternative too.

One important note to point here is that document.getElementById will return an HTMLElement object. This is an interface that represents any HTML element.

This means we have a general type and it would be better to apply the type assertion from TypeScript:

// as-syntax
buttonElement = document.getElementById("submit") as HTMLButtonElement;

// angle-bracket syntax
buttonElement = <HTMLButtonElement>document.getElementById("submit");

The DOM Events as Observables

One way to handle DOM events from JavaScript is through an Event Listener. For example, to handle the click event of the button we can do:

buttonElement.addEventListener('click', function() {
  // Handle click event here
});

However, in the Reactive Programming approach, we'll need to consider streams and Observables.

Let's continue with the definition of every event we can handle at this point: "input"(from HTML Input elements) and "click"(from HTML Button element) as follows:

// DOM events as Observables - declaration
let emailChange$: Observable<InputEvent>;
let passwordChange$: Observable<InputEvent>;
let submit$: Observable<MouseEvent>;

Pay attention (again) to the Generic Types used in every variable type. These generic types will tell the TypeScript compiler the specific type of object that will "flow" through those observables. Also, by a code convention, the $ sign is used at the end of the variable name to represent a stream.

We have the variables needed to assign the Observables. It's time to use the fromEvent operator from RxJS, which turns an event into an Observable:

// DOM events as Observables - assignation
emailChange$ = fromEvent<InputEvent>(emailInput, "input");
passwordChange$ = fromEvent<InputEvent>(passwordInput, "input");
submit$ = fromEvent<MouseEvent>(buttonElement, "click");

The fromEvent is a creation operator and it's feasible to use it without the generic type:

submit$ = fromEvent(buttonElement, "click"); // Observable<Event>

The disadvantage is obvious: we will have a general type that might need the type assertion later to handle the object properly.

input-event mouse-event

Subscribe to Event-Observables

Let's start processing the email value as a stream:

emailChange$
  .pipe(map((event: InputEvent) => (event.target as HTMLInputElement).value))
  .subscribe(email => {
    console.log("email Value: ", email);
    userModel.email = email;
  });

This is what is happening:

  • emailChange$ is an Observable. Picture this: you have a data flow ready to be processed. How are you going to process them? By using functions (pipeable operators in terms of RxJS).
  • .pipe() function provides a readable way to use operators together. For example, you could have a kind of "combination" of Linux commands: ls -l | grep 'json | sort where you're using | as a pipe to perform operations or actions one after another.
  • map() operator comes first and allows to "extract" the value property from the HTMLInputElement(the input field).
  • event.target as HTMLInputElement is needed since event.target will return the EventTarget interface by default, and the HTMLInputElement interface provides the methods and properties for <input> elements.
  • subscribe() call is "needed" to call the Observable. It behaves similar to when you call a function.
  • Once the Observable is called, it returns the email value and the User model can be updated.

We can apply the same logic to process the password value changes:

passwordChange$
  .pipe(map((event: InputEvent) => (event.target as HTMLInputElement).value))
  .subscribe(password => {
    console.log("password Value:", password);
    userModel.password = password;
  });

See the final value is assigned to the userModel.password property.

May you guess a potential improvement here? The email and password value handling are the same. We can create a common function to avoid code duplication as follows:

function getValueFromInputEvent(
  event: Observable<InputEvent>
): Observable<string> {
  return event.pipe(
    tap(event => console.log("event.target", event.target)),
    map((event: InputEvent) => (event.target as HTMLInputElement).value)
  );
}

The previous function receives an Observable<InputEvent>, and returns the string value as an Observable too!

Let's apply that function on emailChange$ stream:

emailChange$.pipe(getValueFromInputEvent).subscribe(email => {
  console.log("email Value: ", email);
  userModel.email = email;
});

This is a short version, and easier to read too. You can apply the same function to process passwordChange$ stream.

Finally, we're ready to process the "click" event. Following the same logic, let's subscribe and process the User model at the end.

submit$
  .pipe(tap((event: MouseEvent) => console.log(event)))
  .subscribe(() => console.log("Sending User", { userModel }));

Source Code Project

Find the complete project running in StackBlitz. Don't forget to open the browser's console to see the results.

You can follow me on Twitter and GitHub to see more about my work.

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

How to host a full-stack app with AWS CloudFront and Elastic Beanstalk cover image

How to host a full-stack app with AWS CloudFront and Elastic Beanstalk

How to host a full-stack JavaScript app with AWS CloudFront and Elastic Beanstalk Let's imagine that you have finished building your app. You have a Single Page Application (SPA) with a NestJS back-end. You are ready to launch, but what if your app is a hit, and you need to be prepared to serve thousands of users? You might need to scale your API horizontally, which means that to serve traffic, you need to have more instances running behind a load balancer. Serving your front-end using a CDN will also be helpful. In this article, I am going to give you steps on how to set up a scalable distribution in AWS, using S3, CloudFront and Elastic Beanstalk. The NestJS API and the simple front-end are both inside an NX monorepo The sample application For the sake of this tutorial, we have put together a very simple HTML page that tries to reach an API endpoint and a very basic API written in NestJS. The UI The UI code is very simple. There is a "HELLO" button on the UI which when clicked, tries to reach out to the /api/hello` endpoint. If there is a response with status code 2xx, it puts an `h1` tag with the response contents into the div with the id `result`. If it errors out, it puts an error message into the same div. `html Frontend HELLO const helloButton = document.getElementById('hello'); const resultDiv = document.getElementById('result'); helloButton.addEventListener('click', async () => { const request = await fetch('/api/hello'); if (request.ok) { const response = await request.text(); console.log(json); resultDiv.innerHTML = ${response}`; } else { resultDiv.innerHTML = An error occurred.`; } }); ` The API We bootstrap the NestJS app to have the api` prefix before every endpoint call. `typescript // main.ts import { Logger } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; import { AppModule } from './app/app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); const globalPrefix = 'api'; app.setGlobalPrefix(globalPrefix); const port = process.env.PORT || 3000; await app.listen(port); Logger.log(🚀 Application is running on: http://localhost:${port}/${globalPrefix}`); } bootstrap(); ` We bootstrap it with the AppModule which only has the AppController in it. `typescript // app.module.ts import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; @Module({ imports: [], controllers: [AppController], }) export class AppModule {} ` And the AppController sets up two very basic endpoints. We set up a health check on the /api` route and our hello endpoint on the `/api/hello` route. `typescript import { Controller, Get } from '@nestjs/common'; @Controller() export class AppController { @Get() health() { return 'OK'; } @Get('hello') hello() { return 'Hello'; } } ` Hosting the front-end with S3 and CloudFront To serve the front-end through a CMS, we should first create an S3 bucket. Go to S3 in your AWS account and create a new bucket. Name your new bucket to something meaningful. For example, if this is going to be your production deployment I recommend having -prod` in the name so you will be able to see at a glance, that this bucket contains your production front-end and nothing should get deleted accidentally. We go with the defaults for this bucket setting it to the us-east-1` region. Let's set up the bucket to block all public access, because we are going to allow get requests through CloudFront to these files. We don't need bucket versioning enabled, because these files will be deleted every time a new front-end version will be uploaded to this bucket. If we were to enable bucket versioning, old front-end files would be marked as deleted and kept, increasing the storage costs in the long run. Let's use server-side encryption with Amazon S3-managed keys and create the bucket. When the bucket is created, upload the front-end files to the bucket and let's go to the CloudFront service and create a distribution. As the origin domain, choose your S3 bucket. Feel free to change the name for the origin. For Origin access, choose the Origin access control settings (recommended)`. Create a new Control setting with the defaults. I recommend adding a description to describe this control setting. At the Web Application Firewall (WAF) settings we would recommend enabling security protections, although it has cost implications. For this tutorial, we chose not to enable WAF for this CloudFront distribution. In the Settings section, please choose the Price class that best fits you. If you have a domain and an SSL certificate you can set those up for this distribution, but you can do that later as well. As the Default root object, please provide index.html` and create the distribution. When you have created the distribution, you should see a warning at the top of the page. Copy the policy and go to your S3 bucket's Permissions` tab. Edit the `Bucket policy` and paste the policy you just copied, then save it. If you have set up a domain with your CloudFront distribution, you can open that domain and you should be able to see our front-end deployed. If you didn't set up a domain the Details section of your CloudFront distribution contains your distribution domain name. If you click on the "Hello" button on your deployed front-end, it should not be able to reach the /api/hello` endpoint and should display an error message on the page. Hosting the API in Elastic Beanstalk Elastic beanstalk prerequisites For our NestJS API to run in Elastic Beanstalk, we need some additional setup. Inside the apps/api/src` folder, let's create a `Procfile` with the contents: `web: node main.js`. Then open the `apps/api/project.json` and under the `build` configuration, extend the `production` build setup with the following (I only ) `json { "targets": { "build": { "configurations": { "development": {}, "production": { "generatePackageJson": true, "assets": [ "apps/api/src/assets", "apps/api/src/Procfile" ] } } } } } ` The above settings will make sure that when we build the API with a production configuration, it will generate a package.json` and a `package-lock.json` near the output file `main.js`. To have a production-ready API, we set up a script in the package.json` file of the repository. Running this will create a `dist/apps/api` and a `dist/apps/frontend` folder with the necessary files. `json { "scripts": { "build:prod": "nx run-many --target=build --projects api,frontend --configuration=production" } } ` After running the script, zip the production-ready api folder so we can upload it to Elastic Beanstalk later. `bash zip -r -j dist/apps/api.zip dist/apps/api ` Creating the Elastic Beanstalk Environment Let's open the Elastic Beanstalk service in the AWS console. And create an application. An application is a logical grouping of several environments. We usually put our development, staging and production environments under the same application name. The first time you are going to create an application you will need to create an environment as well. We are creating a Web server environment`. Provide your application's name in the `Application information` section. You could also provide some unique tags for your convenience. In the `Environment information` section please provide information on your environment. Leave the `Domain` field blank for an autogenerated value. When setting up the platform, we are going to use the Managed Node.js platform with version 18 and with the latest platform version. Let's upload our application code, and name the version to indicate that it was built locally. This version label will be displayed on the running environment and when we set up automatic deployments we can validate if the build was successful. As a Preset, let's choose Single instance (free tier eligible)` On the next screen configure your service access. For this tutorial, we only create a new service-role. You must select the aws-elasticbeanstalk-ec2-role` for the EC2 instance profile. If can't select this role, you should create it in AWS IAM with the AWSElasticBeanstalkWebTier`, `AWSElasticBeanstalkMulticontainerDocker` and the `AWSElasticBeanstalkRoleWorkerTier` managed permissions. The next step is to set up the VPC. For this tutorial, I chose the default VPC that is already present with my AWS account, but you can create your own VPC and customise it. In the Instance settings` section, we want our API to have a public IP address, so it can be reached from the internet, and we can route to it from CloudFront. Select all the instance subnets and availability zones you want to have for your APIs. For now, we are not going to set up a database. We can set it up later in AWS RDS but in this tutorial, we would like to focus on setting up the distribution. Let's move forward Let's configure the instance traffic and scaling. This is where we are going to set up the load balancer. In this tutorial, we are keeping to the defaults, therefore, we add the EC2 instances to the default security group. In the Capacity` section we set the `Environment type` to `Load balanced`. This will bring up a load balancer for this environment. Let's set it up so that if the traffic is large, AWS can spin up two other instances for us. Please select your preferred tier in the `instance types` section, We only set this to `t3.micro` For this tutorial, but you might need to use larger tiers. Configure the Scaling triggers` to your needs, we are going to leave them as defaults. Set the load balancer's visibility to the public and use the same subnets that you have used before. At the Load Balancer Type` section, choose `Application load balancer` and select `Dedicated` for exactly this environment. Let's set up the listeners, to support HTTPS. Add a new listener for the 443 port and connect your SSL certificate that you have set up in CloudFront as well. For the SSL policy choose something that is over TLS 1.2 and connect this port to the default` process. Now let's update the default process and set up the health check endpoint. We set up our API to have the health check endpoint at the /api` route. Let's modify the default process accordingly and set its port to 8080. For this tutorial, we decided not to enable log file access, but if you need it, please set it up with a separate S3 bucket. At the last step of configuring your Elastic Beanstalk environment, please set up Monitoring, CloudWatch logs and Managed platform updates to your needs. For the sake of this tutorial, we have turned most of these options off. Set up e-mail notifications to your dedicated alert e-mail and select how you would like to do your application deployments`. At the end, let's configure the Environment properties`. We have set the default process to occupy port 8080, therefore, we need to set up the `PORT` environment variable to `8080`. Review your configuration and then create your environment. It might take a few minutes to set everything up. After the environment's health transitions to OK` you can go to AWS EC2 / Load balancers in your web console. If you select the freshly created load balancer, you can copy the DNS name and test if it works by appending `/api/hello` at the end of it. Connect CloudFront to the API endpoint Let's go back to our CloudFront distribution and select the Origins` tab, then create a new origin. Copy your load balancer's URL into the `Origin domain` field and select `HTTPS only` protocol if you have set up your SSL certificate previously. If you don't have an SSL certificate set up, you might use `HTTP only`, but please know that it is not secure and it is especially not recommended in production. We also renamed this origin to `API`. Leave everything else as default and create a new origin. Under the Behaviors` tab, create a new behavior. Set up the path pattern as `/api/*` and select your newly created `API` origin. At the `Viewer protocol policy` select `Redirect HTTP to HTTPS` and allow all HTTP methods (GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE). For this tutorial, we have left everything else as default, but please select the Cache Policy and Origin request policy that suits you the best. Now if you visit your deployment, when you click on the HELLO` button, it should no longer attach an error message to the DOM. --- Now we have a distribution that serves the front-end static files through CloudFront, leveraging caching and CDN, and we have our API behind a load balancer that can scale. But how do we deploy our front-end and back-end automatically when a release is merged to our main` branch? For that we are going to leverage AWS CodeBuild and CodePipeline, but in the next blog post. Stay tuned....

Understanding Vue's Reactive Data cover image

Understanding Vue's Reactive Data

Introduction Web development has always been about creating dynamic experiences. One of the biggest challenges developers face is managing how data changes over time and reflecting these changes in the UI promptly and accurately. This is where Vue.js, one of the most popular JavaScript frameworks, excels with its powerful reactive data system. In this article, we dig into the heart of Vue's reactivity system. We unravel how it perfectly syncs your application UI with the underlying data state, allowing for a seamless user experience. Whether new to Vue or looking to deepen your understanding, this guide will provide a clear and concise overview of Vue's reactivity, empowering you to build more efficient and responsive Vue 3 applications. So, let’s kick off and embark on this journey to decode Vue's reactive data system. What is Vue's Reactive Data? What does it mean for data to be ”'reactive”? In essence, when data is reactive, it means that every time the data changes, all parts of the UI that rely on this data automatically update to reflect these changes. This ensures that the user is always looking at the most current state of the application. At its core, Vue's Reactive Data is like a superpower for your application data. Think of it like a mirror - whatever changes you make in your data, the user interface (UI) reflects these changes instantly, like a mirror reflecting your image. This automatic update feature is what we refer to as “reactivity”. To visualize this concept, let's use an example of a simple Vue application displaying a message on the screen: `javascript import { createApp, reactive } from 'vue'; const app = createApp({ setup() { const state = reactive({ message: 'Hello Vue!' }); return { state }; } }); app.mount('#app'); ` In this application, 'message' is a piece of data that says 'Hello Vue!'. Let's say you change this message to 'Goodbye Vue!' later in your code, like when a button is clicked. `javascript state.message = 'Goodbye Vue!'; ` With Vue's reactivity, when you change your data, the UI automatically updates to 'Goodbye Vue!' instead of 'Hello Vue!'. You don't have to write extra code to make this update happen - Vue's Reactive Data system takes care of it. How does it work? Let's keep the mirror example going. Vue's Reactive Data is the mirror that reflects your data changes in the UI. But how does this mirror know when and what to reflect? That's where Vue's underlying mechanism comes into play. Vue has a behind-the-scenes mechanism that helps it stay alerted to any changes in your data. When you create a reactive data object, Vue doesn't just leave it as it is. Instead, it sends this data object through a transformation process and wraps it up in a Proxy. Proxy objects are powerful and can detect when a property is changed, updated, or deleted. Let's use our previous example: `javascript import { createApp, reactive } from 'vue'; const app = createApp({ setup() { const state = reactive({ message: 'Hello Vue!' }); return { state }; } }); app.mount('#app'); ` Consider our “message” data as a book in a library. Vue places this book (our data) within a special book cover (the Proxy). This book cover is unique - it's embedded with a tracking device that notifies Vue every time someone reads the book (accesses the data) or annotates a page (changes the data). In our example, the reactive function creates a Proxy object that wraps around our state object. When you change the 'message': `javascript state.message = 'Goodbye Vue!'; ` The Proxy notices this (like a built-in alarm going off) and alerts Vue that something has changed. Vue then updates the UI to reflect this change. Let’s look deeper into what Vue is doing for us and how it transforms our object into a Proxy object. You don't have to worry about creating or managing the Proxy; Vue handles everything. `javascript const state = reactive({ message: 'Hello Vue!' }); // What vue is doing behind the scenes: function reactive(obj) { return new Proxy(obj, { // target = state and key = message get(target, key) { track(target, key) return target[key] }, set(target, key, value) { target[key] = value // Here Vue will trigger its reactivity system to update the DOM. trigger(target, key) } }) } ` In the example above, we encapsulate our object, in this case, “state”, converting it into a Proxy object. Note that within the second argument of the Proxy, we have two methods: a getter and a setter. The getter method is straightforward: it merely returns the value, which in this instance is “state.message” equating to 'Hello Vue!' Meanwhile, the setter method comes into play when a new value is assigned, as in the case of “state.message = ‘Hey young padawan!’”. Here, “value” becomes our new 'Hey young padawan!', prompting the property to update. This action, in turn, triggers the reactivity system, which subsequently updates the DOM. Venturing Further into the Depths If you have been paying attention to our examples above, you might have noticed that inside the Proxy` method, we call the functions `track` and `trigger` to run our reactivity. Let’s try to understand a bit more about them. You see, Vue 3 reactivity data is more about Proxy objects. Let’s create a new example: `vue import { reactive, watch, computed, effect } from "vue"; const state = reactive({ showSword: false, message: "Hey young padawn!", }); function changeMessage() { state.message = "It's dangerous to go alone! Take this."; } effect(() => { if (state.message === "It's dangerous to go alone! Take this.") { state.showSword = true; } }); {{ state.message }} Click! ` In this example, when you click on the button, the message's value changes. This change triggers the effect function to run, as it's actively listening for any changes in its dependencies__. How does the effect` property know when to be called? Vue 3 has three main functions to run our reactivity: effect`, `track`, and `trigger`. The effect` function is like our supervisor. It steps in and takes action when our data changes – similar to our effect method, we will dive in more later. Next, we have the track` function. It notes down all the important data we need to keep an eye on. In our case, this data would be `state.message`. Lastly, we've got the trigger` function. This one is like our alarm bell. It alerts the `effect` function whenever our important data (the stuff `track` is keeping an eye on) changes. In this way, trigger`, `track`, and `effect` work together to keep our Vue application reacting smoothly to changes in data. Let’s go back to them: `javascript function reactive(obj) { return new Proxy(obj, { get(target, key) { // target = state & key = message track(target, key) // keep an eye for this return target[key] }, set(target, key, value) { target[key] = value trigger(target, key) // trigger the effects! } }) } ` Tracking (Dependency Collection) Tracking is the process of registering dependencies between reactive objects and the effects that depend on them. When a reactive property is read, it's "tracked" as a dependency of the current running effect. When we execute track()`, we essentially store our effects in a Set object. But what exactly is an "effect"? If we revisit our previous example, we see that the effect method must be run whenever any property changes. This action — running the effect method in response to property changes — is what we refer to as an "Effect"! (computed property, watcher, etc.) > Note: We'll outline a basic, high-level overview of what might happen under the hood. Please note that the actual implementation is more complex and optimized, but this should give you an idea of how it works. Let’s see how it works! In our example, we have the following reactive object: `javascript const state = reactive({ showSword: false, message: "Hey young padawn!", }); // which is transformed under the hood to: function reactive(obj) { return new Proxy(obj, { get(target, key) { // target = state | key = message track(target, key) // keep an eye for this return target[key] }, set(target, key, value) { target[key] = value trigger(target, key) // trigger the effects! } }) } ` We need a way to reference the reactive object with its effects. For that, we use a WeakMap. Which type is going to look something like this: `typescript WeakMap>> ` We are using a WeakMap to set our object state as the target (or key). In the Vue code, they call this object `targetMap`. Within this targetMap` object, our value is an object named `depMap` of Map type. Here, the keys represent our properties (in our case, that would be `message` and `showSword`), and the values correspond to their effects – remember, they are stored in a Set that in Vue 3 we refer to as `dep`. Huh… It might seem a bit complex, right? Let's make it more straightforward with a visual example: With the above explained, let’s see what this Track` method kind of looks like and how it uses this `targetMap`. This method essentially is doing something like this: `javascript let activeEffect; // we will see more of this later function track(target, key) { if (activeEffect) { // depsMap` maps targets to their keys and dependent effects let depsMap = targetMap.get(target); // If we don't have a depsMap for this target in our targetMap`, create one. if (!depsMap) { depsMap = new Map(); targetMap.set(target, depsMap); } let dep = depsMap.get(key); if (!dep) { // If we don't have a set of effects for this key in our depsMap`, create one. dep = new Set(); depsMap.set(key, dep); } // Add the current effect as a dependency dep.add(activeEffect); } } ` At this point, you have to be wondering, how does Vue 3 know what activeEffect` should run? Vue 3 keeps track of the currently running effect by using a global variable. When an effect is executed, Vue temporarily stores a reference to it in this global variable, allowing the track` function to access the currently running effect and associate it with the accessed reactive property. This global variable is called inside Vue as `activeEffect`. Vue 3 knows which effect is assigned to this global variable by wrapping the effects functions in a method that invokes the effect whenever a dependency changes. And yes, you guessed, that method is our effect` method. `javascript effect(() => { if (state.message === "It's dangerous to go alone! Take this.") { state.showSword = true; } }); ` This method behind the scenes is doing something similar to this: `javascript function effect(update) { //the function we are passing in const effectMethod = () => { // Assign the effect as our activeEffect` activeEffect = effectMethod // Runs the actual method, also triggering the get` trap inside our proxy update(); // Clean the activeEffect after our Effect has finished activeEffect = null } effectMethod() } ` The handling of activeEffect` within Vue's reactivity system is a dance of careful timing, scoping, and context preservation. Let’s go step by step on how this is working all together. When we run our `Effect` method for the first time, we call the `get` trap of the Proxy. `javascript function effect(update) const effectMethod = () => { // Storing our active effect activeEffect = effectMethod // Running the effect update() ... } ... } effect(() => { // we call the the get` trap when getting our `state.message` if (state.message === "It's dangerous to go alone! Take this.") { state.showSword = true; } }); ` When running the get` trap, we have our `activeEffect` so we can store it as a dependency. `javascript function reactive(obj) { return new Proxy(obj, { // Gets called when our effect runs get(target, key) { track(target, key) // Saves the effect return target[key] }, // ... (other handlers) }) } function track(target, key) { if (activeEffect) { //... rest of the code // Add the current effect as a dependency dep.add(activeEffect); } } ` This coordination ensures that when a reactive property is accessed within an effect, the track function knows which effect is responsible for that access. Trigger Method Our last method makes this Reactive system to be complete. The trigger` method looks up the dependencies for the given target and key and re-runs all dependent effects. `javascript function trigger(target, key) { const depsMap = targetMap.get(target); if (!depsMap) return; // no dependencies, no effects, no need to do anything const dep = depsMap.get(key); if (!dep) return; // no dependencies for this key, no need to do anything // all dependent effects to be re-run dep.forEach(effect => { effect() }); } ` Conclusion Diving into Vue 3's reactivity system has been like unlocking a hidden superpower in my web development toolkit, and honestly, I've had a blast learning about it. From the rudimentary elements of reactive data and instantaneous UI updates to the intricate details involving Proxies, track and trigger functions, and effects, Vue 3's reactivity is an impressively robust framework for building dynamic and responsive applications. In our journey through Vue 3's reactivity, we've uncovered how this framework ensures real-time and precise updates to the UI. We've delved into the use of Proxies to intercept and monitor variable changes and dissected the roles of track and trigger functions, along with the 'effect' method, in facilitating seamless UI updates. Along the way, we've also discovered how Vue ingeniously manages data dependencies through sophisticated data structures like WeakMaps and Sets, offering us a glimpse into its efficient approach to change detection and UI rendering. Whether you're just starting with Vue 3 or an experienced developer looking to level up, understanding this reactivity system is a game-changer. It doesn't just streamline the development process; it enables you to create more interactive, scalable, and maintainable applications. I love Vue 3, and mastering its reactivity system has been enlightening and fun. Thanks for reading, and as always, happy coding!...

How to Update the Application Title based on Routing Changes in Angular cover image

How to Update the Application Title based on Routing Changes in Angular

Have you tried to update the document's title of your application? Maybe you're thinking that applying interpolation should be enough: `html {{myCustomTitleVariable}} ` That solution is not going to work since the ` element is outside of the scope of the Angular application. In fact, the root component of your app is within `` tag, and the title is part of the `` element. Luckily, Angular provides the Title service with the methods to read the current title of the application, and a setTitle(title)` to update that value. However, what happens if you need to update the title on routing changes? Also, you may consider updating it on certain components for Analytics purposes. In this blog post, I'll explain step-by-step how to create a custom Title service to have full control over the title of the current HTML document for your application. Project Setup Prerequisites You'll need to have installed the following tools in your local environment: - Node.js**. Preferably the latest LTS version. - A package manager**. You can use either NPM or Yarn. This tutorial will use NPM. Creating the Angular Project Let's assume we'll need to build an application with the following routes as requirements: `txt /home |- Renders a home component /products |- Renders a list of products /products/ |- Renders a product detail based on its Identifier The app redirects to /home path by default ` Now, let's create the project from scratch using the Angular CLI tool. `bash ng new angular-update-title --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`. it avoids the generations of the `.spec.ts` files, which are used for testing Creating the Modules and Components Once we got the initial structure of the app, we'll continue running the following commands to create a separate module for /home` and `/products`, which are the main paths of the project: `bash ng generate module home --routing ng generate component home/home ng generate module products --routing ng generate component products/products ng generate component products/product-detail ` The `--routing` flag can be using also along with `ng generate module` to create a routing configuration file for that module. Creating the Title Service Similar to the previous section, we will create a shared` module to hold the `Title` service. Both can be generated with the following commands: `bash ng generate module shared --module app ng generate service shared/services/title ` The `--module app` flag is used to "link" the brand new module to the pre-existing `app.module.ts` file. The Routing Configuration Open the app-routing.module.ts` file, and create the initial routes. `ts // app-routing.module.ts const routes: Routes = [ { path: '', pathMatch: 'full', redirectTo: 'home' }, { path: 'home', component: HomeComponent, data: { pageTitle: 'Home' } }, { path: 'products', loadChildren: () => import('./products/products.module').then(m => m.ProductsModule) } ]; ` By default, the application will redirect to the `home` path. When the router loads the `home` path, a `HomeComponent` will be rendered. The `products` path will be loaded using the _lazy loading_ feature. Pay attention to the data provided to the home` path. It contains the configured title through `pageTitle` string. Next, open the products-routing.module.ts` file to enable an additional configuration to load the _Products_ and the _Product Detail_ page. `ts // products-routing.module.ts const routes: Routes = [ { path: '', component: ProductsComponent, children: [ { path: ':id', component: ProductDetailComponent, } ], data: { pageTitle: 'Products' } }, ]; ` The router will render the `ProductsComponent` by default when the path matches to `/products`. This route also defines custom data to be rendered as titles later. When the path also adds an Id on `/products/:id`, the router will render the `ProductDetailComponent`. The Title Service Implementation It's time to implement the custom Title Service for our application. `ts // title.service.ts import { Injectable } from '@angular/core'; import { Title } from '@angular/platform-browser'; import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; import { BehaviorSubject, merge, Observable } from 'rxjs'; import { filter, map, tap } from 'rxjs/operators'; const DEFAULTTITLE = 'Corp'; @Injectable({ providedIn: 'root' }) export class TitleService { title$ = new BehaviorSubject(DEFAULTTITLE); private titleRoute$: Observable = this.router.events.pipe( filter((event) => event instanceof NavigationEnd), map(() => this.getPageTitle(this.activatedRoute.firstChild)) ); private titleState$ = merge(this.title$, this.titleRoute$).pipe( filter((title) => title !== undefined), tap((title) => { this.titleService.setTitle(${DEFAULT_TITLE} - ${title}`); }) ); constructor( private router: Router, private activatedRoute: ActivatedRoute, private titleService: Title ) { this.titleState$.subscribe(); } private getPageTitle( activatedRoute: ActivatedRoute | null ): string | undefined { while (activatedRoute) { if (activatedRoute.firstChild) { activatedRoute = activatedRoute.firstChild; } else if ( activatedRoute.snapshot.data && activatedRoute.snapshot.data['pageTitle'] ) { return activatedRoute.snapshot.data['pageTitle'] as string; } else { return undefined; } } return undefined; } } ` The above service implementation could be understood in just a few steps. First, we'll need to make sure to inject the `Router`, `ActivatedRoute` and `Title` services in the constructor. The `title$` attribute contains the initial value for the title("Corp"), which will be emitted through a _BehaviorSubject_. The `titleRoute$` is an Observable ready to emit any `pageTitle` value defined in the current route. It may use the parent's _pageTitle_ otherwise. The `titleState$` is an Observable ready to _listen_ to either `title$` or `titleRoute$` values. In case incoming value is defined, it will call the Angular Title service to perform the update. The `getPageTitle` method will be in charge of obtaining the `pageTitle` of the current route if it is defined or the title of the parent otherwise. Injecting the Title Service One easy way to apply the custom Title Service in the whole application is by updating the app.module.ts` file and injecting it into the constructor. `ts // app.module.ts export class AppModule { constructor(public titleService: TitleService) {} } ` In that way, once the default component gets rendered, the title will be displayed as Corp - Home`. If you click on Go to Products_ link, then a redirection will be performed and the Title service will be invoked again to display `Corp - Products` at this time. However, we may need to render a different title according to the product detail. In this case, we'll show Corp - Product Detail - :id` where the `Id` matches with the current route parameter. `ts // product-detail.component.ts import { Component, OnDestroy, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { map, Subscription, tap } from 'rxjs'; import { TitleService } from 'src/app/shared/services/title.service'; @Component({ selector: 'corp-product-detail', templateUrl: './product-detail.component.html', styleUrls: ['./product-detail.component.css'], }) export class ProductDetailComponent implements OnInit, OnDestroy { protected subscription = new Subscription(); productId$ = this.route.params.pipe(map((params) => params['id'])); constructor( private route: ActivatedRoute, private titleService: TitleService ) {} ngOnInit(): void { const productIdSubscription = this.productId$ .pipe( tap((id) => this.titleService.title$.next(Product Detail - ${id}`)) ) .subscribe(); this.subscription.add(productIdSubscription); } ngOnDestroy(): void { this.subscription.unsubscribe(); } } ` Let's explain the implementation of this component: The constructor injects the `ActivatedRoute` and the custom `TitleService`. The `productId$` is the _Observable_ which is going to emit the `Id` parameter every time it changes in the URL. Once the component gets initialized, we'll need to _subscribe_ to the `productId$` _Observable_ and then emit a new value for the title after creating a new string using the `id`. That's possible through the `titleService.title$.next()` method. When the component gets _destroyed_, we'll need to _unsubscribe_ from the `productIdSubscription`. We're ready to go! Every time you select a product, the ProductDetail` component will be rendered, and the title will be updated accordingly. Live Demo and Source Code Want to play around with the final application? Just open the following link in your browser: https://luixaviles.github.io/angular-update-title. Find the complete angular project in this GitHub repository: angular-update-title-service. Do not forget to give it a star ⭐️, and play around with the code. Feel free to reach out on Twitter if you have any questions. Follow me on GitHub to see more about my work....

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