src/app/core/language/edit-available-locales/edit-available-locales.component.ts

Description

Edit component for the enabled languages of a system (site settings availableLocales).

Only available if the system is set up as multi-language (multiLingualConfigEnabled), otherwise it points to that feature being disabled. The default language (defaultLanguage in the same form) always stays in the list, because it is always enabled (also if nothing else is selected). It is marked among the options.

Extends

CustomFormControlDirective<ConfigurableEnumValue[]>

Implements

EditComponent OnInit

Example

Metadata

Relationships

Index

Properties
Methods
Inputs
Outputs

Inputs

formFieldConfig
Type : FormFieldConfig
aria-describedby
Type : string
disabled
Type : boolean
ngControl
Type : any
Default value : inject(NgControl, { optional: true, self: true })
placeholder
Type : string
required
Type : boolean
value
Type : T

Outputs

valueChange
Type : EventEmitter

Methods

onContainerClick
onContainerClick(event: MouseEvent)

Open the options when clicking anywhere in the field. Bound to the click of the autocomplete in the template: the surrounding mat-form-field only calls onContainerClick of its direct control (the generic dynamic-edit wrapper, where it is a no-op), so it never reaches this component.

Parameters :
Name Type Optional
event MouseEvent No
Returns : void
blur
blur()
Returns : void
focus
focus()
Returns : void
registerOnChange
registerOnChange(fn: any)
Parameters :
Name Type Optional
fn any No
Returns : void
registerOnTouched
registerOnTouched(fn: any)
Parameters :
Name Type Optional
fn any No
Returns : void
setDescribedByIds
setDescribedByIds(ids: string[])
Parameters :
Name Type Optional
ids string[] No
Returns : void
setDisabledState
setDisabledState(isDisabled: boolean)
Parameters :
Name Type Optional
isDisabled boolean No
Returns : void
writeValue
writeValue(value: T, notifyFormControl: unknown)

Implementation for Angular ControlValueAccessor interface that links the form control value to the component value

Parameters :
Name Type Optional Default value Description
value T No

The new value to set

notifyFormControl unknown No false

Whether to notify the FormControl of this change (for internal updates)

Returns : void

Properties

Readonly defaultLanguage
Type : unknown
Default value : signal<ConfigurableEnumValue | undefined>( undefined, )

the default language, which cannot be removed from the enabled languages

Readonly localeToString
Type : unknown
Default value : () => {...}
Readonly multiLingualConfigEnabled
Type : unknown
Default value : environment.multiLingualConfigEnabled
Readonly options
Type : ConfigurableEnumValue[]
Default value : availableLocales.values

all selectable locales (not user-editable, so no "create option")

controlType
Type : string
Default value : "custom-control"
elementRef
Type : unknown
Default value : inject<ElementRef<HTMLElement>>(ElementRef)
Readonly enabled
Type : Signal<boolean>
Default value : computed(() => !this._disabled())

Whether the control is currently enabled, as a signal (tracks disabled).

errorStateMatcher
Type : unknown
Default value : inject(ErrorStateMatcher)
id
Type : unknown
Default value : `custom-form-control-${CustomFormControlDirective.nextId++}`
Static nextId
Type : number
Default value : 0
onChange
Type : unknown
Default value : () => {...}
onTouched
Type : unknown
Default value : () => {...}
parentForm
Type : unknown
Default value : inject(NgForm, { optional: true })
parentFormGroup
Type : unknown
Default value : inject(FormGroupDirective, { optional: true })
stateChanges
Type : unknown
Default value : new Subject<void>()
Readonly valueSignal
Type : Signal<T>
Default value : computed(() => this._value())

The current value of the control as a signal. Authoritative in both modes: it reflects the bound FormControl (synced in ngDoCheck) as well as [(value)] / writeValue updates.

import {
  ChangeDetectionStrategy,
  Component,
  DestroyRef,
  inject,
  input,
  OnInit,
  signal,
  viewChild,
} from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { ReactiveFormsModule } from "@angular/forms";
import { MatFormFieldControl } from "@angular/material/form-field";
import { MatTooltipModule } from "@angular/material/tooltip";
import { environment } from "#src/environments/environment";
import { BasicAutocompleteComponent } from "../../common-components/basic-autocomplete/basic-autocomplete.component";
import { CustomFormControlDirective } from "../../common-components/basic-autocomplete/custom-form-control.directive";
import { FormFieldConfig } from "../../common-components/entity-form/FormConfig";
import { FaDynamicIconComponent } from "../../common-components/fa-dynamic-icon/fa-dynamic-icon.component";
import { FeatureDisabledInfoComponent } from "../../common-components/feature-disabled-info/feature-disabled-info.component";
import { ConfigurableEnumValue } from "../../basic-datatypes/configurable-enum/configurable-enum.types";
import { DynamicComponent } from "../../config/dynamic-components/dynamic-component.decorator";
import { availableLocales } from "../languages";
import { EditComponent } from "../../entity/entity-field-edit/dynamic-edit/edit-component.interface";

