Skip to content

Commit

Permalink
fix(modal): Remove remote scoping to allow partial build with ivy
Browse files Browse the repository at this point in the history
Signed-off-by: Akshat Patel <[email protected]>
  • Loading branch information
Akshat55 committed Jul 18, 2022
1 parent b496a0c commit 6cbdd51
Show file tree
Hide file tree
Showing 10 changed files with 131 additions and 104 deletions.
6 changes: 3 additions & 3 deletions src/modal/alert-modal.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ import { BaseModal } from "./base-modal.class";
`
})
export class AlertModal extends BaseModal implements AfterViewInit {
@ViewChild("modalContent", { static: true }) modalContent;
@ViewChild("modalContent", { static: true }) modalContent: { nativeElement: any; };
/**
* Creates an instance of `AlertModal`.
*/
Expand Down Expand Up @@ -114,7 +114,7 @@ export class AlertModal extends BaseModal implements AfterViewInit {
}
}

buttonClicked(buttonIndex) {
buttonClicked(buttonIndex: string | number) {
const button = this.buttons[buttonIndex];
if (button.click) {
button.click();
Expand All @@ -123,7 +123,7 @@ export class AlertModal extends BaseModal implements AfterViewInit {
this.closeModal();
}

dismissModal(trigger) {
dismissModal(trigger: any) {
if (this.onClose && this.onClose(trigger) === false) {
return;
}
Expand Down
2 changes: 1 addition & 1 deletion src/modal/alert-modal.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export interface AlertModalData {
/**
* Size of the modal to display.
*/
size?: "xs" | "sm" | "lg";
size?: "xs" | "sm" | "md" | "lg";
/**
* Array of `ModalButton`s
*/
Expand Down
89 changes: 89 additions & 0 deletions src/modal/base-modal.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import {
ComponentFactoryResolver,
ComponentRef,
Injector,
Injectable
} from "@angular/core";
import { PlaceholderService } from "carbon-components-angular/placeholder";
import { tap, delay } from "rxjs/operators";


/**
* Modal service handles instantiating and destroying modal instances.
* Uses PlaceholderService to track open instances, and for it's placeholder view reference.
*/
@Injectable()
export class BaseModalService {
// track all our open modals
protected static modalList: Array<ComponentRef<any>> = [];

/**
* Creates an instance of `ModalService`.
*/
constructor(public resolver: ComponentFactoryResolver, public placeholderService: PlaceholderService) {}

/**
* Creates and renders the modal component that is passed in.
* `inputs` is an optional parameter of `data` that can be passed to the `Modal` component.
*/
create<T>(data: {component: any, inputs?: any}): ComponentRef<any> {
let defaults = {inputs: {}};
data = Object.assign({}, defaults, data);

const inputProviders = Object.keys(data.inputs).map(inputName => ({
provide: inputName,
useValue: data.inputs[inputName]
}));
const injector = Injector.create(inputProviders);
const factory = this.resolver.resolveComponentFactory(data.component);
let focusedElement = document.activeElement as HTMLElement;

let component = this.placeholderService.createComponent(factory, injector);

setTimeout(() => {
component.instance.open = true;
});

component["previouslyFocusedElement"] = focusedElement; // used to return focus to previously focused element

component.instance.close.pipe(
// trigger the close animation
tap(() => {
component.instance.open = false;
}),
// delay closing by an arbitrary amount to allow the animation to finish
delay(150)
).subscribe(() => {
this.placeholderService.destroyComponent(component);
// filter out our component
BaseModalService.modalList = BaseModalService.modalList.filter(c => c !== component);
});

component.onDestroy(() => {
focusedElement.focus();
});

BaseModalService.modalList.push(component);

return component;
}

/**
* Destroys the modal on the supplied index.
* When called without parameters it destroys the most recently created/top most modal.
*/
destroy(index = -1) {
// return if nothing to destroy because it's already destroyed
if (index >= BaseModalService.modalList.length || BaseModalService.modalList.length === 0) {
return;
}
// on negative index destroy the last on the list (top modal)
if (index < 0) {
index = BaseModalService.modalList.length - 1;
}

this.placeholderService.destroyComponent(BaseModalService.modalList[index]);
BaseModalService.modalList.splice(index, 1);
}
}

1 change: 1 addition & 0 deletions src/modal/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export { ModalFooter } from "./modal-footer.component";
export { ModalHeader } from "./modal-header.component";
export { Modal } from "./modal.component";
export { ModalModule } from "./modal.module";
export { BaseModalService } from "./base-modal.service";
export { ModalService } from "./modal.service";
export { Overlay } from "./overlay.component";
export { ModalContent } from "./modal-content.directive";
Expand Down
4 changes: 2 additions & 2 deletions src/modal/modal.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { ModalService } from "./modal.service";
import {
AfterViewInit,
Component,
Expand All @@ -12,6 +11,7 @@ import {
OnChanges
} from "@angular/core";
import { cycleTabs, getFocusElementList } from "carbon-components-angular/common";
import { BaseModalService } from "./base-modal.service";

/**
* Component to create modals for presenting content.
Expand Down Expand Up @@ -156,7 +156,7 @@ export class Modal implements AfterViewInit, OnChanges {
/**
* Creates an instance of `Modal`.
*/
constructor(public modalService: ModalService) {}
constructor(public modalService: BaseModalService) {}

ngOnChanges({ open }: SimpleChanges) {
if (open) {
Expand Down
3 changes: 2 additions & 1 deletion src/modal/modal.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { NgModule } from "@angular/core";
import { CommonModule } from "@angular/common";

// imports
import { BaseModalService } from "./base-modal.service";
import { ModalService } from "./modal.service";
import { Modal } from "./modal.component";
import { ModalFooter } from "./modal-footer.component";
Expand Down Expand Up @@ -44,7 +45,7 @@ import { IconModule } from "carbon-components-angular/icon";
ModalHeaderLabel,
BaseModal
],
providers: [ ModalService ],
providers: [BaseModalService, ModalService],
imports: [
CommonModule,
ButtonModule,
Expand Down
90 changes: 12 additions & 78 deletions src/modal/modal.service.ts
Original file line number Diff line number Diff line change
@@ -1,77 +1,29 @@
import {
ComponentFactoryResolver,
ComponentRef,
Injector
} from "@angular/core";
import { Modal } from "./modal.component";
import { ReplaySubject } from "rxjs";
import { ComponentFactoryResolver } from "@angular/core";
import { Injectable } from "@angular/core";
import { AlertModal } from "./alert-modal.component";
import { AlertModalData } from "./alert-modal.interface";
import { PlaceholderService } from "carbon-components-angular/placeholder";
import { tap, delay } from "rxjs/operators";

import { BaseModalService } from "./base-modal.service";

/**
* Extends Modal Service to create Alert Modal with a function call. Placed in a seperate service
* to prevent remote scoping (NG3003) which has side effects. Hence, import cycles are not allowed when
* compilationMode is set to `partial`.
*
*
* Modal service handles instantiating and destroying modal instances.
* Uses PlaceholderService to track open instances, and for it's placeholder view reference.
*/
@Injectable()
export class ModalService {
// track all our open modals
protected static modalList: Array<ComponentRef<any>> = [];

export class ModalService extends BaseModalService {
/**
* Creates an instance of `ModalService`.
*/
constructor(public resolver: ComponentFactoryResolver, public placeholderService: PlaceholderService) {}

/**
* Creates and renders the modal component that is passed in.
* `inputs` is an optional parameter of `data` that can be passed to the `Modal` component.
*/
create<T>(data: {component: any, inputs?: any}): ComponentRef<any> {
let defaults = {inputs: {}};
data = Object.assign({}, defaults, data);

const inputProviders = Object.keys(data.inputs).map(inputName => ({
provide: inputName,
useValue: data.inputs[inputName]
}));
const injector = Injector.create(inputProviders);
const factory = this.resolver.resolveComponentFactory(data.component);
let focusedElement = document.activeElement as HTMLElement;

let component = this.placeholderService.createComponent(factory, injector);

setTimeout(() => {
component.instance.open = true;
});

component["previouslyFocusedElement"] = focusedElement; // used to return focus to previously focused element

component.instance.close.pipe(
// trigger the close animation
tap(() => {
component.instance.open = false;
}),
// delay closing by an arbitrary amount to allow the animation to finish
delay(150)
).subscribe(() => {
this.placeholderService.destroyComponent(component);
// filter out our component
ModalService.modalList = ModalService.modalList.filter(c => c !== component);
});

component.onDestroy(() => {
focusedElement.focus();
});

ModalService.modalList.push(component);

return component;
constructor(public resolver: ComponentFactoryResolver, public placeholderService: PlaceholderService) {
super(resolver, placeholderService);
}


/**
* Creates and renders a new alert modal component.
* @param data You can pass in:
Expand Down Expand Up @@ -100,28 +52,10 @@ export class ModalService {
hasScrollingContent: data.hasScrollingContent || null,
size: data.size,
buttons: data.buttons || [],
close: data.close || (() => {}),
close: data.close || (() => { }),
showCloseButton: data.showCloseButton
}
});
}

/**
* Destroys the modal on the supplied index.
* When called without parameters it destroys the most recently created/top most modal.
*/
destroy(index = -1) {
// return if nothing to destroy because it's already destroyed
if (index >= ModalService.modalList.length || ModalService.modalList.length === 0) {
return;
}
// on negative index destroy the last on the list (top modal)
if (index < 0) {
index = ModalService.modalList.length - 1;
}

this.placeholderService.destroyComponent(ModalService.modalList[index]);
ModalService.modalList.splice(index, 1);
}
}

16 changes: 8 additions & 8 deletions src/modal/modal.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ class SampleFormModal extends BaseModal { }
})
class ModalStory {
@Input() modalText = "Hello, World";
@Input() size = "default";
@Input() size = "md";
@Input() showCloseButton = true;

constructor(protected modalService: ModalService) { }
Expand Down Expand Up @@ -184,7 +184,7 @@ class InputModal extends BaseModal {
})
class DataPassingModal implements AfterContentInit {
@Input() modalText = "Hello, World";
@Input() size = "default";
@Input() size = "md";

protected modalInputValue = "";
protected data: Observable<string> = new Subject<string>();
Expand Down Expand Up @@ -220,7 +220,7 @@ class AlertModalStory {
@Input() modalTitle: string;
@Input() modalContent: string;
@Input() buttons: Array<ModalButton>;
@Input() size: "xs" | "sm" | "lg";
@Input() size: "xs" | "sm" | "md" | "lg";
@Input() showCloseButton: boolean;

constructor(protected modalService: ModalService) { }
Expand Down Expand Up @@ -368,13 +368,13 @@ const DataPassingTemplate: Story<Modal> = (args) => ({
`
});
export const DataPassing = DataPassingTemplate.bind({});
Passive.args = {
DataPassing.args = {
modalText: "Hello, world!"
};
Passive.argTypes = {
modalType: {
defaultValue: "default",
options: ["xs", "sm", "default", "lg"],
DataPassing.argTypes = {
size: {
defaultValue: "md",
options: ["xs", "sm", "md", "lg"],
control: "select"
}
};
Expand Down
18 changes: 9 additions & 9 deletions src/tsconfig.lib.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"extends": "./../tsconfig.json",
"compilerOptions": {
"declarationMap": false,
"rootDir": "."
},
"angularCompilerOptions": {
"enableIvy": true,
"compliationMode": "partial"
}
"extends": "./../tsconfig.json",
"compilerOptions": {
"declarationMap": false,
"rootDir": "."
},
"angularCompilerOptions": {
"enableIvy": true,
"compliationMode": "partial"
}
}
6 changes: 4 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"moduleResolution": "node",
"outDir": "./dist-pkg",
"rootDir": ".",
"target": "es2015",
"target": "es2020",
"skipLibCheck": true,
"typeRoots": [
"./node_modules/@types"
Expand All @@ -37,6 +37,8 @@
"angularCompilerOptions": {
"skipTemplateCodegen": true,
"strictMetadataEmit": false,
"fullTemplateTypeCheck": false
"fullTemplateTypeCheck": false,
"enableIvy": true,
"compilationMode": "partial"
}
}

0 comments on commit 6cbdd51

Please sign in to comment.