Blog Image for A new version of a old friend.

Angular 18: From The Perspective Of A Beginner

A new version of a old friend.

With the release of Angular 18, there’s a fresh buzz in the community, especially among beginners. So, what’s new in Angular 18, and why should you, as a newbie, care? Let’s dive in and find out.

What is Angular 18?

Angular 18 is the latest major release of the Angular framework, a platform for building mobile and desktop web applications. Known for its robustness and comprehensive suite of tools, Angular continues to be a favorite among developers. This new version brings several updates and enhancements designed to make development smoother and more efficient.

Features of Angular 18

1. Component

Components are truly the heart of Angular applications. They enable the creation of reusable UI building blocks that have their own logic and layout. An Angular component consists of three main parts: a class that controls the data and logic, an HTML template that defines the view, and optionally a CSS stylesheet for styling the view.

1.1 Class File (TypeScript)

// counter.component.ts
import { Component } from '@angular/core';  // Imports the `Component` decorator from Angular Core.

@Component({ // A decorator that defines the following class as a component.
  selector: 'app-counter', // The CSS selector for using this component in an HTML document
  templateUrl: './counter.component.html', // The path to the HTML template file of this component
  styleUrls: ['./counter.component.css'] // The path to the CSS file for styling this component
})
export class CounterComponent {
  count = 0; // A property of the class that holds the current counter state

  increment() {
    this.count++; // Increases the counter state by one
  }

  decrement() {
    this.count--; // Decreases the counter state by one
  }
}

1.2 HTML Template (HTML)

<!-- counter.component.html -->
<div>
  <h1>Current Count: {{ count }}</h1>
  <button (click)="increment()">Increase</button> <!-- Calls the increment() method when the button is clicked -->
  <button (click)="decrement()">Decrease</button> <!-- Calls the decrement() method when the button is clicked -->
</div>

1.3 CSS Stylesheet (CSS)

/* counter.component.css */
h1 {
  color: blue;
}
button {
  margin: 5px;
  padding: 10px;
  font-size: 16px;
}

1.4 Explanation of Component Structure

  • @Component Decorator: This is a function that marks the class as an Angular component. It takes a configuration object with properties such as selector, templateUrl, and styleUrls.
  • Selector: This is the name of the HTML tag used to include this component in other components or HTML pages.
  • TemplateUrl: This points to the external HTML file that defines the layout and structure of the component’s view.
  • StyleUrls: These are the paths to the CSS files that define the appearance of the component.
  • Class: The CounterComponent class defines the component’s data and methods. In this case, count holds the state of the counter, and the methods increment and decrement change this state.
  • Data Binding: In the template, the {{ count }} syntax uses interpolation to display the current value of count.
  • Event Binding: The (click) syntax binds the button click events to the corresponding methods of the class.

1.5 Using the Component

After defining the component, it can be used in any other Angular component or HTML page by adding the selector as a tag:

<app-counter></app-counter>

This tag integrates the CounterComponent with its functionality and styling into the interface.

The concept of components is powerful because it allows you to break down the user interface into smaller, reusable parts that can be developed and tested independently.

2. Module

Angular Modules are a fundamental structure in Angular that allows organizing an application into coherent functional blocks. An Angular module is essentially a context where a group of components, directives, services, and other code files are gathered together to perform a specific task within the application. Here is a detailed explanation of modules in Angular:

2.1 NgModule Decorator

Every Angular module is a class annotated with the @NgModule decorator. This decorator marks the class as an Angular module and takes a configuration object that defines the relationships with other parts of the application. Here are the main components of this object:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { FormsModule } from '@angular/forms'; // For ngModel and forms
import { HttpClientModule } from '@angular/common/http'; // For HTTP requests

@NgModule({
  declarations: [
    AppComponent // Lists all components, directives, and pipes that belong to this module.
  ],
  imports: [
    BrowserModule, // Imports modules required for browser execution.
    FormsModule,   // Enables functionalities like ngModel.
    HttpClientModule // Enables the use of HttpClient service for HTTP requests.
  ],
  providers: [], // Defines services that will be used by components within this module.
  bootstrap: [AppComponent] // Starts the application with the AppComponent.
})
export class AppModule { }

