Tetris for Your App: Components

oscarsbytes

Oscar & Ralph

Posted on October 30, 2024

Tetris for Your App: Components

Hello again, humans! If you're new here, welcome to Oscar's Bytes! I'm a British tuxedo cat with a knack for clean code and sarcasm and I'm here to guide you through your first steps into front-end development.

If you’re returning, you already know the deal: I’ll be your guide to writing code that won’t make my whiskers curl in horror. My human, Ralph, will be along for the ride, mostly as an example of what not to do.


Today we’re tackling the core of any Angular app: components. Think of them like pieces in a game of Tetris — each one has a distinct purpose, and it should fit perfectly into the larger picture. A well-structured component is easy to manage, reusable, and, most importantly, doesn’t send your app (or your sanity) into chaos.

So grab a coffee (or whatever humans drink), and let’s dive into the fundamentals of building strong, clean components; the kind that will keep your app running smoothly without causing collapse.


What Are Angular Components?

So, what exactly is an Angular component? Picture each component as a self-contained piece of a puzzle — it’s got everything it needs to display a specific part of your app, and it’s designed to slot perfectly into the bigger picture. If you’re building a dashboard, each chart, button, and widget can be its own component, each serving a unique purpose while working together to create a cohesive whole.

In Angular, each component has three essential parts:

  1. Template: The HTML structure—the visual part of your component.
  2. Logic: The TypeScript file where data and behavior are handled.
  3. Styles: The CSS that gives each component its own look and feel.

Together, they form a complete, functional component. Think of it as assembling a mini puzzle piece — when you get each part right, it fits seamlessly with the others. Oscar’s rule of paw? Keep each component focused and avoid adding more than it needs. An overloaded component is like a mismatched puzzle piece; it just doesn’t fit.

Let's look at how to build one from scratch...


The Basics of Creating a Component

Let’s get our paws dirty and create an Angular component from scratch. This is where you take control of a piece of your app, giving it a unique role within the larger puzzle. In Angular, it’s as easy as running a single command from the root of your project:

ng generate component [component-name]
Enter fullscreen mode Exit fullscreen mode

This sets up the three files we mentioned earlier: template, logic, and styles, in a neat organised folder ready for action. But remember, a component is only as strong as its foundation. Keep it clean, focused, and, most importantly, single-purpose.

New component

Let’s say we’re building a button component. Rather than dumping loads of unrelated code into here, this component should only manage button-related logic: styles, click actions, and maybe a simple label. If you start tossing in unrelated tasks, it’s like adding extra Tetris blocks that don’t fit, which could will lead to chaos further down the line!

Here’s what you’ll find in your new component’s folder:

  • HTML template for structure.
  • CSS/SCSS file for styling.
  • Spec file for unit tests (we'll touch on these in another post).
  • TypeScript file for logic.

With this setup, you’re ready to start adding some code to your new component. Let's get Ralph to add some simple button code:

button.component.html

<button>Click me</button>
Enter fullscreen mode Exit fullscreen mode

button.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-button',
  standalone: true,
  imports: [],
  templateUrl: './button.component.html',
  styleUrl: './button.component.scss'
})
export class ButtonComponent {}
Enter fullscreen mode Exit fullscreen mode

Well done Ralph (Yawns)! So, we've now got a shiny new button that's ready to slot into the app somewhere. Let's see if Ralph can work it out...


Using Your New Component

Now that your component's set up, it’s time to put it to work. Angular makes it simple to add components to any part of your app, so you can slot it in wherever it’s needed.

1. Importing the Component

First, ensure that the component is accessible within the module where you want to use it. If you’ve created the component in a specific feature module, double-check that it’s included in the module’s declarations.

NB! If you're using the latest version of Angular, new components will be standalone by default, so no need for a module.

2. Using the Component Selector

Each component has a unique selector, defined in its @Component decorator. This selector acts like a custom HTML tag, allowing you to place the component in your templates. Let's add our new ButtonComponent to the main app template:

