Document Properties
Kbid
30Q841
Last Modified
12-Dec-2023
Added to KB
18-Apr-2023
Public Access
Everyone
Status
Online
Doc Type
Guidelines
Product
Intershop Progressive Web App
Guide - Intershop Progressive Web App - Angular Component Development

Declare Components in the Right NgModule

Angular requires you to declare a component in one and only one NgModule.
Find the right one in the following order:

Your Component is used only on one page? - Add it to the declarations of the corresponding page.module.

Your Component is used among multiple pages? - Declare it in the shared.module and also export it there.

Your Component is used in the application shell (and maybe again on certain pages)? - Declare it in the shell.module and also export it there.

(advanced) Your component relates to a specific B2B extension? - Declare it in that extension module and add it as an entryComponent, add a lazy-loaded component and add that to the extension exports, which are then im-/exported in the shared.module.

When using ng generate, the right module should be found automatically.

Do not use NgRx or Services in Components

Using NgRx or Services directly in components violates our model of abstraction.
Only facades should be used in components, as they provide the simplest access to the business logic.

Delegate Complex Component Logic to Services

There should not be any string or URL manipulation, routing mapping or REST endpoint string handling within components.
This is supposed to be handled by methods of services.
See also Angular Style Guide.

Put as Little Logic Into constructor as Possible - Use ngOnInit

See The essential difference between Constructor and ngOnInit in Angular and Angular constructor versus ngOnInit.

Use Property Binding to Bind Dynamic Values to Attributes or Properties

See Explanation of the difference between an HTML attribute and a DOM property.

There are often two ways to bind values dynamically to attributes or properties: interpolation or property binding.
In the PWA we prefer using property binding since this covers more cases in the same way.
So the code will be more consistent.

There is an exception for direct string value bindings where we use for example routerLink="/logout" instead of [routerLink]="'/logout'".

⚠️ Pattern to avoid

<div attr.data-testing-id="category-{{category.id}}">
  <img src="{{base_url + category.images[0].effectiveUrl}}" />
</div>

✔️ Correct pattern

<div [attr.data-testing-id]="'category-' + category.id">
  <img [src]="base_url + category.images[0].effectiveUrl" />
</div>

Pattern for Conditions (ngIf) with Alternative Template (else) in Component Templates

Also for consistency reasons, we want to establish the following pattern for conditions in component templates:

⚠️ Condition in template

<ng-container *ngIf="show; else elseBlock">
 ... (template code for if-branch)
</ng-container>

<ng-template #elseBlock>
  ... (template code for else-branch)
</ng-template>

This pattern provides the needed flexibility if used together with handling observables with *ngIf and the async pipe.
In this case the condition should look like this:

<ng-container *ngIf="observable$ | async as synchronized; else loading">

Pattern for Loops (ngFor) with Changing Data in Component Templates

Looping through an array in a component may sometimes be accompanied by side effects if the data within the array are changing.

<ng-container *ngFor="let element of array$ | async">
  <another-component [element]="element"></another-component>
</ng-container>

In case the values of the array$ observable are varying somehow during the lifetime of the component (reordering elements, add/ delete elements, changing properties), all children DOM elements are destroyed and re-initialized with the new data.

To avoid these many and expensive DOM manipulations and persist the children DOM elements the loop elements has to be uniquely identified in the NgFor directive.
This can be achieved by using custom trackBy functions within the ngFor directive.

@Component({
  ...
  template: `
  <ng-container *ngFor="let element of array$ | async; trackBy: customTrackByFn">
    <another-component [element]="element"></another-component>
  </ng-container>`
})
export class AnyComponent implements OnInit, OnDestroy {
  ...
  customTrackByFn(index, element) {
    return element.id;
  }
}

The custom trackBy function needs to return unique values for all unique inputs.

Do Not Unsubscribe, Use the takeUntilDestroyed Operator Instead

Following the ideas of the article takeUntilDestroyed in Angular v16, the following pattern is used for ending subscriptions to observables that are not handled via async pipe in the templates.

✔️ 'unsubscribe' via takeUntilDestroyed

export class AnyComponent implements OnInit {
  ...
  ngOnInit() {
    ...
    observable$.pipe(takeUntilDestroyed(this.destroyRef))
      .subscribe(/* ... */);
  }
}

The ESLint rule rxjs-angular/prefer-takeuntil enforces the usage of takeUntilDestroyed when subscribing in an Angular artifact.

Use OnPush Change Detection if Possible

To reduce the number of ChangeDetection computation cycles, all components should have their Component decorator property changeDetection set to ChangeDetectionStrategy.OnPush.

DOM Manipulations

When using Angular, avoid to access or change the DOM directly because this may lead to several issues:

  • If you use direct DOM accessors like window, document etc., it might not refer to something relevant on the server-side code.
  • It might open up your app as an easy target for the XSS injection attack or other security issues.
  • The angular synchronization of components amd views are bypassed and this might lead to unwanted side effects.

However, if you need to manipulate the DOM use the multiple DOM manipulation methods of the Angular Renderer2 API or the pwa DomService or other angular techniques.

Split Components When Necessary

Consider splitting one into multiple components when:

  • Size: Component code becomes too complex to be easily understandable

  • Separation of concerns: A component serves different concerns that should be separated

  • Reusability: A component should be reused in different contexts. This can introduce a shared component which could be placed in a shared module.

  • Async data: Component relies on async data from the store which makes the component code unnecessarily complex. Use a container component then which resolves the observables at the outside of the child component and passes data in via property bindings. Do not do this for simple cases.

Single-use dumb components are always okay if it improves readability.

Mock Facades in Tests

Angular Artifacts like Components, Directives and Pipes should solely depend on facades to interact with the State Management.
This is enforced with the ESLint rule no-intelligence-in-artifacts which rejects every usage of REST API Services and NgRx Artifacts.

Use ts-mockito for creating and managing these mocks.
Providers for Facades can easily be added by using the VSCode snippet ish-provider-ts-mockito:

ish-provider-ts-mockito

Disclaimer
The information provided in the Knowledge Base may not be applicable to all systems and situations. Intershop Communications will not be liable to any party for any direct or indirect damages resulting from the use of the Customer Support section of the Intershop Corporate Web site, including, without limitation, any lost profits, business interruption, loss of programs or other data on your information handling system.
The Intershop Knowledge Portal uses only technically necessary cookies. We do not track visitors or have visitors tracked by 3rd parties. Please find further information on privacy in the Intershop Privacy Policy and Legal Notice.
Home
Knowledge Base
Product Releases
Log on to continue
This Knowledge Base document is reserved for registered customers.
Log on with your Intershop Entra ID to continue.
Write an email to supportadmin@intershop.de if you experience login issues,
or if you want to register as customer.