Lesson 2: Angular Advanced Concepts

Lesson 2: Angular Advanced Concepts

This lesson covers Directives, HTTP Requests, Observables, State Management, and Pipes in Angular.


Example 1: Custom Directive for Text Highlighting

A custom directive dynamically changes the text color on hover.

Code:

// highlight.directive.ts
import { Directive, ElementRef, HostListener, Input } from '@angular/core';

@Directive({
  selector: '[appHighlight]'
})
export class HighlightDirective {
  @Input() highlightColor = 'yellow';

  constructor(private el: ElementRef) {}

  @HostListener('mouseenter') onMouseEnter() {
    this.el.nativeElement.style.backgroundColor = this.highlightColor;
  }

  @HostListener('mouseleave') onMouseLeave() {
    this.el.nativeElement.style.backgroundColor = '';
  }
}
// app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent { }
<!-- app.component.html -->
<p appHighlight highlightColor="lightblue">Hover over this text to see the highlight effect.</p>
<p appHighlight highlightColor="lightgreen">Another example of directive usage.</p>

Explanation:

  1. Custom Directive: HighlightDirective uses @Directive() to modify element behavior dynamically.

  2. Element Reference: ElementRef grants access to the HTML element’s properties.

  3. Event Listeners: @HostListener() listens for mouseenter and mouseleave to change styles.

  4. Input Binding: @Input() allows customizable highlight colors.

  5. Reusability: The directive can be applied to any element across multiple components.


Example 2: Fetching Data using HTTPClient (REST API Call)

This example retrieves users from a REST API and displays them.

Code:

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

@Injectable({
  providedIn: 'root'
})
export class UserService {
  private apiUrl = 'https://jsonplaceholder.typicode.com/users';

  constructor(private http: HttpClient) {}

  getUsers(): Observable<any> {
    return this.http.get(this.apiUrl);
  }
}
// app.component.ts
import { Component, OnInit } from '@angular/core';
import { UserService } from './user.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  users: any[] = [];

  constructor(private userService: UserService) {}

  ngOnInit() {
    this.userService.getUsers().subscribe(data => {
      this.users = data;
    });
  }
}
<!-- app.component.html -->
<ul>
  <li *ngFor="let user of users">{{ user.name }} - {{ user.email }}</li>
</ul>

Explanation:

  1. Service & Dependency Injection: UserService fetches API data and is injected into the component.

  2. HTTPClientModule: Uses HttpClient to make API calls.

  3. Observable & Subscription: getUsers() returns an Observable which is subscribed to in ngOnInit().

  4. Async Data Handling: Users list updates dynamically after API response.

  5. Template Binding: *ngFor dynamically renders user data.


Example 3: Observables & Reactive Programming

Demonstrates Observables with a real-time counter.

Code:

// counter.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class CounterService {
  private counter = new BehaviorSubject<number>(0);
  counter$ = this.counter.asObservable();

  increment() {
    this.counter.next(this.counter.value + 1);
  }

  decrement() {
    this.counter.next(this.counter.value - 1);
  }
}
// app.component.ts
import { Component } from '@angular/core';
import { CounterService } from './counter.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  counterValue = 0;

  constructor(private counterService: CounterService) {
    this.counterService.counter$.subscribe(value => {
      this.counterValue = value;
    });
  }

  increase() {
    this.counterService.increment();
  }

  decrease() {
    this.counterService.decrement();
  }
}
<!-- app.component.html -->
<h2>Counter: {{ counterValue }}</h2>
<button (click)="increase()">+</button>
<button (click)="decrease()">-</button>

Explanation:

  1. BehaviorSubject: counter$ holds an initial value and updates components in real time.

  2. Reactive State Management: BehaviorSubject.next() updates the counter value reactively.

  3. Component Subscription: subscribe() listens for counter changes.

  4. Event-Driven Updates: Clicking buttons triggers increment() and decrement().

  5. Reactive UI: Changes are reflected dynamically without manual refresh.


Example 4: State Management with NgRx Store

Uses NgRx Store for managing a global counter state.

Code:

// counter.actions.ts
import { createAction } from '@ngrx/store';

export const increment = createAction('[Counter] Increment');
export const decrement = createAction('[Counter] Decrement');
// counter.reducer.ts
import { createReducer, on } from '@ngrx/store';
import { increment, decrement } from './counter.actions';

export const initialState = 0;

export const counterReducer = createReducer(
  initialState,
  on(increment, state => state + 1),
  on(decrement, state => state - 1)
);
// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { StoreModule } from '@ngrx/store';
import { counterReducer } from './counter.reducer';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, StoreModule.forRoot({ counter: counterReducer })],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
// app.component.ts
import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { increment, decrement } from './counter.actions';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  counter$ = this.store.select('counter');

  constructor(private store: Store<{ counter: number }>) {}

  increase() {
    this.store.dispatch(increment());
  }

  decrease() {
    this.store.dispatch(decrement());
  }
}
<!-- app.component.html -->
<h2>Counter: {{ counter$ | async }}</h2>
<button (click)="increase()">+</button>
<button (click)="decrease()">-</button>

Explanation:

  1. State Management: NgRx StoreModule manages the counter state globally.

  2. Actions & Reducers: increment and decrement modify the state using counterReducer.

  3. Immutable State: State updates are managed reactively.

  4. Selector Subscription: counter$ | async automatically updates UI.

  5. Scalability: NgRx is ideal for large applications needing predictable state changes.


Example 5: Custom Pipe for Formatting Text

A custom Angular Pipe transforms text into uppercase.

Code:

// uppercase.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'uppercasePipe'
})
export class UppercasePipe implements PipeTransform {
  transform(value: string): string {
    return value.toUpperCase();
  }
}
<!-- app.component.html -->
<p>{{ 'hello world' | uppercasePipe }}</p>

Explanation:

  1. Pipe Creation: UppercasePipe transforms text using @Pipe().

  2. PipeTransform Interface: Implements transform(value: string) to modify text.

  3. Reusability: Can be applied across multiple components.

  4. Simple Usage: Used in templates via {{ value | uppercasePipe }}.

  5. Performance: Runs efficiently with Angular’s change detection.


These five advanced Angular examples cover essential concepts for real-world applications