Angular

Angular tutorial

Chapter 1 - Understanding the Basics of Angular:

Before you start learning Angular, it's crucial to have a solid understanding of the core web technologies: HTML, CSS, and JavaScript.

Example: Basic HTML, CSS, and JavaScript

<!DOCTYPE html>

<html lang="en">

<head>

 <meta charset="UTF-8">

 <meta name="viewport" content="width=device-width, initial-scale=1.0">

 <title>My Website</title>

 <link rel="stylesheet" href="styles.css">

</head>

<body>

 <h1>Hello, World!</h1>

 <p>This is a paragraph.</p>

 <script src="script.js"></script>

</body>

</html>

Angular is built with TypeScript, a superset of JavaScript. TypeScript adds optional static typing, classes, interfaces, and other features to JavaScript, making it more robust and maintainable. Familiarizing yourself with TypeScript is essential when working with Angular.

Example: Basic TypeScript syntax

 // Define a function that greets a person

function greet(name: string): string {

 return `Hello, ${name}!`;

}


let message: string = greet("John");

console.log(message); // Output: Hello, John!


Chapter 2 - Create Your First Angular App:

Setting up your development environment for Angular involves installing Node.js, npm (Node Package Manager), and the Angular CLI (Command Line Interface). Below are detailed descriptions and examples for each step:

Node.js is a JavaScript runtime built on Chrome's V8 JavaScript engine. npm is a package manager for JavaScript. Angular requires Node.js and npm to be installed on your system.

Description: Node.js provides the runtime environment for executing JavaScript code outside a web browser. npm is used to manage packages and dependencies for your projects.

Example:

node -v

npm -v

This will display the versions of Node.js and npm installed on your system.

Angular CLI is a command-line interface tool that helps you create and manage Angular projects. It provides commands for generating components, services, modules, and more.

Description: Installing Angular CLI globally allows you to use Angular CLI commands from any directory in your terminal or command prompt.

Example:

npm install -g @angular/cli

ng version

This will display the version of Angular CLI installed on your system.

Chapter 3 Create Your First Angular App:

Angular CLI is a powerful tool that simplifies the process of creating and managing Angular projects. You can use it to generate boilerplate code, create components, services, modules, and more.

Description: Angular CLI provides a convenient command to generate a new Angular project with all the necessary files and configurations.

Example:

ng new my-angular-app

This command creates a new Angular project named my-angular-app in a directory with the same name. Angular CLI will prompt you to choose additional features like routing and styling options during project creation.

After creating the project, you need to navigate into the project directory using the terminal or command prompt.

Description: Changing directory (cd) into the project directory allows you to access and work with the files and folders of your Angular project.

Example: 

cd my-angular-app

This command changes the current directory to my-angular-app, where you'll find all the files and folders generated by Angular CLI for your project.

Once you're inside your project directory, you can start the development server using Angular CLI. This will compile your Angular app and serve it locally on a development server.

Description: The development server provided by Angular CLI allows you to view and interact with your Angular app in a web browser.

Example:

ng serve --open

The ng serve command compiles the Angular app and starts the development server. The --open option automatically opens your app in the default web browser after the server starts.

Chapter 4 - Learn Angular Concepts:

Let's dive into the concepts of Angular, including detailed descriptions and examples for each:

1. Components:

Description:

Components are the building blocks of Angular applications. Each component consists of a TypeScript class that contains the component's logic and properties, and an HTML template that defines the component's view. Components encapsulate specific functionality and UI elements, making them reusable and modular.

Example:

// app.component.ts

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


@Component({

 selector: 'app-root',

 templateUrl: './app.component.html',

 styleUrls: ['./app.component.css']

})

export class AppComponent {

 title = 'My Angular App';

}


<!-- app.component.html -->

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

2. Templates:

Description:

Angular templates define the UI of your application using HTML with Angular directives. Templates can contain data bindings, event bindings, structural directives, and other Angular features to create dynamic and interactive views.

Example:

<!-- app.component.html -->

<div>

 <h1>Welcome {{ name }}</h1>

 <button (click)="sayHello()">Say Hello</button>

