FeedbackArticles

The Angular Manual

Angular is a popular open-source web application framework developed by Google, which simplifies the development and testing of single-page applications by providing a component-based architecture, two-way data binding, and dependency injection.

AngularJS is the first version of the Angular framework, using a directive-based approach. Angular (versions 2 and above) is a complete rewrite, introducing a component-based architecture and improved performance.

Components and Templates

In Angular, components and templates work together to define the user interface of an application.

A component is a TypeScript class that defines the behavior and properties of a part of the user interface. It consists of a decorator, a class, and a template. The decorator is used to mark the class as an Angular component and define its metadata, such as the selector and style URLs. The class contains the logic and properties of the component, while the template defines the structure and appearance of the component's user interface.

A template is an HTML file that defines the structure of the user interface for a component. It can contain static content as well as dynamic data that is bound to the component's properties. Angular uses templates to render the user interface and keep it in sync with the component's state.

Use the command: ng generate component <component-name> to create a new component.

By using that command, you will generate a component.html (view), component.ts (logic) and component.css (styling) files, also the component will be added automatically to the app.module.ts for use in the application.

Run the command: ng generate component my-component you will see the following:

my-component.component.ts:

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

@Component({
 
selector: 'app-my-component',
 
templateUrl: './my-component.component.html',
 
styleUrls: ['./my-component.component.css']
})
export class MyComponentComponent {
 
message: string = 'Hello, world!';
}

my-component.component.html:

<div>
 
<h1>{{ message }}</h1>
 
<p>This is my first Angular component!</p>
</div>

my-component.component.css:

div {
 
background-color: #f0f0f0;
 
padding: 20px;
 
margin: 10px;
 
border-radius: 5px;
}
h1 {
 
font-size: 24px;
 
margin-bottom: 10px;
}

To use the component anywhere in your application you can just reference it in html using the following selector: <app-my-component></app-my-component>

Data Binding

Data binding is a key feature of Angular that allows you to connect your component's data and its template. It allows you to synchronize data between the component class and the HTML template.

There are four types of data binding in Angular:

  1. Interpolation: Allows you to embed expressions in a template string using double curly braces {{ }}, example:
    <h1>{{ title }}</h1>
    In this example, the value of the title property of the component class is interpolated into the h1 element in the template..
  2. Property binding: Allows you to set the value of an HTML element property using square brackets [ ], example:
    <img [src]="imageUrl">
    In this example, the value of the imageUrl property of the component class is bound to the src attribute of the img element.
  3. Event binding: Allows you to respond to user events such as button clicks and form submissions using parentheses ( ), example:
    <button (click)="submitForm()">Submit</button>
    In this example, the submitForm() method of the component class is called when the button is clicked.
  4. Two-way binding: Allows you to bind both the value of an HTML element property and the value of a component property using the ngModel directive, example:

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

In this example, the value of the username property of the component class is bound to the value of the input element, so changes to the input are immediately reflected in the component class and vice versa.

Data binding allows you to create dynamic and interactive user interfaces in Angular. By binding data and events between the component class and the template, you can build complex applications that respond to user input and update dynamically based on changing data.

Directives

A directive is a custom attribute or element that extends the behavior of existing DOM elements. Angular has two types of directives: attribute directives, and structural directives.

Attribute Directives

In Angular, attribute directives are a type of directive that can be used to add behavior to HTML elements or change their appearance. They allow you to modify the behavior or appearance of an element based on the value of an attribute.

Attribute directives are applied to elements as attributes in HTML tags. They are written as a single word with a prefix of "ng", followed by a hyphen, and then the directive name. For example, the built-in ngClass directive is used like this:

<div [ngClass]="{'my-class': condition}">
 ...
</div>

In this example, the ngClass directive is applied to the div element using the [ngClass] attribute binding syntax. The directive adds or removes the my-class class from the element based on the value of the condition expression.

You can also create your own custom attribute directives in Angular. Here's an example:

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

@Directive({
 selector: '[myHighlight]'
})

export
class HighlightDirective {
 
@Input() myHighlight: string;

 
constructor(private el: ElementRef) {}

 ngOnInit() {
   
this.el.nativeElement.style.backgroundColor = this.myHighlight;
 }
}

This code defines a new directive called myHighlight that changes the background color of an element based on the value of its attribute. The @Input decorator is used to create a property on the directive class that can be bound to the attribute value in the HTML.

To use the myHighlight directive, you would add it as an attribute to an element in your template:

<div myHighlight="yellow">
 ...
</
div>

In this example, the myHighlight attribute is added to a div element with the value of "yellow". When the directive is applied, it sets the background color of the element to yellow using the ElementRef service.

Attribute directives are a powerful way to add behavior and styling to your HTML elements in Angular. They allow you to create reusable components that can be easily customized based on the values of their attributes.

Structural Directives

In Angular, structural directives are a type of directive that allow you to conditionally render or remove elements from the DOM based on the value of an expression. They modify the structure of the HTML by adding or removing elements.

There are three built-in structural directives in Angular:

*ngIf: Conditionally renders an element based on the value of an expression.

<div *ngIf="condition">
 ...
</
div>

In this example, the div element is only rendered if the condition expression is truthy.

*ngFor: Renders a set of elements for each item in an array.

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

In this example, a li element is rendered for each item in the items array.

