
committed by
Miško Hevery

parent
46efd4b938
commit
87f60bccfd
@ -6,29 +6,35 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ApplicationRef, ComponentFactory, ComponentRef, EventEmitter, Injector, OnChanges, SimpleChange, SimpleChanges} from '@angular/core';
|
||||
import {ApplicationRef, ComponentFactory, ComponentFactoryResolver, ComponentRef, EventEmitter, Injector, OnChanges, SimpleChange, SimpleChanges, Type} from '@angular/core';
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
import {merge} from 'rxjs/observable/merge';
|
||||
import {map} from 'rxjs/operator/map';
|
||||
|
||||
import {NgElementStrategy, NgElementStrategyEvent, NgElementStrategyFactory} from './element-strategy';
|
||||
import {extractProjectableNodes} from './extract-projectable-nodes';
|
||||
import {camelToDashCase, isFunction, scheduler, strictEquals} from './utils';
|
||||
import {isFunction, scheduler, strictEquals} from './utils';
|
||||
|
||||
/** Time in milliseconds to wait before destroying the component ref when disconnected. */
|
||||
const DESTROY_DELAY = 10;
|
||||
|
||||
/**
|
||||
* Factory that creates new ComponentFactoryNgElementStrategy instances with the strategy factory's
|
||||
* injector. A new strategy instance is created with the provided component factory which will
|
||||
* create its components on connect.
|
||||
* Factory that creates new ComponentNgElementStrategy instance. Gets the component factory with the
|
||||
* constructor's injector's factory resolver and passes that factory to each strategy.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export class ComponentFactoryNgElementStrategyFactory implements NgElementStrategyFactory {
|
||||
constructor(private componentFactory: ComponentFactory<any>, private injector: Injector) {}
|
||||
export class ComponentNgElementStrategyFactory implements NgElementStrategyFactory {
|
||||
componentFactory: ComponentFactory<any>;
|
||||
|
||||
create() { return new ComponentFactoryNgElementStrategy(this.componentFactory, this.injector); }
|
||||
constructor(private component: Type<any>, private injector: Injector) {
|
||||
this.componentFactory =
|
||||
injector.get(ComponentFactoryResolver).resolveComponentFactory(component);
|
||||
}
|
||||
|
||||
create(injector: Injector) {
|
||||
return new ComponentNgElementStrategy(this.componentFactory, injector);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -37,12 +43,12 @@ export class ComponentFactoryNgElementStrategyFactory implements NgElementStrate
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export class ComponentFactoryNgElementStrategy implements NgElementStrategy {
|
||||
export class ComponentNgElementStrategy implements NgElementStrategy {
|
||||
/** Merged stream of the component's output events. */
|
||||
events: Observable<NgElementStrategyEvent>;
|
||||
|
||||
/** Reference to the component that was created on connect. */
|
||||
private componentRef: ComponentRef<any>;
|
||||
private componentRef: ComponentRef<any>|null;
|
||||
|
||||
/** Changes that have been made to the component ref since the last time onChanges was called. */
|
||||
private inputChanges: SimpleChanges|null = null;
|
||||
@ -96,6 +102,7 @@ export class ComponentFactoryNgElementStrategy implements NgElementStrategy {
|
||||
this.scheduledDestroyFn = scheduler.schedule(() => {
|
||||
if (this.componentRef) {
|
||||
this.componentRef !.destroy();
|
||||
this.componentRef = null;
|
||||
}
|
||||
}, DESTROY_DELAY);
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {ComponentFactory} from '@angular/core';
|
||||
import {ComponentFactory, Injector} from '@angular/core';
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
|
||||
/**
|
||||
@ -38,4 +38,7 @@ export interface NgElementStrategy {
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export interface NgElementStrategyFactory { create(): NgElementStrategy; }
|
||||
export interface NgElementStrategyFactory {
|
||||
/** Creates a new instance to be used for an NgElement. */
|
||||
create(injector: Injector): NgElementStrategy;
|
||||
}
|
||||
|
@ -6,12 +6,12 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ComponentFactoryResolver, Injector, Type} from '@angular/core';
|
||||
import {Injector, Type} from '@angular/core';
|
||||
import {Subscription} from 'rxjs/Subscription';
|
||||
|
||||
import {ComponentFactoryNgElementStrategyFactory} from './component-factory-strategy';
|
||||
import {ComponentNgElementStrategyFactory} from './component-factory-strategy';
|
||||
import {NgElementStrategy, NgElementStrategyFactory} from './element-strategy';
|
||||
import {camelToDashCase, createCustomEvent} from './utils';
|
||||
import {createCustomEvent, getComponentInputs, getDefaultAttributeToPropertyInputs} from './utils';
|
||||
|
||||
/**
|
||||
* Class constructor based on an Angular Component to be used for custom element registration.
|
||||
@ -21,7 +21,7 @@ import {camelToDashCase, createCustomEvent} from './utils';
|
||||
export interface NgElementConstructor<P> {
|
||||
readonly observedAttributes: string[];
|
||||
|
||||
new (): NgElement&WithProperties<P>;
|
||||
new (injector: Injector): NgElement&WithProperties<P>;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -50,30 +50,19 @@ export type WithProperties<P> = {
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialization configuration for the NgElementConstructor. Provides the strategy factory
|
||||
* that produces a strategy for each instantiated element. Additionally, provides a function
|
||||
* that takes the component factory and provides a map of which attributes should be observed on
|
||||
* the element and which property they are associated with.
|
||||
* Initialization configuration for the NgElementConstructor which contains the injector to be used
|
||||
* for retrieving the component's factory as well as the default context for the component. May
|
||||
* provide a custom strategy factory to be used instead of the default. May provide a custom mapping
|
||||
* of attribute names to component inputs.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export interface NgElementConfig {
|
||||
injector: Injector;
|
||||
strategyFactory?: NgElementStrategyFactory;
|
||||
propertyInputs?: string[];
|
||||
attributeToPropertyInputs?: {[key: string]: string};
|
||||
}
|
||||
|
||||
/** Gets a map of default set of attributes to observe and the properties they affect. */
|
||||
function getDefaultAttributeToPropertyInputs(inputs: {propName: string, templateName: string}[]) {
|
||||
const attributeToPropertyInputs: {[key: string]: string} = {};
|
||||
inputs.forEach(({propName, templateName}) => {
|
||||
attributeToPropertyInputs[camelToDashCase(templateName)] = propName;
|
||||
});
|
||||
|
||||
return attributeToPropertyInputs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @whatItDoes Creates a custom element class based on an Angular Component. Takes a configuration
|
||||
* that provides initialization information to the created class. E.g. the configuration's injector
|
||||
@ -90,13 +79,10 @@ function getDefaultAttributeToPropertyInputs(inputs: {propName: string, template
|
||||
*/
|
||||
export function createNgElementConstructor<P>(
|
||||
component: Type<any>, config: NgElementConfig): NgElementConstructor<P> {
|
||||
const componentFactoryResolver =
|
||||
config.injector.get(ComponentFactoryResolver) as ComponentFactoryResolver;
|
||||
const componentFactory = componentFactoryResolver.resolveComponentFactory(component);
|
||||
const inputs = componentFactory.inputs;
|
||||
const inputs = getComponentInputs(component, config.injector);
|
||||
|
||||
const defaultStrategyFactory = config.strategyFactory ||
|
||||
new ComponentFactoryNgElementStrategyFactory(componentFactory, config.injector);
|
||||
const strategyFactory =
|
||||
config.strategyFactory || new ComponentNgElementStrategyFactory(component, config.injector);
|
||||
|
||||
const attributeToPropertyInputs =
|
||||
config.attributeToPropertyInputs || getDefaultAttributeToPropertyInputs(inputs);
|
||||
@ -104,13 +90,9 @@ export function createNgElementConstructor<P>(
|
||||
class NgElementImpl extends NgElement {
|
||||
static readonly observedAttributes = Object.keys(attributeToPropertyInputs);
|
||||
|
||||
constructor(strategyFactoryOverride?: NgElementStrategyFactory) {
|
||||
constructor(injector?: Injector) {
|
||||
super();
|
||||
|
||||
// Use the constructor's strategy factory override if it is present, otherwise default to
|
||||
// the config's factory.
|
||||
const strategyFactory = strategyFactoryOverride || defaultStrategyFactory;
|
||||
this.ngElementStrategy = strategyFactory.create();
|
||||
this.ngElementStrategy = strategyFactory.create(injector || config.injector);
|
||||
}
|
||||
|
||||
attributeChangedCallback(
|
||||
@ -120,14 +102,6 @@ export function createNgElementConstructor<P>(
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
// Take element attribute inputs and set them as inputs on the strategy
|
||||
NgElementImpl.observedAttributes.forEach(attrName => {
|
||||
const propName = attributeToPropertyInputs[attrName] !;
|
||||
if (this.hasAttribute(attrName)) {
|
||||
this.ngElementStrategy.setInputValue(propName, this.getAttribute(attrName) !);
|
||||
}
|
||||
});
|
||||
|
||||
this.ngElementStrategy.connect(this);
|
||||
|
||||
// Listen for events from the strategy and dispatch them as custom events
|
||||
@ -149,8 +123,7 @@ export function createNgElementConstructor<P>(
|
||||
|
||||
// Add getters and setters to the prototype for each property input. If the config does not
|
||||
// contain property inputs, use all inputs by default.
|
||||
const propertyInputs = config.propertyInputs || inputs.map(({propName}) => propName);
|
||||
propertyInputs.forEach(property => {
|
||||
inputs.map(({propName}) => propName).forEach(property => {
|
||||
Object.defineProperty(NgElementImpl.prototype, property, {
|
||||
get: function() { return this.ngElementStrategy.getInputValue(property); },
|
||||
set: function(newValue: any) { this.ngElementStrategy.setInputValue(property, newValue); },
|
||||
|
@ -5,8 +5,7 @@
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Type} from '@angular/core';
|
||||
import {ComponentFactoryResolver, Injector, Type} from '@angular/core';
|
||||
|
||||
const elProto = Element.prototype as any;
|
||||
const matches = elProto.matches || elProto.matchesSelector || elProto.mozMatchesSelector ||
|
||||
@ -101,3 +100,25 @@ export function matchesSelector(element: Element, selector: string): boolean {
|
||||
export function strictEquals(value1: any, value2: any): boolean {
|
||||
return value1 === value2 || (value1 !== value1 && value2 !== value2);
|
||||
}
|
||||
|
||||
/** Gets a map of default set of attributes to observe and the properties they affect. */
|
||||
export function getDefaultAttributeToPropertyInputs(
|
||||
inputs: {propName: string, templateName: string}[]) {
|
||||
const attributeToPropertyInputs: {[key: string]: string} = {};
|
||||
inputs.forEach(({propName, templateName}) => {
|
||||
attributeToPropertyInputs[camelToDashCase(templateName)] = propName;
|
||||
});
|
||||
|
||||
return attributeToPropertyInputs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a component's set of inputs. Uses the injector to get the component factory where the inputs
|
||||
* are defined.
|
||||
*/
|
||||
export function getComponentInputs(
|
||||
component: Type<any>, injector: Injector): {propName: string, templateName: string}[] {
|
||||
const componentFactoryResolver: ComponentFactoryResolver = injector.get(ComponentFactoryResolver);
|
||||
const componentFactory = componentFactoryResolver.resolveComponentFactory(component);
|
||||
return componentFactory.inputs;
|
||||
}
|
||||
|
Reference in New Issue
Block a user