Components

Within a Blueriq theme, each Blueriq page element needs to be shown on the page in some manner, depending on the characteristics of the element, such as the type of element or the presentation style it contains. This is where Angular components come into play, with additional metadata for the Blueriq framework to use.

Component Declaration

A typical Angular component needs to be decorated with the @BlueriqComponent decorator that specifies under what conditions the component should be chosen as visual representation of an element. As an example, a component to show the readonly representation of a field element would look as follows:

import { Component } from '@angular/core';
import { BlueriqComponent } from '@blueriq/angular';
import { Field } from '@blueriq/core';

@Component({
  selector: 'app-bq-readonly',
  templateUrl: './readonly.component.html',
})
@BlueriqComponent({
  type: Field,
  selector: '[readonly]:not(.Disabled)',
})
export class ReadonlyComponent {

  constructor(public field: Field) {}

}

In @BlueriqComponent, it is always required to specify the type of the element for which the component is applicable. Notice that Field in above example is included from @blueriq/core. The selector property is optional and is similar to an Angular Component's selector, the only difference is that a Blueriq selector targets the Blueriq page tree instead of an HTML element. The selector allows you to specify additional characteristics that the element must have in order for this component to be used.

In above example, the @Component decorator also specifies a selector. Because the component is always rendered dynamically by the Blueriq framework you may choose to omit it entirely. It does however affect the rendered DOM, as Angular will reflect the selector in the name of the element that it creates as host element for the dynamic component. If you were to omit the selector, Angular will always use ng-component as name of the host element.

In the constructor, you can let Angular inject the Blueriq element that belongs to the component instance by simply requesting it through the element type. This may also be done in Angular directives, as they have access to the same services as a component. In directives that are generic for any kind of Blueriq element, inject Element from @blueriq/core instead of a concrete element type.

Selectors

A Blueriq selector closely resembles a typical CSS selector, where each concept in CSS has been translated to work with the Blueriq element tree. In below table an overview of supported selectors is given.

Selector Description
dashboard_flowwidget Matches on content style equal to 'dashboard_flowwidget'
.icon Matches on presentation style containing 'icon'
#container Matches on element with name 'container'
[multiValued] Matches on a 'multiValued' property that is truthy
[dataType=boolean] Matches on the 'dataType' property equal to 'boolean'
[contentStyle^=bool] Matches on content style starting with 'bool'
[contentStyle$=bool] Matches on content style ending with 'bool'
[contentStyle*=bool] Matches on content style containing 'bool'
[domain-size<=2] Matches on a field with less than 3 domain values
[domain-size>=3] Matches on a field with at least 3 domain values
table * Matches on any element having an ancestor with content style 'table'
table row Matches on content style 'row' having an ancestor with content style 'table'
table > row Matches on content style 'row' having an immediate parent with content style 'table'
.icon:not(.large) Matches on presentation style 'icon' but not having presentation style 'large'
:has(.icon) Matches if a descendant with presentation style 'icon' exists
:has(* > .icon) Matches if a direct child with presentation style 'icon' exists

You may specify multiple selectors by using a comma as separator, equal to normal CSS.

Component Registration

Next to the declaration of a Blueriq component using the @BlueriqComponent decorator, it must separately be registered as Blueriq component in an Angular module using BlueriqComponents:

import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { BlueriqCommonModule, BlueriqComponents } from '@blueriq/angular';
import { ReadonlyComponent } from './readonly/readonly.component';

const BLUERIQ_COMPONENTS = [
  ReadonlyComponent,
];

@NgModule({
  declarations: [
    BLUERIQ_COMPONENTS,
  ],
  providers: [
    BlueriqComponents.register(BLUERIQ_COMPONENTS),
  ],
  imports: [
    CommonModule,
    BlueriqCommonModule,
  ],
})
export class FormsModule {}

In the above example, notice how all Blueriq components are listed in the constant BLUERIQ_COMPONENTS, which is used in the module's declarations and in providers. This approach avoids a situation where you forget to add a new component in only one place.

If you register a component without @BlueriqComponent decorator, a warning will be logged in the browser.

Priority

The ordering of components is important, as the framework will simply select the first component declaration that matches a given element. Hence, the framework first sorts all registered components by a numeric priority value that has been computed from the component selectors. The more specific the selector, the higher its priority will be.

If you encounter a situation where components have equal priority but their mutual ordering is important, you have several options. The component that has been registered first is guaranteed to be ordered first, so changing the registration order may resolve the discrepancy. As an alternative, consider making one of the selectors more specific for its priority to increase. As a last resort, you may influence the priority computation by providing a priority offset with the bySelector function from @blueriq/angular:

import { Component, Host } from '@angular/core';
import { BlueriqComponent, bySelector } from '@blueriq/angular';
import { Field } from '@blueriq/core';

@Component({
  selector: 'app-bq-readonly',
  templateUrl: './readonly.component.html',
})
@BlueriqComponent({
  type: Field,
  selector: bySelector('[readonly]:not(.Disabled)', { priorityOffset: 1 }),
})
export class ReadonlyComponent {}