2.2 Main Areas of an Angular Module

  • declarations: Contains the list of components, directives, and pipes that belong to this module. All these elements are visible within the module and only here, unless they are exported through the exports field.
  • imports: Contains other modules whose exported classes are needed in the components of the current module. For example, BrowserModule is needed in almost every root module because it enables applications to run in a browser.
  • providers: Lists the services used by components within the module. If you add services here, they are available to all components of the module.
  • bootstrap: Specifies the root component that Angular should load at application start. This is usually only needed in the root module.
  • exports: Defines which components, directives, or pipes should be visible and usable to other modules that import this module.

2.3 Feature Modules

For larger applications, it is common to organize specific functionalities into feature modules that are then imported by the main module. This helps to keep the application modular, maintainable, and scalable. An example of a feature module might be:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { LoginComponent } from './login.component';

@NgModule({
  declarations: [LoginComponent],
  imports: [CommonModule], // CommonModule contains many useful directives like ngIf and ngFor.
  exports: [LoginComponent] // Makes LoginComponent visible to other modules.
})
export class LoginModule { }

2.4 Benefits of Angular Modules

  • Organization: Modules provide a clear structuring and partitioning of application logic, making maintenance easier.
  • Reusability: By defining feature modules, parts of the application can be easily reused in other projects.
  • Lazy Loading: Modules can be configured to load only when needed, improving the initial load time of the application.

Modules are a powerful tool in Angular and essential for the development of larger, well-structured applications.

3. Template

A template in Angular is an HTML view layout that includes additional Angular-specific syntax elements such as data bindings, directives, and pipes. These elements allow you to create dynamic and reactive user interfaces.

3.1 Interpolation and Property Binding

Interpolation and Property Binding are used to bind data from the component to the view template.

Interpolation:

<!-- Example of Interpolation -->
<p>My name is {{ name }}</p>

Property Binding:

<!-- Example of Property Binding -->
<img [src]="userImageUrl">

Interpolation (with {{ }}) is used for simple text replacement, while Property Binding (with [ ]) allows dynamic setting of HTML element properties.

3.2 Event Binding

Event Binding allows communication from the view (template) to the component by handling DOM events like clicks, keyboard inputs, etc.

<!-- Example of Event Binding -->
<button (click)="save()">Save</button>

Here, the click event of the button is bound to the save() method of the component.

3.3 Two-Way Data Binding

Two-Way Data Binding allows bidirectional data binding, where both the view and the component are synchronized.

<!-- Example of Two-Way Data Binding with ngModel (requires FormsModule or ReactiveFormsModule) -->
<input [(ngModel)]="username">

By using [(ngModel)], the value of the input field is directly bound to the username property of the component and vice versa.

3.4 Structural Directives

<!-- Structural Directive ngIf and ngFor -->
<p *ngIf="user.isAdmin">Admin Area</p> <!-- Displays the paragraph only if `user.isAdmin` is true. -->
<div *ngFor="let log of logs">{{ log.message }}</div> <!-- Creates a `<div>` for each entry in `logs`. -->

<!-- Attribute Directive ngStyle and ngClass -->
<div [ngStyle]="{ 'font-size': '12px' }">Small Text</div> <!-- Applies the CSS style `font-size: 12px` to the `<div>`. -->
<div [ngClass]="{ 'highlight': isHighlighted }">Highlighted Text</div> <!-- Adds the CSS class `highlight` if `isHighlighted` is true. -->

Structural directives change the structure of the DOM by adding, removing, and manipulating elements. The most common are *ngIf, *ngFor, and *ngSwitch.

ngIf:

<!-- Displays the <div> only if 'isLoggedIn' is true -->
<div *ngIf="isLoggedIn">Welcome back!</div>

ngFor:

<!-- Iterates over an array of users and creates an <li> element for each user -->
<ul>
  <li *ngFor="let user of users">{{ user.name }}</li>
</ul>

3.5 Attribute Directives

Attribute Directives change the behavior or appearance of DOM elements.

<!-- Example of a custom directive that changes the appearance -->
<p [appHighlight]="color">Highlighted Text</p>

In this example, appHighlight could be a directive that colors the background of the <p> element based on the color property.

3.6 Pipes

Pipes are simple functions used in templates to transform or format data.

<!-- Example of using a pipe to format a date -->
<p>Today is {{ today | date:'longDate' }}</p>

Pipes can also be chained together to perform complex data manipulations.

Templates in Angular are extremely powerful and provide many ways to handle dynamic data processing and event handling directly in HTML code. They facilitate the development of interactive applications by cleanly separating application logic and user interface structure.

4. Services

