Skip to content

Commit 2fb66df

Browse files
authored
Merge pull request #2201 from Akshat55/ivy-partial
fix(modal): Remove remote scoping to allow partial build with ivy
2 parents b496a0c + 4685b52 commit 2fb66df

11 files changed

+135
-106
lines changed

src/modal/alert-modal.component.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ import { BaseModal } from "./base-modal.class";
7777
`
7878
})
7979
export class AlertModal extends BaseModal implements AfterViewInit {
80-
@ViewChild("modalContent", { static: true }) modalContent;
80+
@ViewChild("modalContent", { static: true }) modalContent: { nativeElement: any; };
8181
/**
8282
* Creates an instance of `AlertModal`.
8383
*/
@@ -114,7 +114,7 @@ export class AlertModal extends BaseModal implements AfterViewInit {
114114
}
115115
}
116116

117-
buttonClicked(buttonIndex) {
117+
buttonClicked(buttonIndex: string | number) {
118118
const button = this.buttons[buttonIndex];
119119
if (button.click) {
120120
button.click();
@@ -123,7 +123,7 @@ export class AlertModal extends BaseModal implements AfterViewInit {
123123
this.closeModal();
124124
}
125125

126-
dismissModal(trigger) {
126+
dismissModal(trigger: any) {
127127
if (this.onClose && this.onClose(trigger) === false) {
128128
return;
129129
}

src/modal/alert-modal.interface.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export interface AlertModalData {
2323
/**
2424
* Size of the modal to display.
2525
*/
26-
size?: "xs" | "sm" | "lg";
26+
size?: "xs" | "sm" | "md" | "lg";
2727
/**
2828
* Array of `ModalButton`s
2929
*/

src/modal/base-modal.service.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import {
2+
ComponentFactoryResolver,
3+
ComponentRef,
4+
Injector,
5+
Injectable
6+
} from "@angular/core";
7+
import { PlaceholderService } from "carbon-components-angular/placeholder";
8+
import { tap, delay } from "rxjs/operators";
9+
10+
11+
/**
12+
* Modal service handles instantiating and destroying modal instances.
13+
* Uses PlaceholderService to track open instances, and for it's placeholder view reference.
14+
*/
15+
@Injectable()
16+
export class BaseModalService {
17+
// track all our open modals
18+
protected static modalList: Array<ComponentRef<any>> = [];
19+
20+
/**
21+
* Creates an instance of `ModalService`.
22+
*/
23+
constructor(public resolver: ComponentFactoryResolver, public placeholderService: PlaceholderService) {}
24+
25+
/**
26+
* Creates and renders the modal component that is passed in.
27+
* `inputs` is an optional parameter of `data` that can be passed to the `Modal` component.
28+
*/
29+
create<T>(data: {component: any, inputs?: any}): ComponentRef<any> {
30+
let defaults = {inputs: {}};
31+
data = Object.assign({}, defaults, data);
32+
33+
const inputProviders = Object.keys(data.inputs).map(inputName => ({
34+
provide: inputName,
35+
useValue: data.inputs[inputName]
36+
}));
37+
const injector = Injector.create(inputProviders);
38+
const factory = this.resolver.resolveComponentFactory(data.component);
39+
let focusedElement = document.activeElement as HTMLElement;
40+
41+
let component = this.placeholderService.createComponent(factory, injector);
42+
43+
setTimeout(() => {
44+
component.instance.open = true;
45+
});
46+
47+
component["previouslyFocusedElement"] = focusedElement; // used to return focus to previously focused element
48+
49+
component.instance.close.pipe(
50+
// trigger the close animation
51+
tap(() => {
52+
component.instance.open = false;
53+
}),
54+
// delay closing by an arbitrary amount to allow the animation to finish
55+
delay(150)
56+
).subscribe(() => {
57+
this.placeholderService.destroyComponent(component);
58+
// filter out our component
59+
BaseModalService.modalList = BaseModalService.modalList.filter(c => c !== component);
60+
});
61+
62+
component.onDestroy(() => {
63+
focusedElement.focus();
64+
});
65+
66+
BaseModalService.modalList.push(component);
67+
68+
return component;
69+
}
70+
71+
/**
72+
* Destroys the modal on the supplied index.
73+
* When called without parameters it destroys the most recently created/top most modal.
74+
*/
75+
destroy(index = -1) {
76+
// return if nothing to destroy because it's already destroyed
77+
if (index >= BaseModalService.modalList.length || BaseModalService.modalList.length === 0) {
78+
return;
79+
}
80+
// on negative index destroy the last on the list (top modal)
81+
if (index < 0) {
82+
index = BaseModalService.modalList.length - 1;
83+
}
84+
85+
this.placeholderService.destroyComponent(BaseModalService.modalList[index]);
86+
BaseModalService.modalList.splice(index, 1);
87+
}
88+
}
89+

src/modal/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export { ModalFooter } from "./modal-footer.component";
1010
export { ModalHeader } from "./modal-header.component";
1111
export { Modal } from "./modal.component";
1212
export { ModalModule } from "./modal.module";
13+
export { BaseModalService } from "./base-modal.service";
1314
export { ModalService } from "./modal.service";
1415
export { Overlay } from "./overlay.component";
1516
export { ModalContent } from "./modal-content.directive";

src/modal/modal.component.spec.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { Overlay } from "./overlay.component";
77
import { ModalService } from "./modal.service";
88
import { I18nModule } from "../i18n/index";
99
import { PlaceholderModule } from "./../placeholder/index";
10+
import { BaseModalService } from "./base-modal.service";
1011

1112
// snippet to add transform to style so karma doesn't die with
1213
// 'The provided animation property "transform" is not a supported CSS property for animations in karma-test-shim.js'
@@ -32,7 +33,7 @@ describe("Modal", () => {
3233
I18nModule,
3334
PlaceholderModule
3435
],
35-
providers: [ModalService]
36+
providers: [ModalService, BaseModalService]
3637
});
3738

3839
fixture = TestBed.createComponent(Modal);
@@ -55,7 +56,8 @@ describe("Modal", () => {
5556
});
5657

5758
it("should close modal when escape is pressed", waitForAsync(() => {
58-
let modalService = fixture.debugElement.injector.get(ModalService);
59+
// Have to check BaseModalService since ModalService extends it
60+
let modalService = fixture.debugElement.injector.get(BaseModalService);
5961

6062
spyOn(modalService, "destroy");
6163

src/modal/modal.component.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { ModalService } from "./modal.service";
21
import {
32
AfterViewInit,
43
Component,
@@ -12,6 +11,7 @@ import {
1211
OnChanges
1312
} from "@angular/core";
1413
import { cycleTabs, getFocusElementList } from "carbon-components-angular/common";
14+
import { BaseModalService } from "./base-modal.service";
1515

1616
/**
1717
* Component to create modals for presenting content.
@@ -156,7 +156,7 @@ export class Modal implements AfterViewInit, OnChanges {
156156
/**
157157
* Creates an instance of `Modal`.
158158
*/
159-
constructor(public modalService: ModalService) {}
159+
constructor(public modalService: BaseModalService) {}
160160

161161
ngOnChanges({ open }: SimpleChanges) {
162162
if (open) {

src/modal/modal.module.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { NgModule } from "@angular/core";
33
import { CommonModule } from "@angular/common";
44

55
// imports
6+
import { BaseModalService } from "./base-modal.service";
67
import { ModalService } from "./modal.service";
78
import { Modal } from "./modal.component";
89
import { ModalFooter } from "./modal-footer.component";
@@ -44,7 +45,7 @@ import { IconModule } from "carbon-components-angular/icon";
4445
ModalHeaderLabel,
4546
BaseModal
4647
],
47-
providers: [ ModalService ],
48+
providers: [BaseModalService, ModalService],
4849
imports: [
4950
CommonModule,
5051
ButtonModule,

src/modal/modal.service.ts

Lines changed: 12 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,29 @@
1-
import {
2-
ComponentFactoryResolver,
3-
ComponentRef,
4-
Injector
5-
} from "@angular/core";
6-
import { Modal } from "./modal.component";
7-
import { ReplaySubject } from "rxjs";
1+
import { ComponentFactoryResolver } from "@angular/core";
82
import { Injectable } from "@angular/core";
93
import { AlertModal } from "./alert-modal.component";
104
import { AlertModalData } from "./alert-modal.interface";
115
import { PlaceholderService } from "carbon-components-angular/placeholder";
12-
import { tap, delay } from "rxjs/operators";
13-
6+
import { BaseModalService } from "./base-modal.service";
147

158
/**
9+
* Extends Base Modal Service to create Alert Modal with a function call. Placed in a seperate service
10+
* to prevent remote scoping (NG3003) which has side effects. Hence, import cycles are not allowed when
11+
* compilationMode is set to `partial`.
12+
*
13+
*
1614
* Modal service handles instantiating and destroying modal instances.
1715
* Uses PlaceholderService to track open instances, and for it's placeholder view reference.
1816
*/
1917
@Injectable()
20-
export class ModalService {
21-
// track all our open modals
22-
protected static modalList: Array<ComponentRef<any>> = [];
23-
18+
export class ModalService extends BaseModalService {
2419
/**
2520
* Creates an instance of `ModalService`.
2621
*/
27-
constructor(public resolver: ComponentFactoryResolver, public placeholderService: PlaceholderService) {}
28-
29-
/**
30-
* Creates and renders the modal component that is passed in.
31-
* `inputs` is an optional parameter of `data` that can be passed to the `Modal` component.
32-
*/
33-
create<T>(data: {component: any, inputs?: any}): ComponentRef<any> {
34-
let defaults = {inputs: {}};
35-
data = Object.assign({}, defaults, data);
36-
37-
const inputProviders = Object.keys(data.inputs).map(inputName => ({
38-
provide: inputName,
39-
useValue: data.inputs[inputName]
40-
}));
41-
const injector = Injector.create(inputProviders);
42-
const factory = this.resolver.resolveComponentFactory(data.component);
43-
let focusedElement = document.activeElement as HTMLElement;
44-
45-
let component = this.placeholderService.createComponent(factory, injector);
46-
47-
setTimeout(() => {
48-
component.instance.open = true;
49-
});
50-
51-
component["previouslyFocusedElement"] = focusedElement; // used to return focus to previously focused element
52-
53-
component.instance.close.pipe(
54-
// trigger the close animation
55-
tap(() => {
56-
component.instance.open = false;
57-
}),
58-
// delay closing by an arbitrary amount to allow the animation to finish
59-
delay(150)
60-
).subscribe(() => {
61-
this.placeholderService.destroyComponent(component);
62-
// filter out our component
63-
ModalService.modalList = ModalService.modalList.filter(c => c !== component);
64-
});
65-
66-
component.onDestroy(() => {
67-
focusedElement.focus();
68-
});
69-
70-
ModalService.modalList.push(component);
71-
72-
return component;
22+
constructor(public resolver: ComponentFactoryResolver, public placeholderService: PlaceholderService) {
23+
super(resolver, placeholderService);
7324
}
7425

26+
7527
/**
7628
* Creates and renders a new alert modal component.
7729
* @param data You can pass in:
@@ -100,28 +52,10 @@ export class ModalService {
10052
hasScrollingContent: data.hasScrollingContent || null,
10153
size: data.size,
10254
buttons: data.buttons || [],
103-
close: data.close || (() => {}),
55+
close: data.close || (() => { }),
10456
showCloseButton: data.showCloseButton
10557
}
10658
});
10759
}
108-
109-
/**
110-
* Destroys the modal on the supplied index.
111-
* When called without parameters it destroys the most recently created/top most modal.
112-
*/
113-
destroy(index = -1) {
114-
// return if nothing to destroy because it's already destroyed
115-
if (index >= ModalService.modalList.length || ModalService.modalList.length === 0) {
116-
return;
117-
}
118-
// on negative index destroy the last on the list (top modal)
119-
if (index < 0) {
120-
index = ModalService.modalList.length - 1;
121-
}
122-
123-
this.placeholderService.destroyComponent(ModalService.modalList[index]);
124-
ModalService.modalList.splice(index, 1);
125-
}
12660
}
12761

src/modal/modal.stories.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ class SampleFormModal extends BaseModal { }
119119
})
120120
class ModalStory {
121121
@Input() modalText = "Hello, World";
122-
@Input() size = "default";
122+
@Input() size = "md";
123123
@Input() showCloseButton = true;
124124

125125
constructor(protected modalService: ModalService) { }
@@ -184,7 +184,7 @@ class InputModal extends BaseModal {
184184
})
185185
class DataPassingModal implements AfterContentInit {
186186
@Input() modalText = "Hello, World";
187-
@Input() size = "default";
187+
@Input() size = "md";
188188

189189
protected modalInputValue = "";
190190
protected data: Observable<string> = new Subject<string>();
@@ -220,7 +220,7 @@ class AlertModalStory {
220220
@Input() modalTitle: string;
221221
@Input() modalContent: string;
222222
@Input() buttons: Array<ModalButton>;
223-
@Input() size: "xs" | "sm" | "lg";
223+
@Input() size: "xs" | "sm" | "md" | "lg";
224224
@Input() showCloseButton: boolean;
225225

226226
constructor(protected modalService: ModalService) { }
@@ -368,13 +368,13 @@ const DataPassingTemplate: Story<Modal> = (args) => ({
368368
`
369369
});
370370
export const DataPassing = DataPassingTemplate.bind({});
371-
Passive.args = {
371+
DataPassing.args = {
372372
modalText: "Hello, world!"
373373
};
374-
Passive.argTypes = {
375-
modalType: {
376-
defaultValue: "default",
377-
options: ["xs", "sm", "default", "lg"],
374+
DataPassing.argTypes = {
375+
size: {
376+
defaultValue: "md",
377+
options: ["xs", "sm", "md", "lg"],
378378
control: "select"
379379
}
380380
};

src/tsconfig.lib.json

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
{
2-
"extends": "./../tsconfig.json",
3-
"compilerOptions": {
4-
"declarationMap": false,
5-
"rootDir": "."
6-
},
7-
"angularCompilerOptions": {
8-
"enableIvy": true,
9-
"compliationMode": "partial"
10-
}
2+
"extends": "./../tsconfig.json",
3+
"compilerOptions": {
4+
"declarationMap": false,
5+
"rootDir": "."
6+
},
7+
"angularCompilerOptions": {
8+
"enableIvy": true,
9+
"compliationMode": "partial"
10+
}
1111
}

0 commit comments

Comments
 (0)