npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@acontplus/ng-customer

v2.2.5

Published

Angular customer management module following clean architecture principles. Provides customer UI components, business logic, and integrates with notification systems for comprehensive customer relationship management.

Readme

@acontplus/ng-customer

Angular customer management library following clean architecture principles. Provides comprehensive customer CRUD operations with Angular Material UI components, domain-driven design patterns, and external SRI integration.

Installation

# Using npm
npm install @acontplus/ng-customer

# Using pnpm
pnpm add @acontplus/ng-customer

Features

  • Clean Architecture: Domain, Application, Infrastructure, and UI layers
  • Customer Components: Card display and add/edit form components
  • SRI Integration: External customer data validation and retrieval
  • Use Cases: Business logic for customer operations (CRUD)
  • BaseRepository Pattern: Data access abstractions with HTTP implementations
  • DTOs and Mappers: Data transformation and mapping utilities
  • Form Validation: Comprehensive validation including Ecuadorian ID/RUC
  • Angular Material: Consistent Material Design components

Quick Start

1. Import Components

import {
  CompanyCustomerList,
  CompanyCustomerForm,
  CustomerAddEditComponent,
  CompanyCustomerAddEditDialog,
  CompanyCustomerAddEditDirective,
} from '@acontplus/ng-customer';

@Component({
  selector: 'app-customers',
  imports: [CompanyCustomerList],
  template: `
    <acp-company-customer-list (editCustomer)="onEdit($event)" (deleteCustomer)="onDelete($event)">
    </acp-company-customer-list>
  `,
})
export class CustomersComponent {
  onEdit(customer: any) {
    // Handle edit action
  }

  onDelete(customer: any) {
    // Handle delete action
  }
}

2. Customer Add/Edit Dialog

import { MatDialog } from '@angular/material/dialog';
import { CompanyCustomerAddEditDialog } from '@acontplus/ng-customer';

@Component({...})
export class CustomerListComponent {
  constructor(private dialog: MatDialog) {}

  openCustomerDialog(customer?: any) {
    const dialogRef = this.dialog.open(CompanyCustomerAddEditDialog, {
      width: '800px',
      data: {
        id: customer?.id || 0,
        descripcion: customer ? 'Editar Cliente' : 'Nuevo Cliente',
        data: customer || {}
      }
    });

    dialogRef.afterClosed().subscribe(result => {
      if (result) {
        // Handle successful save
        console.log('Customer saved:', result);
      }
    });
  }
}

Architecture Layers

Domain Layer

Entities:

// Customer entity with business logic
export class Customer {
  constructor(
    public readonly id: number,
    public readonly businessName: string,
    public readonly identificationNumber: string,
    public readonly email?: string,
  ) {}

  isValidForCredit(): boolean {
    // Business logic for credit validation
    return this.identificationNumber.length >= 10;
  }
}

// External customer entity for SRI integration
export class CustomerExternal {
  constructor(
    public readonly idCard: string,
    public readonly businessName: string,
    public readonly address: string,
  ) {}
}

BaseRepository Interfaces:

export interface CustomerRepository {
  getById(id: number): Promise<Customer>;
  create(customer: CreateCustomerDto): Promise<Customer>;
  update(id: number, customer: UpdateCustomerDto): Promise<Customer>;
  delete(id: number): Promise<void>;
  checkExistence(identificationNumber: string): Promise<boolean>;
}

export interface CustomerExternalRepository {
  getById(identificationNumber: string): Promise<CustomerExternal>;
}

Application Layer

Use Cases:

// Customer business operations
export class CustomerUseCase {
  constructor(private repository: CustomerRepository) {}

  async create(dto: CreateCustomerDto): Promise<ApiResponse<Customer>> {
    // Business validation
    if (!this.isValidIdentification(dto.numeroIdentificacion)) {
      throw new Error('Invalid identification number');
    }

    return await this.repository.create(dto);
  }

  async getFormData(): Promise<ApiResponse<FormData>> {
    // Load form dropdown data
    return await this.repository.getFormData();
  }

  private isValidIdentification(id: string): boolean {
    // Ecuadorian ID/RUC validation logic
    return id.length >= 10;
  }
}

// External SRI integration
export class CustomerExternalUseCase {
  constructor(private repository: CustomerExternalRepository) {}

  async getById(identificationNumber: string): Promise<ApiResponse<CustomerExternal>> {
    return await this.repository.getById(identificationNumber);
  }
}

Infrastructure Layer

DTOs:

export interface CustomerListItemDto {
  idCliente: number;
  businessName: string;
  email?: string;
  phone?: string;
  identificationNumber?: string;
}

export interface CreateCustomerDto {
  nombreFiscal: string;
  nombreComercial?: string;
  numeroIdentificacion: string;
  idTipoIdentificacion: number;
  direccion: string;
  correo?: string;
  telefono?: string;
}

HTTP Repositories:

export class CustomerHttpRepository implements CustomerRepository {
  constructor(private http: HttpClient) {}

