refactor(ivy): split up directiveCreate for tree shaking (#22838)
PR Close #22838
This commit is contained in:
parent
1612985e48
commit
e27cfd6236
|
@ -12,8 +12,8 @@ import {Injector} from '../di/injector';
|
||||||
import {ComponentRef as viewEngine_ComponentRef} from '../linker/component_factory';
|
import {ComponentRef as viewEngine_ComponentRef} from '../linker/component_factory';
|
||||||
|
|
||||||
import {assertNotNull} from './assert';
|
import {assertNotNull} from './assert';
|
||||||
import {queueLifecycleHooks} from './hooks';
|
import {queueInitHooks, queueLifecycleHooks} from './hooks';
|
||||||
import {CLEAN_PROMISE, _getComponentHostLElementNode, createLView, createTView, directiveCreate, enterView, getDirectiveInstance, getRootView, hostElement, initChangeDetectorIfExisting, locateHostElement, renderComponentOrTemplate} from './instructions';
|
import {CLEAN_PROMISE, _getComponentHostLElementNode, baseDirectiveCreate, createLView, createTView, enterView, getRootView, hostElement, initChangeDetectorIfExisting, locateHostElement, renderComponentOrTemplate} from './instructions';
|
||||||
import {ComponentDef, ComponentType} from './interfaces/definition';
|
import {ComponentDef, ComponentType} from './interfaces/definition';
|
||||||
import {LElementNode} from './interfaces/node';
|
import {LElementNode} from './interfaces/node';
|
||||||
import {RElement, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
|
import {RElement, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
|
||||||
|
@ -22,6 +22,7 @@ import {stringify} from './util';
|
||||||
import {createViewRef} from './view_ref';
|
import {createViewRef} from './view_ref';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/** Options that control how the component should be bootstrapped. */
|
/** Options that control how the component should be bootstrapped. */
|
||||||
export interface CreateComponentOptions {
|
export interface CreateComponentOptions {
|
||||||
/** Which renderer factory to use. */
|
/** Which renderer factory to use. */
|
||||||
|
@ -135,7 +136,7 @@ export function renderComponent<T>(
|
||||||
// Create element node at index 0 in data array
|
// Create element node at index 0 in data array
|
||||||
elementNode = hostElement(hostNode, componentDef);
|
elementNode = hostElement(hostNode, componentDef);
|
||||||
// Create directive instance with n() and store at index 1 in data array (el is 0)
|
// Create directive instance with n() and store at index 1 in data array (el is 0)
|
||||||
component = rootContext.component = directiveCreate(1, componentDef.factory(), componentDef) as T;
|
component = rootContext.component = baseDirectiveCreate(1, componentDef.factory(), componentDef) as T;
|
||||||
initChangeDetectorIfExisting(elementNode.nodeInjector, component);
|
initChangeDetectorIfExisting(elementNode.nodeInjector, component);
|
||||||
} finally {
|
} finally {
|
||||||
// We must not use leaveView here because it will set creationMode to false too early,
|
// We must not use leaveView here because it will set creationMode to false too early,
|
||||||
|
@ -164,6 +165,9 @@ export function renderComponent<T>(
|
||||||
*/
|
*/
|
||||||
export function LifecycleHooksFeature(component: any, def: ComponentDef<any>): void {
|
export function LifecycleHooksFeature(component: any, def: ComponentDef<any>): void {
|
||||||
const elementNode = _getComponentHostLElementNode(component);
|
const elementNode = _getComponentHostLElementNode(component);
|
||||||
|
|
||||||
|
// Root component is always created at dir index 1, after host element at 0
|
||||||
|
queueInitHooks(1, def.onInit, def.doCheck, elementNode.view.tView);
|
||||||
queueLifecycleHooks(elementNode.flags, elementNode.view);
|
queueLifecycleHooks(elementNode.flags, elementNode.view);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1096,6 +1096,34 @@ export function textBinding<T>(index: number, value: T | NO_CHANGE): void {
|
||||||
export function directiveCreate<T>(
|
export function directiveCreate<T>(
|
||||||
index: number, directive: T, directiveDef: DirectiveDef<T>,
|
index: number, directive: T, directiveDef: DirectiveDef<T>,
|
||||||
localNames?: (string | number)[] | null): T {
|
localNames?: (string | number)[] | null): T {
|
||||||
|
const instance = baseDirectiveCreate(index, directive, directiveDef);
|
||||||
|
|
||||||
|
ngDevMode && assertNotNull(previousOrParentNode.tNode, 'previousOrParentNode.tNode');
|
||||||
|
const tNode: TNode|null = previousOrParentNode.tNode !;
|
||||||
|
|
||||||
|
if (currentView.tView.firstTemplatePass && localNames) {
|
||||||
|
tNode.localNames = tNode.localNames ? tNode.localNames.concat(localNames) : localNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tNode && tNode.attrs) {
|
||||||
|
setInputsFromAttrs<T>(instance, directiveDef !.inputs, tNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init hooks are queued now so ngOnInit is called in host components before
|
||||||
|
// any projected components.
|
||||||
|
queueInitHooks(index, directiveDef.onInit, directiveDef.doCheck, currentView.tView);
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A lighter version of directiveCreate() that is used for the root component
|
||||||
|
*
|
||||||
|
* This version does not contain features that we don't already support at root in
|
||||||
|
* current Angular. Example: local refs and inputs on root component.
|
||||||
|
*/
|
||||||
|
export function baseDirectiveCreate<T>(
|
||||||
|
index: number, directive: T, directiveDef: DirectiveDef<T>): T {
|
||||||
let instance;
|
let instance;
|
||||||
ngDevMode &&
|
ngDevMode &&
|
||||||
assertNull(currentView.bindingStartIndex, 'directives should be created before any bindings');
|
assertNull(currentView.bindingStartIndex, 'directives should be created before any bindings');
|
||||||
|
@ -1117,11 +1145,6 @@ export function directiveCreate<T>(
|
||||||
|
|
||||||
if (index >= tData.length) {
|
if (index >= tData.length) {
|
||||||
tData[index] = directiveDef !;
|
tData[index] = directiveDef !;
|
||||||
if (localNames) {
|
|
||||||
ngDevMode && assertNotNull(previousOrParentNode.tNode, 'previousOrParentNode.tNode');
|
|
||||||
const tNode = previousOrParentNode !.tNode !;
|
|
||||||
tNode.localNames = tNode.localNames ? tNode.localNames.concat(localNames) : localNames;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const diPublic = directiveDef !.diPublic;
|
const diPublic = directiveDef !.diPublic;
|
||||||
|
@ -1135,15 +1158,6 @@ export function directiveCreate<T>(
|
||||||
(previousOrParentNode as LElementNode).native, directiveDef !.attributes as string[]);
|
(previousOrParentNode as LElementNode).native, directiveDef !.attributes as string[]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const tNode: TNode|null = previousOrParentNode.tNode !;
|
|
||||||
if (tNode && tNode.attrs) {
|
|
||||||
setInputsFromAttrs<T>(instance, directiveDef !.inputs, tNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init hooks are queued now so ngOnInit is called in host components before
|
|
||||||
// any projected components.
|
|
||||||
queueInitHooks(index, directiveDef.onInit, directiveDef.doCheck, currentView.tView);
|
|
||||||
|
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,9 @@
|
||||||
{
|
{
|
||||||
"name": "appendChild"
|
"name": "appendChild"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "baseDirectiveCreate"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "bindingUpdated"
|
"name": "bindingUpdated"
|
||||||
},
|
},
|
||||||
|
@ -74,9 +77,6 @@
|
||||||
{
|
{
|
||||||
"name": "detectChangesInternal"
|
"name": "detectChangesInternal"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "directiveCreate"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "domRendererFactory3"
|
"name": "domRendererFactory3"
|
||||||
},
|
},
|
||||||
|
@ -98,9 +98,6 @@
|
||||||
{
|
{
|
||||||
"name": "findNextRNodeSibling"
|
"name": "findNextRNodeSibling"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "generateInitialInputs"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "getDirectiveInstance"
|
"name": "getDirectiveInstance"
|
||||||
},
|
},
|
||||||
|
@ -146,9 +143,6 @@
|
||||||
{
|
{
|
||||||
"name": "locateHostElement"
|
"name": "locateHostElement"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "queueInitHooks"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "refreshChildComponents"
|
"name": "refreshChildComponents"
|
||||||
},
|
},
|
||||||
|
@ -179,9 +173,6 @@
|
||||||
{
|
{
|
||||||
"name": "setHostBindings"
|
"name": "setHostBindings"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "setInputsFromAttrs"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "setUpAttributes"
|
"name": "setUpAttributes"
|
||||||
},
|
},
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {withBody} from '@angular/core/testing';
|
||||||
|
|
||||||
import {ChangeDetectionStrategy, ChangeDetectorRef, DoCheck} from '../../src/core';
|
import {ChangeDetectionStrategy, ChangeDetectorRef, DoCheck} from '../../src/core';
|
||||||
import {getRenderedText, whenRendered} from '../../src/render3/component';
|
import {getRenderedText, whenRendered} from '../../src/render3/component';
|
||||||
import {defineComponent, defineDirective, injectChangeDetectorRef} from '../../src/render3/index';
|
import {LifecycleHooksFeature, defineComponent, defineDirective, injectChangeDetectorRef} from '../../src/render3/index';
|
||||||
import {bind, container, containerRefreshEnd, containerRefreshStart, detectChanges, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation2, listener, markDirty, text, textBinding, tick} from '../../src/render3/instructions';
|
import {bind, container, containerRefreshEnd, containerRefreshStart, detectChanges, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation2, listener, markDirty, text, textBinding, tick} from '../../src/render3/instructions';
|
||||||
|
|
||||||
import {containerEl, renderComponent, requestAnimationFrame} from './render_util';
|
import {containerEl, renderComponent, requestAnimationFrame} from './render_util';
|
||||||
|
@ -39,7 +39,7 @@ describe('change detection', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
it('should mark a component dirty and schedule change detection', withBody('my-comp', () => {
|
it('should mark a component dirty and schedule change detection', withBody('my-comp', () => {
|
||||||
const myComp = renderComponent(MyComponent);
|
const myComp = renderComponent(MyComponent, {hostFeatures: [LifecycleHooksFeature]});
|
||||||
expect(getRenderedText(myComp)).toEqual('works');
|
expect(getRenderedText(myComp)).toEqual('works');
|
||||||
myComp.value = 'updated';
|
myComp.value = 'updated';
|
||||||
markDirty(myComp);
|
markDirty(myComp);
|
||||||
|
@ -49,7 +49,7 @@ describe('change detection', () => {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should detectChanges on a component', withBody('my-comp', () => {
|
it('should detectChanges on a component', withBody('my-comp', () => {
|
||||||
const myComp = renderComponent(MyComponent);
|
const myComp = renderComponent(MyComponent, {hostFeatures: [LifecycleHooksFeature]});
|
||||||
expect(getRenderedText(myComp)).toEqual('works');
|
expect(getRenderedText(myComp)).toEqual('works');
|
||||||
myComp.value = 'updated';
|
myComp.value = 'updated';
|
||||||
detectChanges(myComp);
|
detectChanges(myComp);
|
||||||
|
@ -58,7 +58,7 @@ describe('change detection', () => {
|
||||||
|
|
||||||
it('should detectChanges only once if markDirty is called multiple times',
|
it('should detectChanges only once if markDirty is called multiple times',
|
||||||
withBody('my-comp', () => {
|
withBody('my-comp', () => {
|
||||||
const myComp = renderComponent(MyComponent);
|
const myComp = renderComponent(MyComponent, {hostFeatures: [LifecycleHooksFeature]});
|
||||||
expect(getRenderedText(myComp)).toEqual('works');
|
expect(getRenderedText(myComp)).toEqual('works');
|
||||||
expect(myComp.doCheckCount).toBe(1);
|
expect(myComp.doCheckCount).toBe(1);
|
||||||
myComp.value = 'ignore';
|
myComp.value = 'ignore';
|
||||||
|
@ -72,7 +72,7 @@ describe('change detection', () => {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should notify whenRendered', withBody('my-comp', async() => {
|
it('should notify whenRendered', withBody('my-comp', async() => {
|
||||||
const myComp = renderComponent(MyComponent);
|
const myComp = renderComponent(MyComponent, {hostFeatures: [LifecycleHooksFeature]});
|
||||||
await whenRendered(myComp);
|
await whenRendered(myComp);
|
||||||
myComp.value = 'updated';
|
myComp.value = 'updated';
|
||||||
markDirty(myComp);
|
markDirty(myComp);
|
||||||
|
@ -347,7 +347,7 @@ describe('change detection', () => {
|
||||||
|
|
||||||
it('should check the component view when called by component (even when OnPush && clean)',
|
it('should check the component view when called by component (even when OnPush && clean)',
|
||||||
() => {
|
() => {
|
||||||
const comp = renderComponent(MyComp);
|
const comp = renderComponent(MyComp, {hostFeatures: [LifecycleHooksFeature]});
|
||||||
expect(getRenderedText(comp)).toEqual('Nancy');
|
expect(getRenderedText(comp)).toEqual('Nancy');
|
||||||
|
|
||||||
comp.name = 'Bess'; // as this is not an Input, the component stays clean
|
comp.name = 'Bess'; // as this is not an Input, the component stays clean
|
||||||
|
@ -356,7 +356,7 @@ describe('change detection', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should NOT call component doCheck when called by a component', () => {
|
it('should NOT call component doCheck when called by a component', () => {
|
||||||
const comp = renderComponent(MyComp);
|
const comp = renderComponent(MyComp, {hostFeatures: [LifecycleHooksFeature]});
|
||||||
expect(comp.doCheckCount).toEqual(1);
|
expect(comp.doCheckCount).toEqual(1);
|
||||||
|
|
||||||
// NOTE: in current Angular, detectChanges does not itself trigger doCheck, but you
|
// NOTE: in current Angular, detectChanges does not itself trigger doCheck, but you
|
||||||
|
@ -367,7 +367,7 @@ describe('change detection', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should NOT check the component parent when called by a child component', () => {
|
it('should NOT check the component parent when called by a child component', () => {
|
||||||
const parentComp = renderComponent(ParentComp);
|
const parentComp = renderComponent(ParentComp, {hostFeatures: [LifecycleHooksFeature]});
|
||||||
expect(getRenderedText(parentComp)).toEqual('1 - Nancy');
|
expect(getRenderedText(parentComp)).toEqual('1 - Nancy');
|
||||||
|
|
||||||
parentComp.doCheckCount = 100;
|
parentComp.doCheckCount = 100;
|
||||||
|
@ -378,7 +378,7 @@ describe('change detection', () => {
|
||||||
|
|
||||||
it('should check component children when called by component if dirty or check-always',
|
it('should check component children when called by component if dirty or check-always',
|
||||||
() => {
|
() => {
|
||||||
const parentComp = renderComponent(ParentComp);
|
const parentComp = renderComponent(ParentComp, {hostFeatures: [LifecycleHooksFeature]});
|
||||||
expect(parentComp.doCheckCount).toEqual(1);
|
expect(parentComp.doCheckCount).toEqual(1);
|
||||||
|
|
||||||
myComp.name = 'Bess';
|
myComp.name = 'Bess';
|
||||||
|
@ -390,7 +390,7 @@ describe('change detection', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not group detectChanges calls (call every time)', () => {
|
it('should not group detectChanges calls (call every time)', () => {
|
||||||
const parentComp = renderComponent(ParentComp);
|
const parentComp = renderComponent(ParentComp, {hostFeatures: [LifecycleHooksFeature]});
|
||||||
expect(myComp.doCheckCount).toEqual(1);
|
expect(myComp.doCheckCount).toEqual(1);
|
||||||
|
|
||||||
parentComp.cdr.detectChanges();
|
parentComp.cdr.detectChanges();
|
||||||
|
@ -524,7 +524,7 @@ describe('change detection', () => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const comp = renderComponent(DetectChangesComp);
|
const comp = renderComponent(DetectChangesComp, {hostFeatures: [LifecycleHooksFeature]});
|
||||||
expect(getRenderedText(comp)).toEqual('1');
|
expect(getRenderedText(comp)).toEqual('1');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -553,7 +553,7 @@ describe('change detection', () => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const comp = renderComponent(DetectChangesComp);
|
const comp = renderComponent(DetectChangesComp, {hostFeatures: [LifecycleHooksFeature]});
|
||||||
expect(getRenderedText(comp)).toEqual('1');
|
expect(getRenderedText(comp)).toEqual('1');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -940,7 +940,7 @@ describe('change detection', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
it('should throw if bindings in current view have changed', () => {
|
it('should throw if bindings in current view have changed', () => {
|
||||||
const comp = renderComponent(NoChangesComp);
|
const comp = renderComponent(NoChangesComp, {hostFeatures: [LifecycleHooksFeature]});
|
||||||
|
|
||||||
expect(() => comp.cdr.checkNoChanges()).not.toThrow();
|
expect(() => comp.cdr.checkNoChanges()).not.toThrow();
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
import {defineComponent, defineDirective} from '../../src/render3/index';
|
import {defineComponent, defineDirective} from '../../src/render3/index';
|
||||||
import {NO_CHANGE, bind, container, containerRefreshEnd, containerRefreshStart, elementAttribute, elementClassNamed, elementEnd, elementProperty, elementStart, elementStyleNamed, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV, load, projection, projectionDef, text, textBinding} from '../../src/render3/instructions';
|
import {NO_CHANGE, bind, container, containerRefreshEnd, containerRefreshStart, elementAttribute, elementClassNamed, elementEnd, elementProperty, elementStart, elementStyleNamed, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV, load, projection, projectionDef, text, textBinding} from '../../src/render3/instructions';
|
||||||
|
|
||||||
import {containerEl, renderToHtml} from './render_util';
|
import {ComponentFixture, containerEl, renderToHtml} from './render_util';
|
||||||
|
|
||||||
describe('render3 integration test', () => {
|
describe('render3 integration test', () => {
|
||||||
|
|
||||||
|
@ -269,6 +269,21 @@ describe('render3 integration test', () => {
|
||||||
expect(renderToHtml(Template, {})).toEqual('<todo title="two">two</todo>');
|
expect(renderToHtml(Template, {})).toEqual('<todo title="two">two</todo>');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should support root component with host attribute', () => {
|
||||||
|
class HostAttributeComp {
|
||||||
|
static ngComponentDef = defineComponent({
|
||||||
|
type: HostAttributeComp,
|
||||||
|
tag: 'host-attr-comp',
|
||||||
|
factory: () => new HostAttributeComp(),
|
||||||
|
template: (ctx: HostAttributeComp, cm: boolean) => {},
|
||||||
|
attributes: ['role', 'button']
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const fixture = new ComponentFixture(HostAttributeComp);
|
||||||
|
expect(fixture.hostElement.getAttribute('role')).toEqual('button');
|
||||||
|
});
|
||||||
|
|
||||||
it('should support component with bindings in template', () => {
|
it('should support component with bindings in template', () => {
|
||||||
/** <p> {{ name }} </p>*/
|
/** <p> {{ name }} </p>*/
|
||||||
class MyComp {
|
class MyComp {
|
||||||
|
|
Loading…
Reference in New Issue