</div>


3. Modules:

Description:

Angular modules help organize your application into cohesive blocks of functionality. Each Angular application has at least one module, the root module. Modules can contain components, services, directives, and other application-related code.

Example:

// app.module.ts

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

import { BrowserModule } from '@angular/platform-browser';

import { AppComponent } from './app.component';


@NgModule({

 declarations: [

   AppComponent

 ],

 imports: [

   BrowserModule

 ],

 providers: [],

 bootstrap: [AppComponent]

})

export class AppModule { }

4. Services:

Description:

Services are reusable components that encapsulate business logic and data. They provide a way to share data and functionality across components in an Angular application. Services can be injected into other components or services using Angular's dependency injection system.

Example:

// data.service.ts

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


@Injectable({

 providedIn: 'root'

})

export class DataService {

 getData(): string {

   return 'Data from DataService';

 }

}

5. Data Binding:

Description:

Data binding in Angular allows you to connect your application's data to the UI. There are different types of data binding in Angular:

Example:

 <!-- Interpolation -->

<p>{{ title }}</p>


<!-- Property binding -->

<img [src]="imageUrl">


<!-- Event binding -->

<button (click)="handleClick()">Click me</button>


<!-- Two-way binding -->

<input [(ngModel)]="name">

6. Directives:

Description:

Directives in Angular are markers on a DOM element that tell Angular to do something to that element or its children. Angular comes with built-in directives like ngIf, ngFor, and ngSwitch, which are used to add or remove elements from the DOM based on certain conditions. You can also create custom directives to extend the functionality of Angular.

Example:


<!-- Built-in directives -->

<div *ngIf="isLoggedIn">Welcome, {{ username }}</div>

<ul>

 <li *ngFor="let item of items">{{ item }}</li>

</ul>


<!-- Custom directive -->

<appCustomDirective></appCustomDirective>

Chapter 5 - Routing and Navigation

Angular Router is a powerful tool for building single-page applications (SPAs) with multiple views. It allows developers to define routes for different components of an application and navigate between them without reloading the entire page. This enhances user experience and performance by providing a seamless transition between views.

Here's a detailed description and examples for Angular Routing and Navigation:

1. Configuring Routes:

To start using Angular Router, you need to set up routes in your application. This involves importing RouterModule from @angular/router and defining an array of route configurations.

Example:

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

import { RouterModule, Routes } from '@angular/router';

import { HomeComponent } from './home.component';

import { AboutComponent } from './about.component';


const routes: Routes = [

 { path: '', component: HomeComponent },

 { path: 'about', component: AboutComponent }

];


@NgModule({

 imports: [RouterModule.forRoot(routes)],

 exports: [RouterModule]

})

export class AppRoutingModule { }


In this example, we have two routes configured: one for the home component (HomeComponent) and one for the about component (AboutComponent). The empty path '' corresponds to the default route.

2. Navigating Between Views:

Once routes are configured, you can navigate between views programmatically or using Angular directives.

Example:

<!-- app.component.html -->

<nav>

 <a routerLink="/">Home</a>

 <a routerLink="/about">About</a>

</nav>

<router-outlet></router-outlet>

In this example, routerLink directive is used to navigate to different views when the respective links are clicked. The router-outlet directive is a placeholder that Angular Router uses to render the component associated with the current route.

3. Passing Parameters to Routes:

You can pass parameters to routes to customize behavior or content based on dynamic data.

Example:

// app.module.ts

const routes: Routes = [

 { path: 'product/:id', component: ProductDetailComponent }

];


// product-detail.component.ts

import { ActivatedRoute } from '@angular/router';


export class ProductDetailComponent {

 constructor(private route: ActivatedRoute) {

   this.route.params.subscribe(params => {

     console.log(params['id']); // Access the parameter value

   });

 }

}


In this example, the route product/:id expects a parameter id, which is then accessed in the ProductDetailComponent using ActivatedRoute. The parameter value can be retrieved using params['id'].

Angular Router provides many more features such as route guards, lazy loading, and child routes, which further enhance the flexibility and functionality of single-page applications. Mastering Angular Router is essential for building robust and dynamic web applications.