/**
 * Edit component for the enabled languages of a system (site settings `availableLocales`).
 *
 * Only available if the system is set up as multi-language (`multiLingualConfigEnabled`),
 * otherwise it points to that feature being disabled.
 * The default language (`defaultLanguage` in the same form) always stays in the list,
 * because it is always enabled (also if nothing else is selected). It is marked among the options.
 */
@DynamicComponent("EditAvailableLocales")
@Component({
  selector: "app-edit-available-locales",
  templateUrl: "./edit-available-locales.component.html",
  styleUrl: "./edit-available-locales.component.scss",
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [
    ReactiveFormsModule,
    BasicAutocompleteComponent,
    MatTooltipModule,
    FeatureDisabledInfoComponent,
    FaDynamicIconComponent,
  ],
  providers: [
    {
      provide: MatFormFieldControl,
      useExisting: EditAvailableLocalesComponent,
    },
  ],
})
export class EditAvailableLocalesComponent
  extends CustomFormControlDirective<ConfigurableEnumValue[]>
  implements EditComponent, OnInit
{
  formFieldConfig = input<FormFieldConfig>();

  private readonly destroyRef = inject(DestroyRef);

  private readonly autocomplete = viewChild(BasicAutocompleteComponent);

  /** all selectable locales (not user-editable, so no "create option") */
  readonly options: ConfigurableEnumValue[] = availableLocales.values;
  readonly localeToString = (locale: ConfigurableEnumValue) => locale?.label;

  readonly multiLingualConfigEnabled = environment.multiLingualConfigEnabled;

  /** the default language, which cannot be removed from the enabled languages */
  readonly defaultLanguage = signal<ConfigurableEnumValue | undefined>(
    undefined,
  );

  ngOnInit() {
    const defaultLanguageControl =
      this.formControl?.parent?.get("defaultLanguage");
    if (!this.multiLingualConfigEnabled || !defaultLanguageControl) return;

    this.defaultLanguage.set(defaultLanguageControl.value);
    this.keepDefaultLanguage();

    defaultLanguageControl.valueChanges
      .pipe(takeUntilDestroyed(this.destroyRef))
      .subscribe((value) => {
        this.defaultLanguage.set(value);
        this.keepDefaultLanguage();
      });
    this.formControl.valueChanges
      .pipe(takeUntilDestroyed(this.destroyRef))
      .subscribe(() => this.keepDefaultLanguage());
  }

  /**
   * Make sure the default language is part of the list, also an empty one
   * (the enabled languages are the default language plus the selection).
   * It is appended, matching the order of `SiteSettingsService.enabledLocales`.
   */
  private keepDefaultLanguage() {
    const defaultLanguage = this.defaultLanguage();
    const enabled: ConfigurableEnumValue[] = this.formControl.value ?? [];
    if (
      !defaultLanguage?.id ||
      enabled.some(({ id }) => id === defaultLanguage.id)
    ) {
      return;
    }
    this.formControl.setValue([...enabled, defaultLanguage]);
  }

  /**
   * Open the options when clicking anywhere in the field.
   * Bound to the click of the autocomplete in the template: the surrounding `mat-form-field`
   * only calls `onContainerClick` of its direct control (the generic dynamic-edit wrapper,
   * where it is a no-op), so it never reaches this component.
   */
  override onContainerClick(event: MouseEvent) {
    this.autocomplete()?.onContainerClick(event);
  }
}
@if (multiLingualConfigEnabled) {
  <div class="flex-row gap-small">
    <app-basic-autocomplete
      #autocomplete
      (click)="onContainerClick($event)"
      class="locales-select flex-grow"
      [formControl]="formControl"
      [multi]="true"
      [options]="options"
      [optionToString]="localeToString"
      display="chips"
    >
      <ng-template let-option>
        {{ option.label }}
        @if (option.id === defaultLanguage()?.id) {
          <span
            class="default-language-marker"
            matTooltip="The default language is always enabled."
            i18n-matTooltip
            i18n
            >(default language)</span
          >
        }
      </ng-template>
    </app-basic-autocomplete>

    @if (enabled()) {
      <app-fa-dynamic-icon
        icon="caret-down"
        class="form-field-icon-suffix"
        (click)="autocomplete.showAutocomplete()"
      ></app-fa-dynamic-icon>
    }
  </div>
} @else {
  <app-feature-disabled-info
    featureName="multiple languages"
    i18n-featureName
    [featureEnabled]="false"
    [compactView]="true"
  ></app-feature-disabled-info>
}

./edit-available-locales.component.scss

.locales-select {
  min-width: 0;
  overflow: hidden;
}

.default-language-marker {
  margin-inline-start: 4px;
  font-size: 0.85em;
  opacity: 0.7;
}
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""