*ngSwitch: Renders one of several elements based on the value of an expression.

<div [ngSwitch]="condition">
 <
div *ngSwitchCase="'A'">
   ...
 </
div>
 <
div *ngSwitchCase="'B'">
   ...
 </
div>
 <
div *ngSwitchDefault>
   ...
 </
div>
</
div>

In this example, one of three div elements is rendered based on the value of the condition expression.

You can also create your own custom structural directives in Angular. Here's an example:

import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';

@Directive({
 selector:
'[myFor]'
})
export class ForDirective {
 
@Input() set myForOf(items: any[]) {
   
for (let item of items) {
     
this.view.createEmbeddedView(this.templateRef, {
       $implicit: item
     });
   }
 }

 
constructor(
   
private templateRef: TemplateRef<any>,
   
private view: ViewContainerRef
 ) {}
}

This code defines a new structural directive called myFor that renders a set of elements for each item in an array. The @Input decorator is used to create a property on the directive class that can be bound to an array in the HTML.

To use the myFor directive, you would add it as an attribute to a container element in your template and use an ng-template element to define the contents of the repeated elements:

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

<ng-template myFor [myForOf]="items">
 
<li>
   
{{ $implicit }}
 
</li>
</ng-template>

In this example, the li element is repeated for each item in the items array using the myFor directive. The contents of the repeated elements are defined in an ng-template element, which is passed to the TemplateRef service and rendered using the ViewContainerRef service.

Structural directives are a powerful way to create dynamic and flexible HTML structures in Angular. They allow you to render and manipulate elements based on complex business logic and data structures, making your application more modular and easier to maintain.

Pipes

A pipe is a function that transforms input data into a desired output format. Angular provides several built-in pipes, such as DatePipe, CurrencyPipe, and JsonPipe, and allows you to create custom pipes.

In this example, we'll create a custom pipe called capitalize that capitalizes the first letter of each word in a given string. Here's how to define the pipe:

Use the following command to create a pipe ng generate pipe <pipe-name>, in this case the pipe name will be “capitalize”:

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
 
name: 'capitalize'
})
export class CapitalizePipe implements PipeTransform {
 transform(
value: string): string {
   
if (!value) {
     
return '';
   }
   
return value.replace(/\b\w/g, (match) => match.toUpperCase());
 }
}

In this code, we import the Pipe and PipeTransform classes from the Angular core module, define the pipe using the @Pipe decorator with the name 'capitalize', and implement the PipeTransform interface with the transform method. The transform method receives a string value and returns a new string with the first letter of each word capitalized.

Add the CapitalizePipe to the declarations array of your AppModule (or another appropriate module).

Now you can use the capitalize pipe in your templates. Here's an example of how to use the pipe in an Angular component template:

<!-- app.component.html -->
<h1>{{ 'hello world' | capitalize }}</h1>

When the application runs, the text inside the <h1> element will be displayed as "Hello World", with the first letter of each word capitalized.

Decorators

Decorators in Angular are a design pattern borrowed from TypeScript that allows you to add metadata to classes, properties, methods, or parameters. Decorators are functions that wrap around or modify a class or a class member, enabling you to extend or alter their behavior. Angular has several built-in decorators that are used to define various aspects of an Angular application, such as components, directives, pipes, and dependency injection.

Here are some examples of commonly used Angular decorators:

@Component decorator: The @Component decorator is used to define an Angular component. It takes a configuration object that specifies properties like the selector, template, styles, and more. The decorator is applied to a TypeScript class that represents the component's behavior.

@Directive decorator: The @Directive decorator is used to create custom attributes or elements that extend the behavior of existing DOM elements. It takes a configuration object with a selector property that defines the directive's trigger.

@Pipe decorator: The @Pipe decorator is used to create custom pipes that transform input data into a desired output format. It takes a configuration object with a name property that defines the pipe's name.

@Injectable decorator: The @Injectable decorator is used to declare a class as injectable, making it available for Angular's dependency injection system. It is usually applied to services, but can also be used for other classes that need to be injected.

@Input and @Output decorators:

The @Input decorator is used to declare a property that can receive data from a parent component, while the @Output decorator is used to define an EventEmitter property that can emit events to a parent component.

Dependency Injection

Angular Dependency Injection (DI) is a design pattern that deals with how components, services, and other parts of an Angular application get their dependencies. Instead of directly instantiating dependencies within components or services, Angular's DI system provides them as needed. This makes the code more modular, maintainable, and testable.

The main benefits of using dependency injection are:

  • Separation of concerns: Components and services focus on their primary responsibility, delegating dependency management to the DI system.
  • Reusability: Services or other dependencies can be shared across multiple components without duplicating code.
  • Testability: Dependencies can be easily replaced with mock implementations during testing, improving test isolation and simplicity.

Here's an example of using dependency injection in an Angular application:

First, let's create a service called LogService that provides simple logging functionality:

// log.service.ts
import { Injectable } from '@angular/core';

@Injectable({
 providedIn:
'root'
})
export class LogService {
 log(message:
string) {
   
console.log(`Log: ${message}`);
 }
}

In this code, we import the Injectable decorator from the Angular core module, apply it to the LogService class, and define a log method. The @Injectable decorator tells Angular that this class can be injected into other classes using dependency injection.

Now, let's create a component called AppComponent that uses the LogService to log a message:

