src/app/features/location/edit-location/edit-location.component.ts

Description

Input to select and view an address on a map.

Extends

CustomFormControlDirective<GeoLocation>

Implements

EditComponent

Example

Metadata

Relationships

Used by

No results matching.

Depends on

Index

Properties
Methods
Inputs
Outputs

Inputs

autoLookup
Type : boolean
Default value : true

Automatically run an address lookup when the user leaves the input field.

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()
Returns : void
openMap
openMap()
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

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,
  inject,
  input,
} from "@angular/core";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { MatIconButton } from "@angular/material/button";
import { MatDialog } from "@angular/material/dialog";
import { MatFormFieldControl } from "@angular/material/form-field";
import { MatInput } from "@angular/material/input";
import { MatTooltip, MatTooltipModule } from "@angular/material/tooltip";
import { FaIconComponent } from "@fortawesome/angular-fontawesome";
import { filter, map } from "rxjs/operators";
import { CustomFormControlDirective } from "../../../core/common-components/basic-autocomplete/custom-form-control.directive";
import { FormFieldConfig } from "../../../core/common-components/entity-form/FormConfig";
import { DynamicComponent } from "../../../core/config/dynamic-components/dynamic-component.decorator";
import { EditComponent } from "../../../core/entity/entity-field-edit/dynamic-edit/edit-component.interface";
import { GeoLocation } from "../geo-location";
import {
  MapPopupComponent,
  MapPopupConfig,
} from "../map-popup/map-popup.component";

/**
 * Input to select and view an address on a map.
 */
@DynamicComponent("EditLocation")
@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: "app-edit-location",
  templateUrl: "./edit-location.component.html",
  styleUrls: [
    "./edit-location.component.scss",
    "../../../core/entity/entity-field-edit/dynamic-edit/dynamic-edit.component.scss",
  ],
  imports: [
    FormsModule,
    MatInput,
    MatIconButton,
    FaIconComponent,
    MatTooltip,
    ReactiveFormsModule,
    MatTooltipModule,
  ],
  providers: [
    { provide: MatFormFieldControl, useExisting: EditLocationComponent },
  ],
})
export class EditLocationComponent
  extends CustomFormControlDirective<GeoLocation>
  implements EditComponent
{
  private readonly dialog = inject(MatDialog);

  formFieldConfig = input<FormFieldConfig>();

  /**
   * Automatically run an address lookup when the user leaves the input field.
   */
  autoLookup = input<boolean>(true);

  override onContainerClick() {
    if (!this.disabled) {
      this.openMap();
    }
  }

  openMap() {
    const config: MapPopupConfig = {
      selectedLocation: this.valueSignal(),
      disabled: this.disabled,
    };

    const ref = this.dialog.open(MapPopupComponent, {
      width: "90%",
      height: "90vh",
      autoFocus: ".address-search-input",
      restoreFocus: false,
      data: config,
    });

    if (!this.disabled) {
      ref
        .afterClosed()
        .pipe(
          filter((result: GeoLocation[] | undefined) => {
            return Array.isArray(result);
          }),
          map((result: GeoLocation[]) => result[0]),
          filter(
            (result: GeoLocation | undefined) =>
              JSON.stringify(result) !== JSON.stringify(this.valueSignal()),
          ), // nothing changed, skip
        )
        .subscribe((result: GeoLocation) => {
          if (this.ngControl?.control) {
            this.ngControl.control.setValue(result);
          } else {
            this.value = result;
          }
        });
    }
  }
}
<div
  class="flex-row gap-regular align-end"
  [class.clickable]="enabled()"
  (click)="onContainerClick(); $event.stopPropagation()"
>
  <textarea
    #inputElement
    matInput
    [title]="placeholder"
    [disabled]="!enabled()"
    readonly
    [value]="
      valueSignal()?.locationString ??
      valueSignal()?.geoLookup?.display_name ??
      ''
    "
    rows="3"
  ></textarea>

  @if (valueSignal()?.geoLookup) {
    <button
      (click)="openMap(); $event.stopPropagation()"
      mat-icon-button
      type="button"
      class="input-action-button"
      matTooltip="Show the location marked on the map."
      i18n-matTooltip
    >
      <fa-icon icon="map-location-dot"></fa-icon>
    </button>
  } @else {
    <div
      matTooltip="No location marked yet. Edit and search a location on the map here."
      i18n-matTooltip
    >
      <button
        (click)="openMap(); $event.stopPropagation()"
        mat-icon-button
        type="button"
        class="input-action-button"
        [disabled]="!enabled()"
      >
        <fa-icon icon="magnifying-glass-location"></fa-icon>
      </button>
    </div>
  }
</div>

./edit-location.component.scss

:host {
  pointer-events: auto;
}

.clickable,
.clickable * {
  cursor: pointer;
}

../../../core/entity/entity-field-edit/dynamic-edit/dynamic-edit.component.scss

.input-action-button {
  margin: -12px;
}
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""