4. AuthGuard:

// auth.guard.ts

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

import { CanActivate, Router } from '@angular/router';

import { AuthService } from './auth.service';


@Injectable({

 providedIn: 'root'

})

export class AuthGuard implements CanActivate {

 constructor(private authService: AuthService, private router: Router) {}


 canActivate(): boolean {

   if (this.authService.isLoggedIn()) {

     return true;

   } else {

     this.router.navigate(['/login']); // Redirect to login page if not authenticated

     return false;

   }

 }

}

// auth.service.ts

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


@Injectable({

 providedIn: 'root'

})

export class AuthService {

 isLoggedIn(): boolean {

   // Check if user is logged in

   // You can implement your authentication logic here

   return true; // Example: Always return true for demonstration purposes

 }

}

// app.module.ts

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

import { RouterModule, Routes } from '@angular/router';

import { HomeComponent } from './home.component';

import { LoginComponent } from './login.component';

import { AuthGuard } from './auth.guard';


const routes: Routes = [

 { path: '', component: HomeComponent, canActivate: [AuthGuard] },

 { path: 'login', component: LoginComponent },

];


@NgModule({

 imports: [RouterModule.forRoot(routes)],

 exports: [RouterModule]

})

export class AppRoutingModule { }

In this example, the AuthGuard is added to the route configuration for the home path (''). This means that before accessing the home component, the canActivate method of the AuthGuard will be called. If canActivate returns true, the navigation will proceed. If it returns false, the navigation will be redirected to the login page.

Summary:

AuthGuard is a powerful tool in Angular for controlling access to routes based on authentication status. By implementing an AuthGuard service and adding it to the route configurations, developers can ensure that certain parts of the application are only accessible to authenticated users. This helps in building secure and robust web applications.

Chapter 6 - HttpClient

Angular's HttpClient module provides a simplified API for making HTTP requests in Angular applications. It is part of @angular/common/http package. This module simplifies the process of sending asynchronous HTTP requests and processing the responses. Below is a detailed description along with examples for using Angular's HttpClient module:

Description:

Examples:

1. Getting Data (GET Request):

import { HttpClient } from '@angular/common/http';


export class DataService {

 constructor(private http: HttpClient) {}


 getData() {

   return this.http.get<any[]>('https://api.example.com/data');

 }

}

2. Posting Data (POST Request):

import { HttpClient } from '@angular/common/http';


export class DataService {

 constructor(private http: HttpClient) {}


 postData(data: any) {

   return this.http.post<any>('https://api.example.com/data', data);

 }

}

3. Updating Data (PUT Request):

import { HttpClient } from '@angular/common/http';


export class DataService {

 constructor(private http: HttpClient) {}


 updateData(id: number, newData: any) {

   return this.http.put<any>(`https://api.example.com/data/${id}`, newData);

 }

}

4. Deleting Data (DELETE Request):

import { HttpClient } from '@angular/common/http';


export class DataService {

 constructor(private http: HttpClient) {}


 deleteData(id: number) {

   return this.http.delete<any>(`https://api.example.com/data/${id}`);

 }

}

5. Handling Responses:

import { HttpClient } from '@angular/common/http';


export class DataService {

 constructor(private http: HttpClient) {}


 getData() {

   return this.http.get<any[]>('https://api.example.com/data').subscribe(

     (response) => {

       console.log('Response:', response);

       // Process response data here

     },

     (error) => {

       console.error('Error:', error);

       // Handle error

     }

   );

 }

}

6. Error Handling:

import { HttpClient } from '@angular/common/http';


export class DataService {

 constructor(private http: HttpClient) {}


 getData() {

   return this.http.get<any[]>('https://api.example.com/data').subscribe(

     (response) => {

       console.log('Response:', response);

       // Process response data here

     },

     (error) => {

       if (error.status === 404) {

         console.error('Resource not found:', error);

       } else {

         console.error('An error occurred:', error);

       }

       // Handle error

     }

   );

 }

}

