Skip to content

Commit

Permalink
fix: prevent circular dependency
Browse files Browse the repository at this point in the history
  • Loading branch information
StenCalDabran committed Aug 31, 2023
1 parent f5fee43 commit ec23c05
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 38 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { inject, TestBed, waitForAsync } from '@angular/core/testing';
import { AttachToOverlayService } from './attach-to-overlay.service';
import { ModalGalleryService } from './modal-gallery.service';
import { OverlayModule, OverlayRef } from '@angular/cdk/overlay';
import { Image } from '../../model/image.class';
import { ModalGalleryRef } from './modal-gallery-ref';

const IMAGES: Image[] = [
new Image(0, {
// modal
img: '../assets/images/gallery/img1.jpg',
extUrl: 'http://www.google.com'
}),
new Image(1, {
// modal
img: '../assets/images/gallery/img2.png',
description: 'Description 2'
}),
new Image(
2,
{
// modal
img: '../assets/images/gallery/img3.jpg',
description: 'Description 3',
extUrl: 'http://www.google.com'
},
{
// plain
img: '../assets/images/gallery/thumbs/img3.png',
title: 'custom title 2',
alt: 'custom alt 2',
ariaLabel: 'arial label 2'
}
),
new Image(3, {
// modal
img: '../assets/images/gallery/img4.jpg',
description: 'Description 4',
extUrl: 'http://www.google.com'
}),
new Image(
4,
{
// modal
img: '../assets/images/gallery/img5.jpg'
},
{
// plain
img: '../assets/images/gallery/thumbs/img5.jpg'
}
)
];

describe('AttachToOverlayService', () => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [OverlayModule],
providers: [
AttachToOverlayService,
{
provide: ModalGalleryService,
useClass: ModalGalleryService
}
]
});
}));

it('should instantiate service when inject service', inject([AttachToOverlayService], (service: AttachToOverlayService) => {
expect(service instanceof AttachToOverlayService).toEqual(true);
}));

