Skip to content

Commit

Permalink
feat: support setting an injector when creating a modal (overlay). in…
Browse files Browse the repository at this point in the history
…jector is optional.
  • Loading branch information
Shlomi Assaf (shlassaf) committed Oct 3, 2016
1 parent 9db8f5e commit c0a9b71
Show file tree
Hide file tree
Showing 14 changed files with 204 additions and 37 deletions.
16 changes: 5 additions & 11 deletions src/components/angular2-modal/components/base-dynamic-component.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import {
ComponentRef,
ComponentFactoryResolver,
ElementRef,
ResolvedReflectiveProvider,
OnDestroy,
ViewContainerRef,
Renderer, TemplateRef, Type
Renderer
} from '@angular/core';

import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import 'rxjs/add/operator/filter';

import { createComponent } from '../framework/createComponent';
import { createComponent, CreateComponentArgs } from '../framework/createComponent';

const BROWSER_PREFIX = ['webkit', 'moz', 'MS', 'o', ''];

Expand Down Expand Up @@ -95,18 +94,13 @@ export class BaseDynamicComponent implements OnDestroy {
/**
* Add a component, supply a view container ref.
* Note: The components vcRef will result in a sibling.
* @param type The component to add
* @param component The component to add
* @param vcRef The container to add to
* @param bindings Bindings to use (added on top of the ViewContainerRef)
* @returns {Promise<ComponentRef<any>>}
*/
protected _addComponent<T>(type: any,
vcRef: ViewContainerRef,
bindings: ResolvedReflectiveProvider[] = [],
projectableNodes: any[][] = []): ComponentRef<T> {
const cmpRef =
createComponent(vcRef.injector.get(ComponentFactoryResolver), type, vcRef, bindings, projectableNodes);

protected _addComponent<T>(instructions: CreateComponentArgs): ComponentRef<T> {
const cmpRef = createComponent(instructions);
cmpRef.changeDetectorRef.detectChanges();

return cmpRef;
Expand Down
54 changes: 40 additions & 14 deletions src/components/angular2-modal/framework/createComponent.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,53 @@
import {
ComponentRef,
ComponentFactoryResolver,
Injector,
ViewContainerRef,
ReflectiveInjector,
ResolvedReflectiveProvider
} from '@angular/core';

export function createComponent(cfr: ComponentFactoryResolver,
type: any,
vcr: ViewContainerRef,
bindings: ResolvedReflectiveProvider[],
projectableNodes?: any[][]): ComponentRef<any> {
return vcr.createComponent(
cfr.resolveComponentFactory(type),
vcr.length,
getInjector(vcr, bindings),
projectableNodes
export interface CreateComponentArgs {
component: any;
vcRef: ViewContainerRef;
injector?: Injector;
bindings?: ResolvedReflectiveProvider[];
projectableNodes?: any[][];
}

export function createComponent(instructions: CreateComponentArgs): ComponentRef<any> {
let injector: Injector = getInjector(instructions);
return instructions.vcRef.createComponent(
injector.get(ComponentFactoryResolver).resolveComponentFactory(instructions.component),
instructions.vcRef.length,
injector,
instructions.projectableNodes
);
}

function getInjector(viewContainer: ViewContainerRef, bindings: ResolvedReflectiveProvider[]) {
const ctxInjector = viewContainer.parentInjector;
return Array.isArray(bindings) && bindings.length > 0 ?
ReflectiveInjector.fromResolvedProviders(bindings, ctxInjector) : ctxInjector;
function getInjector(instructions: CreateComponentArgs) {
const ctxInjector = instructions.injector || instructions.vcRef.parentInjector;
return Array.isArray(instructions.bindings) && instructions.bindings.length > 0 ?
ReflectiveInjector.fromResolvedProviders(instructions.bindings, ctxInjector) : ctxInjector;

}

// export function createComponent(cfr: ComponentFactoryResolver,
// type: any,
// vcr: ViewContainerRef,
// bindings: ResolvedReflectiveProvider[],
// projectableNodes?: any[][]): ComponentRef<any> {
// return vcr.createComponent(
// cfr.resolveComponentFactory(type),
// vcr.length,
// getInjector(vcr, bindings),
// projectableNodes
// );
// }
//
// function getInjector(viewContainer: ViewContainerRef, bindings: ResolvedReflectiveProvider[]) {
// const ctxInjector = viewContainer.parentInjector;
// return Array.isArray(bindings) && bindings.length > 0 ?
// ReflectiveInjector.fromResolvedProviders(bindings, ctxInjector) : ctxInjector;
//
// }
2 changes: 1 addition & 1 deletion src/components/angular2-modal/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Modal } from './providers/index';

export * from './framework/fluent-assign';
export { extend, arrayUnion, PromiseCompleter, Maybe } from './framework/utils';
export { createComponent } from './framework/createComponent';
export { createComponent, CreateComponentArgs } from './framework/createComponent';

export * from './models/errors';

Expand Down
6 changes: 5 additions & 1 deletion src/components/angular2-modal/models/tokens.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
ComponentRef,
Injector,
ViewContainerRef,
TemplateRef,
Type,
Expand Down Expand Up @@ -32,6 +33,8 @@ export interface OverlayConfig {
*/
context?: OverlayContext;

injector?: Injector;

/**
* Resolved providers that will inject into the component provided.
*/
Expand Down Expand Up @@ -71,5 +74,6 @@ export interface CloseGuard {

export abstract class OverlayRenderer {
public abstract render(dialogRef: DialogRef<any>,
vcRef: ViewContainerRef): ComponentRef<ModalOverlay>;
vcRef: ViewContainerRef,
injector?: Injector): ComponentRef<ModalOverlay>;
}
7 changes: 6 additions & 1 deletion src/components/angular2-modal/overlay/overlay.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,12 @@ export class ModalOverlay extends BaseDynamicComponent {
}

addComponent<T>(type: any, bindings: ResolvedReflectiveProvider[] = [], projectableNodes: any[][] = []): ComponentRef<T> {
return super._addComponent<T>(type, this.innerVcr, bindings, projectableNodes);
return super._addComponent<T>({
component: type,
vcRef: this.innerVcr,
bindings,
projectableNodes
});
}

fullscreen(): void {
Expand Down
2 changes: 1 addition & 1 deletion src/components/angular2-modal/overlay/overlay.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export class Overlay {
let dialog = new DialogRef<any>(this, config.context || {});
dialog.inElement = config.context && !!config.context.inElement;

let cmpRef = renderer.render(dialog, vcRef);
let cmpRef = renderer.render(dialog, vcRef, config.injector);

Object.defineProperty(dialog, 'overlayRef', {value: cmpRef});
_stack.pushManaged(dialog, group);
Expand Down
15 changes: 9 additions & 6 deletions src/components/angular2-modal/providers/dom-modal-renderer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
ViewContainerRef,
ComponentFactoryResolver,
ComponentRef,
Injector,
Injectable,
ReflectiveInjector
} from '@angular/core';
Expand All @@ -13,15 +13,18 @@ import { ModalOverlay } from '../overlay/index';

@Injectable()
export class DOMOverlayRenderer implements OverlayRenderer {
constructor(private _cr: ComponentFactoryResolver) {
}

render(dialog: DialogRef<any>, vcRef: ViewContainerRef): ComponentRef<ModalOverlay> {
const b = ReflectiveInjector.resolve([
render(dialog: DialogRef<any>, vcRef: ViewContainerRef, injector?: Injector): ComponentRef<ModalOverlay> {
const bindings = ReflectiveInjector.resolve([
{ provide: DialogRef, useValue: dialog }
]);

const cmpRef = createComponent(this._cr, ModalOverlay, vcRef, b);
const cmpRef = createComponent({
component: ModalOverlay,
vcRef,
injector,
bindings
});

if (dialog.inElement) {
vcRef.element.nativeElement.appendChild(cmpRef.location.nativeElement);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, TemplateRef, ViewChild } from '@angular/core';
import { Compiler, Component, Injector, TemplateRef, ViewChild, NgModuleRef } from '@angular/core';

import { overlayConfigFactory } from "../../../../components/angular2-modal";
import { Modal, BSModalContext } from '../../../../components/angular2-modal/plugins/bootstrap';
Expand All @@ -7,6 +7,9 @@ import { ModalCommandDescriptor } from '../../demo-head/index';
import { CustomModal } from './custom-modal-sample';
import * as presets from '../presets';

import { RuntimeCompiledModule, RuntimeCompiledComponent } from './runtime-compiled';

let runtimeModuleRefPromise: Promise<NgModuleRef<any>>;

@Component({
selector: 'bootstrap-demo-page',
Expand All @@ -17,7 +20,7 @@ export class BootstrapDemoPage {
modalCommands: ModalCommandDescriptor[];
@ViewChild('templateRef') public templateRef: TemplateRef<any>;

constructor(public modal: Modal) {
constructor(public modal: Modal, private compiler: Compiler, private injector: Injector) {
this.modalCommands = [
{
text: 'alert drop in',
Expand Down Expand Up @@ -56,6 +59,21 @@ export class BootstrapDemoPage {
// we set the baseContextType to BSModalContext so the defaults for bootstrap will apply
}

},
{
text: 'JIT Compiled component',
factory: () => {
if (!runtimeModuleRefPromise) {
runtimeModuleRefPromise = this.compiler.compileModuleAsync(RuntimeCompiledModule)
.then(moduleFactory => moduleFactory.create(this.injector));
}

return runtimeModuleRefPromise.then(module => {
return this.modal.open(RuntimeCompiledComponent, overlayConfigFactory({isBlocking: false}, BSModalContext, {
injector: module.injector
}));
});
}
}
];
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { RuntimeCompiledComponent } from './runtime-compiled.component';
export { RuntimeCompiledModule } from './runtime.compiled.module';
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { InnerRuntimeCompiledComponent } from './inner-runtime-compiled.component';
export { InnerRuntimeCompiledModule } from './inner-runtime.compiled.module';
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Component } from '@angular/core';
import { DialogRef } from "../../../../../../components/angular2-modal";


@Component({
selector: 'runtime-compiled-component',
template:
`<div class="modal-header">
<h3>I'm another JIT compiled component!</h3>
</div>
<div class="modal-body">
<h4>Choose a result:</h4>
<button class="btn btn-primary" (click)="close('A')">A</button>
<button class="btn btn-primary" (click)="close('B')">B</button>
<button class="btn btn-primary" (click)="close('C')">C</button>
</div>`
})
export class InnerRuntimeCompiledComponent {
constructor(private dialogRef: DialogRef<any>) {

}

close(value: string): void {
this.dialogRef.close(value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import { ModalModule } from '../../../../../../components/angular2-modal';

import { InnerRuntimeCompiledComponent } from './inner-runtime-compiled.component';

@NgModule({
imports: [
CommonModule,
ModalModule
],
declarations: [
InnerRuntimeCompiledComponent
],
entryComponents: [
InnerRuntimeCompiledComponent
],
})
export class InnerRuntimeCompiledModule {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Component, Compiler, NgModuleRef } from '@angular/core';

import { DialogRef, overlayConfigFactory } from "../../../../../components/angular2-modal";
import { Modal, BSModalContext } from '../../../../../components/angular2-modal/plugins/bootstrap';

import { InnerRuntimeCompiledModule, InnerRuntimeCompiledComponent } from './inner-runtime-compiled';
let runtimeModuleRefPromise: Promise<NgModuleRef<any>>;

@Component({
selector: 'runtime-compiled-component',
template:
`
<div class="modal-header">
<h3>I'm a JIT compiled component!</h3>
</div>
<div class="modal-body">
<p>This is a demonstration of JIT component displayed as a modal content, it also shows how to link the result of a chain of modals.</p>
<p>To JIT compile another (different) module inside this (JIT) compiled module press the button below.
The value selected on the popup opened will bubble down.</p>
<button class="btn btn-primary" (click)="openModal()">Compile and open again!</button>
</div>`
})
export class RuntimeCompiledComponent {
constructor(private dialogRef: DialogRef<any>, private compiler: Compiler, private modal: Modal) {

}

openModal(): void {
if (!runtimeModuleRefPromise) {
runtimeModuleRefPromise = this.compiler.compileModuleAsync(InnerRuntimeCompiledModule)
.then(moduleFactory => moduleFactory.create(this.modal.overlay.defaultViewContainer.parentInjector));
}

runtimeModuleRefPromise
.then( module => overlayConfigFactory({isBlocking: true}, BSModalContext, { injector: module.injector }) )
.then( overlayConfig => this.modal.open(InnerRuntimeCompiledComponent, overlayConfig) )
.then( dialogRef => dialogRef.result )
.then( value => this.dialogRef.close(value) )
.catch( err => this.dialogRef.dismiss() );
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import { ModalModule } from '../../../../../components/angular2-modal';
import { BootstrapModalModule } from '../../../../../components/angular2-modal/plugins/bootstrap';

import { RuntimeCompiledComponent } from './runtime-compiled.component';

@NgModule({
imports: [
CommonModule,
ModalModule,
BootstrapModalModule
],
declarations: [
RuntimeCompiledComponent
],
entryComponents: [
RuntimeCompiledComponent
],
})
export class RuntimeCompiledModule {

}

0 comments on commit c0a9b71

Please sign in to comment.