// app.component.ts
import { Component, OnInit } from '@angular/core';
import { LogService } from './log.service';

@Component({
 selector:
'app-root',
 template:
`
   <h1>Dependency Injection Example</h1>
 `
,
})
export class AppComponent implements OnInit {
 
constructor(private logService: LogService) {}

 ngOnInit() {
   
this.logService.log('AppComponent initialized');
 }
}

Add the AppComponent to the declarations array and the LogService to the providers array of your AppModule (or another appropriate module).

Now, when you run the application, you should see the message "Log: AppComponent initialized" in the browser console. The LogService is provided by Angular's dependency injection system and used by the AppComponent to log the message. This demonstrates how Angular's DI system simplifies the management and sharing of dependencies across components and services.

Services

Angular services are singleton classes that encapsulate specific functionality, such as data access, business logic, or shared utilities. Services can be injected into components, directives, or other services, making them a core part of Angular's dependency injection system.

Services help you to achieve separation of concerns, making your application more modular, maintainable, and testable. By encapsulating functionality in a service, you can reuse it across multiple components or other services without duplicating code.

Here's an example of creating an Angular service and using it in a component:

First, let's create a service called UserService that fetches user data using the command ng generate service <service-name>, in this case the service name is “user-service”:

// user.service.ts
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export
class UserService {
 
private users = [
   {
id: 1, name: 'Alice' },
   {
id: 2, name: 'Bob' },
   {
id: 3, name: 'Charlie' },
 ];

 constructor() {}

 getUsers() {
   
return this.users;
 }

 getUserById(
id: number) {
   
return this.users.find(user => user.id === id);
 }
}

In this code, we import the Injectable decorator from the Angular core module, apply it to the UserService class, and define two methods: getUsers and getUserById. The @Injectable decorator tells Angular that this class can be injected into other classes using dependency injection.

Now, let's create a component called UserListComponent that uses the UserService to fetch and display a list of users:

// user-list.component.ts
import { Component, OnInit } from '@angular/core';
import { UserService } from './user.service';

@Component({
 selector:
'app-user-list',
 template:
`
   <ul>
     <li *ngFor="let user of users">{{ user.name }}</li>
   </ul>
 `
,
})
export class UserListComponent implements OnInit {
 users = [];

 
constructor(private userService: UserService) {}

 ngOnInit() {
   
this.users = this.userService.getUsers();
 }
}

In this code, we import the Component and OnInit decorators, the UserService, and define the UserListComponent with a template that displays the list of users. The UserService is injected into the component using the constructor. In the ngOnInit lifecycle hook, we fetch the users from the UserService and store them in the users property.

Add the UserListComponent to the declarations array of your AppModule (or another appropriate module).

Modules and Lazy Loading

Modules

Angular modules are a way to organize and group related components, directives, pipes, and services together. They also help manage and configure aspects of the Angular application, such as dependency injection, lazy loading, and application bootstrapping.

Every Angular application has at least one module, the root module, typically called AppModule. This module is responsible for bootstrapping the application and serves as the entry point.

Angular modules are created using the @NgModule decorator. The @NgModule decorator takes a configuration object with the following properties:

  1. declarations: An array of components, directives, and pipes that belong to this module.
  2. imports: An array of other Angular modules that this module depends on. Imported modules export components, directives, pipes, and services that can be used within the current module.
  3. exports: An array of components, directives, pipes, and modules that should be available for use in other modules that import this module.
  4. providers: An array of services that should be available for dependency injection within this module and its imported modules.
  5. bootstrap: An array of components that should be bootstrapped when the application starts. This is typically used for the root component of the application.

Here's an example of a simple Angular module:

// app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { HeaderComponent } from './header.component';
import { UserService } from './user.service';

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

In this example, we have an AppModule that:

  • Declares two components: AppComponent and HeaderComponent.
  • Imports the BrowserModule, which provides essential services and directives for Angular applications running in a web browser.
  • Registers a UserService for dependency injection.
  • Specifies that the AppComponent should be bootstrapped when the application starts.

By organizing your application into modules, you can better manage the dependencies, improve code organization, and enable features like lazy loading.

To create a module you can use the following command: ng generate module <module-name>.

Lazy loading

Lazy loading is a technique in Angular where specific parts of an application, like feature modules, are loaded on-demand when they are needed, rather than being loaded upfront during the initial application startup. This helps to decrease the initial load time, improve performance, and reduce the overall size of the application bundle.

Angular applications typically use the Angular Router to implement lazy loading. The router is configured to load feature modules only when the user navigates to a specific route.

Here's an example of how to implement lazy loading in an Angular application:

First, let's create a feature module called OrdersModule with a simple OrdersComponent:

// orders.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule, Routes } from '@angular/router';

import { OrdersComponent } from './orders.component';

const routes: Routes = [
 { path:
'', component: OrdersComponent },
];

@NgModule({
 declarations: [OrdersComponent],
 imports: [
   CommonModule,
   RouterModule.forChild(routes),
 ],
})
export class OrdersModule { }

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

@Component({
 selector:
'app-orders',
 template: `
   <h2>Orders<
/h2>
 
`,
})
export class OrdersComponent {}

Next, configure the Angular Router to lazy load the OrdersModule when the user navigates to the /orders route:

// app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

