Skip to main content

Overview

The Venzia Datalinks library provides two search-related components:
  • DatalinkSearchComponent: Core search component for querying external REST APIs
  • DatalinkAddPanelComponent: Panel component combining search and data table for adding datalinks

DatalinkSearchComponent

Standalone component for searching external data sources configured in datalink definitions.

Import

import { DatalinkSearchComponent } from 'venzia-datalink';

Selector

'aqua-datalink-search'

Inputs

displayWith
(value: any) => string | null
Function that maps an option’s value to its display value
maxResults
number
default:"20"
Maximum number of results to show in the search
skipResults
number
default:"0"
Number of results to skip from the results pagination
searchTerm
string
default:"''"
Search term to use when executing the search. Updating this value will run a new search.
connectorRest
any
required
REST connector configuration from datalink definition containing:
  • url: External API endpoint
  • searchParam: Query parameter name
  • authentication: Auth configuration (type, username, password)
class
string
CSS class for display customization

Outputs

resultLoaded
EventEmitter<any>
Emitted when search results have fully loaded. Emits the results array.
error
EventEmitter<any>
Emitted when an error occurs during search.

Properties

results
any
Current search results
isOpen
boolean
Whether the results panel is currently open
showPanel
boolean
Whether to show the results panel

Methods

resetResults

Clears search results and hides the panel.
resetResults(): void

reload

Reloads search with current search term.
reload(): void

Example Usage

import { Component } from '@angular/core';
import { DatalinkSearchComponent } from 'venzia-datalink';

@Component({
  selector: 'app-customer-search',
  standalone: true,
  imports: [DatalinkSearchComponent],
  template: `
    <aqua-datalink-search
      [searchTerm]="searchTerm"
      [connectorRest]="customerConnector"
      [maxResults]="10"
      (resultLoaded)="onResultsLoaded($event)"
      (error)="onSearchError($event)">
      
      <!-- Custom result template -->
      <ng-template let-result>
        <div class="result-item">
          <strong>{{ result.name }}</strong>
          <span>{{ result.code }}</span>
        </div>
      </ng-template>
    </aqua-datalink-search>
  `
})
export class CustomerSearchComponent {
  searchTerm = '';
  
  customerConnector = {
    url: 'https://api.example.com/customers',
    searchParam: 'q',
    authentication: {
      type: 'basic',
      username: 'apiuser',
      password: 'apipass'
    }
  };

  onResultsLoaded(results: any[]) {
    console.log('Found', results.length, 'results');
  }

  onSearchError(error: any) {
    console.error('Search failed:', error);
  }
}

Implementation Details

import { Component, Input, Output, EventEmitter, ViewEncapsulation, OnChanges } from '@angular/core';
import { Subject } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
import { VenziaDatalinkRestService } from '../../services/venzia-datalink-rest.service';

@Component({
  standalone: true,
  selector: 'aqua-datalink-search',
  templateUrl: './datalink-search.component.html',
  styleUrls: ['./datalink-search.component.scss'],
  encapsulation: ViewEncapsulation.None,
  host: { class: 'aqua-datalink-search' }
})
export class DatalinkSearchComponent implements OnChanges {
  @Input() displayWith: ((value: any) => string) | null = null;
  @Input() maxResults = 20;
  @Input() skipResults = 0;
  @Input() searchTerm = '';
  @Input() connectorRest: any;
  @Input('class') set classList(classList: string) {
    if (classList?.length) {
      classList.split(' ').forEach((className) => {
        this._classList[className.trim()] = true;
      });
    }
  }

  @Output() resultLoaded: EventEmitter<any> = new EventEmitter();
  @Output() error: EventEmitter<any> = new EventEmitter();

  results: any;
  showPanel = false;
  _isOpen = false;
  keyPressedStream: Subject<string> = new Subject();
  _classList: { [key: string]: boolean } = {};

  constructor(private searchService: VenziaDatalinkRestService) {
    this.keyPressedStream
      .asObservable()
      .pipe(debounceTime(200))
      .subscribe((searchedWord: string) => {
        this.loadSearchResults(searchedWord);
      });
  }

  ngOnChanges(changes) {
    if (changes.searchTerm?.currentValue) {
      this.loadSearchResults(changes.searchTerm.currentValue);
    }
  }

  private loadSearchResults(searchTerm?: string) {
    if (!searchTerm) {
      this.cleanResults();
      return;
    }

    const filters = { limit: this.maxResults };
    filters[this.connectorRest.searchParam] = searchTerm;

    let authdata: string | undefined;
    if (this.connectorRest.authentication?.type === 'basic') {
      authdata = window.btoa(
        `${this.connectorRest.authentication.username}:${this.connectorRest.authentication.password}`
      );
    }

    this.searchService.callApi(this.connectorRest.url, filters, authdata).subscribe(
      (result) => {
        this.results = result;
        this.resultLoaded.emit(this.results);
        this._isOpen = true;
        this.showPanel = true;
      },
      (err) => this.error.emit(err)
    );
  }
}

DatalinkAddPanelComponent

Standalone component that combines search and data table for adding datalink entries.

Import

import { DatalinkAddPanelComponent } from 'venzia-datalink';

Selector

'aqua-datalink-add-panel'

Inputs

Datalink configuration object containing:
  • columns: Column definitions
  • connectorRest: REST API configuration

Outputs

select
EventEmitter<any>
Emitted when items are selected in the results table. Emits array of selected items.

Properties

searchInput
FormControl
Form control for search input field
searchedWord
string
Current search term
Debounce delay in milliseconds
selectedItems
any[]
Currently selected items in the data table
results
any[]
Current search results
Reference to child search component

