DEV Community

Renuka Patil
Renuka Patil

Posted on

Understanding Angular at Its Core: Key Concepts, Utilities and Best Practices

Here’s a structured organization of the Angular topics, grouped by their categories and relationships:


1. Template and Content Projection

  • ng-template
  • ng-container
  • ng-content
  • NgTemplateOutlet

2. Structural and Attribute Directives

  • Structural Directives:
    • *ngIf
    • *ngFor
    • Custom Directives
  • Attribute Directives:
    • HostBinding and HostListener

3. Component Interaction and Content Queries

  • View Queries:
    • @ViewChild and @ViewChildren
  • Content Queries:
    • @ContentChild and @ContentChildren

4. Rendering and DOM Manipulation

  • Renderer API:
    • Renderer2
  • Element Reference:
    • ElementRef

5. Dynamic Component Loading

  • Dynamic Component Creation:
    • ComponentRef
    • ComponentFactoryResolver
    • ViewContainerRef

6. Change Detection and Views

  • Change Detection:
    • ChangeDetectorRef
  • View Management:
    • ViewRef
    • HostViewRef
    • EmbeddedViewRef

7. Dependency Injection and Services

  • Dependency Injection:
    • Injector

8. Advanced Angular Utilities

  • Differ Utilities:
    • KeyValueDiffers
    • IterableDiffers

1. Template and Content Projection

The Template and Content Projection category in Angular provides ways to define reusable templates, project content dynamically, and manipulate the structure of the DOM efficiently. Let’s explore each item in detail:


ng-template

What is it?

ng-template is a directive that defines an Angular template that can be reused dynamically. It does not render anything in the DOM until it is explicitly used.

Use Cases

  • Defining reusable templates for dynamic rendering.
  • Using structural directives for conditional content rendering.

Example

<div>
  <ng-template #greetTemplate>
    <p>Hello, this is a reusable template!</p>
  </ng-template>

  <button (click)="showTemplate(greetTemplate)">Show Greeting</button>
</div>
Enter fullscreen mode Exit fullscreen mode
import { Component, TemplateRef, ViewContainerRef } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
})
export class AppComponent {
  constructor(private viewContainerRef: ViewContainerRef) {}

  showTemplate(template: TemplateRef<any>) {
    this.viewContainerRef.clear();
    this.viewContainerRef.createEmbeddedView(template);
  }
}
Enter fullscreen mode Exit fullscreen mode

Output

Initially, nothing appears. On clicking the button, the message Hello, this is a reusable template! is rendered dynamically.


ng-container

What is it?

ng-container is a logical container that does not render itself in the DOM but is used to group elements or directives.

Use Cases

  • Grouping multiple directives without adding extra nodes to the DOM.
  • Reducing unwanted wrapper elements.

Example

<ng-container *ngIf="show">
  <h1>Welcome!</h1>
  <p>This is displayed conditionally without extra wrappers.</p>
</ng-container>
Enter fullscreen mode Exit fullscreen mode
export class AppComponent {
  show = true;
}
Enter fullscreen mode Exit fullscreen mode

Output

If show is true, it renders:

Welcome!
This is displayed conditionally without extra wrappers.
Enter fullscreen mode Exit fullscreen mode

If show is false, nothing is rendered.


ng-content

What is it?

ng-content allows you to project content from a parent component into a child component, enabling content projection.

Use Cases

  • Creating reusable components with customizable content.
  • Projecting parent content into child components.

Example

Parent Component:

<app-card>
  <h2>Custom Title</h2>
  <p>This is custom content passed from the parent!</p>
</app-card>
Enter fullscreen mode Exit fullscreen mode

Child Component:

<div class="card">
  <ng-content></ng-content>
</div>
Enter fullscreen mode Exit fullscreen mode
@Component({
  selector: 'app-card',
  templateUrl: './card.component.html',
  styleUrls: ['./card.component.css']
})
export class CardComponent {}
Enter fullscreen mode Exit fullscreen mode

Output

Custom Title
This is custom content passed from the parent!
Enter fullscreen mode Exit fullscreen mode

NgTemplateOutlet

What is it?

NgTemplateOutlet is a directive that dynamically embeds an ng-template into a view.

Use Cases

  • Rendering templates conditionally.
  • Reusing templates dynamically.

Example

<div>
  <ng-template #adminTemplate>
    <p>Welcome, Admin!</p>
  </ng-template>

  <ng-template #userTemplate>
    <p>Welcome, User!</p>
  </ng-template>

  <ng-container *ngTemplateOutlet="role === 'admin' ? adminTemplate : userTemplate">
  </ng-container>
</div>
Enter fullscreen mode Exit fullscreen mode
export class AppComponent {
  role = 'admin';
}
Enter fullscreen mode Exit fullscreen mode

Output

If role is 'admin', it renders:

Welcome, Admin!
Enter fullscreen mode Exit fullscreen mode

If role is 'user', it renders:

Welcome, User!
Enter fullscreen mode Exit fullscreen mode

Key Differences and Summary

  • ng-template defines reusable templates but doesn’t render directly.
  • ng-container groups elements logically without adding extra nodes.
  • ng-content projects parent content into a child component.
  • NgTemplateOutlet dynamically embeds templates into views.

These tools allow Angular developers to create dynamic, reusable, and efficient templates with minimal DOM manipulation.


Image description

2. Structural and Attribute Directives

Directives in Angular are used to modify the DOM by changing its structure or behavior.


Structural Directives

Structural directives change the structure of the DOM by adding or removing elements dynamically.

1. *ngIf

What is it?

*ngIf is a built-in structural directive that conditionally includes or excludes elements from the DOM based on a Boolean expression.

