Angular: Components, Directives and Pipes – How to properly create and test them

Łukasz Flak

Introduction

Angular is a popular framework for building web applications. It provides a robust set of tools and features to create dynamic and scalable applications. One of the key concepts in Angular is the use of components, directives, and pipes. In this article, we will explore these concepts in detail and learn how to create and test them effectively.

Components

Components are the building blocks of an Angular application. They are responsible for rendering the user interface and handling user interactions. A component consists of three main parts: the template, the class, and the metadata.

The template defines the structure and layout of the component’s view. It is typically written in HTML and may include Angular-specific syntax and directives. The class contains the component’s logic and data. It is written in TypeScript and defines properties and methods that are used in the template.

Here’s an example of a simple Angular component:

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

@Component({
  selector: 'app-example',
  templateUrl: './example.component.html',
  styleUrls: ['./example.component.css']
})
export class ExampleComponent {
  name: string = 'John Doe';
}

In this example, we have defined a component called ExampleComponent with a selector of app-example. The template and styles for this component are defined in separate files.

Testing Components

Testing components is an important part of ensuring the quality and reliability of an Angular application. Angular provides a testing framework called Jasmine, along with a testing utility called TestBed, to facilitate component testing.

To test a component, we need to create a test file and configure TestBed to create an instance of the component. We can then interact with the component and make assertions about its behavior.

Here’s an example of a test for the ExampleComponent:

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

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

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [ExampleComponent]
    }).compileComponents();
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(ExampleComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

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

  it('should display the correct name', () => {
    const element: HTMLElement = fixture.nativeElement;
    const nameElement = element.querySelector('.name');
    expect(nameElement.textContent).toContain(component.name);
  });
});

In this test, we first configure TestBed to include the ExampleComponent in the test module. We then create an instance of the component and obtain a reference to its fixture. The beforeEach hook is used to set up the test environment before each test case. The first test case checks if the component is created successfully. The second test case verifies that the name displayed in the template matches the component’s name property.

Directives

Directives are a way to extend HTML with custom behaviors. They allow us to manipulate the DOM, apply styles, and add interactivity to elements. In Angular, there are two types of directives: structural directives and attribute directives.

Structural directives modify the structure of the DOM by adding or removing elements. They are denoted by an asterisk (*) before the directive name. Attribute directives, on the other hand, modify the behavior or appearance of elements.

Here’s an example of a custom directive that highlights the background color of an element:

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

@Directive({
  selector: '[appHighlight]'
})
export class HighlightDirective {
  constructor(private elementRef: ElementRef) { }

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

  @HostListener('mouseleave')
  onMouseLeave() {
    this.highlight(null);
  }

  private highlight(color: string | null) {
    this.elementRef.nativeElement.style.backgroundColor = color;
  }
}

In this example, we have defined a directive called HighlightDirective with a selector of appHighlight. The directive listens to the mouseenter and mouseleave events and changes the background color of the element accordingly.

Testing Directives

Testing directives involves creating a test component that uses the directive and making assertions about its behavior. We can use the same testing framework and utilities mentioned earlier for component testing.

Here’s an example of a test for the HighlightDirective:

import { Component } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HighlightDirective } from './highlight.directive';

@Component({
  template: `
    <div appHighlight></div>
  `
})
class TestComponent { }

describe('HighlightDirective', () => {
  let fixture: ComponentFixture<TestComponent>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [HighlightDirective, TestComponent]
    }).compileComponents();
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(TestComponent);
    fixture.detectChanges();
  });

  it('should apply the highlight on mouseenter', () => {
    const element: HTMLElement = fixture.nativeElement.querySelector('div');
    element.dispatchEvent(new Event('mouseenter'));
    fixture.detectChanges();
    expect(element.style.backgroundColor).toBe('yellow');
  });

  it('should remove the highlight on mouseleave', () => {
    const element: HTMLElement = fixture.nativeElement.querySelector('div');
    element.dispatchEvent(new Event('mouseleave'));
    fixture.detectChanges();
    expect(element.style.backgroundColor).toBe('');
  });
});

In this test, we have created a test component that uses the HighlightDirective by adding it as an attribute to a

element. We have then simulated the mouseenter and mouseleave events on the element and asserted that the background color is applied and removed accordingly.

Pipes

Pipes are a way to transform data in Angular templates. They allow us to perform operations such as filtering, sorting, and formatting on data before displaying it. Angular provides several built-in pipes, and we can also create custom pipes to suit our specific needs.

Here’s an example of a custom pipe that transforms a string to uppercase:

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

@Pipe({
  name: 'uppercase'
})
export class UppercasePipe implements PipeTransform {
  transform(value: string): string {
    return value.toUpperCase();
  }
}

In this example, we have defined a pipe called UppercasePipe with the name uppercase. The transform method takes a string as input and returns its uppercase version.

Testing Pipes

Testing pipes involves creating a test bed and invoking the pipe’s transform method with different input values. We can then assert that the transformed output matches our expectations.

Here’s an example of a test for the UppercasePipe:

import { TestBed } from '@angular/core/testing';
import { UppercasePipe } from './uppercase.pipe';

describe('UppercasePipe', () => {
  let pipe: UppercasePipe;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [UppercasePipe]
    });
    pipe = TestBed.inject(UppercasePipe);
  });

  it('should transform the input to uppercase', () => {
    const input = 'hello world';
    const transformed = pipe.transform(input);
    expect(transformed).toBe('HELLO WORLD');
  });
});

In this test, we have configured TestBed to provide an instance of the UppercasePipe. We have then used TestBed.inject() to obtain a reference to the pipe. We have invoked the transform method with a test input and asserted that the transformed output matches our expected value.

Conclusion

Components, directives, and pipes are essential building blocks in Angular applications. Components provide the structure and logic for the user interface, directives extend HTML with custom behaviors, and pipes transform data before displaying it. Testing these elements ensures their proper functionality and helps maintain application quality. By following the examples and guidelines provided in this article, you’ll be well-equipped to create and test components, directives, and pipes effectively in your Angular projects. Happy coding!

References

Meet the geek-tastic people, and allow us to amaze you with what it's like to work with j‑labs!

Contact us