Skip to content

Commit 299e4b4

Browse files
authored
Merge pull request #4 from Microsoft/bugfix/inner-react-render-props-not-render
React-wrapped components inside inside ng-template don't re-render on changes
2 parents 6b91617 + 7088423 commit 299e4b4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+186
-357
lines changed

apps/demo/src/app/counter/counter.component.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
1+
import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/core';
22

33
@Component({
44
selector: 'counter',
55
template: `
6-
<div>{{ count }}</div>
7-
<button (click)="onClick()">+</button>
6+
<fab-default-button [text]="count + '+'" (onClick)="onClick()"></fab-default-button>
87
`,
98
changeDetection: ChangeDetectionStrategy.OnPush,
109
})

libs/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "../../node_modules/ng-packagr/package.schema.json",
33
"name": "@angular-react/core",
4-
"version": "0.3.1",
4+
"version": "0.4.0",
55
"ngPackage": {
66
"deleteDestPath": true,
77
"whitelistedNonPeerDependencies": [

libs/core/src/lib/components/wrapper-component.ts

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,10 @@
1-
import {
2-
AfterViewInit,
3-
ChangeDetectorRef,
4-
ComponentFactoryResolver,
5-
ComponentRef,
6-
ElementRef,
7-
Injector,
8-
Input,
9-
OnChanges,
10-
SimpleChanges,
11-
TemplateRef,
12-
Type,
13-
} from '@angular/core';
1+
import { AfterViewInit, ChangeDetectorRef, ComponentFactoryResolver, ComponentRef, ElementRef, Injector, Input, OnChanges, Renderer2, SimpleChanges, TemplateRef, Type } from '@angular/core';
142
import toStyle from 'css-to-style';
153
import { ReactContentProps } from '../renderer/react-content';
164
import { isReactNode } from '../renderer/react-node';
5+
import { isReactRendererData } from '../renderer/renderer';
176
import { renderComponent, renderFunc, renderTemplate } from '../renderer/renderprop-helpers';
7+
import { afterRenderFinished } from '../utils/render/render-delay';
188
import { unreachable } from '../utils/types/unreachable';
199

2010
// 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)
@@ -93,6 +83,7 @@ export abstract class ReactWrapperComponent<TProps extends {}> implements AfterV
9383
constructor(
9484
public readonly elementRef: ElementRef,
9585
private readonly changeDetectorRef: ChangeDetectorRef,
86+
private readonly renderer: Renderer2,
9687
private readonly setHostDisplay: boolean = false
9788
) {}
9889

@@ -102,6 +93,19 @@ export abstract class ReactWrapperComponent<TProps extends {}> implements AfterV
10293
if (this.setHostDisplay) {
10394
this._setHostDisplay();
10495
}
96+
97+
// NOTE: Workaround/fix for Issue #5 (https://github.com/Microsoft/angular-react/issues/5).
98+
// The wrapper component isn't added to the root react nodes list when it's inside a `ReactContent` node, we manually add it (note that the root nodes list is a `Set`, so it won't duplicate nodes if already exist).
99+
// There's potentially a better solution instead of this
100+
const rendererData = this.renderer.data;
101+
if (isReactRendererData(rendererData)) {
102+
afterRenderFinished(() => {
103+
const nativeElement = this.reactNodeRef.nativeElement;
104+
if (isReactNode(nativeElement)) {
105+
rendererData.addRootNode(nativeElement);
106+
}
107+
});
108+
}
105109
}
106110

107111
ngOnChanges(changes: SimpleChanges) {
@@ -165,10 +169,14 @@ export abstract class ReactWrapperComponent<TProps extends {}> implements AfterV
165169
*/
166170
protected createRenderPropHandler<TProps extends object>(
167171
renderInputValue: InputRendererOptions<TProps>,
168-
jsxRenderer?: JsxRenderFunc<TProps>,
169-
additionalProps?: ReactContentProps
172+
options?: {
173+
jsxRenderer?: JsxRenderFunc<TProps>;
174+
additionalProps?: ReactContentProps;
175+
}
170176
): (props?: TProps, defaultRender?: JsxRenderFunc<TProps>) => JSX.Element | null {
171-
const renderer = jsxRenderer || this.createInputJsxRenderer(renderInputValue, additionalProps);
177+
const renderer =
178+
(options && options.jsxRenderer) ||
179+
this.createInputJsxRenderer(renderInputValue, options && options.additionalProps);
172180

173181
return (props?: TProps, defaultRender?: JsxRenderFunc<TProps>) => {
174182
if (!renderInputValue) {

libs/core/src/lib/renderer/renderer.ts

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export class AngularReactRendererFactory extends ɵDomRendererFactory2 {
1616
// React elements cannot be "inserted" and later have their props
1717
// updated, so the "insert", or React.Render, can only be done once the
1818
// element has been fully defined. Only the topmost [root] nodes are added here.
19-
public reactRootNodes: Array<ReactNode> = [];
19+
public reactRootNodes: Set<ReactNode> = new Set();
2020

2121
private isRenderPending: boolean;
2222
// This flag can only be set to true from outside. It can only be reset
@@ -56,29 +56,39 @@ export class AngularReactRendererFactory extends ɵDomRendererFactory2 {
5656
// are ReadOnly.
5757

5858
// Workaround for ReactNodes inside ReactContent being added to the root of the VDOM and not removed from the VDOM when unmounted from the DOM.
59-
for (let i = 0; i < this.reactRootNodes.length; i++) {
60-
const node = this.reactRootNodes[i];
59+
this.reactRootNodes.forEach(node => {
6160
if (
6261
!isReactNode(node.parent) &&
6362
!document.body.contains(node.parent) &&
6463
ReactDOM.unmountComponentAtNode(node.parent)
6564
) {
66-
this.reactRootNodes.splice(i--, 1);
65+
this.reactRootNodes.delete(node);
6766
}
68-
}
67+
});
6968

7069
if (this.isRenderPending) {
7170
// Remove root nodes that are pending destroy after render.
72-
this.reactRootNodes = this.reactRootNodes.filter(node => !node.render().destroyPending);
71+
this.reactRootNodes = new Set(Array.from(this.reactRootNodes).filter(node => !node.render().destroyPending));
7372
this.isRenderPending = false;
7473
}
7574
}
7675
}
7776

78-
class ReactRenderer implements Renderer2 {
79-
readonly data: StringMap<any> = Object.create(null);
77+
export const isReactRendererData = (data: StringMap): data is ReactRendererData =>
78+
data && typeof (data as ReactRendererData).addRootNode === 'function';
79+
80+
export interface ReactRendererData {
81+
readonly addRootNode: (node: ReactNode) => void;
82+
}
83+
84+
export class ReactRenderer implements Renderer2 {
85+
readonly data: ReactRendererData = {
86+
addRootNode: (node: ReactNode) => {
87+
this.rootRenderer.reactRootNodes.add(node);
88+
},
89+
};
8090

81-
constructor(private rootRenderer: AngularReactRendererFactory) {}
91+
constructor(public readonly rootRenderer: AngularReactRendererFactory) {}
8292

8393
destroy(): void {}
8494

@@ -128,7 +138,8 @@ class ReactRenderer implements Renderer2 {
128138
console.warn('Renderer > appendChild > asDOM > parent:', parent.toString(), 'node:', node.toString());
129139
}
130140
node.setRenderPendingCallback = this.rootRenderer.setRenderPendingCallback;
131-
this.rootRenderer.reactRootNodes.push(node);
141+
142+
this.rootRenderer.reactRootNodes.add(node);
132143
node.parent = parent;
133144
return;
134145
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const afterRenderFinished = (callback: Function) => {
2+
setTimeout(callback, 0);
3+
};

libs/fabric/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "../../node_modules/ng-packagr/package.schema.json",
33
"name": "@angular-react/fabric",
4-
"version": "0.3.1",
4+
"version": "0.4.0",
55
"ngPackage": {
66
"lib": {
77
"entryFile": "public-api.ts",
@@ -70,7 +70,7 @@
7070
],
7171
"private": false,
7272
"peerDependencies": {
73-
"@angular-react/core": "^0.3.0",
73+
"@angular-react/core": "^0.4.0",
7474
"@angular/common": "^6.1.0",
7575
"@angular/core": "^6.1.0",
7676
"@angular/platform-browser-dynamic": "^6.1.0",

libs/fabric/src/lib/components/breadcrumb/breadcrumb.component.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,5 @@
11
import { InputRendererOptions, JsxRenderFunc, ReactWrapperComponent } from '@angular-react/core';
2-
import {
3-
ChangeDetectionStrategy,
4-
Component,
5-
ElementRef,
6-
Input,
7-
OnInit,
8-
ViewChild,
9-
ChangeDetectorRef,
10-
} from '@angular/core';
2+
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, Input, OnInit, Renderer2, ViewChild } from '@angular/core';
113
import { IBreadcrumbItem, IBreadcrumbProps } from 'office-ui-fabric-react/lib/Breadcrumb';
124

135
@Component({
@@ -66,8 +58,8 @@ export class FabBreadcrumbComponent extends ReactWrapperComponent<IBreadcrumbPro
6658

6759
onRenderItem: (props?: IBreadcrumbItem, defaultRender?: JsxRenderFunc<IBreadcrumbItem>) => JSX.Element;
6860

69-
constructor(elementRef: ElementRef, changeDetectorRef: ChangeDetectorRef) {
70-
super(elementRef, changeDetectorRef);
61+
constructor(elementRef: ElementRef, changeDetectorRef: ChangeDetectorRef, renderer: Renderer2) {
62+
super(elementRef, changeDetectorRef, renderer);
7163
}
7264

7365
ngOnInit() {

libs/fabric/src/lib/components/button/action-button.component.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, ViewChild } from '@angular/core';
1+
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, Renderer2, ViewChild } from '@angular/core';
22
import { FabBaseButtonComponent } from './base-button.component';
33

44
@Component({
@@ -54,7 +54,7 @@ export class FabActionButtonComponent extends FabBaseButtonComponent {
5454
@ViewChild('reactNode')
5555
reactNodeRef: ElementRef;
5656

57-
constructor(elementRef: ElementRef, changeDetectorRef: ChangeDetectorRef) {
58-
super(elementRef, changeDetectorRef);
57+
constructor(elementRef: ElementRef, changeDetectorRef: ChangeDetectorRef, renderer: Renderer2) {
58+
super(elementRef, changeDetectorRef, renderer);
5959
}
6060
}

libs/fabric/src/lib/components/button/base-button.component.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { InputRendererOptions, JsxRenderFunc, ReactWrapperComponent } from '@angular-react/core';
2-
import { ChangeDetectorRef, ElementRef, EventEmitter, Input, OnInit, Output } from '@angular/core';
2+
import { ChangeDetectorRef, ElementRef, EventEmitter, Input, OnInit, Output, Renderer2 } from '@angular/core';
33
import { IButtonProps } from 'office-ui-fabric-react/lib/Button';
44

55
export abstract class FabBaseButtonComponent extends ReactWrapperComponent<IButtonProps> implements OnInit {
@@ -87,8 +87,8 @@ export abstract class FabBaseButtonComponent extends ReactWrapperComponent<IButt
8787
onRenderChildren: (props?: IButtonProps, defaultRender?: JsxRenderFunc<IButtonProps>) => JSX.Element;
8888
onRenderMenuIcon: (props?: IButtonProps, defaultRender?: JsxRenderFunc<IButtonProps>) => JSX.Element;
8989

90-
constructor(elementRef: ElementRef, changeDetectorRef: ChangeDetectorRef) {
91-
super(elementRef, changeDetectorRef, true);
90+
constructor(elementRef: ElementRef, changeDetectorRef: ChangeDetectorRef, renderer: Renderer2) {
91+
super(elementRef, changeDetectorRef, renderer, true);
9292

9393
// coming from React context - we need to bind to this so we can access the Angular Component properties
9494
this.onMenuClickHandler = this.onMenuClickHandler.bind(this);

libs/fabric/src/lib/components/button/command-bar-button.component.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, ViewChild } from '@angular/core';
1+
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, Renderer2, ViewChild } from '@angular/core';
22
import { FabBaseButtonComponent } from './base-button.component';
33

44
@Component({
@@ -54,7 +54,7 @@ export class FabCommandBarButtonComponent extends FabBaseButtonComponent {
5454
@ViewChild('reactNode')
5555
reactNodeRef: ElementRef;
5656

57-
constructor(elementRef: ElementRef, changeDetectorRef: ChangeDetectorRef) {
58-
super(elementRef, changeDetectorRef);
57+
constructor(elementRef: ElementRef, changeDetectorRef: ChangeDetectorRef, renderer: Renderer2) {
58+
super(elementRef, changeDetectorRef, renderer);
5959
}
6060
}

0 commit comments

Comments
 (0)