Use Cases

  • Conditionally showing or hiding elements.

Example

<div *ngIf="isLoggedIn">
  <p>Welcome, User!</p>
</div>

<button (click)="toggleLogin()">Toggle Login</button>
Enter fullscreen mode Exit fullscreen mode
export class AppComponent {
  isLoggedIn = false;

  toggleLogin() {
    this.isLoggedIn = !this.isLoggedIn;
  }
}
Enter fullscreen mode Exit fullscreen mode

Output

When isLoggedIn is true, the message "Welcome, User!" is displayed. Otherwise, nothing is rendered.


2. *ngFor

What is it?

*ngFor is a structural directive that iterates over a collection and renders its template for each item.

Use Cases

  • Displaying a list of items dynamically.

Example

<ul>
  <li *ngFor="let item of items; index as i">
    {{ i + 1 }}. {{ item }}
  </li>
</ul>
Enter fullscreen mode Exit fullscreen mode
export class AppComponent {
  items = ['Apple', 'Banana', 'Cherry'];
}
Enter fullscreen mode Exit fullscreen mode

Output

1. Apple
2. Banana
3. Cherry
Enter fullscreen mode Exit fullscreen mode

3. Custom Structural Directives

What is it?

You can create your own structural directives to manipulate the DOM based on custom logic.

Example

Directive:

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

@Directive({
  selector: '[appUnless]'
})
export class UnlessDirective {
  @Input() set appUnless(condition: boolean) {
    if (!condition) {
      this.viewContainer.createEmbeddedView(this.templateRef);
    } else {
      this.viewContainer.clear();
    }
  }

  constructor(
    private templateRef: TemplateRef<any>,
    private viewContainer: ViewContainerRef
  ) {}
}
Enter fullscreen mode Exit fullscreen mode

Usage:

<p *appUnless="isHidden">This text is shown unless the condition is true.</p>
<button (click)="toggle()">Toggle</button>
Enter fullscreen mode Exit fullscreen mode
export class AppComponent {
  isHidden = false;

  toggle() {
    this.isHidden = !this.isHidden;
  }
}
Enter fullscreen mode Exit fullscreen mode

Output

When isHidden is false, the paragraph is displayed. Otherwise, it is removed from the DOM.


Attribute Directives

Attribute directives modify the behavior or appearance of an element without changing the DOM structure.

1. HostBinding and HostListener

What are they?

  • HostBinding: Binds a property of the directive/component to a host element.
  • HostListener: Listens to events on the host element and triggers a method.

Use Cases

  • Changing styles or classes dynamically.
  • Handling events on the host element.

Example

Directive:

import { Directive, HostBinding, HostListener } from '@angular/core';

@Directive({
  selector: '[appHighlight]'
})
export class HighlightDirective {
  @HostBinding('style.backgroundColor') backgroundColor: string;

  @HostListener('mouseenter') onMouseEnter() {
    this.backgroundColor = 'yellow';
  }

  @HostListener('mouseleave') onMouseLeave() {
    this.backgroundColor = 'transparent';
  }
}
Enter fullscreen mode Exit fullscreen mode

Usage:

<p appHighlight>Hover over this text to see the highlight.</p>
Enter fullscreen mode Exit fullscreen mode

Output

When you hover over the paragraph, its background color changes to yellow. When you move the mouse away, it reverts to transparent.


Summary

  • Structural Directives: Change the structure of the DOM by adding or removing elements (*ngIf, *ngFor, Custom Directives).
  • Attribute Directives: Modify the behavior or appearance of existing elements (HostBinding, HostListener).

These directives provide powerful tools to build dynamic and interactive Angular applications.


Image description

3. Component Interaction and Content Queries

This category provides tools to manage and interact with Angular views and their content dynamically, offering flexibility in manipulating templates and view containers.


View Queries

ViewChild and ViewChildren

What are they?

ViewChild and ViewChildren are decorators used to access child components, directives, or DOM elements within the template of a component.

Use Cases

  • Accessing a single or multiple child elements.
  • Interacting with components or elements dynamically.

Example

ViewChild

Template:

<app-child #childComp></app-child>
<button (click)="callChildMethod()">Call Child Method</button>
Enter fullscreen mode Exit fullscreen mode

Component:

import { Component, ViewChild } from '@angular/core';
import { ChildComponent } from './child.component';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
})
export class AppComponent {
  @ViewChild('childComp') child: ChildComponent;

  callChildMethod() {
    this.child.sayHello();
  }
}
Enter fullscreen mode Exit fullscreen mode

Child Component:

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

@Component({
  selector: 'app-child',
  template: '<p>Hello from Child!</p>',
})
export class ChildComponent {
  sayHello() {
    alert('Hello from Child Component!');
  }
}
Enter fullscreen mode Exit fullscreen mode

Output:

Clicking the button triggers the child component's sayHello method, displaying an alert.


ViewChildren

Template:

<app-child *ngFor="let item of items" #child></app-child>
<button (click)="callAllChildMethods()">Call All Children Methods</button>
Enter fullscreen mode Exit fullscreen mode

Component:

import { Component, QueryList, ViewChildren } from '@angular/core';
import { ChildComponent } from './child.component';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
})
export class AppComponent {
  items = [1, 2, 3];

  @ViewChildren('child') children: QueryList<ChildComponent>;

  callAllChildMethods() {
    this.children.forEach((child) => child.sayHello());
  }
}
Enter fullscreen mode Exit fullscreen mode

Output:

Clicking the button calls the sayHello method of all child components in the list.


Content Queries

ContentChild and ContentChildren

What are they?

ContentChild and ContentChildren are decorators used to query projected content within a component using ng-content.

