diff --git a/apps/demo/src/app/app.component.html b/apps/demo/src/app/app.component.html
index bae81dda..9764fe9c 100644
--- a/apps/demo/src/app/app.component.html
+++ b/apps/demo/src/app/app.component.html
@@ -9,6 +9,8 @@
Getting up and running...
+
+
Lorem, ipsum dolor sit amet consectetur adipisicing elit. Autem laboriosam id ad mollitia optio saepe qui aliquid
diff --git a/apps/demo/src/app/app.component.ts b/apps/demo/src/app/app.component.ts
index 28934762..a9d86a57 100644
--- a/apps/demo/src/app/app.component.ts
+++ b/apps/demo/src/app/app.component.ts
@@ -6,7 +6,9 @@ import {
Selection,
DropdownMenuItemType,
IDropdownOption,
+ ICheckboxProps,
} from 'office-ui-fabric-react';
+import { RenderPropOptions } from '@angular-react/core';
import { FabDropdownComponent } from '@angular-react/fabric';
const suffix = ' cm';
@@ -18,6 +20,13 @@ const suffix = ' cm';
encapsulation: ViewEncapsulation.None,
})
export class AppComponent {
+ renderCheckboxLabel: RenderPropOptions = {
+ getProps: defaultProps => ({
+ ...defaultProps,
+ label: defaultProps.label.toUpperCase(),
+ }),
+ };
+
@ViewChild('customRange') customRangeTemplate: TemplateRef<{
item: any;
dismissMenu: (ev?: any, dismissAll?: boolean) => void;
diff --git a/libs/core/src/lib/components/render-props.ts b/libs/core/src/lib/components/render-props.ts
new file mode 100644
index 00000000..70f2c1d8
--- /dev/null
+++ b/libs/core/src/lib/components/render-props.ts
@@ -0,0 +1,121 @@
+import { ComponentFactoryResolver, Type, Injector, TemplateRef, ComponentRef, NgZone } from '@angular/core';
+import {
+ RenderPropContext,
+ createTemplateRenderer,
+ createComponentRenderer,
+ createHtmlRenderer,
+ isRenderPropContext,
+} from '../renderer/renderprop-helpers';
+import { ReactContentProps } from '../renderer/react-content';
+
+export type JsxRenderFunc = (context: TContext) => JSX.Element;
+
+/**
+ * Render props options for creating & rendering a component.
+ */
+export interface RenderComponentOptions {
+ readonly componentType: Type;
+ readonly factoryResolver: ComponentFactoryResolver;
+ readonly injector: Injector;
+}
+
+function isRenderComponentOptions(x: unknown): x is RenderComponentOptions {
+ if (typeof x !== 'object') {
+ return false;
+ }
+
+ const maybeRenderComponentOptions = x as RenderComponentOptions;
+ return (
+ maybeRenderComponentOptions.componentType != null &&
+ maybeRenderComponentOptions.factoryResolver != null &&
+ maybeRenderComponentOptions.injector != null
+ );
+}
+
+/**
+ * Allow intercepting and modifying the default props, which are then used by the default renderer.
+ */
+export interface RenderPropOptions {
+ readonly getProps: (defaultProps?: TContext) => TContext;
+}
+
+function isRenderPropOptions(x: unknown): x is RenderPropOptions {
+ if (typeof x !== 'object') {
+ return false;
+ }
+
+ const maybeRenderPropOptions = x as RenderPropOptions;
+ return maybeRenderPropOptions.getProps && typeof maybeRenderPropOptions.getProps === 'function';
+}
+
+/**
+ * Various options for passing renderers as render props.
+ */
+export type InputRendererOptions =
+ | TemplateRef
+ | ((context: TContext) => HTMLElement)
+ | ComponentRef
+ | RenderComponentOptions
+ | RenderPropContext
+ | RenderPropOptions;
+
+export function createInputJsxRenderer(
+ input: InputRendererOptions,
+ ngZone: NgZone,
+ additionalProps?: ReactContentProps
+): JsxRenderFunc | undefined {
+ if (input instanceof TemplateRef) {
+ const templateRenderer = createTemplateRenderer(input, ngZone, additionalProps);
+ return (context: TContext) => templateRenderer.render(context);
+ }
+
+ if (input instanceof ComponentRef) {
+ const componentRenderer = createComponentRenderer(input, additionalProps);
+ return (context: TContext) => componentRenderer.render(context);
+ }
+
+ if (input instanceof Function) {
+ const htmlRenderer = createHtmlRenderer(input, additionalProps);
+ return (context: TContext) => htmlRenderer.render(context);
+ }
+
+ if (isRenderComponentOptions(input)) {
+ const { componentType, factoryResolver, injector } = input;
+ const componentFactory = factoryResolver.resolveComponentFactory(componentType);
+ const componentRef = componentFactory.create(injector);
+
+ // Call the function again with the created ComponentRef
+ return createInputJsxRenderer(componentRef, ngZone, additionalProps);
+ }
+}
+
+export function createRenderPropHandler(
+ renderInputValue: InputRendererOptions,
+ ngZone: NgZone,
+ options?: {
+ jsxRenderer?: JsxRenderFunc;
+ additionalProps?: ReactContentProps;
+ }
+): (props?: TProps, defaultRender?: JsxRenderFunc) => JSX.Element | null {
+ if (isRenderPropContext(renderInputValue)) {
+ return renderInputValue.render;
+ }
+
+ if (isRenderPropOptions(renderInputValue)) {
+ return (props?: TProps, defaultRender?: JsxRenderFunc) => {
+ return typeof defaultRender === 'function' ? defaultRender(renderInputValue.getProps(props)) : null;
+ };
+ }
+
+ const renderer =
+ (options && options.jsxRenderer) ||
+ createInputJsxRenderer(renderInputValue, ngZone, options && options.additionalProps);
+
+ return (props?: TProps, defaultRender?: JsxRenderFunc) => {
+ if (!renderInputValue) {
+ return typeof defaultRender === 'function' ? defaultRender(props) : null;
+ }
+
+ return renderer(props);
+ };
+}
diff --git a/libs/core/src/lib/components/wrapper-component.ts b/libs/core/src/lib/components/wrapper-component.ts
index 8e05de0d..bc97cf1c 100644
--- a/libs/core/src/lib/components/wrapper-component.ts
+++ b/libs/core/src/lib/components/wrapper-component.ts
@@ -5,50 +5,31 @@
import {
AfterViewInit,
ChangeDetectorRef,
- ComponentFactoryResolver,
- ComponentRef,
ElementRef,
- Injector,
Input,
NgZone,
OnChanges,
Renderer2,
SimpleChanges,
- TemplateRef,
- Type,
AfterContentInit,
} from '@angular/core';
import classnames from 'classnames';
import toStyle from 'css-to-style';
import stylenames, { StyleObject } from 'stylenames';
+
import { Many } from '../declarations/many';
import { ReactContentProps } from '../renderer/react-content';
import { isReactNode } from '../renderer/react-node';
import { isReactRendererData } from '../renderer/renderer';
-import { createComponentRenderer, createHtmlRenderer, createTemplateRenderer } from '../renderer/renderprop-helpers';
import { toObject } from '../utils/object/to-object';
import { afterRenderFinished } from '../utils/render/render-delay';
-import { unreachable } from '../utils/types/unreachable';
+import { InputRendererOptions, JsxRenderFunc, createInputJsxRenderer, createRenderPropHandler } from './render-props';
// Forbidden attributes are still ignored, since they may be set from the wrapper components themselves (forbidden is only applied for users of the wrapper components)
const ignoredAttributeMatchers = [/^_?ng-?.*/, /^style$/, /^class$/];
const ngClassRegExp = /^ng-/;
-export interface RenderComponentOptions {
- readonly componentType: Type;
- readonly factoryResolver: ComponentFactoryResolver;
- readonly injector: Injector;
-}
-
-export type InputRendererOptions =
- | TemplateRef
- | ((context: TContext) => HTMLElement)
- | ComponentRef
- | RenderComponentOptions;
-
-export type JsxRenderFunc = (context: TContext) => JSX.Element;
-
export type ContentClassValue = string[] | Set | { [klass: string]: any };
export type ContentStyleValue = string | StyleObject;
@@ -186,7 +167,7 @@ export abstract class ReactWrapperComponent implements AfterC
/**
* Create an JSX renderer for an `@Input` property.
- * @param input The input property
+ * @param input The input property.
* @param additionalProps optional additional props to pass to the `ReactContent` object that will render the content.
*/
protected createInputJsxRenderer(
@@ -201,31 +182,7 @@ export abstract class ReactWrapperComponent implements AfterC
throw new Error('To create an input JSX renderer you must pass an NgZone to the constructor.');
}
- if (input instanceof TemplateRef) {
- const templateRenderer = createTemplateRenderer(input, this._ngZone, additionalProps);
- return (context: TContext) => templateRenderer.render(context);
- }
-
- if (input instanceof ComponentRef) {
- const componentRenderer = createComponentRenderer(input, additionalProps);
- return (context: TContext) => componentRenderer.render(context);
- }
-
- if (input instanceof Function) {
- const htmlRenderer = createHtmlRenderer(input, additionalProps);
- return (context: TContext) => htmlRenderer.render(context);
- }
-
- if (typeof input === 'object') {
- const { componentType, factoryResolver, injector } = input;
- const componentFactory = factoryResolver.resolveComponentFactory(componentType);
- const componentRef = componentFactory.create(injector);
-
- // Call the function again with the created ComponentRef
- return this.createInputJsxRenderer(componentRef, additionalProps);
- }
-
- unreachable(input);
+ return createInputJsxRenderer(input, this._ngZone, additionalProps);
}
/**
@@ -234,24 +191,14 @@ export abstract class ReactWrapperComponent implements AfterC
* @param jsxRenderer an optional renderer to use.
* @param additionalProps optional additional props to pass to the `ReactContent` object that will render the content.
*/
- protected createRenderPropHandler(
- renderInputValue: InputRendererOptions,
+ protected createRenderPropHandler(
+ renderInputValue: InputRendererOptions,
options?: {
- jsxRenderer?: JsxRenderFunc;
+ jsxRenderer?: JsxRenderFunc;
additionalProps?: ReactContentProps;
}
- ): (props?: TProps, defaultRender?: JsxRenderFunc) => JSX.Element | null {
- const renderer =
- (options && options.jsxRenderer) ||
- this.createInputJsxRenderer(renderInputValue, options && options.additionalProps);
-
- return (props?: TProps, defaultRender?: JsxRenderFunc) => {
- if (!renderInputValue) {
- return typeof defaultRender === 'function' ? defaultRender(props) : null;
- }
-
- return renderer(props);
- };
+ ): (props?: TRenderProps, defaultRender?: JsxRenderFunc) => JSX.Element | null {
+ return createRenderPropHandler(renderInputValue, this._ngZone, options);
}
private _passAttributesAsProps() {
@@ -300,7 +247,7 @@ export abstract class ReactWrapperComponent implements AfterC
}
private _setHostDisplay() {
- const nativeElement: HTMLElement = this.elementRef.nativeElement;
+ const nativeElement = this.elementRef.nativeElement;
// We want to wait until child elements are rendered
afterRenderFinished(() => {
diff --git a/libs/core/src/lib/renderer/renderprop-helpers.ts b/libs/core/src/lib/renderer/renderprop-helpers.ts
index ac069387..f2bab2bc 100644
--- a/libs/core/src/lib/renderer/renderprop-helpers.ts
+++ b/libs/core/src/lib/renderer/renderprop-helpers.ts
@@ -9,6 +9,15 @@ export interface RenderPropContext {
readonly render: (context: TContext) => JSX.Element;
}
+export function isRenderPropContext(x: unknown): x is RenderPropContext {
+ if (typeof x !== 'object') {
+ return false;
+ }
+
+ const maybeRenderPropContext = x as RenderPropContext;
+ return maybeRenderPropContext.render && typeof maybeRenderPropContext.render === 'function';
+}
+
function renderReactContent(rootNodes: HTMLElement[], additionalProps?: ReactContentProps): JSX.Element {
return createReactContentElement(rootNodes, additionalProps);
}
diff --git a/libs/core/src/lib/utils/render/render-delay.ts b/libs/core/src/lib/utils/render/render-delay.ts
index 69481e4c..e77936f0 100644
--- a/libs/core/src/lib/utils/render/render-delay.ts
+++ b/libs/core/src/lib/utils/render/render-delay.ts
@@ -1,6 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
+/**
+ * Delays the execution of a function to be after the next render.
+ *
+ * @param callback The function to execute
+ */
export const afterRenderFinished = (callback: Function) => {
setTimeout(callback, 0);
};
diff --git a/libs/core/src/public-api.ts b/libs/core/src/public-api.ts
index a5387bde..63b74187 100644
--- a/libs/core/src/public-api.ts
+++ b/libs/core/src/public-api.ts
@@ -9,3 +9,9 @@ export { getPassProps, passProp, PassProp } from './lib/renderer/pass-prop-decor
export { createReactContentElement, ReactContent, ReactContentProps } from './lib/renderer/react-content';
export * from './lib/renderer/react-template';
export { registerElement } from './lib/renderer/registry';
+export {
+ JsxRenderFunc,
+ RenderComponentOptions,
+ InputRendererOptions,
+ RenderPropOptions,
+} from './lib/components/render-props';