Getting started
Install @sinequa/atomic-angular, connect a client, sign a user in, and render a first authenticated search screen with a facet.
This walks through the whole path from an empty Angular application to a first authenticated screen showing search results and a facet. Each step links to the page that covers it in full — this tutorial only shows the minimum needed to reach the next step.
Prerequisite — a Sinequa server
You need a Sinequa server reachable from the browser, with at least one query web service configured. Nothing here works against a mock — the client, the guard and the query service all make real HTTP requests.
Install
@sinequa/atomic-angular composes two lower-level libraries — @sinequa/galactik (presentation) and
@sinequa/atomic (the framework-agnostic client). Both are peer dependencies, alongside @angular/aria.
npm install @sinequa/atomic-angular @sinequa/atomic @sinequa/galactik @angular/ariachart.js/chartjs-chart-matrix and markdown-it are also peer dependencies, but only needed if you use the
Chart.js-based data visualization components or the preview's Markdown fallback —
skip them otherwise. Likewise vanilla-calendar-pro, with the @sinequa/atomic-angular/styles/calendar.css
stylesheet, is only needed for the custom range of a date facet.
Provide a client and bootstrap the application
Every store, service and component reaches the backend through one client. Registering it and bootstrapping
the application are two providers in app.config.ts.
import { ApplicationConfig, provideAppInitializer } from "@angular/core";
import { bootstrapApp, provideAtomicClient } from "@sinequa/atomic-angular";
export const appConfig: ApplicationConfig = {
providers: [
// backendUrl may be omitted — bootstrapApp derives it from the browser URL.
provideAtomicClient({ app: "my-app" }),
provideAppInitializer(() => bootstrapApp({ createRoutes: true })),
],
};bootstrapApp() resolves the authentication mode, signs the user in if a session already exists, and
initializes the application's stores before the first view renders — see Provide a
client and Bootstrap the application
for the full contract, including what to do when part of the configuration is only known at runtime.
Authenticate the user
Route /login and /logout to AuthPageComponent — it switches between the credentials form,
change-password and forgot-password based on the route and the mode query param — and guard every protected
route with AuthGuard().
import { AuthGuard, AuthPageComponent, ErrorComponent } from "@sinequa/atomic-angular";
import type { Routes } from "@angular/router";
import { SearchComponent } from "./search.component";
export const routes: Routes = [
{ path: "login", component: AuthPageComponent },
{ path: "logout", component: AuthPageComponent },
{ path: "error", component: ErrorComponent },
{ path: "search", component: SearchComponent, canActivate: [AuthGuard()] },
{ path: "", redirectTo: "search", pathMatch: "full" },
];AuthGuard() checks the session, loads the signed-in user, and — in credentials mode — redirects to
change-password when the session's password has expired; it never guards /login//logout themselves. See
Sign In for embedding the form outside a full page, and Authentication
flows for how the guard, the interceptor, sign-in and logout fit together
end to end.
Render a first search screen
QueryService runs the search and exposes the result as a signal; <Aggregation> renders one facet from that
same result and turns a click into an applied filter.
import { Component, inject } from "@angular/core";
import { AggregationComponent, QueryService } from "@sinequa/atomic-angular";
@Component({
selector: "search-page",
imports: [AggregationComponent],
template: `
<input type="search" (change)="search($any($event.target).value)" placeholder="Search…" />
<Aggregation name="Sources" column="sourcestr4" [showFiltersCount]="true" />
@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 SearchComponent {
protected readonly queryService = inject(QueryService);
constructor() {
this.queryService.search({ text: "" }).subscribe();
}
protected search(text: string) {
this.queryService.search({ text }).subscribe();
}
}Clicking a value in the "Sources" facet applies a filter to the shared query state and re-runs the search on
its own — nothing in SearchComponent has to react to that click by hand. See Filtering
results for facet types, controlled mode and custom item templates, and
Query for paging, running several queries in parallel, and telling a failed request
apart from a genuine zero-match.
What's next
Authentication flows
How the guard, the interceptor, sign-in and logout fit together end to end.
Filtering results
Facet types, controlled mode, custom item templates, and every input and pitfall in depth.
Provide a client
Configuration resolved at runtime, reading the client without redirecting, and serving a second backend.
Query
Paging, running several queries in parallel, and the result/failure contract in full.