Use Cases

  • Accessing and interacting with content projected into a component.

Example

ContentChild

Parent Template:

<app-container>
  <p #projectedContent>Projected Content</p>
</app-container>
Enter fullscreen mode Exit fullscreen mode

Container Component:

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

@Component({
  selector: 'app-container',
  template: '<ng-content></ng-content>',
})
export class ContainerComponent implements AfterContentInit {
  @ContentChild('projectedContent') content: ElementRef;

  ngAfterContentInit() {
    console.log(this.content.nativeElement.textContent);
  }
}
Enter fullscreen mode Exit fullscreen mode

Output:

Logs "Projected Content" to the console.


ContentChildren

Parent Template:

<app-container>
  <p *ngFor="let item of items" #projected>{{ item }}</p>
</app-container>
Enter fullscreen mode Exit fullscreen mode

Container Component:

import { Component, ContentChildren, QueryList, AfterContentInit } from '@angular/core';

@Component({
  selector: 'app-container',
  template: '<ng-content></ng-content>',
})
export class ContainerComponent implements AfterContentInit {
  @ContentChildren('projected') contents: QueryList<ElementRef>;

  ngAfterContentInit() {
    this.contents.forEach((content) => {
      console.log(content.nativeElement.textContent);
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

Output:

Logs each item from the items array to the console.


Summary

  • ViewChild and ViewChildren: Access child components, directives, or DOM elements within the component's template.
  • ContentChild and ContentChildren: Access projected content provided via ng-content in the parent component.

These tools enable dynamic interaction with both the view and the projected content, offering fine-grained control over Angular templates.


Image description

4. Rendering and DOM Manipulation

This category focuses on dynamically manipulating DOM elements and rendering changes efficiently using Angular's APIs like Renderer2 and ElementRef. These tools ensure safe and framework-compliant interaction with the DOM.


Renderer API

Renderer2

What is Renderer2?

Renderer2 is an Angular service for DOM manipulation that provides an abstraction over direct DOM operations. This ensures that the code is compatible across different rendering environments, such as server-side rendering (SSR).

Use Cases

  • Adding, removing, or modifying DOM elements.
  • Changing styles, classes, or attributes of elements dynamically.

Example

Adding and Removing an Element

Template:

<div #container></div>
<button (click)="addElement()">Add Element</button>
<button (click)="removeElement()">Remove Element</button>
Enter fullscreen mode Exit fullscreen mode

Component:

import { Component, Renderer2, ElementRef, ViewChild } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
})
export class AppComponent {
  @ViewChild('container') container: ElementRef;
  private createdElement: HTMLElement;

  constructor(private renderer: Renderer2) {}

  addElement() {
    this.createdElement = this.renderer.createElement('p');
    const text = this.renderer.createText('This is a dynamically added element.');
    this.renderer.appendChild(this.createdElement, text);
    this.renderer.appendChild(this.container.nativeElement, this.createdElement);
  }

  removeElement() {
    if (this.createdElement) {
      this.renderer.removeChild(this.container.nativeElement, this.createdElement);
      this.createdElement = null;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Output:

  • Clicking Add Element dynamically inserts a paragraph into the div.
  • Clicking Remove Element removes the added paragraph.

Styling an Element

Template:

<p #textElement>Style me!</p>
<button (click)="applyStyles()">Apply Styles</button>
Enter fullscreen mode Exit fullscreen mode

Component:

import { Component, Renderer2, ElementRef, ViewChild } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
})
export class AppComponent {
  @ViewChild('textElement') textElement: ElementRef;

  constructor(private renderer: Renderer2) {}

  applyStyles() {
    this.renderer.setStyle(this.textElement.nativeElement, 'color', 'blue');
    this.renderer.setStyle(this.textElement.nativeElement, 'font-weight', 'bold');
  }
}
Enter fullscreen mode Exit fullscreen mode

Output:

Clicking Apply Styles changes the paragraph text to blue and makes it bold.


Element Reference

ElementRef

What is ElementRef?

ElementRef is a service that provides direct access to a DOM element. It is generally used in conjunction with @ViewChild or @ContentChild to manipulate DOM elements.

Use Cases

  • Accessing native DOM elements for custom logic.
  • Retrieving element properties (e.g., dimensions).

Example

Accessing and Modifying DOM Properties

Template:

<p #textElement>Modify my text!</p>
<button (click)="changeText()">Change Text</button>
Enter fullscreen mode Exit fullscreen mode

Component:

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

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
})
export class AppComponent {
  @ViewChild('textElement') textElement: ElementRef;

  changeText() {
    this.textElement.nativeElement.textContent = 'Text has been modified!';
  }
}
Enter fullscreen mode Exit fullscreen mode

Output:

Clicking Change Text updates the paragraph's text content dynamically.


Getting Element Dimensions

Template:

<div #boxElement style="width: 200px; height: 100px; background-color: lightgray;"></div>
<button (click)="logDimensions()">Log Dimensions</button>
Enter fullscreen mode Exit fullscreen mode

Component:

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

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
})
export class AppComponent {
  @ViewChild('boxElement') boxElement: ElementRef;

  logDimensions() {
    const element = this.boxElement.nativeElement;
    console.log(`Width: ${element.offsetWidth}, Height: ${element.offsetHeight}`);
  }
}
Enter fullscreen mode Exit fullscreen mode

Output:

Clicking Log Dimensions logs the dimensions of the div to the console.


Best Practices

  1. Prefer Renderer2 over direct DOM manipulation for compatibility and security.
  2. Use ElementRef cautiously to avoid potential XSS vulnerabilities by accessing DOM elements safely.

