Skip to main content

Angular Components

The Seekora UI SDK provides Angular components and services for building search experiences.

Installation

npm install @seekora-ai/ui-sdk-angular @seekora-ai/search-sdk

Setup

Module Import

// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { SeekoraModule } from '@seekora-ai/ui-sdk-angular';
import { AppComponent } from './app.component';

@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
SeekoraModule.forRoot({
storeId: 'your-store-id',
readSecret: 'your-read-secret',
environment: 'production',
}),
],
bootstrap: [AppComponent],
})
export class AppModule {}

Standalone Components (Angular 14+)

// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { provideSeekoraConfig } from '@seekora-ai/ui-sdk-angular';
import { AppComponent } from './app.component';

bootstrapApplication(AppComponent, {
providers: [
provideSeekoraConfig({
storeId: 'your-store-id',
readSecret: 'your-read-secret',
}),
],
});

Components

SearchProviderComponent

<!-- search-page.component.html -->
<seekora-search-provider [theme]="customTheme">
<seekora-search-bar></seekora-search-bar>
<seekora-search-results></seekora-search-results>
</seekora-search-provider>
// search-page.component.ts
import { Component } from '@angular/core';
import { Theme } from '@seekora-ai/ui-sdk-angular';

@Component({
selector: 'app-search-page',
templateUrl: './search-page.component.html',
})
export class SearchPageComponent {
customTheme: Partial<Theme> = {
colors: {
primary: '#6366f1',
},
};
}

SearchBarComponent

<seekora-search-bar
placeholder="Search products..."
[showSuggestions]="true"
[debounceMs]="300"
[maxSuggestions]="10"
(search)="onSearch($event)"
(queryChange)="onQueryChange($event)"
(suggestionSelect)="onSuggestionSelect($event)"
>
</seekora-search-bar>
@Component({
selector: 'app-search',
templateUrl: './search.component.html',
})
export class SearchComponent {
onSearch(event: { query: string; results: SearchResponse }) {
console.log(`Found ${event.results.totalResults} results`);
}

onQueryChange(query: string) {
console.log('Query:', query);
}

onSuggestionSelect(suggestion: string) {
console.log('Selected:', suggestion);
}
}

SearchResultsComponent

<seekora-search-results
viewMode="grid"
[itemsPerPage]="24"
(resultClick)="onResultClick($event)"
>
<ng-template #resultTemplate let-result let-index="index">
<div class="product-card">
<img [src]="result.image" [alt]="result.title" />
<h3>{{ result.title }}</h3>
<span class="price">{{ result.price | currency }}</span>
</div>
</ng-template>

<ng-template #emptyTemplate>
<div class="empty-state">
<p>No results found</p>
</div>
</ng-template>

<ng-template #loadingTemplate>
<div class="loading">
<app-spinner></app-spinner>
</div>
</ng-template>
</seekora-search-results>

FacetsComponent

<seekora-facets
[showCount]="true"
sortBy="count"
[limit]="10"
[showMore]="true"
(refinementAdd)="onRefinementAdd($event)"
(refinementRemove)="onRefinementRemove($event)"
>
</seekora-facets>

PaginationComponent

<seekora-pagination
[showFirst]="true"
[showLast]="true"
[siblingCount]="2"
(pageChange)="onPageChange($event)"
>
</seekora-pagination>

SortByComponent

<seekora-sort-by
label="Sort by"
[items]="sortOptions"
(change)="onSortChange($event)"
>
</seekora-sort-by>
@Component({...})
export class SearchComponent {
sortOptions = [
{ label: 'Relevance', value: 'relevance:desc' },
{ label: 'Price: Low to High', value: 'price:asc' },
{ label: 'Price: High to Low', value: 'price:desc' },
];
}

CurrentRefinementsComponent

<seekora-current-refinements
(remove)="onRefinementRemove($event)"
>
</seekora-current-refinements>

ClearRefinementsComponent

<seekora-clear-refinements
label="Clear all filters"
(clear)="onClearRefinements()"
>
</seekora-clear-refinements>