const routes: Routes = [
 {
   path:
'orders',
   loadChildren: () =>
import('./orders/orders.module').then(m => m.OrdersModule)
 },
];

@NgModule({
 imports: [RouterModule.forRoot(routes)],
 exports: [RouterModule]
})
export class AppRoutingModule { }

In this code, we define a route with the loadChildren property, which is a function that uses the dynamic import() syntax to load the OrdersModule when the /orders route is requested. The then() method extracts the module class from the loaded module.

Import the AppRoutingModule in the AppModule:

// app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';

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

Add a router-outlet element in your AppComponent template:

<!-- app.component.html -->
<h1>My Angular App</h1>
<a routerLink="/orders">Go to Orders</a>
<router-outlet></router-outlet>

Now, when you run the application and navigate to the /orders route, the OrdersModule will be loaded lazily, and the OrdersComponent will be displayed. Until the user navigates to the /orders route, the OrdersModule and its components won't be loaded, reducing the initial bundle size and improving the application's startup performance.

Change Detection

In Angular, change detection is the process of detecting changes to the data in a component and updating the view to reflect those changes. Angular uses a technique called "zone.js" to detect changes and update the view automatically.

There are two types of change detection in Angular: Default and OnPush.

Default: In default change detection, Angular automatically detects changes to the component's properties, and updates the view accordingly. This happens in response to user events, timers, HTTP requests, and other asynchronous events. Angular uses a mechanism called the "zone" to detect these changes.

Here's an example of how default change detection works:

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

@Component({
 selector:
'app-root',
 template: `
   <h1>{{ title }}<
/h1>
   <button (click)="changeTitle()">Change Title</
button>
 `
})
export class AppComponent {
 title =
'My App';

 
changeTitle() {
   
this.title = 'New Title';
 }
}

In this example, we've defined an Angular component called AppComponent. It has a property called title, which is initially set to 'My App'. The component also has a method called changeTitle(), which updates the title property to 'New Title' when the button is clicked.

When the changeTitle() method is called, Angular automatically detects the change to the title property and updates the view to reflect the new value. This happens because the component is using default change detection.

OnPush: In OnPush change detection, Angular only detects changes to the component's properties if they are triggered by an @Input() property or an observable. This is a more optimized change detection mechanism and can improve the performance of your application.

Here's an example of how OnPush change detection works:

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

@Component({
 selector:
'app-item',
 template: `
   <div>
     
<h2>{{ title }}</h2>
     <p>{{ description }}<
/p>
     <button (click)="changeTitle()">Change Title</
button>
   
</div>
 `,
 changeDetection: ChangeDetectionStrategy.OnPush
})
export class ItemComponent {
 @Input() title: string;
 @Input() description: string;

 
changeTitle() {
   
this.title = 'New Title';
 }
}

In this example, we've defined an Angular component called ItemComponent. It has two @Input() properties, title and description. The component also has a method called changeTitle(), which updates the title property to 'New Title' when the button is clicked.

Change detection is always triggered by DOM events such as, mouse clicks, however for non DOM events, such as timeouts or intervals, change detection will not be triggered.

The example below shows a case when change detection is not triggered:

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

@Component({

  selector: 'app-root',

  template: `

        <div>{{ message }}</div>

        <button (click)="updateMessage()">Update Message</button>

  `,

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

})

export class AppComponent {

  message = 'Hello';

  updateMessage() {

        setTimeout(() => {

          this.message = 'Hello, Angular'; // Angular won't detect this change

        }, 1000);

  }

}

In this example, the message variable is being updated inside a setTimeout callback. This is asynchronous code that runs outside of Angular's "zone", and as a result, Angular doesn't automatically trigger change detection.

To solve this, you can manually trigger change detection using Angular's ChangeDetectorRef and its markForCheck method:

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

@Component({

  selector: 'app-root',

  template: `

    <div>{{ message }}</div>

    <button (click)="updateMessage()">Update Message</button>

  `,

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

})

export class AppComponent {

  message = 'Hello';

  constructor(private cdr: ChangeDetectorRef) {}

  updateMessage() {

    setTimeout(() => {

      this.message = 'Hello, Angular';

      this.cdr.markForCheck(); // Manually trigger change detection

    }, 1000);

  }

}

By manually triggering change detection, we ensure that the view is updated with the new title value.

Forms and Validations

Forms are a crucial part of many web applications, and Angular provides powerful tools for building and validating forms.

In Angular, forms are built using the FormsModule or ReactiveFormsModule module. The FormsModule module provides two-way data binding and template-driven forms, while the ReactiveFormsModule module provides a more powerful and flexible approach using reactive forms.

Let's look at an example of how to build a reactive form with validation in Angular.

First, we need to import the ReactiveFormsModule module in our application module.

Create the form in the component:

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

@Component({
 selector:
'app-root',
 template: `
   <form [formGroup]=
"myForm" (ngSubmit)="onSubmit()">
     
<label>
       Name:
       
<input type="text" formControlName="name">
     
</label>
     
<div *ngIf="name.errors && (name.dirty || name.touched)">
       
<div *ngIf="name.errors.required">Name is required.</div>
       
<div *ngIf="name.errors.minlength">Name must be at least 3 characters.</div>
     
</div>
     
<button type="submit">Submit</button>
   
</form>
 `
})
export class AppComponent {
 myForm: FormGroup;

