Hey there, fellow code enthusiasts! Welcome to the second installment of what I'm calling my "Learn JS Frameworks With Me" series. I'm absolutely thrilled to kick this off with you because, let's face it, learning frameworks can be intimidating - but it's so much better when we do it together!

The Framework Journey Begins

I gotta admit. When I first started working with JavaScript, I found the native DOM API frustrating. Selecting elements felt 'troublesome', event handling was inconsistent across browsers, and animations required writing dozens of lines of code. Sound familiar?

Back then, jQuery came to the rescue with its "Write less, do more" philosophy. It was revolutionary and transformed how an entire generation of developers (myself included) approached frontend development.

But today, we're venturing into new territory: Angular. It's a framework that's been around for a while, has enterprise-level backing from Google, and promises to solve many of the headaches we face with building modern web applications.

Why Angular in 2025?

You might be wondering: "With all these modern frameworks out there, why should I learn Angular?"

Great question! Here's why I think Angular deserves your attention:

  1. Enterprise Adoption: Angular is widely used in large-scale applications. During my consulting work, I've encountered numerous businesses running critical systems on Angular.

  2. Complete Solution: While some frameworks focus on the view layer, Angular provides a complete platform with everything from routing to form validation baked in.

  3. TypeScript Integration: Angular embraces TypeScript, which adds static typing to JavaScript. Trust me, once you get used to it, you'll wonder how you ever lived without it!

  4. Job Market Reality: Browse through job listings and you'll see Angular mentioned frequently, especially for enterprise-level positions.

  5. Opinionated Structure: Angular has opinions about how your app should be structured, which can be a blessing when working on large teams.

Setting Up Your First Angular Project

Let's get our hands dirty! I spent about an hour yesterday just getting my environment ready, and oh boy, was it a journey. But don't worry, I'll walk you through it step by step.

Prerequisites

Before we dive in, you'll need:

  • Node.js and npm: Angular requires Node.js version 14.x or later
  • Angular CLI: The command line interface for Angular

Step 1: Install Node.js and npm

If you don't already have Node.js installed, head over to nodejs.org and download the latest LTS version.

To verify your installation, open your terminal and run:

node -v
npm -v

Step 2: Install Angular CLI

Angular CLI makes it easy to create applications, generate code, and perform various development tasks. Install it globally using npm:

npm install -g @angular/cli

Verify the installation:

ng version

When I first ran this command and saw the elaborate ASCII art logo in my terminal, I literally said "Whoa!" out loud. It's the little things, right?

Step 3: Create Your First Angular Project

Now we're ready to create our first project:

ng new learn-angular-with-me

During the setup, Angular CLI will ask:

  • Whether you want to add Angular routing (Yes!)
  • Which CSS preprocessor you want to use (I'm sticking with regular CSS for now)

This was the moment I realized Angular wasn't messing around. The CLI asks thoughtful questions and sets up a comprehensive project structure.

Step 4: Run Your Application

Navigate to your project directory:

cd learn-angular-with-me

Start the development server:

ng serve --open

The --open flag will automatically open your browser to http://localhost:4200/.

When I first saw that Angular logo spinning in my browser, I felt like I'd just joined some elite developer club. It was both exciting and slightly intimidating!

The Angular Project Structure

Coming from jQuery, Angular feels... comprehensive. And by comprehensive, I mean there's a LOT going on. My first reaction was honestly a bit of overwhelm when I opened the project structure:

learn-angular-with-me/
├── node_modules/
├── src/
│   ├── app/
│   │   ├── app.component.css
│   │   ├── app.component.html
│   │   ├── app.component.spec.ts
│   │   ├── app.component.ts
│   │   └── app.module.ts
│   ├── assets/
│   ├── environments/
│   ├── favicon.ico
│   ├── index.html
│   ├── main.ts
│   ├── polyfills.ts
│   ├── styles.css
│   └── test.ts
├── angular.json
├── package.json
├── tsconfig.json
└── ... (more config files)

Wait, what? Where do I even start? In jQuery, I'd just create an HTML file, link a JS file, and start coding. Here, I'm looking at something that feels more like an enterprise-level application structure.

Let's break down the key parts:

  • src/app/: This is where most of your application code will live
  • app.component.ts: The main component class
  • app.component.html: The HTML template for the main component
  • app.module.ts: The root module that tells Angular how to assemble your application

The Mental Shift: Component-Based Thinking

The biggest mental shift I've had to make is thinking in components. In jQuery, I was used to writing functions and manipulating the DOM directly. With Angular, everything revolves around components.

A component in Angular is basically a building block that contains:

  • An HTML template (the view)
  • A TypeScript class (the logic)
  • CSS styles (the look)

Let me show you what a basic component looks like:

// task.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-task',
  templateUrl: './task.component.html',
  styleUrls: ['./task.component.css']
})
export class TaskComponent {
  taskName: string = '';
  isCompleted: boolean = false;