By utilizing Angular's HttpClient module, you can easily perform CRUD operations with a backend server in your Angular applications. Remember to import HttpClientModule in your application's module, and then inject HttpClient wherever you need to make HTTP requests.

Chapter 7 - Forms

Angular provides two main approaches for handling forms: template-driven forms and reactive forms.

Template-driven Forms:

Template-driven forms are based on directives that allow you to build forms in the template file (HTML). This approach is suitable for simple forms with minimal validation requirements.

Creating a Template-driven Form:

 <form #myForm="ngForm" (ngSubmit)="onSubmit(myForm)">

   <input type="text" name="username" ngModel required>

   <input type="email" name="email" ngModel email required>

   <button type="submit">Submit</button>

 </form>

Validating Template-driven Forms:

 <div *ngIf="myForm.controls['username'].invalid && (myForm.controls['username'].dirty || myForm.controls['username'].touched)">

   <div *ngIf="myForm.controls['username'].errors.required">Username is required.</div>

 </div>

 <div *ngIf="myForm.controls['email'].invalid && (myForm.controls['email'].dirty || myForm.controls['email'].touched)">

   <div *ngIf="myForm.controls['email'].errors.required">Email is required.</div>

   <div *ngIf="myForm.controls['email'].errors.email">Invalid email format.</div>

 </div>

Reactive Forms:

Reactive forms are based on explicit control of the form model in the component class. This approach is more flexible and scalable, especially for complex forms with dynamic requirements.

Creating a Reactive Form:

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

import { FormBuilder, FormGroup, Validators } from '@angular/forms';


@Component({

 selector: 'app-my-form',

 templateUrl: './my-form.component.html',

 styleUrls: ['./my-form.component.css']

})

export class MyFormComponent implements OnInit {

 myForm: FormGroup;


 constructor(private formBuilder: FormBuilder) { }


 ngOnInit() {

   this.myForm = this.formBuilder.group({

     username: ['', Validators.required],

     email: ['', [Validators.required, Validators.email]]

   });

 }


 onSubmit() {

   if (this.myForm.valid) {

     // Form submission logic

   }

 }

}

Using Reactive Form in Template:

 <form [formGroup]="myForm" (ngSubmit)="onSubmit()">

   <input type="text" formControlName="username">

   <div *ngIf="myForm.get('username').invalid && myForm.get('username').dirty">

     <div *ngIf="myForm.get('username').errors.required">Username is required.</div>

   </div>

   <input type="email" formControlName="email">

   <div *ngIf="myForm.get('email').invalid && myForm.get('email').dirty">

     <div *ngIf="myForm.get('email').errors.required">Email is required.</div>

     <div *ngIf="myForm.get('email').errors.email">Invalid email format.</div>

   </div>

   <button type="submit">Submit</button>

 </form>

Chapter 8 - RxJS and Observables:

RxJS (Reactive Extensions for JavaScript) is a library for reactive programming using observables that makes it easier to compose asynchronous or callback-based code. It provides a powerful toolset for handling events, asynchronous operations, and managing data streams in a reactive manner. In Angular, RxJS is extensively used to handle various asynchronous operations such as HTTP requests, user inputs, and timer events.

Observables are the core building blocks of RxJS. They represent asynchronous data streams and allow you to subscribe to them to receive values over time. Observables can emit multiple values asynchronously and can also emit errors or completion notifications. They enable you to write reactive code by providing operators to transform, filter, combine, and handle asynchronous data streams.

Here's a detailed description and examples of using Angular RxJS and Observables:

import { Observable } from 'rxjs';


 // Creating a simple Observable that emits numbers

 const observable = new Observable<number>(subscriber => {

   subscriber.next(1);

   subscriber.next(2);

   subscriber.next(3);

   subscriber.complete();

 });

  // Subscribing to the Observable

 observable.subscribe({

   next: value => console.log(value),

   complete: () => console.log('Observable completed')

 });

import { Observable } from 'rxjs';


// Simulating an asynchronous operation (e.g., HTTP request)

const fetchData = (): Observable<string> => {

 return new Observable<string>(subscriber => {

   setTimeout(() => {

     subscriber.next('Data received');

     subscriber.complete();

   }, 2000);

 });

};