 constructor(private formBuilder: FormBuilder) {}

 ngOnInit() {
   
this.myForm = this.formBuilder.group({
     name: [
'', [Validators.required, Validators.minLength(3)]]
   });
 }

 get name() {
return this.myForm.get('name'); }

 onSubmit() {
   
console.log(this.myForm.value);
 }
}

In this example, we've defined an Angular component called AppComponent. It has a myForm property of type FormGroup that represents the form.

We've also injected the FormBuilder service in the constructor, which provides a convenient way to create form groups and form controls.

In the ngOnInit() method, we've created a form group with a single form control for the name field. We've also added validation to the name field using the Validators class.

In the template, we've bound the myForm property to the form element using the formGroup directive. We've also bound the name field to the input element using the formControlName directive.

We've added a div element that shows error messages for the name field if it is invalid. We've also added a get method for the name field to make it easier to access in the template.

Finally, we've added a submit event handler to the form element that calls the onSubmit() method of the component.

Add validation messages: To display validation messages for the form, we've added a div element that shows error messages for the name field if it is invalid. We've also added a get method for the name field to make it easier to access in the template.

Handle form submission: In the template, we've added a submit event handler to the form element that calls the onSubmit() method of the component.

<form [formGroup]="myForm" (ngSubmit)="onSubmit()">
 
<!-- form controls -->
 
<button type="submit">Submit</button>
</form>

In the component class, we've defined the onSubmit() method to handle form submission.

onSubmit() {
 console.
log(this.myForm.value);
}

In this example, we're simply logging the form value to the console. In a real application, you would typically send the form data to a server or perform some other action.

By using reactive forms and validation in Angular, you can create powerful and flexible forms that provide a better user experience. Angular provides a wide range of validation functions and directives, which you can use to implement custom validation rules as needed.

Routing and Navigation

Routing and navigation are important parts of many web applications, and Angular provides powerful tools for managing routing and navigation.

In Angular, routing and navigation are handled by the RouterModule module. This module provides a way to map URLs to components and navigate between them.

Let's look at an example of how to configure routing and navigation in Angular.

First, we need to import the RouterModule module in our application module.

Create the components:

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

@Component({
 selector:
'app-home',
 template: `
   <h1>
Home Page</h1>
   <p>Welcome to the home page.</
p>
 `
})
export class HomeComponent { }

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

@Component({
 selector:
'app-about',
 template: `
   <h1>About Page<
/h1>
   <p>Welcome to the about page.</
p>
 `
})
export class AboutComponent { }

In these examples, we've defined two Angular components, HomeComponent and AboutComponent. Each component has a simple template that displays a heading and some text.

Add the router outlet: To display the routed components, we need to add a <router-outlet> element to our app component template.

<router-outlet></router-outlet>

In this example, we're using the <router-outlet> element to display the routed components. The <router-outlet> element acts as a placeholder for the routed components.

Add navigation links: Finally, we need to add navigation links to our app component template.

<nav>
 
<ul>
   
<li><a routerLink="">Home</a></li>
   
<li><a routerLink="about">About</a></li>
 
</ul>
</nav>

In this example, we're using the routerLink directive to create links to the home and about pages. The routerLink directive automatically generates the appropriate links based on the defined routes.

By using routing and navigation in Angular, you can create powerful and flexible applications that provide a better user experience. Angular provides a wide range of routing and navigation features, which you can use to implement custom routing rules as needed.

HTTP and Observables

HTTP and observables are important parts of many web applications, and Angular provides powerful tools for working with them.

HTTP is a protocol used for sending and receiving data over the internet. In Angular, HTTP is used to make requests to a server and receive responses.

Observables are a way of handling asynchronous data streams in Angular. Observables can represent any type of data stream, including HTTP requests.

Let's look at an example of how to use HTTP and observables in Angular.

First, we need to import the HttpClientModule module in our application module.

Make an HTTP request: Next, we need to make an HTTP request in our component.

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

@Component({
 selector:
'app-root',
 template:
`
   <ul>
     <li *ngFor="let user of users">{{ user.name }}</li>
   </ul>
 `

})
export class AppComponent {
 users:
any[];

 
constructor(private http: HttpClient) {}

 ngOnInit() {
   
this.http.get<any[]>('https://jsonplaceholder.typicode.com/users')
     .subscribe(users => {
       
this.users = users;
     });
 }
}

In this example, we've injected the HttpClient service in the constructor of our component. We've also defined a users property that will hold the results of the HTTP request.

In the ngOnInit() method, we're making an HTTP GET request to the https://jsonplaceholder.typicode.com/users endpoint using the get() method of the HttpClient service.

The get() method returns an observable, which we're subscribing to using the subscribe() method. When the HTTP request completes, the subscribe() method will be called with the response data.

In the subscribe() method, we're updating the users property with the response data.

Display the data: Finally, we need to display the data in our template.

<ul>
 
<li *ngFor="let user of users">{{ user.name }}</li>
</ul>

In this example, we're using the ngFor directive to iterate over the users array and display each user's name in a list item element.

By using HTTP and observables in Angular, you can create powerful and flexible applications that provide a better user experience. Angular provides a wide range of features and options for working with HTTP and observables, including support for various request types, error handling, and more.

Authentication and Authorization