StatsComponent

<seekora-stats>
<ng-template #statsTemplate let-stats>
{{ stats.totalResults | number }} results
({{ stats.processingTimeMs }}ms)
</ng-template>
</seekora-stats>

Services

SeekoraSearchService

import { Component, OnInit } from '@angular/core';
import { SeekoraSearchService } from '@seekora-ai/ui-sdk-angular';

@Component({
selector: 'app-custom-search',
template: `
<input [(ngModel)]="query" (keyup.enter)="search()" />
<button (click)="search()" [disabled]="loading$ | async">
{{ (loading$ | async) ? 'Searching...' : 'Search' }}
</button>

<div *ngIf="error$ | async as error">{{ error.message }}</div>

<div *ngFor="let result of (results$ | async)?.results">
{{ result.title }}
</div>
`,
})
export class CustomSearchComponent implements OnInit {
query = '';

results$ = this.searchService.results$;
loading$ = this.searchService.loading$;
error$ = this.searchService.error$;

constructor(private searchService: SeekoraSearchService) {}

search() {
this.searchService.search(this.query, {
per_page: 20,
facet_by: 'category,brand',
});
}
}

QuerySuggestionsService

import { Component } from '@angular/core';
import { QuerySuggestionsService } from '@seekora-ai/ui-sdk-angular';
import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';

@Component({
selector: 'app-autocomplete',
template: `
<input [formControl]="queryControl" />

<ul *ngIf="suggestions$ | async as suggestions">
<li *ngFor="let suggestion of suggestions" (click)="select(suggestion)">
{{ suggestion }}
</li>
</ul>
`,
})
export class AutocompleteComponent {
queryControl = new FormControl('');

suggestions$ = this.queryControl.valueChanges.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap(query => this.suggestionsService.getSuggestions(query))
);

constructor(private suggestionsService: QuerySuggestionsService) {}

select(suggestion: string) {
this.queryControl.setValue(suggestion);
}
}

SearchStateService

import { Component } from '@angular/core';
import { SearchStateService } from '@seekora-ai/ui-sdk-angular';

@Component({
selector: 'app-search-controls',
template: `
<input [value]="query$ | async" (input)="setQuery($event.target.value)" />

<div *ngFor="let ref of refinements$ | async">
{{ ref.field }}: {{ ref.value }}
<button (click)="removeRefinement(ref)">×</button>
</div>

<button (click)="clearRefinements()">Clear all</button>

<select [value]="sortBy$ | async" (change)="setSortBy($event.target.value)">
<option value="relevance:desc">Relevance</option>
<option value="price:asc">Price: Low to High</option>
<option value="price:desc">Price: High to Low</option>
</select>
`,
})
export class SearchControlsComponent {
query$ = this.stateService.query$;
refinements$ = this.stateService.refinements$;
sortBy$ = this.stateService.sortBy$;

constructor(private stateService: SearchStateService) {}

setQuery(query: string) {
this.stateService.setQuery(query);
}

removeRefinement(refinement: { field: string; value: string }) {
this.stateService.removeRefinement(refinement.field, refinement.value);
}

clearRefinements() {
this.stateService.clearRefinements();
}

setSortBy(value: string) {
this.stateService.setSortBy(value);
}
}

AnalyticsService

import { Component, Input } from '@angular/core';
import { AnalyticsService } from '@seekora-ai/ui-sdk-angular';

@Component({
selector: 'app-product-card',
template: `
<div class="product-card" (click)="handleClick()">
<img [src]="product.image" [alt]="product.title" />
<h3>{{ product.title }}</h3>
<span class="price">{{ product.price | currency }}</span>
<button (click)="handleAddToCart($event)">Add to Cart</button>
</div>
`,
})
export class ProductCardComponent {
@Input() product: any;
@Input() index: number;
@Input() context: SearchContext;

constructor(private analytics: AnalyticsService) {}

handleClick() {
this.analytics.trackClick(this.product.id, this.index + 1, this.context);
this.router.navigate(['/product', this.product.id]);
}

handleAddToCart(event: Event) {
event.stopPropagation();
this.analytics.trackConversion(
this.product.id,
this.product.price,
'USD',
this.context
);
this.cartService.add(this.product);
}
}

Complete Example

// search-page.component.ts
import { Component } from '@angular/core';
import { SeekoraSearchService, SearchStateService } from '@seekora-ai/ui-sdk-angular';

@Component({
selector: 'app-search-page',
templateUrl: './search-page.component.html',
styleUrls: ['./search-page.component.scss'],
})
export class SearchPageComponent {
results$ = this.searchService.results$;
loading$ = this.searchService.loading$;

sortOptions = [
{ label: 'Relevance', value: 'relevance:desc' },
{ label: 'Price: Low to High', value: 'price:asc' },
{ label: 'Price: High to Low', value: 'price:desc' },
];

theme = {
colors: {
primary: '#6366f1',
},
};

constructor(
private searchService: SeekoraSearchService,
private stateService: SearchStateService
) {}

onSearch(event: { query: string; results: any }) {
console.log(`Found ${event.results.totalResults} results`);
}

onResultClick(event: { result: any; index: number }) {
this.router.navigate(['/product', event.result.id]);
}

onPageChange(page: number) {
window.scrollTo({ top: 0, behavior: 'smooth' });
}
}
<!-- search-page.component.html -->
<seekora-search-provider [theme]="theme">
<div class="search-page">
<header class="search-header">
<seekora-search-bar
placeholder="Search products..."
[showSuggestions]="true"
(search)="onSearch($event)"
>
</seekora-search-bar>
</header>

<div class="search-layout">
<aside class="sidebar">
<seekora-clear-refinements></seekora-clear-refinements>
<seekora-facets [showCount]="true"></seekora-facets>
</aside>

<main class="content">
<div class="toolbar">
<seekora-stats></seekora-stats>
<seekora-current-refinements></seekora-current-refinements>
<seekora-sort-by [items]="sortOptions"></seekora-sort-by>
</div>

<seekora-search-results
viewMode="grid"
(resultClick)="onResultClick($event)"
>
<ng-template #resultTemplate let-result>
<app-product-card [product]="result"></app-product-card>
</ng-template>
</seekora-search-results>

<seekora-pagination
(pageChange)="onPageChange($event)"
>
</seekora-pagination>
</main>
</div>
</div>
</seekora-search-provider>

Reactive Forms Integration

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { SeekoraSearchService, SearchStateService } from '@seekora-ai/ui-sdk-angular';

@Component({
selector: 'app-advanced-search',
template: `
<form [formGroup]="searchForm" (ngSubmit)="onSubmit()">
<input formControlName="query" placeholder="Search..." />

<select formControlName="category">
<option value="">All Categories</option>
<option *ngFor="let cat of categories" [value]="cat.id">
{{ cat.name }}
</option>
</select>

<div formGroupName="priceRange">
<input type="number" formControlName="min" placeholder="Min" />
<input type="number" formControlName="max" placeholder="Max" />
</div>

<button type="submit">Search</button>
</form>
`,
})
export class AdvancedSearchComponent implements OnInit {
searchForm: FormGroup;
categories = [];

constructor(
private fb: FormBuilder,
private searchService: SeekoraSearchService,
private stateService: SearchStateService
) {}

ngOnInit() {
this.searchForm = this.fb.group({
query: [''],
category: [''],
priceRange: this.fb.group({
min: [null],
max: [null],
}),
});
}

onSubmit() {
const { query, category, priceRange } = this.searchForm.value;

// Build filter string
const filters = [];
if (category) filters.push(`category:${category}`);
if (priceRange.min || priceRange.max) {
const min = priceRange.min || 0;
const max = priceRange.max || '*';
filters.push(`price:${min}-${max}`);
}

this.searchService.search(query, {
filter_by: filters.join(' && '),
per_page: 20,
});
}
}

Next Steps