// Subscribing to the Observable

fetchData().subscribe({

 next: value => console.log(value),

 complete: () => console.log('Data fetching completed')

});

import { of } from 'rxjs';

import { map, filter } from 'rxjs/operators';


// Creating an Observable of numbers

const observable = of(1, 2, 3, 4, 5);


// Using operators to transform and filter the data stream

observable

 .pipe(

   map(value => value * 2), // Multiply each value by 2

   filter(value => value > 5) // Filter values greater than 5

 )

 .subscribe({

   next: value => console.log(value),

   complete: () => console.log('Observable completed')

 });

import { Observable } from 'rxjs';


// Simulating an Observable that emits an error

const observableWithError = new Observable<number>(subscriber => {

 try {

   subscriber.next(1);

   throw new Error('Something went wrong');

   subscriber.complete();

 } catch (error) {

   subscriber.error(error);

 }

});


// Subscribing to handle errors

observableWithError.subscribe({

 next: value => console.log(value),

 error: err => console.error('Error:', err),

 complete: () => console.log('Observable completed')

});

These examples demonstrate how to create Observables, handle asynchronous operations, use operators to transform data streams, and handle errors. RxJS and Observables play a crucial role in Angular applications, enabling developers to write reactive and efficient code for handling asynchronous operations.

Chapter 9 - Testing

Testing Angular applications involves using various tools and frameworks to ensure the correctness and reliability of the code. The two main types of testing used in Angular development are unit testing and end-to-end testing.

Unit Testing:

Unit testing involves testing individual units or components of the Angular application in isolation to verify that they behave as expected. In Angular, unit tests are typically written using Jasmine, a behavior-driven development framework for JavaScript.
Example:

// app.component.ts

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


@Component({

 selector: 'app-root',

 template: `<h1>{{ greeting }}</h1>`

})

export class AppComponent {

 greeting: string = 'Hello, world!';

}

We can write a unit test for this component using Jasmine as follows:


// app.component.spec.ts

import { TestBed, ComponentFixture } from '@angular/core/testing';

import { AppComponent } from './app.component';


describe('AppComponent', () => {

 let fixture: ComponentFixture<AppComponent>;

 let component: AppComponent;


 beforeEach(() => {

   TestBed.configureTestingModule({

     declarations: [AppComponent],

   });

   fixture = TestBed.createComponent(AppComponent);

   component = fixture.componentInstance;

 });


 it('should create the app', () => {

   expect(component).toBeTruthy();

 });


 it('should display the correct greeting message', () => {

   fixture.detectChanges();

   const compiled = fixture.nativeElement;

   expect(compiled.querySelector('h1').textContent).toContain('Hello, world!');

 });

});

In this example, we're testing if the AppComponent is created successfully and if it displays the correct greeting message.

End-to-End Testing:

End-to-end testing involves testing the complete flow of the application, including interactions between various components, services, and modules. In Angular, end-to-end tests are typically written using Protractor, an end-to-end testing framework for Angular and AngularJS applications.
Example:

// contact-form.component.ts

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


@Component({

 selector: 'app-contact-form',

 template: `

   <form (ngSubmit)="onSubmit()">

     <input type="text" name="name" [(ngModel)]="name" placeholder="Name">

     <input type="email" name="email" [(ngModel)]="email" placeholder="Email">

     <button type="submit">Submit</button>

   </form>

 `

})

export class ContactFormComponent {

 name: string = '';

 email: string = '';


 onSubmit() {

   // Submit logic

 }

}

We can write an end-to-end test for this component using Protractor as follows:

// contact-form.e2e-spec.ts

import { browser, by, element } from 'protractor';


describe('ContactFormComponent', () => {

 beforeEach(() => {

   browser.get('/contact');

 });


 it('should display form fields', () => {

   expect(element(by.css('input[name="name"]')).isPresent()).toBeTruthy();

   expect(element(by.css('input[name="email"]')).isPresent()).toBeTruthy();

   expect(element(by.css('button[type="submit"]')).isPresent()).toBeTruthy();

 });


 it('should submit the form with valid data', () => {

   element(by.css('input[name="name"]')).sendKeys('John Doe');

   element(by.css('input[name="email"]')).sendKeys('john@example.com');

   element(by.css('button[type="submit"]')).click();


   // Add assertions for successful submission

 });

});