Methods

clearSearch

Clears the search input and resets results.
clearSearch(): void

elementClicked / elementUnClicked

Handles row selection/deselection in the results table.
elementClicked(event: any): void
elementUnClicked(event: any): void

Example Usage

import { Component, Input } from '@angular/core';
import { DatalinkAddPanelComponent } from 'venzia-datalink';

@Component({
  selector: 'app-add-customer-dialog',
  standalone: true,
  imports: [DatalinkAddPanelComponent],
  template: `
    <h2>Add Customer Data</h2>
    
    <aqua-datalink-add-panel
      [dataLink]="customerDataLink"
      (select)="onCustomersSelected($event)">
    </aqua-datalink-add-panel>

    <div class="dialog-actions">
      <button 
        [disabled]="selectedCount === 0"
        (click)="confirmSelection()">
        Add {{ selectedCount }} Customer(s)
      </button>
      <button (click)="cancel()">Cancel</button>
    </div>
  `,
  styles: [`
    :host {
      display: block;
      padding: 20px;
      width: 600px;
      height: 500px;
    }
    .dialog-actions {
      display: flex;
      justify-content: flex-end;
      gap: 10px;
      margin-top: 20px;
    }
  `]
})
export class AddCustomerDialogComponent {
  @Input() customerDataLink: any;
  
  selectedCustomers: any[] = [];
  selectedCount = 0;

  onCustomersSelected(items: any[]) {
    this.selectedCustomers = items;
    this.selectedCount = items.length;
    console.log('Selected:', items);
  }

  confirmSelection() {
    console.log('Confirmed:', this.selectedCustomers);
    // Emit or save selection
  }

  cancel() {
    console.log('Cancelled');
  }
}

Template Structure

The component template includes:
<div class="datalink-add-panel">
  <!-- Search input -->
  <mat-form-field appearance="outline">
    <mat-label>Search</mat-label>
    <input 
      matInput
      [formControl]="searchInput"
      placeholder="Type to search..." />
    <button 
      *ngIf="searchedWord"
      matSuffix
      mat-icon-button
      (click)="clearSearch()">
      <mat-icon>close</mat-icon>
    </button>
  </mat-form-field>

  <!-- Search component -->
  <aqua-datalink-search
    #search
    [searchTerm]="searchedWord"
    [connectorRest]="dataLink.connectorRest"
    (resultLoaded)="results = $event">
  </aqua-datalink-search>

  <!-- Results table -->
  <adf-datatable
    *ngIf="results?.length"
    [rows]="results"
    [selectionMode]="'multiple'"
    (row-click)="elementClicked($event)"
    (row-unselect)="elementUnClicked($event)">
    
    <data-columns>
      <data-column
        *ngFor="let column of dataLink.columns"
        [key]="column.name"
        [title]="column.name">
      </data-column>
    </data-columns>

    <adf-empty-list>
      <div>No results found</div>
    </adf-empty-list>
  </adf-datatable>

  <!-- Loading indicator -->
  <mat-spinner *ngIf="!results && searchedWord"></mat-spinner>
</div>

Implementation Details

import { Component, EventEmitter, Output, ViewChild, Input } from '@angular/core';
import { FormControl } from '@angular/forms';
import { debounceTime } from 'rxjs/operators';
import { DatalinkSearchComponent } from '../datalink-search/datalink-search.component';

@Component({
  standalone: true,
  selector: 'aqua-datalink-add-panel',
  templateUrl: './datalink-add-panel.component.html',
  styleUrls: ['./datalink-add-panel.component.scss']
})
export class DatalinkAddPanelComponent {
  @ViewChild('search') search: DatalinkSearchComponent;
  @Input() dataLink: any;
  @Output() select: EventEmitter<any> = new EventEmitter();

  searchInput: FormControl = new FormControl();
  searchedWord = '';
  debounceSearch = 200;
  selectedItems: any[] = [];
  results: any[] = [];

  constructor() {
    this.searchInput.valueChanges
      .pipe(debounceTime(this.debounceSearch))
      .subscribe((searchValue) => {
        this.searchedWord = searchValue;
        if (!searchValue) {
          this.search.resetResults();
        }
      });
  }

  elementClicked(event: any) {
    this.updateSelected(event);
  }

  elementUnClicked(event: any) {
    this.updateSelected(event);
  }

  private updateSelected(event: any) {
    this.selectedItems = event.detail.selection;
    this.select.emit(this.selectedItems);
  }

  clearSearch() {
    this.searchedWord = '';
    this.searchInput.setValue('');
    this.selectedItems = [];
    this.search.resetResults();
  }
}

Features

Search Debouncing

Both components include debounced search:
  • Default 200ms delay
  • Reduces API calls during typing
  • Configurable delay period

Multi-Select Support

  • Uses ADF DataTable with multi-select mode
  • Emits selection changes
  • Tracks selected items

External API Integration

  • Supports REST API calls
  • Configurable endpoints
  • Basic authentication support
  • Custom query parameters

Custom Templates

Supports custom result templates:
  • Use <ng-template> for custom rendering
  • Access result data via template variables
  • Full control over result presentation

Styling

Both components include:
  • SCSS stylesheets
  • Material Design integration
  • Host class bindings
  • ViewEncapsulation.None for global styles

Dependencies

  • @angular/core: Angular framework
  • @angular/forms: Reactive forms
  • @angular/material: Material components
  • @alfresco/adf-core: ADF data table
  • rxjs: Reactive programming

See Also

Build docs developers (and LLMs) love