diff --git a/integration/_payload-limits.json b/integration/_payload-limits.json index 0a359e573a..0ab5e1b809 100644 --- a/integration/_payload-limits.json +++ b/integration/_payload-limits.json @@ -18,7 +18,7 @@ "hello_world__render3__closure": { "master": { "uncompressed": { - "bundle": 6671 + "bundle": 7065 } } }, diff --git a/packages/core/src/render3/definition.ts b/packages/core/src/render3/definition.ts index 3616b5e81e..8a174ebfb1 100644 --- a/packages/core/src/render3/definition.ts +++ b/packages/core/src/render3/definition.ts @@ -6,13 +6,15 @@ * found in the LICENSE file at https://angular.io/license */ +import {SimpleChange} from '../change_detection/change_detection_util'; +import {OnChanges, SimpleChanges} from '../metadata/lifecycle_hooks'; import {RendererType2} from '../render/api'; import {Type} from '../type'; import {resolveRendererType2} from '../view/util'; import {diPublic} from './di'; import {componentRefresh} from './instructions'; -import {ComponentDef, ComponentDefArgs, DirectiveDef, DirectiveDefArgs} from './interfaces/definition'; +import {ComponentDef, ComponentDefArgs, DirectiveDef, DirectiveDefArgs, TypedDirectiveDef} from './interfaces/definition'; @@ -54,8 +56,57 @@ export function defineComponent(componentDefinition: ComponentDefArgs): Co return def; } -export function NgOnChangesFeature(definition: DirectiveDef) { - // TODO: implement. See: https://app.asana.com/0/443577627818617/465170715764659 + +const PRIVATE_PREFIX = '__ngOnChanges_'; + +type OnChangesExpando = OnChanges & { + __ngOnChanges_: SimpleChanges|null|undefined; + [key: string]: any; +}; + +export function NgOnChangesFeature(type: Type): (definition: DirectiveDef) => void { + return function(definition: DirectiveDef): void { + const inputs = definition.inputs; + const proto = type.prototype; + // Place where we will store SimpleChanges if there is a change + Object.defineProperty(proto, PRIVATE_PREFIX, {value: undefined, writable: true}); + for (let pubKey in inputs) { + const minKey = inputs[pubKey]; + const privateMinKey = PRIVATE_PREFIX + minKey; + // Create a place where the actual value will be stored and make it non-enumerable + Object.defineProperty(proto, privateMinKey, {value: undefined, writable: true}); + + const existingDesc = Object.getOwnPropertyDescriptor(proto, minKey); + + // create a getter and setter for property + Object.defineProperty(proto, minKey, { + get: function(this: OnChangesExpando) { + return (existingDesc && existingDesc.get) ? existingDesc.get.call(this) : + this[privateMinKey]; + }, + set: function(this: OnChangesExpando, value: any) { + let simpleChanges = this[PRIVATE_PREFIX]; + let isFirstChange = simpleChanges === undefined; + if (simpleChanges == null) { + simpleChanges = this[PRIVATE_PREFIX] = {}; + } + simpleChanges[pubKey] = new SimpleChange(this[privateMinKey], value, isFirstChange); + (existingDesc && existingDesc.set) ? existingDesc.set.call(this, value) : + this[privateMinKey] = value; + } + }); + } + proto.ngDoCheck = (function(delegateDoCheck) { + return function(this: OnChangesExpando) { + let simpleChanges = this[PRIVATE_PREFIX]; + if (simpleChanges != null) { + this.ngOnChanges(simpleChanges); + this[PRIVATE_PREFIX] = null; + } + delegateDoCheck && delegateDoCheck.apply(this); + }; + })(proto.ngDoCheck); + }; } export function PublicFeature(definition: DirectiveDef) { diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index 65c669e160..5686dc3bb4 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -17,12 +17,16 @@ import {ViewContainerRef as viewEngine_ViewContainerRef} from '../linker/view_co import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, ViewRef as viewEngine_ViewRef} from '../linker/view_ref'; import {Type} from '../type'; -import {assertPreviousIsParent, getPreviousOrParentNode} from './instructions'; +import {assertLessThan} from './assert'; +import {assertPreviousIsParent, getPreviousOrParentNode, getRenderer, renderEmbeddedTemplate} from './instructions'; import {ComponentTemplate, DirectiveDef, TypedDirectiveDef} from './interfaces/definition'; import {LInjector} from './interfaces/injector'; -import {LContainerNode, LElementNode, LNode, LNodeFlags} from './interfaces/node'; +import {LContainerNode, LElementNode, LNode, LNodeFlags, LViewNode} from './interfaces/node'; import {QueryReadType} from './interfaces/query'; +import {Renderer3} from './interfaces/renderer'; +import {LView} from './interfaces/view'; import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert'; +import {insertView} from './node_manipulation'; import {notImplemented, stringify} from './util'; @@ -189,8 +193,8 @@ export function diPublic(def: TypedDirectiveDef): void { * @param flags Injection flags (e.g. CheckParent) * @returns The instance found */ -export function inject(token: Type, flags?: InjectFlags): T { - return getOrCreateInjectable(getOrCreateNodeInjector(), token, flags); +export function inject(token: Type, flags?: InjectFlags, defaultValue?: T): T { + return getOrCreateInjectable(getOrCreateNodeInjector(), token, flags, defaultValue); } /** @@ -240,7 +244,8 @@ export function injectViewContainerRef(): viewEngine_ViewContainerRef { * @param flags Injection flags (e.g. CheckParent) * @returns The instance found */ -export function getOrCreateInjectable(di: LInjector, token: Type, flags?: InjectFlags): T { +export function getOrCreateInjectable( + di: LInjector, token: Type, flags?: InjectFlags, defaultValue?: T): T { const bloomHash = bloomHashBit(token); // If the token has a bloom hash, then it is a directive that is public to the injection system @@ -248,6 +253,9 @@ export function getOrCreateInjectable(di: LInjector, token: Type, flags?: if (bloomHash === null) { const moduleInjector = di.injector; if (!moduleInjector) { + if (defaultValue != null) { + return defaultValue; + } throw createInjectionError('NotFound', token); } moduleInjector.get(token); @@ -418,31 +426,6 @@ class ElementRef implements viewEngine_ElementRef { constructor(nativeElement: any) { this.nativeElement = nativeElement; } } -/** - * Creates a TemplateRef and stores it on the injector. Or, if the TemplateRef already - * exists, retrieves the existing TemplateRef. - * - * @param di The node injector where we should store a created TemplateRef - * @returns The TemplateRef instance to use - */ -export function getOrCreateTemplateRef(di: LInjector): viewEngine_TemplateRef { - ngDevMode && assertNodeType(di.node, LNodeFlags.Container); - const data = (di.node as LContainerNode).data; - return di.templateRef || - (di.templateRef = new TemplateRef(getOrCreateElementRef(di), data.template)); -} - -/** A ref to a particular template. */ -class TemplateRef implements viewEngine_TemplateRef { - readonly elementRef: viewEngine_ElementRef; - - constructor(elementRef: viewEngine_ElementRef, template: ComponentTemplate|null) { - this.elementRef = elementRef; - } - - createEmbeddedView(context: T): viewEngine_EmbeddedViewRef { throw notImplemented(); } -} - /** * Creates a ViewContainerRef and stores it on the injector. Or, if the ViewContainerRef * already exists, retrieves the existing ViewContainerRef. @@ -463,7 +446,7 @@ class ViewContainerRef implements viewEngine_ViewContainerRef { injector: Injector; parentInjector: Injector; - constructor(node: LContainerNode) {} + constructor(private _node: LContainerNode) {} clear(): void { throw notImplemented(); } get(index: number): viewEngine_ViewRef|null { throw notImplemented(); } @@ -471,7 +454,9 @@ class ViewContainerRef implements viewEngine_ViewContainerRef { createEmbeddedView( templateRef: viewEngine_TemplateRef, context?: C|undefined, index?: number|undefined): viewEngine_EmbeddedViewRef { - throw notImplemented(); + const viewRef = templateRef.createEmbeddedView(context !); + this.insert(viewRef, index); + return viewRef; } createComponent( componentFactory: viewEngine_ComponentFactory, index?: number|undefined, @@ -480,7 +465,29 @@ class ViewContainerRef implements viewEngine_ViewContainerRef { throw notImplemented(); } insert(viewRef: viewEngine_ViewRef, index?: number|undefined): viewEngine_ViewRef { - throw notImplemented(); + if (index == null) { + index = this._node.data.views.length; + } else { + // +1 because it's legal to insert at the end. + ngDevMode && assertLessThan(index, this._node.data.views.length + 1, 'index'); + } + const lView = (viewRef as EmbeddedViewRef)._lViewNode; + insertView(this._node, lView, index); + + // If the view is dynamic (has a template), it needs to be counted both at the container + // level and at the node above the container. + if (lView.data.template !== null) { + // Increment the container view count. + this._node.data.dynamicViewCount++; + + // Look for the parent node and increment its dynamic view count. + if (this._node.parent !== null && this._node.parent.data !== null) { + ngDevMode && + assertNodeOfPossibleTypes(this._node.parent, LNodeFlags.View, LNodeFlags.Element); + this._node.parent.data.dynamicViewCount++; + } + } + return viewRef; } move(viewRef: viewEngine_ViewRef, currentIndex: number): viewEngine_ViewRef { throw notImplemented(); @@ -489,3 +496,57 @@ class ViewContainerRef implements viewEngine_ViewContainerRef { remove(index?: number|undefined): void { throw notImplemented(); } detach(index?: number|undefined): viewEngine_ViewRef|null { throw notImplemented(); } } + +/** + * Creates a TemplateRef and stores it on the injector. Or, if the TemplateRef already + * exists, retrieves the existing TemplateRef. + * + * @param di The node injector where we should store a created TemplateRef + * @returns The TemplateRef instance to use + */ +export function getOrCreateTemplateRef(di: LInjector): viewEngine_TemplateRef { + ngDevMode && assertNodeType(di.node, LNodeFlags.Container); + const data = (di.node as LContainerNode).data; + return di.templateRef || (di.templateRef = new TemplateRef( + getOrCreateElementRef(di), data.template !, getRenderer())); +} + +class TemplateRef implements viewEngine_TemplateRef { + readonly elementRef: viewEngine_ElementRef; + private _template: ComponentTemplate; + + constructor( + elementRef: viewEngine_ElementRef, template: ComponentTemplate, + private _renderer: Renderer3) { + this.elementRef = elementRef; + this._template = template; + } + + createEmbeddedView(context: T): viewEngine_EmbeddedViewRef { + let viewNode: LViewNode = renderEmbeddedTemplate(null, this._template, context, this._renderer); + return new EmbeddedViewRef(viewNode, this._template, context); + } +} + +class EmbeddedViewRef implements viewEngine_EmbeddedViewRef { + context: T; + rootNodes: any[]; + /** + * @internal + */ + _lViewNode: LViewNode; + + constructor(viewNode: LViewNode, template: ComponentTemplate, context: T) { + this._lViewNode = viewNode; + this.context = context; + } + + destroy(): void { notImplemented(); } + destroyed: boolean; + onDestroy(callback: Function) { notImplemented(); } + markForCheck(): void { notImplemented(); } + detach(): void { notImplemented(); } + detectChanges(): void { notImplemented(); } + checkNoChanges(): void { notImplemented(); } + reattach(): void { notImplemented(); } +} diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index c2f0dadaf4..bde29d5c5f 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -21,7 +21,7 @@ import {LQuery, QueryReadType} from './interfaces/query'; import {LView, TData, TView} from './interfaces/view'; import {LContainerNode, LElementNode, LNode, LNodeFlags, LProjectionNode, LTextNode, LViewNode, TNode, TContainerNode, InitialInputData, InitialInputs, PropertyAliases, PropertyAliasValue,} from './interfaces/node'; -import {assertNodeType} from './node_assert'; +import {assertNodeType, assertNodeOfPossibleTypes} from './node_assert'; import {appendChild, insertChild, insertView, processProjectedNode, removeView} from './node_manipulation'; import {isNodeMatchingSelector} from './node_selector_matcher'; import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveType, TypedDirectiveDef, TypedComponentDef} from './interfaces/definition'; @@ -173,7 +173,9 @@ export function leaveView(newView: LView): void { enterView(newView, null); } -export function createLView(viewId: number, renderer: Renderer3, tView: TView): LView { +export function createLView( + viewId: number, renderer: Renderer3, tView: TView, + template: ComponentTemplate| null = null, context: any | null = null): LView { const newView = { parent: currentView, id: viewId, // -1 for component views @@ -187,7 +189,10 @@ export function createLView(viewId: number, renderer: Renderer3, tView: TView): next: null, bindingStartIndex: null, creationMode: true, - viewHookStartIndex: null + viewHookStartIndex: null, + template: template, + context: context, + dynamicViewCount: 0, }; return newView; @@ -305,6 +310,32 @@ export function renderTemplate( return host; } +export function renderEmbeddedTemplate( + viewNode: LViewNode | null, template: ComponentTemplate, context: T, + renderer: Renderer3): LViewNode { + const _isParent = isParent; + const _previousOrParentNode = previousOrParentNode; + try { + isParent = true; + previousOrParentNode = null !; + let cm: boolean = false; + if (viewNode == null) { + const view = createLView(-1, renderer, {data: []}, template, context); + viewNode = createLNode(null, LNodeFlags.View, null, view); + cm = true; + } + enterView(viewNode.data, viewNode); + + template(context, cm); + } finally { + refreshDynamicChildren(); + leaveView(currentView !.parent !); + isParent = _isParent; + previousOrParentNode = _previousOrParentNode; + } + return viewNode; +} + export function renderComponentOrTemplate( node: LElementNode, hostView: LView, componentOrContext: T, template?: ComponentTemplate) { const oldView = enterView(hostView, node); @@ -1045,7 +1076,8 @@ export function container( nextIndex: 0, renderParent, template: template == null ? null : template, next: null, - parent: currentView + parent: currentView, + dynamicViewCount: 0, }); if (node.tNode == null) { @@ -1101,6 +1133,18 @@ export function containerRefreshEnd(): void { } } +function refreshDynamicChildren() { + for (let current = currentView.child; current !== null; current = current.next) { + if (current.dynamicViewCount !== 0 && (current as LContainer).views) { + const container = current as LContainer; + for (let i = 0; i < container.views.length; i++) { + const view = container.views[i]; + renderEmbeddedTemplate(view, view.data.template !, view.data.context !, renderer); + } + } + } +} + /** * Creates an LViewNode. * @@ -1160,22 +1204,25 @@ export function viewEnd(): void { isParent = false; const viewNode = previousOrParentNode = currentView.node as LViewNode; const container = previousOrParentNode.parent as LContainerNode; - ngDevMode && assertNodeType(viewNode, LNodeFlags.View); - ngDevMode && assertNodeType(container, LNodeFlags.Container); - const lContainer = container.data; - const previousView = lContainer.nextIndex <= lContainer.views.length ? - lContainer.views[lContainer.nextIndex - 1] as LViewNode : - null; - const viewIdChanged = previousView == null ? true : previousView.data.id !== viewNode.data.id; + if (container) { + ngDevMode && assertNodeType(viewNode, LNodeFlags.View); + ngDevMode && assertNodeType(container, LNodeFlags.Container); + const containerState = container.data; + const previousView = containerState.nextIndex <= containerState.views.length ? + containerState.views[containerState.nextIndex - 1] as LViewNode : + null; + const viewIdChanged = previousView == null ? true : previousView.data.id !== viewNode.data.id; - if (viewIdChanged) { - insertView(container, viewNode, lContainer.nextIndex - 1); - currentView.creationMode = false; + if (viewIdChanged) { + insertView(container, viewNode, containerState.nextIndex - 1); + currentView.creationMode = false; + } } leaveView(currentView !.parent !); ngDevMode && assertEqual(isParent, false, 'isParent'); ngDevMode && assertNodeType(previousOrParentNode, LNodeFlags.View); } + ///////////// /** @@ -1193,7 +1240,7 @@ export const componentRefresh: directiveIndex: number, elementIndex: number, template: ComponentTemplate) { ngDevMode && assertDataInRange(elementIndex); const element = data ![elementIndex] as LElementNode; - ngDevMode && assertNodeType(element, LNodeFlags.Element); + ngDevMode && assertNodeOfPossibleTypes(element, LNodeFlags.Element, LNodeFlags.Container); ngDevMode && assertNotEqual(element.data, null, 'isComponent'); ngDevMode && assertDataInRange(directiveIndex); const hostView = element.data !; @@ -1204,6 +1251,7 @@ export const componentRefresh: template(directive, creationMode); } finally { hostView.creationMode = false; + refreshDynamicChildren(); leaveView(oldView); } }; @@ -1827,6 +1875,10 @@ export function getPreviousOrParentNode(): LNode { return previousOrParentNode; } +export function getRenderer(): Renderer3 { + return renderer; +} + export function assertPreviousIsParent() { assertEqual(isParent, true, 'isParent'); } diff --git a/packages/core/src/render3/interfaces/container.ts b/packages/core/src/render3/interfaces/container.ts index 956e7f472e..fb6d2eecc2 100644 --- a/packages/core/src/render3/interfaces/container.ts +++ b/packages/core/src/render3/interfaces/container.ts @@ -66,6 +66,13 @@ export interface LContainer { * The template extracted from the location of the Container. */ readonly template: ComponentTemplate|null; + + + /** + * A count of dynamic views rendered into this container. If this is non-zero, the `views` array + * will be traversed when refreshing dynamic views on this container. + */ + dynamicViewCount: number; } /** diff --git a/packages/core/src/render3/interfaces/definition.ts b/packages/core/src/render3/interfaces/definition.ts index f6c3359b0b..1b2547a0f4 100644 --- a/packages/core/src/render3/interfaces/definition.ts +++ b/packages/core/src/render3/interfaces/definition.ts @@ -16,7 +16,6 @@ import {resolveRendererType2} from '../../view/util'; export type ComponentTemplate = { (ctx: T, creationMode: boolean): void; ngPrivateData?: never; }; -export type EmbeddedTemplate = (ctx: T) => void; export interface ComponentType extends Type { ngComponentDef: ComponentDef; } diff --git a/packages/core/src/render3/interfaces/view.ts b/packages/core/src/render3/interfaces/view.ts index 6db78fcd02..2cf567e487 100644 --- a/packages/core/src/render3/interfaces/view.ts +++ b/packages/core/src/render3/interfaces/view.ts @@ -7,7 +7,7 @@ */ import {LContainer} from './container'; -import {DirectiveDef} from './definition'; +import {ComponentTemplate, DirectiveDef} from './definition'; import {LElementNode, LViewNode, TNode} from './node'; import {Renderer3} from './renderer'; @@ -138,6 +138,24 @@ export interface LView { * directive defs are stored). */ tView: TView; + + /** + * For dynamically inserted views, the template function to refresh the view. + */ + template: ComponentTemplate<{}>|null; + + /** + * For embedded views, the context with which to render the template. + */ + context: {}|null; + + /** + * A count of dynamic views that are children of this view (indirectly via containers). + * + * This is used to decide whether to scan children of this view when refreshing dynamic views + * after refreshing the view itself. + */ + dynamicViewCount: number; } /** Interface necessary to work with view tree traversal */ diff --git a/packages/core/test/render3/BUILD.bazel b/packages/core/test/render3/BUILD.bazel index cb62372f78..79b074e729 100644 --- a/packages/core/test/render3/BUILD.bazel +++ b/packages/core/test/render3/BUILD.bazel @@ -22,6 +22,7 @@ ts_library( "//packages/animations", "//packages/animations/browser", "//packages/animations/browser/testing", + "//packages/common", "//packages/core", "//packages/platform-browser", "//packages/platform-browser/animations", diff --git a/packages/core/test/render3/common_integration_spec.ts b/packages/core/test/render3/common_integration_spec.ts new file mode 100644 index 0000000000..2be5b561e0 --- /dev/null +++ b/packages/core/test/render3/common_integration_spec.ts @@ -0,0 +1,56 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * 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 {NgForOfContext} from '@angular/common'; + +import {C, E, T, b, cR, cr, defineComponent, e, p, t} from '../../src/render3/index'; + +import {NgForOf} from './common_with_def'; +import {renderComponent, toHtml} from './render_util'; + +describe('@angular/common integration', () => { + describe('NgForOf', () => { + it('should update a loop', () => { + class MyApp { + items: string[] = ['first', 'second']; + + static ngComponentDef = defineComponent({ + factory: () => new MyApp(), + tag: 'my-app', + //
    + //
  • {{item}}
  • + //
