Query
Run a search, tell a failed request apart from a genuine zero results, and page through a result — the service every result-rendering component in this library sits on top of.
QueryService is the one place a search actually happens: it turns a Query into a request, exposes the last
Result and its failure state as signals, and moves between pages. Almost every other component in this
section — sorting, spelling correction, missing terms — reads the Result this service produced rather than
issuing a request of its own.
Concept — Result vs. failure
A search that fails still resolves to a Result — the request never throws — with the failure captured on
$error instead. An empty records array is ambiguous by itself: it means "the corpus matched nothing" on a
successful request, and "something went wrong" on a failed one. Check $error first, always.
Minimal example
import { Component, inject } from "@angular/core";
import { QueryService } from "@sinequa/atomic-angular";
@Component({
selector: "results-component",
template: `
@if (queryService.failure(); as error) {
<p>Search failed: {{ error.message }}</p>
} @else if (queryService.result().rowCount === 0) {
<p>No results</p>
} @else {
<ul>
@for (record of queryService.result().records; track record.id) {
<li>{{ record.title }}</li>
}
</ul>
}
`,
})
export class ResultsComponent {
protected readonly queryService = inject(QueryService);
constructor() {
this.queryService.search({ text: "quarterly report" }).subscribe();
}
}How it works
search() still returns an Observable<Result> for callers that want to compose it, but the same Result
also lands on the result signal (and failure, result().$error) — which is what a template reads instead
of subscribing manually. The observable never errors: a failed request still emits, because throwing would
cancel the navigation of any route resolver built on this service, which historically rendered an empty page
instead.
A global toast is not a substitute
provideAtomicNotifications() (see Notify failed requests as
toasts) already surfaces every failed request to the user as a
toast — but that leaves the component still rendering "0 results" underneath unless it also checks
$error itself. The two are complementary, not either/or.
Recipes
Paging through a result
import { Component, inject } from "@angular/core";
import { QueryService } from "@sinequa/atomic-angular";
@Component({
selector: "pager-component",
template: `
<button type="button" (click)="queryService.gotoPage(currentPage() - 1)" [disabled]="currentPage() <= 1">
Previous
</button>
<button type="button" (click)="queryService.gotoPage(currentPage() + 1)">Next</button>
`,
})
export class PagerComponent {
protected readonly queryService = inject(QueryService);
protected readonly currentPage = () => this.queryService.result().page ?? 1;
}Running several queries in parallel
import { Component, inject } from "@angular/core";
import { QueryService } from "@sinequa/atomic-angular";
import type { Query } from "@sinequa/atomic";
@Component({
selector: "compare-component",
template: ``,
})
export class CompareComponent {
private readonly queryService = inject(QueryService);
protected compare(a: Query, b: Query) {
this.queryService.bulkSearch([a, b]).subscribe(([resultA, resultB]) => {
console.log(resultA.rowCount, resultB.rowCount);
});
}
}Results come back in the same order as the input queries — index them by position, not by matching them back to a query afterward.
Options
Prop
Type
Pitfalls
What's next
Results
Running a query and rendering what comes back — the query service, autocomplete, spelling correction, sorting, empty states, and the small display components a result list is built from.
Autocomplete
Suggestions as the user types — from configured suggest queries on the server, and from the user's own recent/saved searches and bookmarks.