Summary

  • Renderer2 provides a safe, framework-compliant way to manipulate the DOM.
  • ElementRef gives direct access to DOM elements but should be used with care.

These tools empower developers to create dynamic, interactive user interfaces while adhering to Angular's best practices.


Image description

5.Dynamic Component Loading in Angular

Angular allows developers to load components dynamically at runtime. This capability is essential when dealing with scenarios where the component to be displayed is not known at compile time, such as custom modals, pop-ups, or dynamic form components. In Angular, dynamic component loading is achieved using ComponentRef, ComponentFactoryResolver, and ViewContainerRef.

Key Concepts and Terminology

  1. ComponentRef: This interface provides a reference to the created component. It allows us to interact with the component's instance, manipulate its lifecycle methods, and pass data to it.

  2. ComponentFactoryResolver: This service is used to resolve a ComponentFactory for a particular component type. It acts as a factory for creating instances of components.

  3. ViewContainerRef: It is a place in the Angular application where components can be dynamically inserted. It acts as a container for the component instance.

Example Use Cases

1. Loading a Component Dynamically into a View

Scenario: You have a button in your Angular app, and upon clicking it, a modal component is dynamically loaded.

Steps:

  1. Create Modal Component:
   // modal.component.ts
   import { Component, Input } from '@angular/core';

   @Component({
     selector: 'app-modal',
     template: `
       <div class="modal">
         <h1>{{title}}</h1>
         <button (click)="close()">Close</button>
       </div>
     `
   })
   export class ModalComponent {
     @Input() title: string;

     close() {
       // Logic to close the modal
     }
   }
Enter fullscreen mode Exit fullscreen mode
  1. Component Creation Method:
   // app.component.ts
   import { Component, ComponentFactoryResolver, ViewChild, ViewContainerRef } from '@angular/core';
   import { ModalComponent } from './modal.component';

   @Component({
     selector: 'app-root',
     template: `<button (click)="loadComponent()">Show Modal</button>`
   })
   export class AppComponent {
     @ViewChild('container', { read: ViewContainerRef, static: true }) container: ViewContainerRef;

     constructor(private componentFactoryResolver: ComponentFactoryResolver) {}

     loadComponent() {
       const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
       const componentRef = this.container.createComponent(factory);
       componentRef.instance.title = 'Dynamic Modal';
     }
   }
Enter fullscreen mode Exit fullscreen mode

2. Passing Data to the Dynamic Component

Scenario: You want to pass data to the dynamically loaded component.

Example:

// app.component.ts
import { Component, ComponentFactoryResolver, ViewChild, ViewContainerRef } from '@angular/core';
import { ModalComponent } from './modal.component';

@Component({
  selector: 'app-root',
  template: `<button (click)="loadComponentWithData()">Show Modal with Data</button>`
})
export class AppComponent {
  @ViewChild('container', { read: ViewContainerRef, static: true }) container: ViewContainerRef;

  constructor(private componentFactoryResolver: ComponentFactoryResolver) {}