+ template: (myApp: MyApp, cm: boolean) => { + if (cm) { + E(0, 'ul'); + { C(1, [NgForOf], liTemplate); } + e(); + } + p(1, 'ngForOf', b(myApp.items)); + cR(1); + NgForOf.ngDirectiveDef.r(2, 0); + cr(); + + function liTemplate(row: NgForOfContext, cm: boolean) { + if (cm) { + E(0, 'li'); + { T(1); } + e(); + } + t(1, b(row.$implicit)); + } + } + }); + } + + const myApp = renderComponent(MyApp); + expect(toHtml(myApp)).toEqual('
  • first
  • second
'); + }); + // TODO: Test inheritance + }); +}); \ No newline at end of file diff --git a/packages/core/test/render3/common_with_def.ts b/packages/core/test/render3/common_with_def.ts new file mode 100644 index 0000000000..d07803c8e4 --- /dev/null +++ b/packages/core/test/render3/common_with_def.ts @@ -0,0 +1,30 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * 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 {NgForOf as NgForOfDef} from '@angular/common'; +import {IterableDiffers} from '@angular/core'; + +import {defaultIterableDiffers} from '../../src/change_detection/change_detection'; +import {DirectiveType, InjectFlags, NgOnChangesFeature, defineDirective, inject, injectTemplateRef, injectViewContainerRef, m} from '../../src/render3/index'; + +export const NgForOf: DirectiveType> = NgForOfDef as any; + +NgForOf.ngDirectiveDef = defineDirective({ + factory: () => new NgForOfDef( + injectViewContainerRef(), injectTemplateRef(), + inject(IterableDiffers, InjectFlags.Default, defaultIterableDiffers)), + features: [NgOnChangesFeature(NgForOf)], + refresh: (directiveIndex: number, elementIndex: number) => { + m>(directiveIndex).ngDoCheck(); + }, + inputs: { + ngForOf: 'ngForOf', + ngForTrackBy: 'ngForTrackBy', + ngForTemplate: 'ngForTemplate', + } +}); diff --git a/packages/core/test/render3/define_spec.ts b/packages/core/test/render3/define_spec.ts new file mode 100644 index 0000000000..2d9e0e821a --- /dev/null +++ b/packages/core/test/render3/define_spec.ts @@ -0,0 +1,51 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * 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 {DoCheck, OnChanges, SimpleChanges} from '../../src/core'; +import {NgOnChangesFeature, defineDirective} from '../../src/render3/index'; + +describe('define', () => { + describe('component', () => { + describe('NgOnChangesFeature', () => { + it('should patch class', () => { + class MyDirective implements OnChanges, DoCheck { + public log: string[] = []; + public valA: string = 'initValue'; + public set valB(value: string) { this.log.push(value); } + + public get valB() { return 'works'; } + + ngDoCheck(): void { this.log.push('ngDoCheck'); } + ngOnChanges(changes: SimpleChanges): void { + this.log.push('ngOnChanges'); + this.log.push('valA', changes['valA'].previousValue, changes['valA'].currentValue); + this.log.push('valB', changes['valB'].previousValue, changes['valB'].currentValue); + } + + static ngDirectiveDef = defineDirective({ + factory: () => new MyDirective(), + features: [NgOnChangesFeature(MyDirective)], + inputs: {valA: 'valA', valB: 'valB'} + }); + } + + const myDir = MyDirective.ngDirectiveDef.n(); + myDir.valA = 'first'; + expect(myDir.valA).toEqual('first'); + myDir.valB = 'second'; + expect(myDir.log).toEqual(['second']); + expect(myDir.valB).toEqual('works'); + myDir.log.length = 0; + myDir.ngDoCheck(); + expect(myDir.log).toEqual([ + 'ngOnChanges', 'valA', 'initValue', 'first', 'valB', undefined, 'second', 'ngDoCheck' + ]); + }); + }); + }); +}); \ No newline at end of file diff --git a/packages/core/test/render3/di_spec.ts b/packages/core/test/render3/di_spec.ts index 62ffbaa44b..cd2b7b89d5 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -8,13 +8,14 @@ import {ElementRef, TemplateRef, ViewContainerRef} from '@angular/core'; -import {bloomAdd, bloomFindPossibleInjector, getOrCreateNodeInjector} from '../../src/render3/di'; +import {defineComponent} from '../../src/render3/definition'; +import {InjectFlags, bloomAdd, bloomFindPossibleInjector, getOrCreateNodeInjector} from '../../src/render3/di'; import {C, E, PublicFeature, T, V, b, b2, cR, cr, defineDirective, e, inject, injectElementRef, injectTemplateRef, injectViewContainerRef, m, t, v} from '../../src/render3/index'; import {createLNode, createLView, enterView, leaveView} from '../../src/render3/instructions'; import {LInjector} from '../../src/render3/interfaces/injector'; import {LNodeFlags} from '../../src/render3/interfaces/node'; -import {renderToHtml} from './render_util'; +import {renderComponent, renderToHtml} from './render_util'; describe('di', () => { describe('no dependencies', () => { @@ -217,6 +218,23 @@ describe('di', () => { }); }); + describe('flags', () => { + it('should return defaultValue not found', () => { + class MyApp { + constructor(public value: string) {} + + static ngComponentDef = defineComponent({ + // type: MyApp, + tag: 'my-app', + factory: () => new MyApp(inject(String as any, InjectFlags.Default, 'DefaultValue')), + template: () => null + }); + } + const myApp = renderComponent(MyApp); + expect(myApp.value).toEqual('DefaultValue'); + }); + }); + it('should inject from parent view', () => { class ParentDirective { static ngDirectiveDef = diff --git a/packages/core/test/render3/view_container_ref_spec.ts b/packages/core/test/render3/view_container_ref_spec.ts new file mode 100644 index 0000000000..33be3d439e --- /dev/null +++ b/packages/core/test/render3/view_container_ref_spec.ts @@ -0,0 +1,56 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * 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 {TemplateRef, ViewContainerRef} from '../../src/core'; +import {C, T, b, cR, cr, defineComponent, defineDirective, injectTemplateRef, injectViewContainerRef, m, t} from '../../src/render3/index'; + +import {renderComponent, toHtml} from './render_util'; + +describe('ViewContainerRef', () => { + class TestDirective { + constructor(public viewContainer: ViewContainerRef, public template: TemplateRef, ) {} + + static ngDirectiveDef = defineDirective({ + factory: () => new TestDirective(injectViewContainerRef(), injectTemplateRef(), ), + }); + } + + class TestComponent { + testDir: TestDirective; + + static ngComponentDef = defineComponent({ + tag: 'test-cmp', + factory: () => new TestComponent(), + template: (cmp: TestComponent, cm: boolean) => { + if (cm) { + const subTemplate = (ctx: any, cm: boolean) => { + if (cm) { + T(0); + } + t(0, b(ctx.$implicit)); + }; + C(0, [TestDirective], subTemplate); + } + cR(0); + cmp.testDir = m(1) as TestDirective; + TestDirective.ngDirectiveDef.r(1, 0); + cr(); + }, + }); + } + + + it('should add embedded view into container', () => { + const testCmp = renderComponent(TestComponent); + expect(toHtml(testCmp)).toEqual(''); + const dir = testCmp.testDir; + const childCtx = {$implicit: 'works'}; + const viewRef = dir.viewContainer.createEmbeddedView(dir.template, childCtx); + expect(toHtml(testCmp)).toEqual('works'); + }); +});