  async getById(id: number): Promise<Customer> {
    const response = await firstValueFrom(
      this.http.get<ApiResponse<CustomerDto>>(`/api/customers/${id}`),
    );
    return CustomerMapper.toDomain(response.data);
  }

  async create(dto: CreateCustomerDto): Promise<Customer> {
    const response = await firstValueFrom(
      this.http.post<ApiResponse<CustomerDto>>('/api/customers', dto),
    );
    return CustomerMapper.toDomain(response.data);
  }
}

Mappers:

export class CustomerMapper {
  static toDomain(dto: CustomerDto): Customer {
    return new Customer(dto.idCliente, dto.nombreFiscal, dto.numeroIdentificacion, dto.correo);
  }

  static toDto(customer: Customer): CustomerDto {
    return {
      idCliente: customer.id,
      nombreFiscal: customer.businessName,
      numeroIdentificacion: customer.identificationNumber,
      correo: customer.email,
    };
  }
}

UI Layer

Customer Card Component:

@Component({
  selector: 'acp-customer-card',
  template: `
    <mat-card>
      <mat-card-header>
        <div mat-card-avatar class="customer-avatar">
          {{ getLogoSliceBusinessName() }}
        </div>
        <mat-card-title>{{ customer().businessName }}</mat-card-title>
        <mat-card-subtitle>{{ customer().email }}</mat-card-subtitle>
      </mat-card-header>

      <mat-card-content>
        <p><strong>ID:</strong> {{ customer().identificationNumber }}</p>
        <p><strong>Phone:</strong> {{ customer().phone }}</p>
      </mat-card-content>

      <mat-card-actions>
        <button mat-button (click)="onEditClick()">Edit</button>
        <button mat-button color="warn" (click)="onDeleteClick()">Delete</button>
      </mat-card-actions>
    </mat-card>
  `,
})
export class CustomerCard {
  customer = input.required<CustomerListItemDto>();
  editCustomer = output<CustomerListItemDto>();
  deleteCustomer = output<CustomerListItemDto>();

  getLogoSliceBusinessName = computed(() => {
    return this.customer()?.businessName?.slice(0, 1) || '?';
  });
}

Advanced Features

SRI Integration

Automatic customer data retrieval from Ecuador's SRI (Tax Service):

// Automatic SRI lookup on ID number input
onKeyDownGovernmentId() {
  const identificationNumber = this.customerForm.get('numeroIdentificacion')?.value;
  const identificationType = this.customerForm.get('idTipoIdentificacion')?.value;

  if (identificationNumber && identificationType) {
    this.customerExternalUseCase.getById(identificationNumber)
      .subscribe(response => {
        if (response.success) {
          // Auto-fill form with SRI data
          this.customerForm.patchValue({
            nombreFiscal: response.data.businessName,
            direccion: response.data.address,
            validationSri: true
          });
        }
      });
  }
}

Form Validation

Comprehensive validation including Ecuadorian ID/RUC:

// Custom validator for RUC format (must end with 001)
endsWith001Validator(control: AbstractControl): ValidationErrors | null {
  const value = control.value;
  if (value && value.length >= 3 && !value.endsWith('001')) {
    return { endsWith001: true };
  }
  return null;
}

// Dynamic validation based on identification type
updateFormControlNumeroIdentificacion(codigoSri: string): void {
  const idNumberControl = this.customerForm.get('numeroIdentificacion');

  if (codigoSri === SRI_DOCUMENT_TYPE.CEDULA) {
    idNumberControl?.setValidators([
      Validators.required,
      Validators.pattern(/^\d+$/),
      Validators.minLength(10),
      Validators.maxLength(10)
    ]);
  } else if (codigoSri === SRI_DOCUMENT_TYPE.RUC) {
    idNumberControl?.setValidators([
      Validators.required,
      Validators.pattern(/^\d+$/),
      Validators.minLength(13),
      Validators.maxLength(13),
      this.endsWith001Validator
    ]);
  }

  idNumberControl?.updateValueAndValidity();
}

Multi-Contact Support

Handle multiple emails, phones, and license plates:

// Arrays for multiple contact methods
emails: string[] = [];
telephones: string[] = [];
placas = signal<string[]>([]);

// Save with concatenated values
onSave() {
  this.customerForm.patchValue({
    telefono: this.telephones.join('|'),
    correo: this.emails.join('|'),
    placa: this.placas().join('|')
  });

  // Submit form...
}

API Reference

Components

  • CustomerCard: Display customer information in card format
  • CustomerAddEditComponent: Form for creating/editing customers

Use Cases

  • CustomerUseCase: Main customer business operations
  • CustomerExternalUseCase: SRI integration operations

Repositories

  • CustomerHttpRepository: HTTP-based customer data access
  • CustomerExternalHttpRepository: External SRI data access

DTOs

  • CustomerListItemDto: Customer list display data
  • CreateCustomerDto: Customer creation data
  • CustomerExternalDto: External SRI customer data