diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index fedee6c755..41a4a91bce 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -18,6 +18,7 @@ export { injectTemplateRef as ɵinjectTemplateRef, injectViewContainerRef as ɵinjectViewContainerRef, injectChangeDetectorRef as ɵinjectChangeDetectorRef, + injectAttribute as ɵinjectAttribute, InjectFlags as ɵInjectFlags, PublicFeature as ɵPublicFeature, NgOnChangesFeature as ɵNgOnChangesFeature, diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index 27446d1503..120b7d00e2 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -18,7 +18,7 @@ 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 {assertLessThan} from './assert'; +import {assertLessThan, assertNotNull} from './assert'; import {assertPreviousIsParent, getDirectiveInstance, getPreviousOrParentNode, getRenderer, renderEmbeddedTemplate} from './instructions'; import {ComponentTemplate, DirectiveDef} from './interfaces/definition'; import {LInjector} from './interfaces/injector'; @@ -234,6 +234,54 @@ export function injectChangeDetectorRef(): viewEngine_ChangeDetectorRef { return getOrCreateChangeDetectorRef(getOrCreateNodeInjector(), null); } +/** + * Inject static attribute value into directive constructor. + * + * This method is used with `factory` functions which are generated as part of + * `defineDirective` or `defineComponent`. The method retrieves the static value + * of an attribute. (Dynamic attributes are not supported since they are not resolved + * at the time of injection and can change over time.) + * + * # Example + * Given: + * ``` + * @Component(...) + * class MyComponent { + * constructor(@Attribute('title') title: string) { ... } + * } + * ``` + * When instantiated with + * ``` + * + * ``` + * + * Then factory method generated is: + * ``` + * MyComponent.ngComponentDef = defineComponent({ + * factory: () => new MyComponent(injectAttribute('title')) + * ... + * }) + * ``` + * + * @experimental + */ +export function injectAttribute(attrName: string): string|undefined { + ngDevMode && assertPreviousIsParent(); + const lElement = getPreviousOrParentNode() as LElementNode; + ngDevMode && assertNodeType(lElement, LNodeFlags.Element); + const tElement = lElement.tNode !; + ngDevMode && assertNotNull(tElement, 'expecting tNode'); + const attrs = tElement.attrs; + if (attrs) { + for (let i = 0; i < attrs.length; i = i + 2) { + if (attrs[i] == attrName) { + return attrs[i + 1]; + } + } + } + return undefined; +} + /** * Creates a ViewRef and stores it on the injector as ChangeDetectorRef (public alias). * Or, if it already exists, retrieves the existing instance. diff --git a/packages/core/src/render3/index.ts b/packages/core/src/render3/index.ts index 6391cb5a9b..7d764f6f34 100644 --- a/packages/core/src/render3/index.ts +++ b/packages/core/src/render3/index.ts @@ -11,10 +11,11 @@ import {NgOnChangesFeature, PublicFeature, defineComponent, defineDirective, def import {InjectFlags} from './di'; import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFlags, DirectiveType} from './interfaces/definition'; -export {InjectFlags, QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF, inject, injectChangeDetectorRef, injectElementRef, injectTemplateRef, injectViewContainerRef} from './di'; +export {InjectFlags, QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF, inject, injectAttribute, injectChangeDetectorRef, injectElementRef, injectTemplateRef, injectViewContainerRef} from './di'; export {CssSelector} from './interfaces/projection'; + // Naming scheme: // - Capital letters are for creating things: T(Text), E(Element), D(Directive), V(View), // C(Container), L(Listener) diff --git a/packages/core/test/render3/compiler_canonical/injection_spec.ts b/packages/core/test/render3/compiler_canonical/injection_spec.ts index 463a4a7b60..5e69202be0 100644 --- a/packages/core/test/render3/compiler_canonical/injection_spec.ts +++ b/packages/core/test/render3/compiler_canonical/injection_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, Injectable, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '../../../src/core'; +import {Attribute, ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, Injectable, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '../../../src/core'; import * as $r3$ from '../../../src/core_render3_private_export'; import {renderComponent, toHtml} from '../render_util'; @@ -60,4 +60,49 @@ describe('injection', () => { expect(toHtml(app)).toEqual('ViewRef'); }); + it('should inject attributes', () => { + type $MyComp$ = MyComp; + type $MyApp$ = MyApp; + + @Component({selector: 'my-comp', template: `{{ title }}`}) + class MyComp { + constructor(@Attribute('title') public title: string|undefined) {} + + // NORMATIVE + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyComp, + tag: 'my-comp', + factory: function MyComp_Factory() { return new MyComp($r3$.ɵinjectAttribute('title')); }, + template: function MyComp_Template(ctx: $MyComp$, cm: $boolean$) { + if (cm) { + $r3$.ɵT(0); + } + $r3$.ɵt(0, $r3$.ɵb(ctx.title)); + } + }); + // /NORMATIVE + } + + class MyApp { + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyApp, + tag: 'my-app', + factory: function MyApp_Factory() { return new MyApp(); }, + /** */ + template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, MyComp, e0_attrs); + $r3$.ɵe(); + } + MyComp.ngComponentDef.h(1, 0); + $r3$.ɵr(1, 0); + } + }); + } + const e0_attrs = ['title', 'WORKS']; + const app = renderComponent(MyApp); + // ChangeDetectorRef is the token, ViewRef is historically the constructor + expect(toHtml(app)).toEqual('WORKS'); + }); + }); \ 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 97c79c2bb7..33b97de204 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -9,7 +9,7 @@ import {ChangeDetectorRef, ElementRef, TemplateRef, ViewContainerRef} from '@angular/core'; import {defineComponent} from '../../src/render3/definition'; -import {InjectFlags, bloomAdd, bloomFindPossibleInjector, getOrCreateNodeInjector} from '../../src/render3/di'; +import {InjectFlags, bloomAdd, bloomFindPossibleInjector, getOrCreateNodeInjector, injectAttribute} from '../../src/render3/di'; import {NgOnChangesFeature, PublicFeature, defineDirective, inject, injectChangeDetectorRef, injectElementRef, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index'; import {bind, container, containerRefreshEnd, containerRefreshStart, createLNode, createLView, createTView, directiveRefresh, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, enterView, interpolation2, leaveView, load, projection, projectionDef, text, textBinding} from '../../src/render3/instructions'; import {LInjector} from '../../src/render3/interfaces/injector'; @@ -465,6 +465,29 @@ describe('di', () => { expect(dir !.cdr).toBe(dirSameInstance !.cdr); }); + it('should injectAttribute', () => { + let exist: string|undefined = 'wrong'; + let nonExist: string|undefined = 'wrong'; + class MyApp { + static ngComponentDef = defineComponent({ + type: MyApp, + tag: 'my-app', + factory: () => new MyApp(), + template: function(ctx: MyApp, cm: boolean) { + if (cm) { + elementStart(0, 'div', ['exist', 'existValue', 'other', 'ignore']); + exist = injectAttribute('exist'); + nonExist = injectAttribute('nonExist'); + } + } + }); + } + + const app = renderComponent(MyApp); + expect(exist).toEqual('existValue'); + expect(nonExist).toEqual(undefined); + }); + }); describe('inject', () => {