myHotTake

Tag: JavaScript best practices

  • How Do ESLint and Prettier Polish Your JavaScript Code?

    If you enjoy this story and find it helpful, feel free to like or share it with others who might appreciate a good tale about JavaScript!


    I’m standing in a workshop, staring at a rough piece of wood. This wood is like my JavaScript code—full of potential but in need of some serious refining. I have two trusty tools at my side: a wise old craftsman named ESLint and a smooth-talking artisan named Prettier. Both are eager to help me transform this rough block into a masterpiece.

    I pick up ESLint first. He’s like a seasoned mentor, pointing out every flaw in my wood. “See that notch? That’s going to be a problem later,” he says, tapping his finger on a splinter. ESLint is all about the structure. He highlights where my wood might crack or where the grain isn’t quite right. He ensures that every joint will fit perfectly, and that the foundation of my piece is solid. His attention to detail is impeccable, ensuring that the integrity of my final product is flawless.

    Then, I turn to Prettier. With a flourish, he begins to sand away the rough edges. Unlike ESLint, Prettier isn’t concerned with the structural integrity; he’s focused on the surface. He makes the wood shine, smoothing out the surface until it’s pleasing to both the eye and the touch. With Prettier, the wood becomes something beautiful, with every edge softened and every surface gleaming.

    As I work, I realize that both ESLint and Prettier are essential. ESLint helps me ensure that my piece won’t fall apart—that it’s functional and robust. Prettier, on the other hand, makes sure it looks good, that it’s polished and elegant.


    First, I call upon ESLint. I run it against my codebase, and it immediately starts pointing out issues. For instance, let’s say I have this piece of code:

    const greet = (name) => {
        console.log("Hello, " + name)
    }
    
    greet('Alice')

    ESLint steps in and says, “Hold on! There’s a missing semicolon at the end of the console.log statement.” It also suggests using template literals for cleaner string concatenation. With ESLint’s guidance, I make the changes:

    const greet = (name) => {
        console.log(`Hello, ${name}`);
    }
    
    greet('Alice');

    My code is now structurally sound, thanks to ESLint’s keen eye for detail.

    Next, I bring Prettier into the mix. Prettier isn’t concerned with the logic or semantics of my code; instead, it focuses on its appearance. It takes care of inconsistent spacing, line breaks, and other formatting issues. This makes my code more readable and consistent. With Prettier, even if I have a long function that’s a bit messy, like this:

    function calculateSum(a, b) {
        let sum = a+b; return sum;
    }

    Prettier will automatically reformat it to look neat and tidy:

    function calculateSum(a, b) {
      let sum = a + b;
      return sum;
    }

    With both ESLint and Prettier at work, my code is not only correct but also easy on the eyes. ESLint ensures that my code follows best practices and is free of errors, while Prettier makes it visually clean and consistent.


    Key Takeaways:

    1. ESLint is like the craftsman who ensures your code is structurally sound, catching errors and enforcing coding standards.
    2. Prettier is the artisan who polishes your code, making it look consistent and aesthetically pleasing without changing its functionality.
    3. Using both tools together creates a codebase that is both robust and easy to read, much like a well-crafted piece of wood that is both sturdy and beautiful.
  • Why Avoid Inline Event Handlers in JavaScript Projects?

    Hey there! If you enjoy this story, feel free to give it a like or share it with your friends. Let’s dive in!


    I’m in a office, asked to sort a pile of files alphabetically. As I start, I decide to place sticky notes directly on each file, jotting down the order they should be arranged in. It’s quick and seems efficient at first. But soon, I notice a problem: every time I rearrange the files, those sticky notes get misplaced or fall off entirely. I realize that relying on these temporary markers is risky; they make it hard to keep track of the actual order and can easily lead to chaos if they’re lost or misapplied.

    This chaotic scenario is a lot like using inline event handlers in JavaScript. Inline event handlers are like those sticky notes—quick to apply directly to HTML elements but risky in the long run. They clutter my HTML, making it harder to maintain and read. Just like a misplaced sticky note can ruin my filing system, a misplaced inline handler can lead to bugs and security issues, like inadvertently exposing my code to cross-site scripting attacks.

    To regain control, I decide to sort the files using a well-organized filing system instead. I create a master list, separate from the files, detailing the order. Now, whenever I need to reorganize, I simply refer to my list rather than fussing with sticky notes. This approach mirrors using JavaScript to attach event handlers separate from my HTML. By keeping my JavaScript code organized and distinct from my HTML structure, I reduce clutter and enhance security.


    After learning my lesson with the sticky notes, I decide to apply this newfound wisdom to my JavaScript projects. Moving away from inline event handlers is like organizing my files with a robust system. Instead of embedding event logic directly within HTML, I use JavaScript to manage everything externally. Here’s how I do it:

    I have a simple button in my HTML:

    <button id="sortButton">Sort Files</button>

    Previously, I might have been tempted to use an inline event handler like this:

    <button onclick="sortFiles()">Sort Files</button>

    But just like those pesky sticky notes, this approach can become messy and hard to manage, especially as my project grows. So, I switch gears and keep my HTML clean:

    <button id="sortButton">Sort Files</button>

    Then, I move to my JavaScript file and add my event listener there:

    document.getElementById('sortButton').addEventListener('click', sortFiles);
    
    function sortFiles() {
        console.log('Files are being sorted!');
        // Sorting logic goes here
    }

    By doing this, I maintain a clear separation between structure (HTML) and behavior (JavaScript), making my code more maintainable and secure. Plus, I can easily see all the event logic in one place without sifting through the HTML.

    Key Takeaways:

    1. Separation of Concerns: Keeping HTML and JavaScript separate makes your code cleaner and easier to maintain.
    2. Improved Readability: Having all your event handlers in one JavaScript file or section means you don’t have to hunt through HTML to find event logic.
    3. Enhanced Security: Reducing inline JavaScript minimizes the risk of certain security vulnerabilities, like cross-site scripting (XSS).
    4. Scalability: As projects grow, managing events through JavaScript allows for easier updates and changes.
  • Why Use Interfaces in TypeScript? A Space Adventure Analogy

    If you find this story helpful or entertaining, feel free to like or share it!


    I am the captain of a spaceship, and my mission is to explore distant galaxies. Before setting off, I need a crew that knows exactly what their roles are, without needing me to constantly check on them. This is where the concept of interfaces in TypeScript comes into play. Think of an interface as the job description for each crew member on my spaceship.

    I start by defining an interface for my crew members—it’s like writing down the qualifications and duties for each position. For example, I might need a navigator, an engineer, and a medic. The navigator must have skills in charting courses and operating the navigation console. The engineer should be adept at maintaining the ship’s systems, while the medic needs to be capable of providing medical care.

    In TypeScript, I write these job descriptions as interfaces. Each interface specifies the properties and methods that a crew member must have. For instance, the navigator interface might include methods like chartCourse() and adjustTrajectory(). This ensures that anyone filling the navigator role on my crew must implement these methods.

    As we prepare for launch, I recruit crew members, each one fulfilling an interface I’ve defined. Each crew member—whether they are human or an advanced robot—knows exactly what is expected of them because they adhere to their specific interface. This way, I can confidently command the ship, knowing that everyone is equipped for their tasks without micromanagement.

    As the captain, I can focus on leading the mission while trusting that each crew member will perform their duties as specified by their interfaces. Just like that, interfaces in TypeScript allow me to define clear contracts for my team members, ensuring smooth interstellar journeys without unexpected hiccups.

    So, in this galactic adventure, interfaces are my blueprint for a well-functioning crew, ensuring our mission to explore the stars is a success.


    Back on my spaceship, I’ve defined the roles for my crew using TypeScript interfaces. Let’s dive into how this translates to code. I have defined an interface for my navigator, called NavigatorInterface, just like I created a clear set of expectations for the navigator role.

    interface NavigatorInterface {
      chartCourse(destination: string): void;
      adjustTrajectory(trajectory: string): boolean;
    }

    This interface is like a checklist for any crew member who claims to be a navigator. Now, when I recruit a navigator, I can ensure they meet these requirements by implementing the interface:

    class Navigator implements NavigatorInterface {
      chartCourse(destination: string): void {
        console.log(`Charting course to ${destination}`);
      }
    
      adjustTrajectory(trajectory: string): boolean {
        console.log(`Adjusting trajectory to ${trajectory}`);
        return true;
      }
    }

    Here, my Navigator class fulfills the NavigatorInterface contract. It provides the exact methods that the interface mandates, ensuring my navigator knows how to chart a course and adjust the ship’s trajectory.

    But what if I also need an engineer? I would define another interface:

    interface EngineerInterface {
      maintainSystems(): void;
      repairSystem(systemName: string): boolean;
    }

    And just like with the navigator, I can have an Engineer class implement this interface:

    class Engineer implements EngineerInterface {
      maintainSystems(): void {
        console.log('Maintaining all systems.');
      }
    
      repairSystem(systemName: string): boolean {
        console.log(`Repairing ${systemName}`);
        return true;
      }
    }

    With these interfaces, I can ensure that each crew member, like my navigator and engineer, meets the specific requirements of their roles.

    Key Takeaways:

    1. Clear Contracts: Interfaces in TypeScript provide a way to define clear contracts for objects or classes, specifying what methods and properties they must have.
    2. Consistency and Safety: By implementing interfaces, I ensure consistency and type safety in my code, reducing the risk of runtime errors.
    3. Flexibility: Interfaces allow for flexibility in how roles are fulfilled. Different classes can implement the same interface in their unique ways, as long as they adhere to the defined contract.
    4. Interoperability: Using interfaces, I can swap out different implementations as needed, without changing the code that depends on them, similar to recruiting different crew members with the same qualifications.
  • How to Fix Common Angular Anti-Patterns with Simple Tips

    If you find this story helpful or entertaining, feel free to like or share it with others who might appreciate it!


    I’m hosting a potluck dinner at my house, and I’ve invited several friends to bring their favorite dishes. My house is like an Angular application, and each room represents a different component of the app. The goal is to ensure that each room, or component, functions properly and that the entire event runs smoothly.

    As I organize the potluck, I notice a few things that could potentially go wrong, much like common anti-patterns in Angular applications. The first potential issue is when someone brings a dish that requires too much setup. This is like creating components that have too many responsibilities. If I allow this, the kitchen becomes cluttered, and it takes forever to get things ready. So, I ensure that each dish is simple and self-contained, reflecting the Single Responsibility Principle.

    Next, I notice a friend who insists on checking every dish and adjusting the seasoning. This is akin to tightly coupled components in Angular. If I let this happen, it creates a bottleneck, and nothing can proceed without this friend’s input. To avoid this, I encourage everyone to trust each other’s cooking skills, promoting loose coupling and modularity.

    Then, there’s a guest who keeps going back and forth between the living room and the kitchen for every small item, like salt or napkins. This is similar to making too many HTTP requests in a service. To streamline things, I set up a small station with essentials, reducing unnecessary traffic and improving efficiency.

    Finally, one friend brings a giant, elaborate cake that can only fit in the hallway. This cake is like a large, monolithic component that doesn’t fit well into the structure of the app. To manage this, I ask them to slice it into smaller, manageable pieces that can be enjoyed in any room, emphasizing the importance of small, reusable components.

    By addressing these issues, I ensure that the potluck is a success, much like how avoiding anti-patterns leads to a well-functioning Angular application. If this analogy made you smile or think differently about Angular, feel free to like or share!


    Part 2: Tying It Back to JavaScript

    Continuing from our potluck scenario, let’s see how these concepts translate into JavaScript and Angular code.

    1. Single Responsibility Principle At the potluck, I ensured each dish was simple and self-contained. In Angular, this means creating components that focus on a single responsibility. Here’s a code example:
       // app.component.ts
       import { Component } from '@angular/core';
    
       @Component({
         selector: 'app-dish',
         template: `
           <div>
             <h2>{{ title }}</h2>
             <p>{{ description }}</p>
           </div>
         `
       })
       export class DishComponent {
         title: string = 'Spaghetti';
         description: string = 'A classic Italian pasta dish.';
       }

    This component only handles displaying a dish, keeping it simple and focused.

    1. Loose Coupling Just like trusting my friends’ cooking skills, Angular components should be loosely coupled. Use @Input() and @Output() decorators to communicate between components:
       // parent.component.html
       <app-dish [title]="dishTitle" (notify)="onNotify($event)"></app-dish>
    
       // parent.component.ts
       dishTitle = 'Spaghetti';
    
       onNotify(event: any) {
         console.log('Notification from child:', event);
       }
    
       // dish.component.ts
       import { Component, Input, Output, EventEmitter } from '@angular/core';
    
       @Component({
         selector: 'app-dish',
         template: `
           <div>
             <h2>{{ title }}</h2>
             <button (click)="notifyParent()">Notify Parent</button>
           </div>
         `
       })
       export class DishComponent {
         @Input() title: string;
         @Output() notify = new EventEmitter();
    
         notifyParent() {
           this.notify.emit('Dish is ready!');
         }
       }

    This setup allows components to communicate without being tightly coupled.

    1. Reducing Unnecessary Traffic Just like setting up a station with essentials, we should optimize data fetching in Angular:
       // data.service.ts
       import { Injectable } from '@angular/core';
       import { HttpClient } from '@angular/common/http';
       import { Observable } from 'rxjs';
    
       @Injectable({
         providedIn: 'root',
       })
       export class DataService {
         constructor(private http: HttpClient) {}
    
         fetchDishes(): Observable<any> {
           return this.http.get('api/dishes');
         }
       }

    Here, data fetching is centralized in a service, reducing redundant requests and improving efficiency.

    1. Breaking Down Monoliths Like slicing the giant cake into smaller pieces, break down large components into smaller, reusable ones:
       // large.component.ts (before refactor)
       // Contains logic for display, interaction, and data handling
    
       // After refactor, split into:
       // display.component.ts
       // interaction.component.ts
       // data-handler.service.ts

    By breaking down large components, each piece is easier to manage and reuse.

    Key Takeaways

    • Single Responsibility Principle: Keep components focused on a single task to reduce complexity.
    • Loose Coupling: Use @Input() and @Output() for flexible component communication.
    • Optimized Data Fetching: Centralize HTTP requests in services to reduce redundancy.
    • Reusable Components: Break large components into smaller, manageable pieces for better maintainability.