A service in Angular is a class with a narrow, well-defined purpose. It is usually responsible for tasks such as fetching data from the server, performing calculations, or interacting with an API. Services can be reused throughout the application.

Example of a simple service:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root' // The service is available in the root injector and therefore app-wide singleton.
})
export class DataService {
  constructor(private http: HttpClient) {}

  fetchData(): Observable<any> {
    return this.http.get('https://api.example.com/data');
  }
}

In this example, we have a DataService that is responsible for accessing external data via HTTP. By declaring it with @Injectable({ providedIn: 'root' }), this service is treated as a singleton by Angular and made available in the root injector of the application.

5. Dependency Injection (DI)

Dependency Injection is a design pattern that Angular uses to link classes (typically services) together. DI allows dependencies (e.g., services) to be injected into a class (e.g., a component or another service) from the outside instead of instantiating them directly within the class. This promotes modularity and testability of the application.

Example of using DI in a component:

import { Component, OnInit } from '@angular/core';
import { DataService } from './data.service';

@Component({
  selector: 'app-data-consumer',
  template: `<div *ngIf="data">{{ data | json }}</div>`
})
export class DataConsumerComponent implements OnInit {
  data: any;

  constructor(private dataService: DataService) {} // DataService is injected here

  ngOnInit() {
    this.dataService.fetchData().subscribe({
      next: data => this.data = data,
      error: err => console.error(err)
    });
  }
}

In this example, the DataService is injected into the DataConsumerComponent through the constructor. Angular takes care of creating an instance of DataService and making it available to the component when needed.

Advantages of Dependency Injection in Angular

  1. Maintainability: Services can be easily swapped or modified without changing the components that use them.
  2. Testability: With DI, it is easy to inject mock objects or alternative implementations for services during testing.
  3. Modularity: DI promotes a clean separation of responsibilities within the application. Components only handle the presentation of the user interface, while services handle the business logic.

Dependency Injection in Angular is a powerful tool that simplifies the development of large and complex applications while keeping the code clean, maintainable, and testable.

6. Observables

An Observable can be thought of as a collection of future values or events. Subscribers can “subscribe” to these data streams and react when values are emitted, an error occurs, or the stream completes. Observables are especially useful for handling asynchronous data sources such as data fetches from a server, user inputs, or other time-based events.

Naming Conventions for Observables:

  • With $ Suffix: It is a common practice to mark Observable variables with a $ suffix, e.g., user$, data$. This helps to easily identify them as Observables in the code.
  • Descriptive Names: The name before the $ should describe what the Observable represents, e.g., userData$ for user data, clickEvents$ for click events.

6.1 Creating an Observable:

import { Observable } from 'rxjs';

// Creating a new Observable that emits incremental numbers
const observable = new Observable(subscriber => {
  let count = 1;
  const interval = setInterval(() => {
    subscriber.next(count++);
    if (count > 5) {
      subscriber.complete();
    }
  }, 1000);

  // Cleanup function
  return () => {
    clearInterval(interval);
  };
});

6.2 Subscribing to an Observable:

// Subscribing to the Observable
const subscription = observable.subscribe({
  next(x) { console.log('Next value: ' + x); },
  error(err) { console.error('An error occurred: ' + err); },
  complete() { console.log('Completed'); }
});

// Unsubscribing from the Observable
setTimeout(() => {
  subscription.unsubscribe();
}, 7000);

6.3 Application in Angular

In Angular, Observables are often used with services to fetch data:

Service:

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) { }

  getData(): Observable<any> {
    return this.http.get('https://api.example.com/data');
  }
}

Component:

import { Component, OnInit } from '@angular/core';
import { DataService } from './data.service';

@Component({
  selector: 'app-example',
  template: `<div *ngIf="data$ | async as data">
               Data: {{ data | json }}
             </div>`
})
export class ExampleComponent implements OnInit {
  data$: Observable<any>;

  constructor(private dataService: DataService) { }

  ngOnInit() {
    this.data$ = this.dataService.getData();
  }
}

7. State Management

Here is a list of state management options in Angular, sorted by simplicity:

7.1 Embedded State Variables

  • Description: Use local variables within the component. Use Input and Output decorators to share data between parent and child components.

  • Advantages: Very easy to implement, no additional dependencies.

  • Disadvantages: Can become complex with deeply nested component structures.

  • Example:

    export class MyComponent {
      counter: number = 0;
    
      increment() {
        this.counter++;
      }
    }
    