Authentication and authorization are important parts of many web applications, and Angular provides powerful tools for implementing them.

Authentication is the process of verifying the identity of a user, while authorization is the process of granting or denying access to resources based on the user's identity and permissions.

Let's look at an example of how to implement authentication and authorization in Angular.

First, we need to implement a login form in our application. This form will collect the user's credentials and authenticate them with a server.

import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { AuthService } from './auth.service';

@Component({
 selector:
'app-login',
 template: `
   <h1>Login<
/h1>
   <form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
     <label>
       Username:
       <input type="text" formControlName="username">
     </
label>
     
<label>
       Password:
       
<input type="password" formControlName="password">
     
</label>
     
<button type="submit">Submit</button>
   
</form>
   <div *ngIf=
"errorMessage">{{ errorMessage }}</div>
 
`
})
export class LoginComponent {
 loginForm: FormGroup;
 errorMessage: string;

 constructor(private fb: FormBuilder, private authService: AuthService) {
   
this.loginForm = this.fb.group({
     username: [
'', Validators.required],
     password: [
'', Validators.required]
   });
 }

 onSubmit() {
   const { username, password } =
this.loginForm.value;
   
this.authService.login(username, password).subscribe({
     error: error => {
       
this.errorMessage = 'Invalid username or password';
     }
   });
 }
}

In this example, we've defined an Angular component called LoginComponent. It has a loginForm property of type FormGroup that represents the login form.

We've also injected the FormBuilder service and AuthService service in the constructor, which provides a convenient way to create form groups and authenticate users.

In the ngOnInit() method, we've created a form group with two form controls for the email and password fields. We've also added validation to the email and password fields using the Validators class.

In the template, we've bound the loginForm property to the form element using the formGroup directive. We've also bound the email and password fields to the input elements using the formControlName directive.

We've added a submit event handler to the form element that calls the onSubmit() method of the component. In the onSubmit() method, we're extracting the email and password values from the form and passing them to the login() method of the AuthService.

If the login is successful, we can navigate the user to a protected page. If the login fails, we're displaying an error message.

Implement an authentication service: Next, we need to implement an authentication service that will authenticate users with a server.

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { tap } from 'rxjs/operators';
import { CookieService } from 'ngx-cookie-service';

@Injectable({ providedIn: 'root' })
export class AuthService {
 
constructor(private http: HttpClient, private cookieService: CookieService) {}

 login(username:
string, password: string) {
   
return this.http.post('/api/login', { username, password }, { withCredentials: true })
     .pipe(
       tap(() => {
         
this.cookieService.set('sessionId', 'someSessionId');
       })
     );
 }

 logout() {
   
return this.http.post('/api/logout', {}, { withCredentials: true })
     .pipe(
       tap(() => {
         
this.cookieService.delete('sessionId');
       })
     );
 }
}

In this example, we're using the HttpClient service to send HTTP requests to the server. The login() method sends a POST request to the /api/login endpoint with the user's email and password. If the server responds with a successful authentication token, the tap() operator sets the loggedIn property to true.

The logout() method sends a POST request to the /api/logout endpoint to invalidate the user's session. If the server responds with a successful logout message, the tap() operator sets the loggedIn property to false.

Finally, we've defined an isLoggedIn() method that returns the current authentication status of the user.

Implement route guards: Finally, we need to implement route guards to restrict access to protected pages based on the user's authentication status.

import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { CookieService } from 'ngx-cookie-service';

@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
 
constructor(private cookieService: CookieService, private router: Router) {}

 canActivate() {
   
const sessionId = this.cookieService.get('sessionId');

   
if (sessionId) {
     
return true;
   }
else {
     
this.router.navigate(['/login']);
     
return false;
   }
 }
}

In this example, we've defined an Angular route guard called AuthGuard that implements the CanActivate interface. The CanActivate interface requires a canActivate() method that returns a boolean indicating whether the user is allowed to access the requested route.

In the canActivate() method, we're checking the current authentication status of the user using the AuthService. If the user is authenticated, we're allowing access to the requested route by returning true. If the user is not authenticated, we're redirecting them to the login page and preventing access to the requested route by returning false.

By using authentication and authorization in Angular, you can create powerful and secure applications that provide a better user experience. Angular provides a wide range of features and options for implementing authentication and authorization, including support for various authentication mechanisms, route guards, and more.

Unit Testing

Unit testing in Angular focuses on testing individual units of code, such as components, services, and directives, in isolation. The goal is to verify that each unit behaves correctly and produces the expected results.

Angular provides robust support for unit testing through the Jasmine testing framework and the Karma test runner. Jasmine provides a clean and expressive syntax for writing test cases, and Karma allows you to run tests in real browsers or headless environments.

Let's explore how to perform unit testing in Angular using Jasmine and Karma.

Setting up the Testing Environment

Angular provides a pre-configured testing environment that sets up the necessary testing modules and utilities. To generate the initial testing setup, use the Angular CLI command:

ng generate module app-name --module=app --name=app

This will create a separate module specifically for testing, named app-name-testing.module.ts, which will be automatically imported into your test files.

Writing Test Suites and Test Cases

In Angular, a test suite consists of one or more related test cases, organized in a describe block. Each test case is defined using the it or test function within the describe block.

Here's an example of a test suite for a simple Angular component:

import { TestBed, ComponentFixture } from '@angular/core/testing';
import { MyComponent } from './my.component';

