Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"vsicons.presets.angular": true,
"typescript.tsdk": "./node_modules/typescript/lib"
}
97 changes: 46 additions & 51 deletions src/app/af-spawn.service.ts
Original file line number Diff line number Diff line change
@@ -1,91 +1,86 @@
import {
Injectable, ViewContainerRef, ComponentFactoryResolver, ApplicationRef, Injector,
ComponentFactory
Injectable,
ViewContainerRef,
ComponentFactoryResolver,
ApplicationRef,
Injector,
ComponentFactory,
ComponentRef
} from '@angular/core';
import {BehaviorSubject, Subscription} from "rxjs";
import {getSymbolObservable} from "rxjs/symbol/observable";
import {SpawnReference} from "./interfaces/SpawnReference";
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Subscription } from 'rxjs/Subscription';
import { getSymbolObservable } from 'rxjs/symbol/observable';
import { SpawnReference } from './interfaces/SpawnReference';

@Injectable()
export class AFSpawnService {


constructor(
private cfr: ComponentFactoryResolver
, private appRef: ApplicationRef
, private injector: Injector
private cfr: ComponentFactoryResolver,
private appRef: ApplicationRef,
private injector: Injector
) { }

createComponent(type: any, vcr?: ViewContainerRef, context?: any): SpawnReference{
createComponent(componentType: any, viewContainerRef?: ViewContainerRef, context?: any): SpawnReference {

// Resolve the factory for incoming component `type`.
let factory = this.cfr.resolveComponentFactory(type);
const factory: ComponentFactory<any> = this.cfr.resolveComponentFactory(componentType);

// Create an instance of the component, and add it to the DOM
let componentRef;
if(vcr){
componentRef = vcr.createComponent(factory);
let componentRef: ComponentRef<any>;
if (viewContainerRef) {
componentRef = viewContainerRef.createComponent(factory);
} else {
componentRef = factory.create(this.injector);
this.appRef.attachView(componentRef.hostView);
document.body.appendChild( (componentRef.hostView as any).rootNodes[0]);
document.body.appendChild((componentRef.hostView as any).rootNodes[0]);
}

// Wire up the outputs, and get reference to un-wire outputs
let unsubs = this._wireOutputs(factory, componentRef, context);

// Turn the provided inputs into an observable (if not already an observable)
let observableSymbol = getSymbolObservable(window);
let context$;
if(context && context[observableSymbol]){
const subscriptions: Subscription[] = this.wireOutputs(factory, componentRef, context);
const observableSymbol: any = getSymbolObservable(window);
let context$: BehaviorSubject<any>;
if (context && context[observableSymbol]) {
context$ = context;
} else {
context$ = new BehaviorSubject(context);
}

// Subscribe to the new observable for updated input values
unsubs.push( context$.subscribe(()=>{
factory.inputs.forEach(i=>{
if(context[i.propName] !== undefined){
componentRef.instance[i.propName] = context[i.propName];
subscriptions.push(context$.subscribe(() => {
factory.inputs.forEach(input => {
if (context[input.propName] !== undefined) {
componentRef.instance[input.propName] = context[input.propName];
}
})
}) );
});
}));

// This function will be returned to the caller, to be called when their context is destroyed
let detach = ()=>{
if (!vcr) {
const detach = () => {
if (!viewContainerRef) {
this.appRef.detachView(componentRef.hostView);
}
componentRef.destroy();
unsubs.map(u => { if (!u.closed) { u.unsubscribe(); } });
subscriptions.map(subscription => { if (!subscription.closed) { subscription.unsubscribe(); } });
};

// This function will be returned to the caller, to be called when there are new values for the inputs
let next = (data)=>{
if(context$ == context){
throw `When passing an observable as a context, you cannot call the \`.next\` function from the result.
If you wish to update the values in your context, send the data through the observable that you
passed in as the context.`;
const next = (data: any) => {
if (context$ === context) {
throw new Error(`When passing an observable as a context, you cannot call the \`.next\` function from the result.
If you wish to update the values in your context, send the data through the observable that you passed in as the context.`);
}

context$.next(data);
};

return {
detach,
next,
}
next
};
}

// Internal function to add event emitters for each of the provide outputs
_wireOutputs(factory: ComponentFactory<any>, componentRef: any, context: {[key:string]: any}): Array<Subscription>{
let unsubs = [];
factory.outputs.forEach(o=>{
if(context[o.propName] && context[o.propName] instanceof Function){
unsubs.push(componentRef.instance[o.propName].subscribe(context[o.propName]));
private wireOutputs(factory: ComponentFactory<any>, componentRef: ComponentRef<any>, context: { [key: string]: any }): Array<Subscription> {
const subscriptions: Subscription[] = [];
factory.outputs.forEach(output => {
if (context[output.propName] && context[output.propName] instanceof Function) {
subscriptions.push(componentRef.instance[output.propName].subscribe(context[output.propName]));
}
});
return unsubs;
return subscriptions;
}

}
156 changes: 85 additions & 71 deletions tslint.json
Original file line number Diff line number Diff line change
@@ -1,116 +1,130 @@
{
"rulesDirectory": [
{
"rulesDirectory": [
"node_modules/codelyzer"
],
"rules": {
"callable-types": true,
"class-name": true,
"comment-format": [
"rules": {
"directive-selector": [true, "attribute", "", "kebab-case"],
"component-selector": [true, "element", "", "kebab-case"],
"use-input-property-decorator": true,
"use-output-property-decorator": true,
"use-host-property-decorator": false,
"no-attribute-parameter-decorator": true,
"no-input-rename": true,
"no-output-rename": true,
"no-forward-ref" :true,
"use-life-cycle-interface": true,
"use-pipe-transform-interface": true,
"pipe-naming": [true, "camelCase"],
"component-class-suffix": true,
"directive-class-suffix": true,
"import-destructuring-spacing": true,
"templates-use-public": true,
"no-access-missing-member": false,
"invoke-injectable": true,
"member-access": false,
"member-ordering": [
true,
"check-space"
{ "order": "statics-first" }
],
"no-any": false,
"no-inferrable-types": [true, "ignore-params"],
"no-internal-module": true,
"no-namespace": true,
"no-reference": true,
"no-var-requires": false,
"typedef": false,
"typedef-whitespace": [
true, {
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}, {
"call-signature": "space",
"index-signature": "space",
"parameter": "space",
"property-declaration": "space",
"variable-declaration": "space"
}
],
"curly": true,
"eofline": true,
"forin": true,
"import-blacklist": [true, "rxjs"],
"import-spacing": true,
"indent": [
true,
"spaces"
],
"interface-over-type-literal": true,
"label-position": true,
"max-line-length": [
true,
140
],
"member-access": false,
"member-ordering": [
true,
"static-before-instance",
"variables-before-functions"
],
"no-arg": true,
"no-bitwise": true,
"no-conditional-assignment": false,
"no-console": [
true,
"log",
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-construct": true,
"no-debugger": true,
"no-debugger": false,
"no-duplicate-variable": true,
"no-empty": false,
"no-empty-interface": true,
"no-empty": true,
"no-eval": true,
"no-inferrable-types": [true, "ignore-params"],
"no-invalid-this": true,
"no-null-keyword": false,
"no-shadowed-variable": true,
"no-string-literal": false,
"no-string-throw": true,
"no-switch-case-fall-through": true,
"no-trailing-whitespace": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-var-keyword": true,
"radix": true,
"switch-default": false,
"triple-equals": [true, "allow-null-check"],
"use-isnan": true,
"eofline": true,
"indent": [true, "spaces"],
"max-line-length": [true, 150],
"no-require-imports": false,
"no-trailing-whitespace": true,
"object-literal-sort-keys": false,
"trailing-comma": [
true,
{
"multiline": "never",
"singleline": "never"
}
],
"align": false,
"class-name": true,
"comment-format": [true, "check-space"],
"interface-name": [true, "never-prefix"],
"jsdoc-format": true,
"no-angle-bracket-type-assertion": true,
"no-consecutive-blank-lines": false,
"one-line": [
true,
"check-open-brace",
"check-catch",
"check-else",
"check-finally",
"check-open-brace",
"check-whitespace"
],
"prefer-const": true,
"quotemark": [
true,
"single"
],
"radix": true,
"semicolon": [
"always"
],
"triple-equals": [
true,
"allow-null-check"
],
"typedef-whitespace": [
"one-variable-per-declaration": true,
"quotemark": [true, "single"],
"semicolon": [true, "always"],
"variable-name": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}
"check-format",
"ban-keywords"
],
"typeof-compare": true,
"unified-signatures": true,
"variable-name": false,
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-module",
"check-separator",
"check-type"
],

"directive-selector": [true, "attribute", "app", "camelCase"],
"component-selector": [true, "element", "app", "kebab-case"],
"use-input-property-decorator": true,
"use-output-property-decorator": true,
"use-host-property-decorator": true,
"no-input-rename": true,
"no-output-rename": true,
"use-life-cycle-interface": true,
"use-pipe-transform-interface": true,
"component-class-suffix": true,
"directive-class-suffix": true,
"no-access-missing-member": true,
"templates-use-public": true,
"invoke-injectable": true
]
}
}