test(ivy): add injectAttribute spec (#22510)

PR Close #22510
This commit is contained in:
Misko Hevery 2018-02-28 22:18:34 -08:00 committed by Alex Eagle
parent 69d359bb51
commit 51ca643c27
5 changed files with 122 additions and 4 deletions

View File

@ -18,6 +18,7 @@ export {
injectTemplateRef as ɵinjectTemplateRef, injectTemplateRef as ɵinjectTemplateRef,
injectViewContainerRef as ɵinjectViewContainerRef, injectViewContainerRef as ɵinjectViewContainerRef,
injectChangeDetectorRef as ɵinjectChangeDetectorRef, injectChangeDetectorRef as ɵinjectChangeDetectorRef,
injectAttribute as ɵinjectAttribute,
InjectFlags as ɵInjectFlags, InjectFlags as ɵInjectFlags,
PublicFeature as ɵPublicFeature, PublicFeature as ɵPublicFeature,
NgOnChangesFeature as ɵNgOnChangesFeature, NgOnChangesFeature as ɵNgOnChangesFeature,

View File

@ -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 {EmbeddedViewRef as viewEngine_EmbeddedViewRef, ViewRef as viewEngine_ViewRef} from '../linker/view_ref';
import {Type} from '../type'; import {Type} from '../type';
import {assertLessThan} from './assert'; import {assertLessThan, assertNotNull} from './assert';
import {assertPreviousIsParent, getDirectiveInstance, getPreviousOrParentNode, getRenderer, renderEmbeddedTemplate} from './instructions'; import {assertPreviousIsParent, getDirectiveInstance, getPreviousOrParentNode, getRenderer, renderEmbeddedTemplate} from './instructions';
import {ComponentTemplate, DirectiveDef} from './interfaces/definition'; import {ComponentTemplate, DirectiveDef} from './interfaces/definition';
import {LInjector} from './interfaces/injector'; import {LInjector} from './interfaces/injector';
@ -234,6 +234,54 @@ export function injectChangeDetectorRef(): viewEngine_ChangeDetectorRef {
return getOrCreateChangeDetectorRef(getOrCreateNodeInjector(), null); 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
* ```
* <my-component title="Hello"></my-component>
* ```
*
* 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). * Creates a ViewRef and stores it on the injector as ChangeDetectorRef (public alias).
* Or, if it already exists, retrieves the existing instance. * Or, if it already exists, retrieves the existing instance.

View File

@ -11,10 +11,11 @@ import {NgOnChangesFeature, PublicFeature, defineComponent, defineDirective, def
import {InjectFlags} from './di'; import {InjectFlags} from './di';
import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFlags, DirectiveType} from './interfaces/definition'; 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'; export {CssSelector} from './interfaces/projection';
// Naming scheme: // Naming scheme:
// - Capital letters are for creating things: T(Text), E(Element), D(Directive), V(View), // - Capital letters are for creating things: T(Text), E(Element), D(Directive), V(View),
// C(Container), L(Listener) // C(Container), L(Listener)

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * 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 * as $r3$ from '../../../src/core_render3_private_export';
import {renderComponent, toHtml} from '../render_util'; import {renderComponent, toHtml} from '../render_util';
@ -60,4 +60,49 @@ describe('injection', () => {
expect(toHtml(app)).toEqual('<my-comp>ViewRef</my-comp>'); expect(toHtml(app)).toEqual('<my-comp>ViewRef</my-comp>');
}); });
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(); },
/** <my-comp></my-comp> */
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('<my-comp title="WORKS">WORKS</my-comp>');
});
}); });

View File

@ -9,7 +9,7 @@
import {ChangeDetectorRef, ElementRef, TemplateRef, ViewContainerRef} from '@angular/core'; import {ChangeDetectorRef, ElementRef, TemplateRef, ViewContainerRef} from '@angular/core';
import {defineComponent} from '../../src/render3/definition'; 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 {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 {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'; import {LInjector} from '../../src/render3/interfaces/injector';
@ -465,6 +465,29 @@ describe('di', () => {
expect(dir !.cdr).toBe(dirSameInstance !.cdr); 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', () => { describe('inject', () => {