describe('#attachToOverlay()', () => {
describe('---YES---', () => {
it('should call the attach method on the given overlayRef', inject([ModalGalleryService], (modalGalleryService: ModalGalleryService) => {
const ID: number = 1;
const config = {
id: ID,
images: IMAGES,
currentImage: IMAGES[0]
};
const ref: ModalGalleryRef | undefined = modalGalleryService.open({
id: ID,
images: IMAGES,
currentImage: IMAGES[0]
});
expect(ref).toBeDefined();
expect(ref instanceof ModalGalleryRef).toBeTrue();
let attachCalled = false;
const mockOverlayRef = {
attach: () => {
attachCalled = true;
}
};

modalGalleryService.triggerAttachToOverlay.emit({
config,
dialogRef: ref as ModalGalleryRef,
overlayRef: mockOverlayRef as unknown as OverlayRef
});

expect(attachCalled).toBeTrue();
}));
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Injectable, Injector } from '@angular/core';
import { ModalGalleryRef } from './modal-gallery-ref';
import { ModalGalleryComponent } from './modal-gallery.component';
import { DIALOG_DATA } from './modal-gallery.tokens';
import { ComponentPortal } from '@angular/cdk/portal';
import { AttachToOverlayPayload, ModalGalleryService } from './modal-gallery.service';

@Injectable({ providedIn: 'root' })
export class AttachToOverlayService {
constructor(private injector: Injector, private modalGalleryService: ModalGalleryService) {}

/**
* To be called by a provider with the APP_INITIALIZER token.
*/
public initialize(): void {
this.modalGalleryService.triggerAttachToOverlay.subscribe(payload => this.attachToOverlay(payload));
}

/**
* Private method to attach ModalGalleryComponent to the overlay.
* @param payload {@link AttachToOverlayPayload} with all necessary information
* @private
*/
private attachToOverlay(payload: AttachToOverlayPayload): void {
const injector: Injector = Injector.create({
parent: this.injector,
providers: [
{ provide: ModalGalleryRef, useValue: payload.dialogRef },
{ provide: DIALOG_DATA, useValue: payload.config }
]
});

const containerPortal = new ComponentPortal(ModalGalleryComponent, null, injector);
payload.overlayRef.attach(containerPortal);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,25 @@ describe('ModalGalleryService', () => {
expect(ref instanceof ModalGalleryRef).toBeTrue();
})
);

it('should trigger attachment of the component to the overlay', inject([ModalGalleryService], (service: ModalGalleryService) => {
const ID: number = 1;
const config = {
id: ID,
images: IMAGES,
currentImage: IMAGES[0]
};
let hasEmitted = false;

service.triggerAttachToOverlay.subscribe(payload => {
expect(payload.config).toEqual(config);
hasEmitted = true;
});

service.open(config);

expect(hasEmitted).toBeTrue();
}));
});
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
import { Injectable, Injector, ComponentRef } from '@angular/core';
import {EventEmitter, Injectable} from '@angular/core';

import { GlobalPositionStrategy, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
import { ComponentPortal } from '@angular/cdk/portal';
import {GlobalPositionStrategy, Overlay, OverlayConfig, OverlayRef} from '@angular/cdk/overlay';

import { Subject } from 'rxjs';

import { DIALOG_DATA } from './modal-gallery.tokens';
import { ModalGalleryComponent } from './modal-gallery.component';
import { ModalGalleryRef } from './modal-gallery-ref';
import { Image, ImageModalEvent } from '../../model/image.class';
import { ConfigService } from '../../services/config.service';
import { ButtonEvent } from '../../model/buttons-config.interface';
import { ModalGalleryConfig } from '../../model/modal-gallery-config.interface';
import { LibConfig } from '../../model/lib-config.interface';
import {Subject} from 'rxjs';
import {ModalGalleryComponent} from './modal-gallery.component';
import {ModalGalleryRef} from './modal-gallery-ref';
import {Image, ImageModalEvent} from '../../model/image.class';
import {ConfigService} from '../../services/config.service';
import {ButtonEvent} from '../../model/buttons-config.interface';
import {ModalGalleryConfig} from '../../model/modal-gallery-config.interface';
import {LibConfig} from '../../model/lib-config.interface';

// private interface used only in this file
interface ModalDialogConfig {
Expand All @@ -21,6 +18,26 @@ interface ModalDialogConfig {
backdropClass: string;
}

/**
* Payload to be emitted via {@link triggerAttachToOverlay} to enable {@link AttachToOverlayService}
* to attach the {@link ModalGalleryComponent} to the overlay
*/
export interface AttachToOverlayPayload {
/**
* Overlay object created using Angular CDK APIs
*/
overlayRef: OverlayRef;
/**
* Dialog data to be injected into the {@link ModalGalleryComponent}
* contains: id, array of images, current image and optionally the configuration object
*/
config: ModalGalleryConfig;
/**
* Object to control the dialog instance
*/
dialogRef: ModalGalleryRef;
}

const DEFAULT_DIALOG_CONFIG: ModalDialogConfig = {
hasBackdrop: true,
backdropClass: 'ks-modal-gallery-backdrop',
Expand All @@ -34,7 +51,9 @@ export class ModalGalleryService {

private dialogRef: ModalGalleryRef | undefined;

constructor(private injector: Injector, private overlay: Overlay, private configService: ConfigService) {}
public triggerAttachToOverlay = new EventEmitter<AttachToOverlayPayload>();

constructor(private overlay: Overlay, private configService: ConfigService) {}

/**
* Method to open modal gallery passing the configuration
Expand All @@ -47,7 +66,11 @@ export class ModalGalleryService {
// Instantiate a reference to the dialog
this.dialogRef = new ModalGalleryRef(overlayRef);
// Attach dialog container
const overlayComponent: ModalGalleryComponent = this.attachDialogContainer(overlayRef, config, this.dialogRef);
this.triggerAttachToOverlay.emit({
overlayRef,
config,
dialogRef: this.dialogRef
});
overlayRef.backdropClick().subscribe(() => {
if (this.dialogRef) {
this.dialogRef.closeModal();
Expand Down Expand Up @@ -168,28 +191,6 @@ export class ModalGalleryService {
return this.overlay.create(overlayConfig);
}

/**
* Private method to attach ModalGalleryComponent to the overlay.
* @param overlayRef OverlayRef is the Overlay object created using Angular CDK APIs
* @param config ModalGalleryConfig that contains: id, array of images, current image and optionally the configuration object
* @param dialogRef ModalGalleryRef is the object to control the dialog instance
* @private
*/
private attachDialogContainer(overlayRef: OverlayRef, config: ModalGalleryConfig, dialogRef: ModalGalleryRef): ModalGalleryComponent {
const injector: Injector = Injector.create({
parent: this.injector,
providers: [
{ provide: ModalGalleryRef, useValue: dialogRef },
{ provide: DIALOG_DATA, useValue: config }
]
});

const containerPortal = new ComponentPortal(ModalGalleryComponent, null, injector);
const containerRef: ComponentRef<ModalGalleryComponent> = overlayRef.attach(containerPortal);

return containerRef.instance;
}

/**
* Private method to create an OverlayConfig instance
* @private
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,32 @@
SOFTWARE.
*/

import { NgModule } from '@angular/core';
import { APP_INITIALIZER, NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { OverlayModule } from '@angular/cdk/overlay';

import { COMPONENTS, CarouselComponent } from './components/components';
import { PlainGalleryComponent } from './components/plain-gallery/plain-gallery.component';
import { DIRECTIVES } from './directives/directives';
import { AttachToOverlayService } from './components/modal-gallery/attach-to-overlay.service';

/**
* Module to import it in the root module of your application.
*/
@NgModule({
imports: [CommonModule, OverlayModule],
declarations: [COMPONENTS, DIRECTIVES],
providers: [
AttachToOverlayService,
{
provide: APP_INITIALIZER,
multi: true,
deps: [AttachToOverlayService],
useFactory: (service: AttachToOverlayService): (() => void) => {
return () => service.initialize();
}
}
],
exports: [PlainGalleryComponent, CarouselComponent]
})
export class GalleryModule {}

0 comments on commit ec23c05

Please sign in to comment.