Skip to content

Commit

Permalink
feat: Migrate decorators to signal queries (#178)
Browse files Browse the repository at this point in the history
* feat: migrate Output ViewChild ContentChild decorators to signal queries

* feat: replace @input decorator

* fix: code review

* refactor: source components

* refector: change composition instead of inheritance for the sources
  • Loading branch information
n-elhk authored Aug 18, 2024
1 parent cc57875 commit a48bb7d
Show file tree
Hide file tree
Showing 45 changed files with 2,268 additions and 2,253 deletions.
Original file line number Diff line number Diff line change
@@ -1,51 +1,54 @@
import { AfterContentInit, Directive, Host, Input } from '@angular/core';
import { AttributionControl } from 'maplibre-gl';
import { Directive, afterNextRender, inject, input } from '@angular/core';
import {
AttributionControl,
type AttributionControlOptions,
} from 'maplibre-gl';
import { MapService } from '../map/map.service';
import { ControlComponent } from './control.component';
import { keepAvailableObjectValues } from '../shared/utils/functions/object.fn';

/**
* `mglAttribution` - an attribution control directive
*
*
* @category Directives
*
*
* @see [Add custom attribution](https://maplibre.org/ngx-maplibre-gl/demo/custom-attribution)
* @see [AttributionControl](https://maplibre.org/maplibre-gl-js/docs/API/classes/AttributionControl)
*/
@Directive({
selector: '[mglAttribution]',
standalone: true,
})
export class AttributionControlDirective implements AfterContentInit {
export class AttributionControlDirective {
/* Init injection */
private readonly mapService = inject(MapService);
private readonly controlComponent = inject<
ControlComponent<AttributionControl>
>(ControlComponent, { host: true });

/** Init input */
@Input() compact?: boolean;
readonly compact = input<boolean>();
/** Init input */
@Input() customAttribution?: string | string[];
readonly customAttribution = input<string | string[]>();

constructor() {
afterNextRender(() => {
this.mapService.mapCreated$.subscribe(() => {
if (this.controlComponent.control) {
throw new Error('Another control is already set for this control');
}

constructor(
private mapService: MapService,
@Host() private controlComponent: ControlComponent<AttributionControl>
) {}
const options = keepAvailableObjectValues<AttributionControlOptions>({
compact: this.compact(),
customAttribution: this.customAttribution(),
});

ngAfterContentInit() {
this.mapService.mapCreated$.subscribe(() => {
if (this.controlComponent.control) {
throw new Error('Another control is already set for this control');
}
const options: {
compact?: boolean;
customAttribution?: string | string[];
} = {};
if (this.compact !== undefined) {
options.compact = this.compact;
}
if (this.customAttribution !== undefined) {
options.customAttribution = this.customAttribution;
}
this.controlComponent.control = new AttributionControl(options);
this.mapService.addControl(
this.controlComponent.control,
this.controlComponent.position
);
this.controlComponent.control = new AttributionControl(options);
this.mapService.addControl(
this.controlComponent.control,
this.controlComponent.position()
);
});
});
}
}
40 changes: 23 additions & 17 deletions projects/ngx-maplibre-gl/src/lib/control/control.component.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import {
ChangeDetectionStrategy,
Component,
DestroyRef,
ElementRef,
Input,
OnDestroy,
ViewChild,
afterNextRender
afterNextRender,
inject,
input,
viewChild,
} from '@angular/core';
import { ControlPosition, IControl } from 'maplibre-gl';
import type { ControlPosition, IControl } from 'maplibre-gl';
import { MapService } from '../map/map.service';

export class CustomControl implements IControl {
Expand Down Expand Up @@ -57,28 +58,33 @@ export class CustomControl implements IControl {
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
})
export class ControlComponent<T extends IControl> implements OnDestroy {
export class ControlComponent<T extends IControl> {
/** Init injection */
private readonly mapService = inject(MapService);
private readonly destroyRef = inject(DestroyRef);

/** Init input */
@Input() position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
readonly position = input<ControlPosition>();

/** @hidden */
@ViewChild('content', { static: true }) content: ElementRef;
readonly content = viewChild.required<ElementRef<HTMLDivElement>>('content');

control: T | CustomControl;

constructor(private mapService: MapService) {
constructor() {
afterNextRender(() => {
if (this.content.nativeElement.childNodes.length) {
this.control = new CustomControl(this.content.nativeElement);
if (this.content().nativeElement.childNodes.length) {
this.control = new CustomControl(this.content().nativeElement);
this.mapService.mapCreated$.subscribe(() => {
this.mapService.addControl(this.control!, this.position);
this.mapService.addControl(this.control, this.position());
});
}
});
}

ngOnDestroy() {
if (this.mapService?.mapInstance?.hasControl(this.control)) {
this.mapService.removeControl(this.control);
}
this.destroyRef.onDestroy(() => {
if (this.mapService?.mapInstance?.hasControl(this.control)) {
this.mapService.removeControl(this.control);
}
});
}
}
Original file line number Diff line number Diff line change
@@ -1,48 +1,49 @@
import {
AfterContentInit,
Directive,
Host,
HostListener,
Input,
} from '@angular/core';
import { Directive, afterNextRender, inject, input } from '@angular/core';
import { FullscreenControl } from 'maplibre-gl';
import { MapService } from '../map/map.service';
import { ControlComponent } from './control.component';

/**
* `mglFullscreen` - a fullscreen control directive
*
*
* @category Directives
*/
@Directive({
selector: '[mglFullscreen]',
standalone: true,
host: {
// eslint-disable-next-line @typescript-eslint/naming-convention
'(window:webkitfullscreenchange)': 'onFullscreen()',
},
})
export class FullscreenControlDirective implements AfterContentInit {
/* Init inputs */
@Input() container?: HTMLElement;
@HostListener('window:webkitfullscreenchange', ['$event.target'])
onFullscreen() {
this.mapService.mapInstance.resize();
}
export class FullscreenControlDirective {
/* Init injection */
private readonly mapService = inject(MapService);
private readonly controlComponent = inject<
ControlComponent<FullscreenControl>
>(ControlComponent, { host: true });

constructor(
private mapService: MapService,
@Host() private controlComponent: ControlComponent<FullscreenControl>
) {}
/* Init inputs */
readonly container = input<HTMLElement>();

ngAfterContentInit() {
this.mapService.mapCreated$.subscribe(() => {
if (this.controlComponent.control) {
throw new Error('Another control is already set for this control');
}
this.controlComponent.control = new FullscreenControl({
container: this.container,
constructor() {
afterNextRender(() => {
this.mapService.mapCreated$.subscribe(() => {
if (this.controlComponent.control) {
throw new Error('Another control is already set for this control');
}
this.controlComponent.control = new FullscreenControl({
container: this.container(),
});
this.mapService.addControl(
this.controlComponent.control,
this.controlComponent.position()
);
});
this.mapService.addControl(
this.controlComponent.control,
this.controlComponent.position
);
});
}

onFullscreen() {
this.mapService.mapInstance.resize();
}
}
Original file line number Diff line number Diff line change
@@ -1,69 +1,73 @@
import {
AfterContentInit,
Directive,
EventEmitter,
Host,
Input,
Output,
afterNextRender,
inject,
input,
output,
} from '@angular/core';
import { FitBoundsOptions, GeolocateControl } from 'maplibre-gl';
import {
type FitBoundsOptions,
GeolocateControl,
GeolocateControlOptions,
} from 'maplibre-gl';
import { MapService } from '../map/map.service';
import { ControlComponent } from './control.component';
import { Position } from '../map/map.types';
import type { Position } from '../map/map.types';
import { keepAvailableObjectValues } from '../shared/utils/functions/object.fn';

/**
* `mglGeolocate` - a geolocate control directive
*
*
* @category Directives
*
*
* @see [Locate user](https://maplibre.org/ngx-maplibre-gl/demo/locate-user)
* @see [GeolocateControl](https://maplibre.org/maplibre-gl-js/docs/API/classes/GeolocateControl)
*/
@Directive({
selector: '[mglGeolocate]',
standalone: true,
})
export class GeolocateControlDirective implements AfterContentInit {
export class GeolocateControlDirective {
/* Init injection */
private readonly mapService = inject(MapService);
private readonly controlComponent = inject<
ControlComponent<GeolocateControl>
>(ControlComponent, { host: true });

/* Init inputs */
readonly positionOptions = input<PositionOptions>();
/* Init inputs */
readonly fitBoundsOptions = input<FitBoundsOptions>();
/* Init inputs */
@Input() positionOptions?: PositionOptions;
@Input() fitBoundsOptions?: FitBoundsOptions;
@Input() trackUserLocation?: boolean;
@Input() showUserLocation?: boolean;
readonly trackUserLocation = input<boolean>();
/* Init inputs */
readonly showUserLocation = input<boolean>();

@Output()
geolocate: EventEmitter<Position> = new EventEmitter<Position>();
readonly geolocate = output<Position>();

constructor(
private mapService: MapService,
@Host() private controlComponent: ControlComponent<GeolocateControl>
) {}
constructor() {
afterNextRender(() => {
this.mapService.mapCreated$.subscribe(() => {
if (this.controlComponent.control) {
throw new Error('Another control is already set for this control');
}

ngAfterContentInit() {
this.mapService.mapCreated$.subscribe(() => {
if (this.controlComponent.control) {
throw new Error('Another control is already set for this control');
}
const options = {
positionOptions: this.positionOptions,
fitBoundsOptions: this.fitBoundsOptions,
trackUserLocation: this.trackUserLocation,
showUserLocation: this.showUserLocation,
};
const options = keepAvailableObjectValues<GeolocateControlOptions>({
positionOptions: this.positionOptions(),
fitBoundsOptions: this.fitBoundsOptions(),
trackUserLocation: this.trackUserLocation(),
showUserLocation: this.showUserLocation(),
});

Object.keys(options).forEach((key: string) => {
const tkey = <keyof typeof options>key;
if (options[tkey] === undefined) {
delete options[tkey];
}
});
this.controlComponent.control = new GeolocateControl(options);
this.controlComponent.control.on('geolocate', (data: Position) => {
this.geolocate.emit(data);
this.controlComponent.control = new GeolocateControl(options);
this.controlComponent.control.on('geolocate', (data: Position) => {
this.geolocate.emit(data);
});
this.mapService.addControl(
this.controlComponent.control,
this.controlComponent.position()
);
});
this.mapService.addControl(
this.controlComponent.control,
this.controlComponent.position
);
});
}
}
Loading

0 comments on commit a48bb7d

Please sign in to comment.