Use the Angular wrapper
Use the Angular wrapper
This is for:
DeveloperThe integration of Atomic web components in Angular projects can be tricky. Atomic Angular is a wrapper around the core Atomic library meant to address this issue.
Since Atomic Angular is built on top of the core Atomic library, most concepts that apply to the core Atomic library apply directly to Atomic Angular. The goal of this article is to go over the few areas where the use of Atomic Angular differs from the use of the core Atomic library.
For a complete example you may want to start from or refer to throughout this article, see this Atomic Angular project. |
Import AtomicAngularModule
In the module where you wish to use Atomic Angular components, declare and import the AtomicAngular
module, as in the following example:
import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {AtomicAngularModule} from '@coveo/atomic-angular';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, AtomicAngularModule],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
Once this is done, you’ll be able to reference all atomic components inside that module.
Initialize your search interface
You can initialize your search interface at any time during the life cycle of your application.
A suitable lifecyle hook is AfterViewInit
.
The following example uses this AfterViewInit
hook and relies on the AppModule
in the preceding section as a starting point, which bootstraps AppComponent
.
// app.component.ts
import {AfterViewInit, Component} from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent implements AfterViewInit {
ngAfterViewInit(): void {
const searchInterface = document.querySelector('atomic-search-interface');
searchInterface
?.initialize({
accessToken: '<REPLACE_WITH_TOKEN>',
organizationId: '<REPLACE_WITH_COVEO_ORGANIZATION_ID>',
})
.then(() => {
searchInterface.executeFirstSearch();
});
}
}
<!-- app.component.html -->
<atomic-search-interface>
<!-- content of the interface -->
</atomic-search-interface>
Load static assets
For performance reasons, the generated Atomic Angular JavaScript bundle doesn’t automatically include static assets that are loaded on demand. This impacts language support, as well as the use of included SVG icons.
You must make available external assets distributed with Atomic Angular by including them in the public directory of your app. Without this, you’ll face various issues. For example, labels in the app will appear as temporary placeholders.
The location of the public directory depends on how you build, configure, and distribute your app. For example, for any project created with the Angular CLI, this would mean copying language and icon assets to the root of the source directory.
cp -r node_modules/@coveo/atomic-angular/assets src/assets
cp -r node_modules/@coveo/atomic-angular/lang src/lang
Note
Be sure to respect the folder hierarchy, with SVG icons under the |
Once this is done, these folders must be configured as asset folders in the application.
You can do so using the angular.json
configuration file.
"build": {
// ...
"options": {
"assets": ["src/favicon.ico", "src/assets", "src/lang"],
},
},
Include the default Coveo theme
You can include the default Coveo theme via the angular.json
configuration file.
"build": {
// ...
"options": {
"styles": [
"./node_modules/@coveo/atomic/dist/atomic/themes/coveo.css",
"src/styles.css"
],
},
},
Note
This is however optional, and all theme variables can be configured in the global application stylesheet (for example, |
Wrap Atomic Angular components
We recommend creating application-specific components that wrap out-of-the-box Atomic Angular components. In other words, combine multiple Atomic Angular component into a higher level parent component, which you can then reuse throughout your application.
When doing so, you can’t use the standard @Input()
angular decorator directly to pass down properties to Atomic web components in a component template.
You need to create getter and setter functions that assign properties to the DOM, without the standard Angular rendering engine.
The following example wraps an atomic-text
component inside a parent app-field-label
component, which would pass down props.
<!-- field-label.component.html -->
<atomic-text #atomictext></atomic-text>
<!-- Example usage in app.component.html -->
<app-field-label label="My label"> </app-field-label>
// field-label.component.ts
@Component({
selector: 'app-field-label',
templateUrl: './field-label.component.html',
})
export class FieldLabelComponent implements AfterViewInit {
@ViewChild('atomictext') atomicText?: AtomicText;
constructor(private z: NgZone) {}
private val = '';
@Input()
get label(): string {
if (this.atomicText) {
this.val = this.atomicTextValueAttribute;
}
return this.val;
}
set label(v: string) {
this.val = v;
this.atomicTextValueAttribute = this.val;
}
ngAfterViewInit(): void {
this.atomicTextValueAttribute = this.val;
}
private get atomicTextValueAttribute() {
if (!this.atomicText) {
return '';
}
return this.atomicText['el'].getAttribute('value') as string;
}
private set atomicTextValueAttribute(v: string) {
if (!this.atomicText) {
return;
}
this.z.runOutsideAngular(() => {
this.atomicText!['el'].setAttribute('value', v);
});
}
}
Use the ViewChild('atomictext') decorator to fetch the atomic-text component defined in field-label.component.html . |
|
Annotate get label() and set label() with the @Input() decorator. |
|
Since the atomicText reference will only be populated once ngAfterViewInit has executed, we code defensively against undefined references. |
|
Execute the property change inside a special runOutsideAngular() function to make sure that Angular doesn’t needlessly recompute property changes and trigger the rendering lifecyle, as this isn’t needed. |
|
Finally, pass the label down and propagate it to <atomic-text> .
This ensures that this web component will receive the property without any modification by the Angular rendering engine. |
Note
The previous example is irrelevant if you’re trying to pass down native DOM properties, such as |
Styling result templates
The template
element is added inside the atomic-result-template
component to define what HTML content should be displayed for each result returned by the user’s query.
To style a template in Angular, you must specify the styling using inline style
HTML attributes.
You can’t style a template using an embedded <style>
tag.
The first example will apply the color red to the field-label <span>
, but the second one won’t.
<!-- Example usage in app.component.html -->
<atomic-result-template>
<span class="field-label" style="color: red;">This will be red</span>
</atomic-result-template>
<!-- Example usage in app.component.html -->
<atomic-result-template>
<template>
<style>
.field-label {
color: red;
}
</style>
</template>
<span class="field-label">This won't be red</span>
</atomic-result-template>