app.component.html

<h1>{{ title }}</h1>
<app-button></app-button>
Enter fullscreen mode Exit fullscreen mode

app.component.ts

import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { ButtonComponent } from "./button/button.component";

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet, ButtonComponent],
  templateUrl: './app.component.html',
  styleUrl: './app.component.scss'
})
export class AppComponent {
  title = 'Oscar Bytes';
}
Enter fullscreen mode Exit fullscreen mode

3. Passing Data with @Input()

Many components benefit from customisation. We can use @Input() properties to pass data from a parent down to a child component. This lets you adapt your component’s behaviour based on the context in which it’s used. Let's update our button code so it can handle an @Input:

button.component.html

<button>{{ buttonLabel }}</button>
Enter fullscreen mode Exit fullscreen mode

button.component.ts

import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-button',
  standalone: true,
  imports: [],
  templateUrl: './button.component.html',
  styleUrl: './button.component.scss'
})
export class ButtonComponent {
  @Input() buttonLabel: string = '';
}
Enter fullscreen mode Exit fullscreen mode

We can now update our app-button tag and set the new property we've just created:

app.component.html

<app-button buttonLabel="Say Hello"></app-button>
Enter fullscreen mode Exit fullscreen mode

If we wanted to bind this to a dynamic value, we need to wrap the attribute in square brackets:

app.component.html

<app-button [buttonLabel]="someVariable"></app-button>
Enter fullscreen mode Exit fullscreen mode

4. Responding to @Output() Events

If your component needs to trigger actions (like button clicks) or emit data, we can set up @Output() properties to send data or events back to the parent component. This will help keep your components interactive and connected to the rest of the app.

Come on Ralph! Time to update the ButtonComponent (again!):

button.component.ts

import { Component, EventEmitter, Input, Output } from '@angular/core';

@Component({
  selector: 'app-button',
  standalone: true,
  imports: [],
  templateUrl: './button.component.html',
  styleUrl: './button.component.scss'
})
export class ButtonComponent {
  @Input() buttonLabel: string = '';
  @Output() buttonClicked = new EventEmitter<string>();

  onClick(message: string) {
    this.buttonClicked.emit(message);
  }
}
Enter fullscreen mode Exit fullscreen mode

button.component.html

<button (click)="onClick('Hello world!')">{{ buttonLabel }}</button>
Enter fullscreen mode Exit fullscreen mode

app.component.ts

import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { ButtonComponent } from "./button/button.component";

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet, ButtonComponent],
  templateUrl: './app.component.html',
  styleUrl: './app.component.scss'
})
export class AppComponent {
  title = 'Oscar Bytes';

  setTitle(message: string) {
    this.title = message;
  }
}
Enter fullscreen mode Exit fullscreen mode

app.component.html

<h1>{{ title }}</h1>

<app-button 
  (buttonClicked)="setTitle($event)" 
  buttonLabel="Say Hello">
</app-button>
Enter fullscreen mode Exit fullscreen mode

When you run this in the browser, clicking the button will now set the title. Parent child communication done! Well done Ralph!

Image description


If you've gotten this far well done! Here's a picture of me lounging in the garden...
Cat lounging in the garden


Organising Components Effectively

Now that we’ve got our basic component set up, we need to talk about being organised! Components are like puzzle pieces, each with their own place. Without a clear structure, it’s easy for things to spiral into chaos. Properly organised components mean less headache down the line and a smoother experience for anyone who maintains or expands your codebase.

  1. Hierarchy and Nesting: Just as a puzzle is assembled in sections, organise components into logical groupings. For instance, a dashboard might have individual components for each widget—charts, stats, and buttons—all under a main dashboard component. This way, each smaller component reports to a “parent” component, forming a clean hierarchy.

  2. Reusability: A good component is versatile, able to be reused without extra fuss. For example, a button component should work across the entire app by adjusting its input properties. This flexibility keeps your app lean and reduces redundant code.

  3. Keep It Single-Purpose: Remember, a component should handle one job only. When you try to make it do too much, it turns into a jumbled mess, like trying to force a piece into the wrong spot in the puzzle. If your component starts to grow beyond its original purpose, it’s a sign you should split it up.