  toggleComplete() {
    this.isCompleted = !this.isCompleted;
  }
}
class="task" [class.completed]="isCompleted">
  {{ taskName }}
   (click)="toggleComplete()">Toggle
/* task.component.css */
.task {
  padding: 10px;
  border-bottom: 1px solid #eee;
}

.completed {
  text-decoration: line-through;
  color: #888;
}

This was my first "aha!" moment with Angular. Instead of having your HTML, JS, and CSS scattered across different files, they're bundled together based on functionality. It's like each component is its own mini-application!

Two-Way Data Binding: The Magic Begins

Remember how in jQuery, we had to manually update the DOM when data changed? That looked something like this:

$('#task-input').val('New Task');
// And then later:
const taskValue = $('#task-input').val();

Angular introduces two-way data binding, which feels like magic when you first see it:

[(ngModel)]="taskName" placeholder="Enter task">
Current task: {{ taskName }}

That [(ngModel)] syntax is called "banana in a box" (seriously, that's what Angular devs call it!), and it automatically keeps your component property and the input value in sync.

When I first saw this working, I literally said "Wait, that's it? Where's the rest of the code?" It felt too good to be true!

Building Our First Feature: A Task List

Let's build a simple task list component to get a feel for how Angular works in practice. First, we'll generate a new component:

ng generate component task-list

Now, let's update our component files:

// task-list.component.ts
import { Component } from '@angular/core';

interface Task {
  id: number;
  text: string;
  completed: boolean;
}

@Component({
  selector: 'app-task-list',
  templateUrl: './task-list.component.html',
  styleUrls: ['./task-list.component.css']
})
export class TaskListComponent {
  newTaskText: string = '';
  tasks: Task[] = [];

  addTask() {
    if (this.newTaskText.trim()) {
      this.tasks.push({
        id: Date.now(),
        text: this.newTaskText,
        completed: false
      });
      this.newTaskText = '';
    }
  }

  toggleComplete(task: Task) {
    task.completed = !task.completed;
  }

  deleteTask(id: number) {
    this.tasks = this.tasks.filter(task => task.id !== id);
  }
}
class="task-manager">
  Task List

   class="add-task">
     [(ngModel)]="newTaskText" placeholder="Add a new task..." (keyup.enter)="addTask()">
     (click)="addTask()">Add
  

   class="task-list">
     *ngFor="let task of tasks" [class.completed]="task.completed">
       (click)="toggleComplete(task)">{{ task.text }}
       (click)="deleteTask(task.id)">Delete
/* task-list.component.css */
.task-manager {
  max-width: 500px;
  margin: 0 auto;
}

.add-task {
  display: flex;
  margin-bottom: 20px;
}

.add-task input {
  flex-grow: 1;
  padding: 8px;
  border: 1px solid #ddd;
  border-radius: 4px 0 0 4px;
}

.add-task button {
  padding: 8px 16px;
  background-color: #4CAF50;
  color: white;
  border: none;
  border-radius: 0 4px 4px 0;
  cursor: pointer;
}

.task-list {
  list-style-type: none;
  padding: 0;
}

.task-list li {
  padding: 10px;
  border-bottom: 1px solid #eee;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.task-list li.completed span {
  text-decoration: line-through;
  color: #888;
}

.task-list li button {
  background-color: #f44336;
  color: white;
  border: none;
  padding: 5px 10px;
  border-radius: 4px;
  cursor: pointer;
}

And don't forget to update the app.module.ts file to include the FormsModule (needed for ngModel):

// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';

import { AppComponent } from './app.component';
import { TaskListComponent } from './task-list/task-list.component';

@NgModule({
  declarations: [
    AppComponent,
    TaskListComponent
  ],
  imports: [
    BrowserModule,
    FormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Finally, let's use our new component in the app.component.html:

class="container">
  My Angular Task Manager

This was another mind-blowing moment for me. In jQuery, I would've had to write event handlers for each interaction, manually update the DOM, and keep track of state. With Angular, the framework handles all of that for us!

Angular vs jQuery: The Mental Shift

Let's compare how we'd approach the same task manager in jQuery versus Angular:

jQuery Approach:

  1. Select DOM elements
  2. Attach event handlers
  3. Manually update the DOM when data changes
  4. Handle state yourself

Angular Approach:

  1. Define component properties (your data)
  2. Create HTML templates with data binding
  3. Angular automatically updates the DOM
  4. State is managed within components

This shift from imperative to declarative programming was a game-changer for me. Instead of telling the browser exactly what to do, I'm just describing what I want the result to be, and Angular figures out how to make it happen.

Directives: Angular's Superpowers

Angular includes several built-in directives that extend HTML with new capabilities:

  • ngFor: Repeats a template for each item in a collection
  • ngIf: Conditionally includes or excludes a template
  • ngClass: Dynamically applies CSS classes
  • ngStyle: Dynamically applies styles
  • ngModel: Creates two-way data binding

These directives make dynamic UIs much easier to build. For example, instead of this jQuery code:

$('#task-list').empty();
tasks.forEach(function(task) {
  const $li = $('').text(task.text);
  if (task.completed) {
    $li.addClass('completed');
  }
  $('#task-list').append($li);
});

In Angular, we simply write:

*ngFor="let task of tasks" [class.completed]="task.completed">
    {{ task.text }}

The declarative nature of Angular's templates makes your code more readable and maintainable. When I first saw this in action, I thought, "This is how web development should have been all along!"

Services: Sharing Data Between Components

As your application grows, you'll want to share data between components. This is where services come in. Let's create a task service:

ng generate service task
// task.service.ts
import { Injectable } from '@angular/core';

export interface Task {
  id: number;
  text: string;
  completed: boolean;
}

@Injectable({
  providedIn: 'root'
})
export class TaskService {
  private tasks: Task[] = [];

  getTasks(): Task[] {
    return this.tasks;
  }

  addTask(text: string): void {
    this.tasks.push({
      id: Date.now(),
      text,
      completed: false
    });
  }

  deleteTask(id: number): void {
    this.tasks = this.tasks.filter(task => task.id !== id);
  }

  toggleComplete(id: number): void {
    this.tasks = this.tasks.map(task => {
      if (task.id === id) {
        return { ...task, completed: !task.completed };
      }
      return task;
    });
  }
}

Now we can update our TaskListComponent to use this service:

// task-list.component.ts
import { Component } from '@angular/core';
import { TaskService, Task } from '../task.service';

@Component({
  selector: 'app-task-list',
  templateUrl: './task-list.component.html',
  styleUrls: ['./task-list.component.css']
})
export class TaskListComponent {
  newTaskText: string = '';

  constructor(private taskService: TaskService) {}

  get tasks(): Task[] {
    return this.taskService.getTasks();
  }

  addTask() {
    if (this.newTaskText.trim()) {
      this.taskService.addTask(this.newTaskText);
      this.newTaskText = '';
    }
  }

  toggleComplete(task: Task) {
    this.taskService.toggleComplete(task.id);
  }

  deleteTask(id: number) {
    this.taskService.deleteTask(id);
  }
}

This is a fundamental concept in Angular: dependency injection. When we declare a dependency in the constructor, Angular automatically provides an instance of that service. This makes testing and reusing