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
displayWith
(value: any) => string | null
Function that maps an option’s value to its display value
Maximum number of results to show in the search
Number of results to skip from the results pagination
Search term to use when executing the search. Updating this value will run a new search.
REST connector configuration from datalink definition containing:
url: External API endpoint
searchParam: Query parameter name
authentication: Auth configuration (type, username, password)
CSS class for display customization
Outputs
Emitted when search results have fully loaded. Emits the results array.
Emitted when an error occurs during search.
Properties
Whether the results panel is currently open
Whether to show the results panel
Methods
resetResults
Clears search results and hides the panel.
reload
Reloads search with current search term.
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'
Datalink configuration object containing:
columns: Column definitions
connectorRest: REST API configuration
Outputs
Emitted when items are selected in the results table. Emits array of selected items.
Properties
Form control for search input field
Debounce delay in milliseconds
Currently selected items in the data table
Reference to child search component
Methods
clearSearch
Clears the search input and resets results.
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