Oscar’s advice? Think like a cat: territorial and focused. Each component should “own” its space and purpose, nothing more, nothing less. This approach keeps your app modular and your components easy to manage.


Common Component Pitfalls

Alright, humans, let’s talk about the mistakes you’re likely to encounter (and, hopefully, avoid). Components may look simple, but like any puzzle, they can quickly get messy without the right approach. Here are some classic pitfalls to watch out for:

  1. Doing Too Much: The most common blunder. When a component tries to handle everything, it becomes overloaded and hard to manage. Remember, a component should aim to do one thing well. If you’re adding multiple responsibilities, break them into smaller, single-purpose components. Trust me; you’ll thank yourself later.

  2. Hardcoding Data: It’s tempting to plug in fixed values, especially for quick tests, but hardcoding data makes your components inflexible. Use Angular’s @Input() properties to pass data into components. This way your components can adapt and be reused without modification.

  3. Ignoring Reusability: Each component should be like a versatile puzzle piece that can slot into multiple places. Build your components with an eye toward reuse, making them adaptable to different contexts. For example, if you’re creating a button, make it flexible enough to work across various screens.

  4. Poor Communication: Components often need to “talk” to each other. Use @Input() and @Output() decorators to pass data and trigger events between parent and child components. Ignoring communication patterns leads to messy, isolated components that won’t work well together—like trying to solve a puzzle with mismatched pieces.

  5. Neglecting Style Encapsulation: Angular components encapsulate styles by default, meaning CSS in one component won’t affect others. This can be a lifesaver for keeping styles organised. Make sure your CSS is scoped to each component, so you don’t end up with a global style free-for-all.

Oscar’s tip? Keep your components lean, flexible, and communicative. Avoid these mistakes, and you’ll have a smooth, scalable app.


Building for Scalability

If you want your app to scale smoothly, you need components that can grow and adapt without breaking a sweat. Think of your components like flexible Tetris blocks—small, reusable pieces that fit together easily, making it simple to add new features as your app grows. Here’s how to keep things scalable:

  1. Stick to Small, Focused Components: When each component has a specific purpose, it’s much easier to manage and reuse them across different parts of your app. Plus, if something needs tweaking, you won’t be ripping apart multiple sections of your code.

  2. Use Services for Shared Logic: Don’t overload your components with logic they don’t need to own. Instead, offload shared tasks like data fetching to Angular services. This keeps your components light and lets them focus on UI tasks.

  3. Embrace Modularity: Build your app as a series of modules, each containing a set of related components. This makes it easy to add or remove entire sections of the app as needed. Think of modules like individual puzzle sections—they’re self-contained but part of the larger picture.

  4. Prepare for Change: Code that scales is code that anticipates change. Use input properties to make components adaptable, and keep your components decoupled so changes in one area don’t ripple throughout the entire app.

Remember, the goal is to create a component system that won’t topple like a poorly constructed Tetris tower the moment you add a new feature. Build with flexibility in mind, and your app will handle growth like a pro.


Conclusion

Congratulations, humans! You’ve now got a foundation for building well-structured, scalable Angular components. Whether you’re crafting a simple button or a full-featured dashboard, following these principles will help you keep your app clean, efficient, and modular—qualities that are bound to make any feline proud.

Next, we’ll dive a bit more into component communication and how to get them talking and working together like a well-coordinated team. Until then, keep those puzzle pieces organised, and remember: messy code is never a good look.

Stay tuned, humans. You’ll need me.

💖 💪 🙅 🚩
oscarsbytes
Oscar & Ralph

Posted on October 30, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related