Using bySelector will cause the selector to be eagerly parsed. If the selector is invalid, the application will fail to start instead of logging the parse failure and ignoring the faulty component.

Scoped Components

Consider the scenario where you want to use custom field components within table rows. You could include a parent constraint in your selector, for example table [dataType=string] would only match fields that are contained within an element with content style table. This however is suboptimal, as you would 1) have to repeat the parent constraint for each individual component, 2) lose the connection between the table component and its custom children, and 3) negatively affect performance by having a larger number of components to consider for each element on the page. These downsides can be avoided by registering the custom elements from the table component as scoped components.

Scoped components are not registered globally, but locally at component level. Local components always take precedence over globally registered components, regardless of priority. To register a scoped component, add providers to the @Component decorator and use BlueriqComponents.scoped([...]) with a list of component classes.

import { Component, Self } from '@angular/core';
import { BlueriqComponent, BlueriqComponents } from '@blueriq/angular';
import { Container } from '@blueriq/core';
import { StringComponent } from './string/string.component';

@Component({
  templateUrl: './table.component.html',
  providers: [
    BlueriqComponents.scoped([StringComponent]),
  ],
})
@BlueriqComponent({
  type: Container,
  selector: 'table',
})
export class TableComponent {}

Scoped components will not be shown in the components table during startup, as that table only shows the globally registered components.

Scoped components still need to be included in an Angular module in declarations, but should not be registered with Blueriq using BlueriqComponents.register as that is for global registration.

We recommend to always use bySelector in components that are locally registered, as otherwise the selector has to be parsed each time it comes into scope.

Specializing Components

It may occur that a module you include registers a component for which you want to alter its behavior, for example. This is possible through component specialization using BlueriqComponents.specialize:

import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { BlueriqCommonModule, BlueriqComponents } from '@blueriq/angular';
import { ReadonlyComponent } from './readonly/readonly.component';
import { SpecializedReadonlyComponent } from './specialized-readonly/specialized-readonly.component';

@NgModule({
  declarations: [
    SpecializedReadonlyComponent,
  ],
  providers: [
    BlueriqComponents.specialize(ReadonlyComponent, SpecializedReadonlyComponent),
  ],
  imports: [
    CommonModule,
    BlueriqCommonModule,
  ],
})
export class FormsModule {}

With above module imported in your application, anytime the ReadonlyComponent is chosen the framework will use SpecializedReadonlyComponent instead. The SpecializedReadonlyComponent class does not need to have a BlueriqComponent decorator, as component resolution has already taken place at this point.

Any registered specialization will even be used for scoped components that have been registered locally on a component.

Rendering Components

Now that we have been able to register components, we need to actually render them from somewhere in our templates. For this purpose the Angular directive bqElement exists, which accepts a Blueriq element and determines the appropriate component type to render dynamically. A typical usage would be iterating over all page/container children:

<ng-container *ngFor="let child of container.children | bqIncluded" [bqElement]="child"></ng-container>

The usage of the bqIncluded pipe ensures that any element that has been excluded will be skipped over. For more details on what that means exactly, please refer to the querying guide.

Notice that Angular's ngFor directive is applied to an ng-container. This is a special kind of element for which no DOM element is rendered. If you need to render the component within an actual DOM element, replacing the ng-container with the desired element tag will not have the desired effect. The reason is that components rendered by bqElement will be inserted as sibling instead of as child, so we would need to change the structure a bit:

<div *ngFor="let child of container.children | bqIncluded">
  <ng-container [bqElement]="child"></ng-container>
</div>

To use bqElement and bqIncluded in feature modules, ensure that BlueriqCommonModule is imported.

Interacting with the session

The most common session interaction in a Blueriq application is handling form field refresh events and button presses. These actions are exposed by the BlueriqSession service that is made available within bq-session:

import { Component } from '@angular/core';
import { BlueriqSession } from '@blueriq/angular';
import { Button, Field } from '@blueriq/core';

@Component({})
export class InteractionComponent {
  
  constructor(private session: BlueriqSession) {}
  
  recompose(): void {
    this.session.recompose();
  }
  
  onClick(button: Button): void {
    if (button.enabled) {
      this.session.pressed(button); // Note: prefer using bqButton directive instead
    }
  }
  
  onChange(field: Field): void {
    this.session.changed(field); // Note: prefer using Angular Forms integration instead
  }
  
}

As noted in the above example, button clicks are rather handled using the bqButton directive in a template, as it avoids custom code in the component at all. Within a Blueriq component for a Button element, simply add the bqButton attribute without a value to an element for click events to be handled.

<button bqButton></button>

In the case where you want to tell bqButton to use a different button, bind its value to a Button instance instead:

<button [bqButton]="someButton"></button>

To use bqButton in feature modules, ensure that BlueriqCommonModule is imported.

results matching ""

    No results matching ""