  loadComponentWithData() {
    const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
    const componentRef = this.container.createComponent(factory);
    componentRef.instance.title = 'Dynamic Modal with Data';
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Accessing Lifecycle Hooks

Scenario: Interacting with lifecycle hooks of the dynamically loaded component, like ngOnInit.

Example:

// modal.component.ts
import { Component, Input, OnInit } from '@angular/core';

@Component({
  selector: 'app-modal',
  template: `
    <div class="modal">
      <h1>{{title}}</h1>
      <button (click)="close()">Close</button>
    </div>
  `
})
export class ModalComponent implements OnInit {
  @Input() title: string;

  ngOnInit() {
    console.log('Modal Component Initialized');
  }

  close() {
    // Logic to close the modal
  }
}

// app.component.ts
import { Component, ComponentFactoryResolver, ViewChild, ViewContainerRef } from '@angular/core';
import { ModalComponent } from './modal.component';

@Component({
  selector: 'app-root',
  template: `<button (click)="loadComponent()">Show Modal</button>`
})
export class AppComponent {
  @ViewChild('container', { read: ViewContainerRef, static: true }) container: ViewContainerRef;

  constructor(private componentFactoryResolver: ComponentFactoryResolver) {}

  loadComponent() {
    const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
    const componentRef = this.container.createComponent(factory);
    componentRef.instance.title = 'Dynamic Modal with Lifecycle';
  }
}
Enter fullscreen mode Exit fullscreen mode

Detailed Explanation

  1. ComponentRef:

    • ComponentRef allows you to interact with the component instance created. You can call methods of the component (componentRef.instance.methodName()) and access its properties.
    • Example: componentRef.instance.title = 'New Title'; sets a new title for the dynamically created modal.
  2. ComponentFactoryResolver:

    • It provides the ComponentFactory for a specific component type. The ComponentFactory object is used to create new instances of a component.
    • Example: const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
    • The factory contains the necessary information to create instances of ModalComponent.
  3. ViewContainerRef:

    • This container is used to insert components at runtime. It allows components to be dynamically created, attached, and removed.
    • Example: const componentRef = this.container.createComponent(factory);
    • The createComponent method on ViewContainerRef is used to instantiate a component. The factory here is the ComponentFactory for the component you want to load.

Summary

Dynamic component loading in Angular provides a powerful way to create and interact with components at runtime. Using ComponentRef, ComponentFactoryResolver, and ViewContainerRef, developers can seamlessly load components, pass data, and interact with their lifecycle hooks. This approach is essential for creating modular, flexible Angular applications that can adapt to various scenarios and requirements.


Image description

6. Change Detection and Views in Angular

Angular provides mechanisms for tracking and responding to changes in the data and views through Change Detection and View Management. Understanding these concepts is crucial for building dynamic and reactive Angular applications. This guide covers ChangeDetectorRef, ViewRef, HostViewRef, and EmbeddedViewRef, providing examples and explanations for each.

1. Change Detection

Change Detection is the process Angular uses to track changes in the data and automatically update the views when these changes occur. Angular uses a change detection tree to monitor changes across components and update the UI accordingly.

ChangeDetectorRef

ChangeDetectorRef is a direct API that allows manual control over the change detection process. It provides methods to force the detection of changes in the view.

  • Methods:
    • markForCheck(): Marks a component for change detection. The change detection mechanism will consider the component for the next run.
    • detectChanges(): Manually triggers change detection for the component.
    • detach(): Detaches the component from the change detection tree, meaning that it will not be checked automatically.
    • reattach(): Reattaches a previously detached component to the change detection tree.

Example:

// app.component.ts
import { Component, ChangeDetectorRef } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <div>
      <p>{{message}}</p>
      <button (click)="updateMessage()">Change Message</button>
      <button (click)="forceUpdate()">Force Update</button>
    </div>
  `
})
export class AppComponent {
  message: string = 'Initial Message';

  constructor(private cdr: ChangeDetectorRef) {}

  updateMessage() {
    this.message = 'Updated Message';
  }

  forceUpdate() {
    this.cdr.detectChanges(); // Forces change detection to update the view
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Explanation:
    • In this example, detectChanges() is used to manually trigger change detection. When updateMessage() is called, it changes the message property. If forceUpdate() is clicked, detectChanges() ensures that the view is updated to reflect this change.
    • ChangeDetectorRef allows developers to manually control when change detection happens, which is particularly useful when dealing with complex UI or third-party libraries that might not trigger change detection automatically.

2. View Management

Angular components are built from View objects. These views can be manipulated using ViewRef, HostViewRef, and EmbeddedViewRef.

ViewRef

ViewRef provides access to a view's API and lifecycle hooks. It represents the view of a component and allows for operations on that view.

  • Methods:
    • detach(): Detaches the view from the change detection cycle.
    • attach(): Re-attaches a detached view back to the change detection cycle.
    • destroy(): Destroys the view and cleans up resources.

Example:

// app.component.ts
import { Component, ViewChild, ViewContainerRef, ComponentFactoryResolver, ViewRef } from '@angular/core';
import { ModalComponent } from './modal.component';

@Component({
  selector: 'app-root',
  template: `
    <button (click)="loadComponent()">Show Modal</button>
  `
})
export class AppComponent {
  @ViewChild('container', { read: ViewContainerRef, static: true }) container: ViewContainerRef;

  constructor(private componentFactoryResolver: ComponentFactoryResolver) {}

  loadComponent() {
    const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
    const componentRef = this.container.createComponent(factory);
    const viewRef = componentRef.hostView;

    viewRef.detach(); // Detaching view from change detection
    console.log('View Detached');

    setTimeout(() => {
      viewRef.attach(); // Re-attaching view after 2 seconds
      console.log('View Re-attached');
    }, 2000);
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Explanation:
    • In this example, after the component (ModalComponent) is loaded and attached, the ViewRef is detached using viewRef.detach().
    • This temporarily removes the view from the change detection cycle, preventing it from being checked automatically.
    • After 2 seconds, the view is re-attached using viewRef.attach(), allowing it to be checked again for updates.

HostViewRef

HostViewRef represents the view of a host component (the component that includes other components as children). It allows access to the DOM for the host component.

  • Methods:
    • detach(): Detaches the host view from the change detection cycle.
    • attach(): Re-attaches the host view.
    • destroy(): Destroys the host view and all its child views.

Example:

// app.component.ts
import { Component, ViewChild, ViewContainerRef, ComponentFactoryResolver, HostViewRef } from '@angular/core';
import { ModalComponent } from './modal.component';

@Component({
  selector: 'app-root',
  template: `
    <button (click)="loadComponent()">Show Modal</button>
  `
})
export class AppComponent {
  @ViewChild('container', { read: ViewContainerRef, static: true }) container: ViewContainerRef;

  constructor(private componentFactoryResolver: ComponentFactoryResolver) {}

  loadComponent() {
    const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
    const componentRef = this.container.createComponent(factory);
    const hostViewRef: HostViewRef = componentRef.hostView;

    hostViewRef.detach(); // Detaching host view
    console.log('Host View Detached');

    setTimeout(() => {
      hostViewRef.attach(); // Re-attaching host view after 2 seconds
      console.log('Host View Re-attached');
    }, 2000);
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Explanation:
    • Similar to ViewRef, HostViewRef allows detaching and attaching the host view.
    • By detaching the host view, changes to the view will not be checked automatically. This can be useful for scenarios like temporary modals or when you need to manage a large number of DOM manipulations without performance impacts.
    • Re-attaching the host view restores the component to its normal operation within the change detection cycle.

EmbeddedViewRef

EmbeddedViewRef represents a dynamically created view. It provides methods to manage the lifecycle and DOM of an embedded view within another component.

  • Methods:
    • detach(): Detaches the embedded view from the change detection cycle.
    • attach(): Re-attaches the embedded view.
    • destroy(): Destroys the embedded view and cleans up resources.

Example:

// app.component.ts
import { Component, ViewChild, ViewContainerRef, ComponentFactoryResolver, EmbeddedViewRef } from '@angular/core';
import { ModalComponent } from './modal.component';

@Component({
  selector: 'app-root',
  template: `
    <button (click)="loadComponent()">Show Modal</button>
  `
})
export class AppComponent {
  @ViewChild('container', { read: ViewContainerRef, static: true }) container: ViewContainerRef;

  constructor(private componentFactoryResolver: ComponentFactoryResolver) {}

  loadComponent() {
    const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
    const componentRef = this.container.createComponent(factory);
    const embeddedViewRef: EmbeddedViewRef = componentRef.hostView;

    embeddedViewRef.detach(); // Detaching embedded view
    console.log('Embedded View Detached');

    setTimeout(() => {
      embeddedViewRef.attach(); // Re-attaching embedded view after 2 seconds
      console.log('Embedded View Re-attached');
    }, 2000);
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Explanation:
    • Similar to the previous examples, EmbeddedViewRef detaches and attaches views.
    • The example demonstrates using embeddedViewRef.detach() and embeddedViewRef.attach() to manage the lifecycle of a dynamically created component.
    • This is useful in scenarios like loading a component conditionally where you want to temporarily hide it from the UI without destroying it.

Summary

Understanding Change Detection and View Management is essential for developing high-performance Angular applications. By using ChangeDetectorRef, ViewRef, HostViewRef, and EmbeddedViewRef, developers can precisely control when and how components and views are updated. These tools allow for advanced scenarios such as dynamic loading, conditional rendering, and performance optimization by selectively managing when change detection occurs.


Image description

7.Dependency Injection and Services in Angular

Dependency Injection (DI) is a powerful design pattern in Angular that allows developers to manage dependencies in a modular and testable way. Angular provides a powerful DI system using the Injector. Understanding DI and how it works with services is crucial for building scalable and maintainable Angular applications. Below, we will cover Injector, its usage, and provide examples for different scenarios.

1. Dependency Injection

Dependency Injection (DI) is a technique where an object (the dependent) receives its dependencies from an external source rather than creating them internally. This approach reduces the coupling between components and enhances modularity, making the codebase more testable and easier to maintain.

Injector

Injector is the core of Angular’s DI system. It’s responsible for resolving dependencies and providing services to components. DI in Angular is hierarchical and follows a hierarchy similar to the component tree. This allows services to be shared across components when they are declared in the same module.

Key Features of Injector:

  1. Resolution of Dependencies: The Injector resolves dependencies for a specific service and provides it when requested.
  2. Hierarchical DI: Each Angular component and module has its own Injector, allowing for scoped dependency resolution.
  3. Scope: The Injector scope defines how dependencies are shared. Common scopes include the Root, Component, and Directive scopes.

Example Scenarios

1. Basic DI Example

Scenario: Injecting a simple service into a component.

Service:

// app.service.ts
export class AppService {
  getMessage(): string {
    return 'Hello from AppService!';
  }
}
Enter fullscreen mode Exit fullscreen mode

Component:

// app.component.ts
import { Component } from '@angular/core';
import { AppService } from './app.service';

@Component({
  selector: 'app-root',
  template: `<p>{{message}}</p>`,
  providers: [AppService] // Providing service in the component level
})
export class AppComponent {
  message: string;

  constructor(private appService: AppService) {
    this.message = this.appService.getMessage();
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Explanation:
    • AppService is a simple service that provides a message.
    • In the AppComponent, AppService is injected through the constructor using Angular’s DI system.
    • The providers array in the component’s decorator tells Angular how to provide dependencies for this component.
    • By injecting AppService, AppComponent can access its methods and properties directly without creating a new instance.

2. Service Injection Across Components

Scenario: Sharing a service across multiple components in the same module.

Service:

// app.service.ts
export class AppService {
  getGreeting(): string {
    return 'Hello Angular!';
  }
}
Enter fullscreen mode Exit fullscreen mode

Component 1:

// component1.component.ts
import { Component } from '@angular/core';
import { AppService } from './app.service';

@Component({
  selector: 'app-component1',
  template: `<p>{{greeting}}</p>`
})
export class Component1 {
  greeting: string;

  constructor(private appService: AppService) {
    this.greeting = this.appService.getGreeting();
  }
}
Enter fullscreen mode Exit fullscreen mode

Component 2:

// component2.component.ts
import { Component } from '@angular/core';
import { AppService } from './app.service';

@Component({
  selector: 'app-component2',
  template: `<p>{{greeting}}</p>`
})
export class Component2 {
  greeting: string;

  constructor(private appService: AppService) {
    this.greeting = this.appService.getGreeting();
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Explanation:
    • Both Component1 and Component2 share the same AppService because they are part of the same module.
    • The AppService instance is shared across these components because Angular’s DI system provides a singleton instance when declared in the providers list.
    • This allows Component1 and Component2 to use the AppService without instantiating it themselves, ensuring the consistency of service data across components.

3. Dynamic Injection and Injector API

Scenario: Using Injector for dynamic resolution of dependencies.

  • Example:
  // dynamic-injection.component.ts
  import { Component, Injector } from '@angular/core';
  import { AppService } from './app.service';

  @Component({
    selector: 'app-dynamic-injection',
    template: `<button (click)="resolveService()">Resolve Service</button>`
  })
  export class DynamicInjectionComponent {
    constructor(private injector: Injector) {}

    resolveService() {
      // Resolve AppService dynamically
      const service = this.injector.get(AppService);
      console.log(service.getGreeting());
    }
  }
Enter fullscreen mode Exit fullscreen mode
  • Explanation:
    • In this example, DynamicInjectionComponent demonstrates dynamic resolution of a service using Angular’s Injector.
    • The Injector is injected into the component and used to get the instance of AppService at runtime using this.injector.get(AppService).
    • This approach is useful when services need to be dynamically resolved, especially when dealing with circular dependencies or resolving services in a different context.

4. Providing Services at Global Level (Root Injector)

Scenario: Providing a service globally so it can be used across the entire Angular application.

Example:

// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppService } from './app.service';
import { AppComponent } from './app.component';
import { DynamicInjectionComponent } from './dynamic-injection.component';

@NgModule({
  declarations: [
    AppComponent,
    DynamicInjectionComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [AppService], // Providing service globally
  bootstrap: [AppComponent]
})
export class AppModule { }
Enter fullscreen mode Exit fullscreen mode
  • Explanation:
    • By adding AppService to the providers array in the NgModule, it becomes a singleton across the entire Angular application.
    • This allows any component to access AppService without needing to be explicitly provided again.
    • Using Injector, services can be resolved globally across the application, which is useful for services like logging, configuration, or common utilities.

5. Hierarchical Injection

Scenario: Using hierarchical injection to override services within nested components.

Example:

// app.service.ts
export class AppService {
  getValue(): string {
    return 'Value from root service';
  }
}

// child.service.ts
export class ChildService {
  getValue(): string {
    return 'Value from child service';
  }
}

// parent.component.ts
import { Component, Injector } from '@angular/core';
import { AppService } from './app.service';
import { ChildService } from './child.service';

@Component({
  selector: 'app-parent',
  template: `<app-child></app-child>`
})
export class ParentComponent {
  constructor(private injector: Injector) {}

  useChildService() {
    const childService = this.injector.get(ChildService);
    console.log(childService.getValue());
  }
}

// child.component.ts
import { Component, Injector } from '@angular/core';
import { AppService } from './app.service';
import { ChildService } from './child.service';

@Component({
  selector: 'app-child',
  template: `<p>{{childValue}}</p>`
})
export class ChildComponent {
  childValue: string;

  constructor(private injector: Injector) {
    const childService = this.injector.get(ChildService);
    this.childValue = childService.getValue();
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Explanation:
    • The ParentComponent and ChildComponent share the Injector.
    • By using the Injector in ParentComponent, we override the root AppService with ChildService.
    • ChildComponent resolves ChildService instead of AppService because the Injector in ParentComponent provides the ChildService.
    • This demonstrates the flexibility and hierarchical nature of Angular’s DI system.

Summary

Dependency Injection and services are fundamental concepts in Angular, allowing developers to structure their applications cleanly, test services independently, and maintain separation of concerns. By using Injector, AppService, and ChildService examples, the explanation shows how services can be resolved dynamically, shared across components, and overridden in nested contexts. This approach significantly enhances modularity, reusability, and maintainability in Angular applications.


Image description

8. Advanced Angular Utilities: Differ Utilities

In Angular, Differ utilities are essential for tracking changes to data structures and efficiently updating the DOM based on these changes. Angular provides two main differ utilities: KeyValueDiffers and IterableDiffers. These utilities enable developers to detect changes in collections and automatically update the view, thus minimizing performance overhead.

1. Differ Utilities Overview

Angular’s differ utilities are used internally by Angular’s change detection mechanism. They help in efficiently detecting changes in complex data structures like arrays and objects. By using these differ utilities, Angular can perform change detection without iterating over collections every time a change is detected, thus improving performance.

Key Concepts:

  • KeyValueDiffers: Used for changes in objects (maps, dictionaries) where keys are compared.
  • IterableDiffers: Used for changes in collections (arrays, lists) where elements are compared by value.

2. KeyValueDiffers

KeyValueDiffers are used to detect changes in objects or maps. It checks for changes in keys, values, or both, and updates the view accordingly.

Example:

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

@Component({
  selector: 'app-key-value-diff',
  template: `
    <ul>
      <li *ngFor="let key of keys">
        {{key}}: {{data[key]}}
      </li>
    </ul>
  `
})
export class KeyValueDiffComponent {
  keys: string[];
  data: { [key: string]: string };

  constructor(private differs: KeyValueDiffers) {
    const differ = this.differs.find(this.data).create();

    this.data = {
      'name': 'John',
      'age': '30'
    };

    differ.diff(this.data); // Initial detection

    // Simulating changes
    setTimeout(() => {
      this.data['age'] = '31';
      const changes = differ.diff(this.data);
      if (changes) {
        this.keys = Object.keys(this.data);
      }
    }, 2000);
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Explanation:
    • In this example, KeyValueDiffComponent uses KeyValueDiffers to detect changes in the data object.
    • The differs.find(this.data).create() line creates a differ instance for the data object.
    • Initially, the data object contains name and age.
    • After 2 seconds, the age property is updated to 31. The differ detects this change and updates the view by comparing keys and values.
    • The changes object helps in determining what changes have occurred (e.g., age changed from 30 to 31).

Use Cases for KeyValueDiffers:

  • Real-time updates: Updating data from an API in real-time.
  • Detecting updates: Notifying the user when specific data changes.
  • Object manipulation: Detecting changes in nested objects or collections.

3. IterableDiffers

IterableDiffers are used to detect changes in collections such as arrays, lists, and other iterable objects. It helps in efficiently managing the DOM by detecting added, removed, or moved items in the collection.

Example:

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

@Component({
  selector: 'app-iterable-diff',
  template: `
    <ul>
      <li *ngFor="let item of items">{{item}}</li>
    </ul>
  `
})
export class IterableDiffComponent {
  items: string[];
  differ: any;

  constructor(private differs: IterableDiffers) {
    this.items = ['apple', 'banana', 'orange'];

    this.differ = this.differs.find(this.items).create();

    // Simulating changes
    setTimeout(() => {
      this.items.push('grape');
      const changes = this.differ.diff(this.items);
      if (changes) {
        console.log('Items added:', changes.added);
      }
    }, 2000);
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Explanation:
    • In this example, IterableDiffComponent uses IterableDiffers to detect changes in the items array.
    • The differs.find(this.items).create() line creates a differ instance for the items array.
    • Initially, the items array contains ['apple', 'banana', 'orange'].
    • After 2 seconds, a new item ('grape') is added to the array.
    • The differ detects this change, compares the new and old states of the array, and updates the view to show the added item ('grape').
    • The changes object provides insights into what changes occurred (e.g., the item was added).

Use Cases for IterableDiffers:

  • Dynamic lists: Updating lists with real-time data.
  • Animations: Detecting movements or rearrangements in lists.
  • Batch updates: Handling updates from APIs or user interactions where multiple changes occur at once.

4. Combining KeyValueDiffers and IterableDiffers

Often, applications require both object and array differ utilities. Angular allows you to use these differ utilities in combination when needed.

Example:

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

@Component({
  selector: 'app-combined-diff',
  template: `
    <h3>Object Changes:</h3>
    <ul>
      <li *ngFor="let key of objKeys">{{key}}: {{objData[key]}}</li>
    </ul>
    <h3>Array Changes:</h3>
    <ul>
      <li *ngFor="let item of listItems">{{item}}</li>
    </ul>
  `
})
export class CombinedDiffComponent {
  objData: { [key: string]: string };
  listItems: string[];
  objDiffer: any;
  listDiffer: any;

  constructor(private differs: KeyValueDiffers, private iterableDiffers: IterableDiffers) {
    this.objData = {
      'name': 'Alice',
      'age': '25'
    };
    this.listItems = ['cat', 'dog', 'fish'];

    this.objDiffer = this.differs.find(this.objData).create();
    this.listDiffer = this.iterableDiffers.find(this.listItems).create();

    // Simulating changes
    setTimeout(() => {
      this.objData['age'] = '26';
      this.listItems.push('bird');
      const objChanges = this.objDiffer.diff(this.objData);
      const listChanges = this.listDiffer.diff(this.listItems);

      if (objChanges) {
        this.objKeys = Object.keys(this.objData);
      }

      if (listChanges) {
        console.log('Items added:', listChanges.added);
      }
    }, 2000);
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Explanation:
    • In this example, CombinedDiffComponent demonstrates the use of both KeyValueDiffers and IterableDiffers.
    • objData is an object being monitored by KeyValueDiffers, and listItems is an array monitored by IterableDiffers.
    • Changes in objData and listItems are detected separately using objDiffer and listDiffer.
    • The combined use case illustrates how these differ utilities can coexist to track changes across different data structures in Angular.

Summary

The KeyValueDiffers and IterableDiffers utilities in Angular are crucial for efficiently handling dynamic updates to data structures. They allow Angular to detect changes with minimal overhead, providing smooth UI updates. Through practical examples, we’ve seen how these differ utilities can be used to manage changes in objects and arrays, ensuring that the Angular framework remains responsive and performant even with complex data updates. This knowledge helps developers optimize their Angular applications by managing state changes effectively.


Image description

Image description

Here is a summary:

1. Template and Content Projection

  • ng-template: Used to define reusable HTML blocks without rendering it to the DOM directly. It is commonly used for conditional rendering or component customization.
  • ng-container: A wrapper element that doesn't render any actual HTML but allows grouping elements and directives together for structural directives.
  • ng-content: Used for content projection. It allows you to slot content into a component or directive template.
  • NgTemplateOutlet: A directive used to dynamically change the template being rendered in a component.

2. Structural and Attribute Directives

  • Structural Directives:
    • *ngIf: Controls the rendering of elements based on a condition.
    • *ngFor: Iterates over a collection to render each item.
    • Custom Directives: Allows for the creation of reusable logic that can be applied to elements across an application.
  • Attribute Directives:
    • HostBinding and HostListener: Used to bind properties and listen for events on the host element.

3. Component Interaction and Content Queries

  • View Queries:
    • @ViewChild and @ViewChildren: Used to query for elements or directives within a component's view.
  • Content Queries:
    • @ContentChild and @ContentChildren: Used to query for content projected into a component's view.

4. Rendering and DOM Manipulation

  • Renderer API:
    • Renderer2: Provides methods for manipulating the DOM in a way that is compatible with different platforms (like web and server-side rendering).
  • Element Reference:
    • ElementRef: Provides a reference to the native element in the DOM, allowing for direct manipulation.

5. Dynamic Component Loading

  • Dynamic Component Creation:
    • ComponentRef: Represents an instance of a component created dynamically.
    • ComponentFactoryResolver: Used to resolve and create components at runtime.
    • ViewContainerRef: Holds the instance of the component being created dynamically and is required for attaching the created component to the DOM.

6. Change Detection and Views

  • Change Detection:
    • ChangeDetectorRef: Used to manually trigger change detection for a component.
  • View Management:
    • ViewRef: Provides a reference to an Angular view (template).
    • HostViewRef: Represents the host view of a component (the main view where the component template is rendered).
    • EmbeddedViewRef: Represents an embedded view, typically used within a component to display projected content.

7. Dependency Injection and Services

  • Dependency Injection:
    • Injector: The core service used to provide and manage dependencies across the Angular application. It allows components to access services and other injectable classes.

8. Advanced Angular Utilities

  • Differ Utilities:
    • KeyValueDiffers: Detects changes in objects.
    • IterableDiffers: Detects changes in collections like arrays.

Each of these concepts plays a vital role in the Angular ecosystem, contributing to efficient and maintainable development. Incorporating these into your Angular expertise will give a comprehensive understanding of how to build applications that are modular, maintainable, and performant.

Top comments (0)