Using @Input() and @Output() Decorators

  • Example:

    // Parent component
    export class ParentComponent {
      parentData: string = 'Hello';
    }
    
    // Child component
    @Component({
      selector: 'child-component',
      template: `{{ data }}`
    })
    export class ChildComponent {
      @Input() data: string;
    }
    

7.2 Service-based Approach

  • Description: Use Angular services to store and share the application’s state between components by creating a dedicated service class.
  • Advantages: Easy to implement, no additional dependencies.
  • Disadvantages: Can be hard to manage in large applications, especially when many components are involved.

7.3 Local State Management with RxJS

  • Description: Use RxJS and Subjects/BehaviorSubjects to manage reactive states within a component.

  • Advantages: Reactive programming, easy to test.

  • Disadvantages: Requires understanding of RxJS.

  • Example:

    import { BehaviorSubject } from 'rxjs';
    
    export class MyComponent {
      private counterSubject = new BehaviorSubject<number>(0);
      counter$ = this.counterSubject.asObservable();
    
      increment() {
        this.counterSubject.next(this.counterSubject.value + 1);
      }
    }
    

7.4 MobX

  • Description: A reactive state management tool that focuses on automating state management through decorators.
  • Advantages: Very reactive, minimal boilerplate code, easy integration with Angular.
  • Disadvantages: Not specifically designed for Angular, smaller community.

7.5 NGXS

  • Description: A state management library for Angular focused on simplicity and productivity.
  • Advantages: Easier to learn and use than NgRx, less boilerplate code, good integration with Angular.
  • Disadvantages: Smaller community than NgRx, less powerful for very large applications.

7.6 Akita

  • Description: A state management framework specialized in entity management.
  • Advantages: Simpler API than NgRx, provides a clear structure for managing states, less boilerplate code.
  • Disadvantages: Smaller community compared to NgRx, less documentation.

7.7 NgRx

  • Description: A Redux-like state management for Angular. Uses actions, reducers, and effects.
  • Advantages: Great for large and complex applications, facilitates debugging and testability, offers time-travel debugging.
  • Disadvantages: Steep learning curve, boilerplate code can be overwhelming.

7.8 Component Store (Part of NgRx)

  • Description: A lightweight solution within the NgRx ecosystem, specifically for managing local state in components.
  • Advantages: Less boilerplate than full NgRx, good for component-specific states.
  • Disadvantages: Not as powerful as full NgRx for global states.

Why Angular 18 is Great for Beginners

  1. Simplified Learning Curve Angular 18’s improved documentation and updated tutorials make it easier than ever for beginners to learn the framework. The community has also grown, offering plenty of resources like forums, video tutorials, and example projects to help you along the way.

  2. Robust Ecosystem The Angular ecosystem is vast and well-supported, with a plethora of third-party libraries, tools, and extensions. This robust ecosystem allows beginners to find solutions to common problems quickly and integrate powerful features into their applications with minimal effort.

  3. Strong Community Support A strong, active community is one of Angular’s biggest assets. Whether you’re facing a bug or need advice on best practices, the Angular community is always ready to help. Platforms like Stack Overflow, GitHub, and various social media groups provide ample support and learning opportunities.

  4. Comprehensive Tooling Angular 18 comes with a comprehensive suite of tools that streamline the development process. From the Angular CLI to development servers and testing utilities, everything you need to build, test, and deploy your application is included, reducing the complexity for beginners.

Getting Started with Angular 18

If you’re ready to dive into Angular 18, here are some steps to get you started:

Install Node.js:

Before you can start using Angular, you’ll need to install Node.js and npm. You can download and install them from the official Node.js website.

Install Angular CLI:

Once Node.js is installed, you can install the latest Angular CLI by running the following command in your terminal:

npm install -g @angular/cli@latest

Create a New Angular Project:

Use the CLI to create a new Angular project by running:

ng new my-angular-app [--standalone=false] [--routing=true]

The “—standalone=false” transfer parameter is importatnt to get an ng-modules based application (with a modules.ts file). The “—routing=true” parameter is important if you need the app-routing.module.ts file. If you want change the behaviour for different environments, the following command is also important, but first we want to navigate into the project folder:

cd my-angular-app
ng g environments

Run the Development Server:

Navigate to your project directory and start the development server:

ng serve

Open your browser and navigate to http://localhost:4200/ to see your new Angular app in action.

Explore the Documentation:

The official Angular documentation is an invaluable resource. Spend some time exploring the guides and tutorials to get a solid understanding of the framework.