describe(
'MyComponent', () => {
 let component: MyComponent;
 let fixture: ComponentFixture<MyComponent>;

 beforeEach(() => {
   TestBed.configureTestingModule({
     declarations: [MyComponent],
   }).compileComponents();

   fixture = TestBed.createComponent(MyComponent);
   component = fixture.componentInstance;
   fixture.detectChanges();
 });

 it(
'should create the component', () => {
   expect(component).toBeTruthy();
 });

 it(
'should have a default value for the name property', () => {
   expect(component.name).toEqual(
'John');
 });

 
// Add more test cases...
});

In this example, we have a test suite for the MyComponent component. The beforeEach block is executed before each test case, setting up the testing environment by configuring the testing module, creating the component fixture, and detecting changes.

The first test case checks if the component is created successfully using the toBeTruthy matcher from Jasmine. The second test case verifies that the name property of the component has a default value of 'John' using the toEqual matcher.

Executing Tests with Karma:

To run the tests, you need to use the Karma test runner, which is included with the Angular CLI. Karma launches a browser or a headless environment and executes the test suite.

To start the tests, use the following Angular CLI command:

ng test

Karma will watch for any changes in the test files and re-run the tests automatically. The test results will be displayed in the terminal, indicating whether each test case passed or failed.

Using Matchers and Assertions:

Jasmine provides a rich set of matchers and assertions to validate expected behavior. Some commonly used matchers include:

  • expect(value).toBe(expected): Checks for strict equality between value and expected.
  • expect(value).toEqual(expected): Checks for deep equality between value and expected.
  • expect(value).toBeDefined(): Checks if value is defined.
  • expect(value).toBeNull(): Checks if value is null.
  • expect(value).toBeTruthy(): Checks if value is truthy.
  • expect(value).toBeFalsy(): Checks if value is falsy.
  • expect(value).toContain(expected): Checks if value contains expected.
  • expect(value).toThrow(): Checks if value throws an error.

You can also use various other matchers and assertion functions provided by Jasmine, such as toBeLessThan, toBeGreaterThan, toHaveBeenCalled, toHaveBeenCalledWith, and more. These matchers help you write expressive and precise test assertions.

Testing Components

When testing Angular components, you can interact with their properties, methods, and the DOM to validate their behavior. Angular's ComponentFixture provides methods to trigger events, update the component's properties, and detect changes.

Here's an example of a component test case that interacts with the component's DOM element:

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MyComponent } from './my.component';

describe(
'MyComponent', () => {
 let component: MyComponent;
 let fixture: ComponentFixture<MyComponent>;

 beforeEach(() => {
   TestBed.configureTestingModule({
     declarations: [MyComponent],
   }).compileComponents();

   fixture = TestBed.createComponent(MyComponent);
   component = fixture.componentInstance;
   fixture.detectChanges();
 });

 it(
'should update the name when the button is clicked', () => {
   const button = fixture.nativeElement.querySelector(
'button');
   button.click();
   fixture.detectChanges();

   expect(component.name).toEqual(
'Alice');
 });
});

In this example, we select the button element from the component's DOM using fixture.nativeElement.querySelector and trigger a click event by calling button.click(). After that, we call fixture.detectChanges() to detect and apply any changes to the component.

Finally, we assert that the name property of the component is updated to 'Alice' after the button click.

Testing Services

When testing Angular services, you can directly instantiate and test the service without the need for component fixtures. You can use the TestBed utility to configure the testing module and provide any necessary dependencies.

Here's an example of testing a service method:

import { TestBed } from '@angular/core/testing';
import { MyService } from './my.service';

describe(
'MyService', () => {
 let service: MyService;

 beforeEach(() => {
   TestBed.configureTestingModule({});
   service = TestBed.inject(MyService);
 });

 it(
'should return the sum of two numbers', () => {
   const result = service.addNumbers(
2, 3);
   expect(result).toEqual(
5);
 });
});

In this example, we use TestBed.configureTestingModule({}) to configure the testing module. Then, we use TestBed.inject(MyService) to instantiate the MyService service.

We can now call the service methods, such as addNumbers, and validate the expected results using Jasmine matchers.

Mocking Dependencies

When testing components or services with dependencies, you may need to mock those dependencies to isolate the unit under test. You can create mock implementations or use Jasmine's spy functions to create spies and stubs for dependencies.

For example, when testing a component that depends on a service, you can create a mock service using the jasmine.createSpyObj function:

import { TestBed, ComponentFixture } from '@angular/core/testing';
import { MyComponent } from './my.component';
import { MyService } from './my.service';

describe(
'MyComponent', () => {
 let component: MyComponent;
 let fixture: ComponentFixture<MyComponent>;
 let mockService: jasmine.SpyObj<MyService>;

 beforeEach(() => {
   mockService = jasmine.createSpyObj(
'MyService', ['getData']);
   mockService.getData.
and.returnValue('Mocked data');

   TestBed.configureTestingModule({
     declarations: [MyComponent],
     providers: [{ provide: MyService, useValue: mockService }],
   }).compileComponents();

   fixture = TestBed.createComponent(MyComponent);
   component = fixture.componentInstance;
   fixture.detectChanges();
 });

 it(
'should display data returned by the service', () => {
   expect(component.data).toEqual(
'Mocked data');
 });
});