Chapter 10: Explore Advanced Topics:

Angular CLI:

Angular CLI (Command Line Interface) is a powerful tool for initializing, developing, and maintaining Angular applications. It simplifies the process of creating components, services, modules, etc., by providing commands that automate these tasks. Here are some key features and examples:

ng generate component my-component

ng generate service my-service

ng generate module my-module

State Management with NgRx:

NgRx is a popular state management library for Angular applications inspired by Redux. It provides a predictable state container pattern to manage the state of your application in a single immutable store. 

State management in Angular with NgRx involves managing the application's state using the principles of Redux, a predictable state container pattern. NgRx provides a set of libraries that help in organizing and managing the state of your Angular application efficiently. Here's a detailed explanation along with examples:

Actions are plain JavaScript objects that represent unique events or user actions in your application. They describe what happened in your application and carry any necessary payload data. Actions are dispatched to the store, where reducers respond to them by updating the application state.

Example of defining an action:

import { createAction, props } from '@ngrx/store';


export const increment = createAction('[Counter Component] Increment');

export const decrement = createAction('[Counter Component] Decrement');

export const reset = createAction('[Counter Component] Reset');


export const customAction = createAction(

 '[Custom Component] Custom Action',

 props<{ payload: any }>()

);


Reducers are pure functions that take the current state and an action as arguments and return a new state. Reducers are responsible for updating the state of your application based on the actions dispatched.

Example of defining a reducer:

import { createReducer, on } from '@ngrx/store';

import { increment, decrement, reset } from './counter.actions';


export const initialState = 0;


const _counterReducer = createReducer(

 initialState,

 on(increment, state => state + 1),

 on(decrement, state => state - 1),

 on(reset, state => 0)

);


export function counterReducer(state, action) {

 return _counterReducer(state, action);

}


Selectors are pure functions used to extract specific pieces of state from the store. They provide an optimized way to access the state and derive data from it.

Example of defining a selector:

import { createSelector } from '@ngrx/store';

import { AppState } from './app.state';


export const selectCounter = (state: AppState) => state.counter;


export const selectCounterValue = createSelector(

 selectCounter,

 counter => counter

);


Effects are used for managing side effects such as asynchronous operations (e.g., HTTP requests). They listen for actions dispatched to the store, perform side effects, and then dispatch new actions to update the state.

Example of defining an effect:

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

import { Actions, ofType, createEffect } from '@ngrx/effects';

import { mergeMap, map, catchError } from 'rxjs/operators';

import { of } from 'rxjs';

import { DataService } from './data.service';

import * as DataActions from './data.actions';


@Injectable()

export class DataEffects {


 loadData$ = createEffect(() => this.actions$.pipe(

   ofType(DataActions.loadData),

   mergeMap(() => this.dataService.getData()

     .pipe(

       map(data => DataActions.loadDataSuccess({ data })),

       catchError(error => of(DataActions.loadDataFailure({ error })))

     ))

   )

 );


 constructor(

   private actions$: Actions,

   private dataService: DataService

 ) {}

}

NgRx helps in managing complex state interactions, sharing data between components efficiently, and maintaining a unidirectional data flow within your Angular application.

The store is where your application's state resides. It holds the entire state tree of your application. State can only be modified by dispatching actions to the store, and state changes are handled by reducers.

Example of initializing the store:

import { ActionReducerMap } from '@ngrx/store';

import { counterReducer } from './counter.reducer';

import { CounterState } from './counter.state';


export interface AppState {

 counter: CounterState;

}


export const reducers: ActionReducerMap<AppState> = {

 counter: counterReducer

};

Putting It All Together:

Server-Side Rendering (SSR):

Server-Side Rendering (SSR) involves rendering web pages on the server rather than on the client's browser. It can greatly improve the performance and SEO (Search Engine Optimization) of your Angular application. Here's how you can implement SSR in Angular: