Use the Atomic Angular Wrapper

The 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 component 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.

Tip

For a complete example you may want to start from or refer to throughout this article, see this Atomic Angular project.

Install Atomic Angular

Install Atomic Angular using the npm package.

npm i @coveo/atomic-angular

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 lifecycle of your application. A suitable lifecyle hook is AfterViewInit.

The following example uses this AfterViewInit hook and relies on the above AppModule 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 does not 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 assets subdirectory and labels and languages under the lang subdirectory.

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 (e.g., src/style.css).

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 cannot 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 properly 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; 1
 
  constructor(private z: NgZone) {}
 
  private val = '';
  @Input() 2
  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) { 3
      return '';
    }
    return this.atomicText['el'].getAttribute('value') as string;
  }
 
  private set atomicTextValueAttribute(v: string) {
    if (!this.atomicText) { 3
      return;
    }
    this.z.runOutsideAngular(() => { 4
      this.atomicText!['el'].setAttribute('value', v); 5
    });
  }
}
1 Use the ViewChild('atomictext') decorator to fetch the atomic-text component defined in field-label.component.html.
2 Annotate get label() and set label() with the @Input() decorator.
3 Since the atomicText reference will only be populated once ngAfterViewInit has executed, we code defensively against undefined references.
4 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.
5 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 above example is irrelevant if you are simply trying to pass down native DOM properties, such as id, class, etc. For these properties, you can use the standard Angular methodology.

What's next for me?