In this example, we created a mock service using jasmine.createSpyObj. We defined a method getData on the mock service and specified the return value using and.returnValue method. This allows us to simulate the behavior of the getData method in our test.

Then, in the TestBed.configureTestingModule, we provided the mock service by using { provide: MyService, useValue: mockService }. This ensures that the component uses the mock service instead of the actual service.

Finally, in the test case, we assert that the data property of the component is equal to the value returned by the mock service.

By creating mock objects and using spies, you can control the behavior of dependencies and focus on testing the specific unit of code in isolation.

Remember, unit testing is crucial for ensuring the correctness and reliability of your Angular application. It helps catch issues early, provides documentation for the behavior of your code, and facilitates code maintainability and refactoring.

Error Handling and Logging

Error handling and logging are critical aspects of building robust applications in Angular. They help identify and handle errors gracefully and provide valuable insights into the application's behavior. In Angular, you can implement error handling and logging using various techniques and tools.

Error Handling

Angular provides several mechanisms for error handling:

Try-Catch

You can use standard JavaScript try-catch blocks to catch and handle errors within your code. This is useful for capturing and handling synchronous errors that occur during the execution of a particular function or block of code.

try {
 
// Code that may throw an error
}
catch (error) {
 
// Handle the error
}

Error Handlers

Angular allows you to define global error handlers using the ErrorHandler interface. By implementing this interface and providing your custom error handler, you can capture and handle uncaught errors that occur within your application.

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

class CustomErrorHandler implements ErrorHandler {
 handleError(
error: any) {
   
// Handle the error
 }
}

To use the custom error handler, you can provide it in the root module of your application:

import { NgModule, ErrorHandler } from '@angular/core';
import { CustomErrorHandler } from './custom-error-handler';

@NgModule({
 providers: [{ provide: ErrorHandler, useClass: CustomErrorHandler }]
})
export class AppModule {}

HTTP Interceptor

Angular's HttpClient module allows you to intercept HTTP requests and responses using interceptors. You can create an interceptor that catches and handles specific types of errors, such as HTTP errors. This is useful for centralizing error handling related to API requests.

import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpErrorResponse } from '@angular/common/http';
import { catchError } from 'rxjs/operators';
import { throwError } from 'rxjs';

@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
 intercept(
req: HttpRequest<any>, next: HttpHandler) {
   
return next.handle(req).pipe(
     catchError((
error: HttpErrorResponse) => {
       // Handle the
error
       
return throwError(error);
     })
   );
 }
}

To use the interceptor, you need to provide it
in the HTTP_INTERCEPTORS token:

import { NgModule, ErrorHandler, HTTP_INTERCEPTORS } from '@angular/core';
import { ErrorInterceptor } from './error-interceptor';

@NgModule({
 
providers: [
   {
provide: ErrorHandler, useClass: CustomErrorHandler },
   {
provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true }
 ]
})
export class AppModule {}

Logging

Logging is essential for understanding the flow of your application, diagnosing issues, and monitoring the behavior of your code. Angular provides various logging mechanisms:

Console Logging

The simplest form of logging is using the console object provided by browsers. You can log messages, objects, and stack traces using methods like console.log(), console.error(), console.warn(), and console.trace().

console.log('This is a log message');
console.
error('An error occurred:', error);

Angular Logger

Angular provides its own logging service called Logger. You can inject the Logger into your components, services, or modules and use its methods to log messages. The advantage of using Angular's Logger is that you can configure different log levels and control the logging behavior.

To use the Logger, you need to import it from @angular/core and inject it into your components, services, or modules:

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

@Component({
 selector:
'app-my-component',
 template:
`...`
})
export class MyComponent {
 
constructor(private logger: Logger) {}

 logMessage() {
   
this.logger.log('This is a log message');
 }

 logError() {
   
this.logger.error('An error occurred');
 }
}

You can use the log(), error(), warn(), and info() methods of the Logger instance to log messages at different log levels.

Third-Party Logging Libraries

Angular also integrates well with third-party logging libraries like ngx-logger or log4js. These libraries provide additional features such as log formatting, log storage, and log levels.

To use a third-party logging library, you typically install the library, configure it according to your needs, and use its provided logger within your components or services.

For example, with the ngx-logger library:

  1. Install the library:
npm install ngx-logger
  1. Configure the logger in your root module:
import { NgModule } from '@angular/core';
import { LoggerModule, NgxLoggerLevel } from 'ngx-logger';

@NgModule({
 imports: [LoggerModule.forRoot({ level: NgxLoggerLevel.DEBUG })]
})
export class AppModule {}

Use the logger
in your components or services:

import { Component } from '@angular/core';
import { NGXLogger } from 'ngx-logger';

@Component({
 selector:
'app-my-component',
 template:
`...`
})
export class MyComponent {
 
constructor(private logger: NGXLogger) {}

 logMessage() {
   
this.logger.debug('This is a log message');
 }

 logError() {
   
this.logger.error('An error occurred');
 }
}

By utilizing logging mechanisms in Angular, you can capture valuable information during development, testing, and production. It helps you trace the flow of your application, diagnose errors, and analyze the behavior of your code. Whether using built-in console logging, Angular's Logger, or third-party logging libraries, proper logging practices enhance the maintainability and reliability of your Angular applications.