Multi-Service Communication with a Single Component using a Directive

Step 1: Define Service B (Validation Service)

Service B will be responsible for validating the input from component A. It will emit an event or a state change when validation criteria are met or not met.

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

@Injectable({
  providedIn: 'root'
})
export class ValidationService {
  private errorState = new BehaviorSubject<string | null>(null);

  validateInput(value: string, type: 'name' | 'date'): void {
    let isValid = true;
    if (type === 'name') {
      isValid = value.trim().length > 0;
    } else if (type === 'date') {
      isValid = /^\d{4}-\d{2}-\d{2}$/.test(value); // Simple YYYY-MM-DD validation
    }

    this.errorState.next(isValid ? null : 'error');
  }

  getErrorState() {
    return this.errorState.asObservable();
  }
}

Step 2: Define Service A (Control Service)

Service A triggers Service B and could handle other logic that coordinates between different parts of your application.

import { Injectable } from '@angular/core';
import { ValidationService } from './validation.service';

@Injectable({
  providedIn: 'root'
})
export class ControlService {

  constructor(private validationService: ValidationService) { }

  processValidation(value: string, type: 'name' | 'date'): void {
    this.validationService.validateInput(value, type);
  }
}

Step 3: Create a Directive for Handling DOM Manipulations

A directive is a more appropriate place to handle DOM manipulations. It can listen to the validation state and apply classes accordingly.

import { Directive, ElementRef, Renderer2, OnInit, OnDestroy } from '@angular/core';
import { ValidationService } from './validation.service';
import { Subscription } from 'rxjs';

@Directive({
  selector: '[appValidate]'
})
export class ValidateDirective implements OnInit, OnDestroy {
  private subscription: Subscription;

  constructor(
    private el: ElementRef,
    private renderer: Renderer2,
    private validationService: ValidationService
  ) {}

  ngOnInit(): void {
    this.subscription = this.validationService.getErrorState().subscribe(state => {
      if (state === 'error') {
        this.renderer.addClass(this.el.nativeElement, 'error');
      } else {
        this.renderer.removeClass(this.el.nativeElement, 'error');
      }
    });
  }

  ngOnDestroy(): void {
    this.subscription.unsubscribe();
  }
}

Step 4: Usage in Component A

Finally, ensure that component A uses this directive and connects with the services appropriately.

import { Component } from '@angular/core';
import { ControlService } from './control.service';

@Component({
  selector: 'app-component-a',
  template: `
    <input id="myInput" [(ngModel)]="inputValue" appValidate type="text">
    <button (click)="validateInput()">Check</button>
  `
})
export class ComponentA {
  inputValue: string = '';

  constructor(private controlService: ControlService) {}

  validateInput(): void {
    // Assuming it's a 'name' validation for simplicity
    this.controlService.processValidation(this.inputValue, 'name');
  }
}

Conclusion

This setup achieves the desired functionality with a clearer separation of concerns:

  • Services manage state and logic.
  • Directives manage DOM manipulation.
  • Components manage user interactions and data binding.

This structure helps keep each part of the app focused on its responsibilities, improving maintainability and testability.

Scroll to Top