feat(core): Moving Renderer3 into @angular/core (#20855)

PR Close #20855
This commit is contained in:
Miško Hevery
2017-12-01 14:23:03 -08:00
committed by Igor Minar
parent bc66d27938
commit 0fa818b318
39 changed files with 8544 additions and 4 deletions

View File

@ -0,0 +1,39 @@
/**
* @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
*/
function stringify(value: any) {
return typeof value === 'string' ? `"${value}"` : '' + value;
}
export function assertNumber(actual: any, condition: string) {
(typeof actual != 'number') && assertThrow(actual, 'number', condition, 'typeof ==');
}
export function assertEqual<T>(
actual: T, expected: T, condition: string, serializer?: ((v: T) => string)) {
(actual != expected) && assertThrow(actual, expected, condition, '==', serializer);
}
export function assertLessThan<T>(actual: T, expected: T, condition: string) {
(actual < expected) && assertThrow(actual, expected, condition, '>');
}
export function assertNotNull<T>(actual: T, condition: string) {
assertNotEqual(actual, null, condition);
}
export function assertNotEqual<T>(actual: T, expected: T, condition: string) {
(actual == expected) && assertThrow(actual, expected, condition, '!=');
}
export function assertThrow<T>(
actual: T, expected: T, condition: string, operator: string,
serializer: ((v: T) => string) = stringify) {
throw new Error(
`ASSERT: expected ${condition} ${operator} ${serializer(expected)} but was ${serializer(actual)}!`);
}

View File

@ -0,0 +1,187 @@
/**
* @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 {ComponentRef, EmbeddedViewRef, Injector} from '../core';
import {assertNotNull} from './assert';
import {NG_HOST_SYMBOL, createError, createViewState, directiveCreate, elementHost, enterView, leaveView} from './instructions';
import {LElement} from './interfaces';
import {ComponentDef, ComponentType} from './public_interfaces';
import {RElement, Renderer3, RendererFactory3} from './renderer';
import {stringify} from './util';
/**
* Options which control how the component should be bootstrapped.
*/
export interface CreateComponentOptionArgs {
/**
* Which renderer to use.
*/
renderer?: Renderer3;
rendererFactory?: RendererFactory3;
/**
* Which host element should the component be bootstrapped on. If not specified
* the component definition's `tag` is used to query the existing DOM for the
* element to bootstrap.
*/
host?: RElement|string;
/**
* Optional Injector which is the Module Injector for the component.
*/
injector?: Injector;
/**
* a set of features which should be applied to this component.
*/
features?: (<T>(component: T, componentDef: ComponentDef<T>) => void)[];
}
/**
* Bootstrap a Component into an existing host element and return `ComponentRef`.
*
* @param componentType Component to bootstrap
* @param options Optional parameters which control bootstrapping
*/
export function createComponentRef<T>(
componentType: ComponentType<T>, opts: CreateComponentOptionArgs): ComponentRef<T> {
const component = renderComponent(componentType, opts);
const hostView = createViewRef(detectChanges.bind(component), component);
return {
location: {nativeElement: getHostElement(component)},
injector: opts.injector || NULL_INJECTOR,
instance: component,
hostView: hostView,
changeDetectorRef: hostView,
componentType: componentType,
destroy: function() {},
onDestroy: function(cb: Function): void {}
};
}
function createViewRef<T>(detectChanges: () => void, context: T): EmbeddedViewRef<T> {
return addDestroyable(
{
rootNodes: null !,
// inherited from core/ChangeDetectorRef
markForCheck: () => {
if (ngDevMode) {
implement();
}
},
detach: () => {
if (ngDevMode) {
implement();
}
},
detectChanges: detectChanges,
checkNoChanges: () => {
if (ngDevMode) {
implement();
}
},
reattach: () => {
if (ngDevMode) {
implement();
}
},
},
context);
}
interface DestroyRef<T> {
context: T;
destroyed: boolean;
destroy(): void;
onDestroy(cb: Function): void;
}
function implement() {
throw new Error('NotImplemented');
}
function addDestroyable<T, C>(obj: any, context: C): T&DestroyRef<C> {
let destroyFn: Function[]|null = null;
obj.destroyed = false;
obj.destroy = function() {
destroyFn && destroyFn.forEach((fn) => fn());
this.destroyed = true;
};
obj.onDestroy = (fn: Function) => (destroyFn || (destroyFn = [])).push(fn);
return obj;
}
// TODO: A hack to not pull in the NullInjector from @angular/core.
export const NULL_INJECTOR: Injector = {
get: function(token: any, notFoundValue?: any) {
throw new Error('NullInjector: Not found: ' + stringify(token));
}
};
/**
* Bootstrap a Component into an existing host element and return `NgComponent`.
*
* NgComponent is a light weight Custom Elements inspired API for bootstrapping and
* interacting with bootstrapped component.
*
* @param componentType Component to bootstrap
* @param options Optional parameters which control bootstrapping
*/
export function renderComponent<T>(
componentType: ComponentType<T>, opts: CreateComponentOptionArgs = {}): T {
const renderer = opts.renderer || document;
const componentDef = componentType.ngComponentDef;
let component: T;
const oldView = enterView(createViewState(-1, renderer), null);
try {
elementHost(opts.host || componentDef.tag);
component = directiveCreate(0, componentDef.n(), componentDef);
} finally {
leaveView(oldView);
}
opts.features && opts.features.forEach((feature) => feature(component, componentDef));
detectChanges(component);
return component;
}
export function detectChanges<T>(component: T) {
ngDevMode && assertNotNull(component, 'component');
const hostNode = (component as any)[NG_HOST_SYMBOL] as LElement;
if (ngDevMode && !hostNode) {
createError('Not a directive instance', component);
}
ngDevMode && assertNotNull(hostNode.data, 'hostNode.data');
const oldView = enterView(hostNode.view !, hostNode);
try {
(component.constructor as ComponentType<T>).ngComponentDef.r(0, 0);
isDirty = false;
} finally {
leaveView(oldView);
}
}
let isDirty = false;
export function markDirty<T>(
component: T, scheduler: (fn: () => void) => void = requestAnimationFrame) {
ngDevMode && assertNotNull(component, 'component');
if (!isDirty) {
isDirty = true;
scheduler(detectChanges.bind(null, component));
}
}
export function getHostElement<T>(component: T): RElement {
return ((component as any)[NG_HOST_SYMBOL] as LElement).native;
}

View File

@ -0,0 +1,156 @@
/**
* @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 {ComponentFactory, ComponentRef as IComponentRef, ElementRef as IElementRef, EmbeddedViewRef as IEmbeddedViewRef, Injector, NgModuleRef as INgModuleRef, TemplateRef as ITemplateRef, Type, ViewContainerRef as IViewContainerRef, ViewRef as IViewRef} from '../core';
import {BLOOM_SIZE, NG_ELEMENT_ID, getOrCreateNodeInjector} from './instructions';
import {LContainer, LNodeFlags, LNodeInjector} from './interfaces';
import {ComponentTemplate} from './public_interfaces';
import {stringify} from './util';
export const enum InjectFlags {
Optional = 1 << 0,
CheckSelf = 1 << 1,
CheckParent = 1 << 2,
Default = CheckSelf | CheckParent
}
function createError(text: string, token: any) {
return new Error(`ElementInjector: ${text} [${stringify(token)}]`);
}
export function inject<T>(token: Type<T>, flags?: InjectFlags): T {
const di = getOrCreateNodeInjector();
const bloomHash = bloomHashBit(token);
if (bloomHash === null) {
const moduleInjector = di.injector;
if (!moduleInjector) {
throw createError('NotFound', token);
}
moduleInjector.get(token);
} else {
let injector: LNodeInjector|null = di;
while (injector) {
injector = bloomFindPossibleInjector(injector, bloomHash);
if (injector) {
const node = injector.node;
const flags = node.flags;
let size = flags & LNodeFlags.SIZE_MASK;
if (size !== 0) {
size = size >> LNodeFlags.SIZE_SHIFT;
const start = flags >> LNodeFlags.INDX_SHIFT;
const directives = node.view.directives;
if (directives) {
for (let i = start, ii = start + size; i < ii; i++) {
const def = directives[(i << 1) | 1];
if (def.diPublic && def.type == token) {
return directives[i << 1];
}
}
}
}
injector = injector.parent;
}
}
}
throw createError('Not found', token);
}
function bloomHashBit(type: Type<any>): number|null {
let id: number|undefined = (type as any)[NG_ELEMENT_ID];
return typeof id === 'number' ? id % BLOOM_SIZE : null;
}
export function bloomFindPossibleInjector(injector: LNodeInjector, bloomBit: number): LNodeInjector|
null {
const mask = 1 << bloomBit;
let di: LNodeInjector|null = injector;
while (di) {
// See if the current injector may have the value.
let value: number =
bloomBit < 64 ? (bloomBit < 32 ? di.bf0 : di.bf1) : (bloomBit < 96 ? di.bf2 : di.bf3);
if ((value & mask) === mask) {
return di;
}
// See if the parent injectors may have the value
value =
bloomBit < 64 ? (bloomBit < 32 ? di.cbf0 : di.cbf1) : (bloomBit < 96 ? di.cbf2 : di.cbf3);
// Only go to parent if parent may have value otherwise exit.
di = (value & mask) ? di.parent : null;
}
return null;
}
export function injectElementRef(): IElementRef {
let di = getOrCreateNodeInjector();
return di.elementRef || (di.elementRef = new ElementRef(di.node.native));
}
class ElementRef implements IElementRef {
readonly nativeElement: any;
constructor(nativeElement: any) { this.nativeElement = nativeElement; }
}
export function injectTemplateRef(): ITemplateRef<any> {
let di = getOrCreateNodeInjector();
const data = (di.node as LContainer).data;
if (data === null || data.template === null) {
throw createError('Directive not used in structural way.', null);
}
return di.templateRef ||
(di.templateRef = new TemplateRef<any>(injectElementRef(), data.template));
}
class TemplateRef<T> implements ITemplateRef<T> {
readonly elementRef: IElementRef;
constructor(elementRef: IElementRef, template: ComponentTemplate<T>) {
this.elementRef = elementRef;
}
createEmbeddedView(context: T): IEmbeddedViewRef<T> { throw notImplemented(); }
}
export function injectViewContainerRef(): IViewContainerRef {
let di = getOrCreateNodeInjector();
return di.viewContainerRef || (di.viewContainerRef = new ViewContainerRef(di.node as LContainer));
}
class ViewContainerRef implements IViewContainerRef {
element: IElementRef;
injector: Injector;
parentInjector: Injector;
constructor(node: LContainer) {}
clear(): void { throw notImplemented(); }
get(index: number): IViewRef|null { throw notImplemented(); }
length: number;
createEmbeddedView<C>(
templateRef: ITemplateRef<C>, context?: C|undefined,
index?: number|undefined): IEmbeddedViewRef<C> {
throw notImplemented();
}
createComponent<C>(
componentFactory: ComponentFactory<C>, index?: number|undefined,
injector?: Injector|undefined, projectableNodes?: any[][]|undefined,
ngModule?: INgModuleRef<any>|undefined): IComponentRef<C> {
throw notImplemented();
}
insert(viewRef: IViewRef, index?: number|undefined): IViewRef { throw notImplemented(); }
move(viewRef: IViewRef, currentIndex: number): IViewRef { throw notImplemented(); }
indexOf(viewRef: IViewRef): number { throw notImplemented(); }
remove(index?: number|undefined): void { throw notImplemented(); }
detach(index?: number|undefined): IViewRef|null { throw notImplemented(); }
}
function notImplemented() {
return new Error('Method not implemented.');
}

View File

@ -0,0 +1,81 @@
/**
* @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 {createComponentRef, detectChanges, getHostElement, markDirty, renderComponent} from './component';
import {inject, injectElementRef, injectTemplateRef, injectViewContainerRef} from './di';
import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFlags, NgOnChangesFeature, PublicFeature, defineComponent, defineDirective} from './public_interfaces';
// Naming scheme:
// - Capital letters are for creating things: T(Text), E(Element), D(Directive), V(View),
// C(Container), L(Listener)
// - lower case letters are for binding: b(bind)
// - lower case letters are for binding target: p(property), a(attribute), k(class), s(style),
// i(input)
// - lower case letters for guarding life cycle hooks: l(lifeCycle)
// - lower case for closing: c(containerEnd), e(elementEnd), v(viewEnd)
// clang-format off
export {
LifeCycleGuard,
NO_CHANGE as NC,
bind as b,
bind1 as b1,
bind2 as b2,
bind3 as b3,
bind4 as b4,
bind5 as b5,
bind6 as b6,
bind7 as b7,
bind8 as b8,
bindV as bV,
containerCreate as C,
containerEnd as c,
contentProjection as P,
directiveCreate as D,
directiveLifeCycle as l,
distributeProjectedNodes as dP,
elementAttribute as a,
elementClass as k,
elementCreate as E,
elementEnd as e,
elementProperty as p,
elementStyle as s,
listenerCreate as L,
memory as m,
queryCreate as Q,
refreshComponent as r,
refreshContainer as rC,
refreshContainerEnd as rc,
refreshQuery as rQ,
textCreate as T,
textCreateBound as t,
viewCreate as V,
viewEnd as v,
} from './instructions';
// clang-format on
export {QueryList} from './query';
export {inject, injectElementRef, injectTemplateRef, injectViewContainerRef};
export {
ComponentDef,
ComponentTemplate,
ComponentType,
DirectiveDef,
DirectiveDefFlags,
NgOnChangesFeature,
PublicFeature,
defineComponent,
defineDirective,
};
export {createComponentRef, detectChanges, getHostElement, markDirty, renderComponent};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,603 @@
/**
* @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 {ElementRef, Injector, QueryList, TemplateRef, Type, ViewContainerRef} from '../core';
import {ComponentTemplate} from './public_interfaces';
import {RComment, RElement, RText, Renderer3} from './renderer';
declare global {
const ngDevMode: boolean;
}
export const enum LNodeFlags {
Container = 0b00,
Projection = 0b01,
View = 0b10,
Element = 0b11,
ViewOrElement = 0b10,
SIZE_SKIP = 0b100,
SIZE_SHIFT = 2,
INDX_SHIFT = 12,
TYPE_MASK = 0b00000000000000000000000000000011,
SIZE_MASK = 0b00000000000000000000111111111100,
INDX_MASK = 0b11111111111111111111000000000000,
}
/**
* NOTES:
*
* Each Array costs 70 bytes and is composed of `Array` and `(array)` object
* - `Array` javascript visible object: 32 bytes
* - `(array)` VM object where the array is actually stored in: 38 bytes
*
* Each Object cost is 24 bytes plus 8 bytes per property.
*
* For small arrays, it is more efficient to store the data as a linked list
* of items rather than small arrays. However, the array access is faster as
* shown here: https://jsperf.com/small-arrays-vs-linked-objects
*/
/**
* `ViewState` stores all of the information needed to process the instructions as
* they are invoked from the template. `ViewState` is saved when a child `View` is
* being processed and restored when the child `View` is done.
*
* Keeping separate state for each view facilities view insertion / deletion, so we
* don't have to edit the nodes array or directives array based on which views
* are present.
*/
export interface ViewState {
/**
* The parent view is needed when we exit the view and must restore the previous
* `ViewState`. Without this, the render method would have to keep a stack of
* views as it is recursively rendering templates.
*/
readonly parent: ViewState|null;
/**
* Pointer to the `LView` node which represents the root of the view. We
* need this to be able to efficiently find the `LView` when inserting the
* view into an anchor.
*/
readonly node: LView|LElement;
/**
* ID to determine whether this view is the same as the previous view
* in this position. If it's not, we know this view needs to be inserted
* and the one that exists needs to be removed (e.g. if/else statements)
*/
readonly id: number;
/**
* Renderer to be used for this view.
*/
readonly renderer: Renderer3;
/**
* This array stores all element/text/container nodes created inside this view
* and their bindings. Stored as an array rather than a linked list so we can
* look up nodes directly in the case of forward declaration or bindings
* (e.g. E(1))..
*
* All bindings for a given view are stored in the order in which they
* appear in the template, starting with `bindingStartIndex`.
* We use `bindingIndex` to internally keep track of which binding
* is currently active.
*
* NOTE: We also use nodes == null as a marker for creationMode. We
* do this by creating ViewState in incomplete state with nodes == null
* and we initialize it on first run.
*/
readonly nodesAndBindings: any[];
/**
* All directives created inside this view. Stored as an array
* rather than a linked list so we can look up directives directly
* in the case of forward declaration or DI.
*
* The array alternates between instances and directive tokens.
* - even indices: contain the directive token (type)
* - odd indices: contain the directive def
*
* We must store the directive def (rather than token | null)
* because we need to be able to access the inputs and outputs
* of directives that aren't diPublic.
*/
readonly directives: any[];
/**
* The binding start index is the index at which the nodes array
* starts to store bindings only. Saving this value ensures that we
* will begin reading bindings at the correct point in the array when
* we are in update mode.
*/
bindingStartIndex: number|null;
/**
* When a view is destroyed, listeners need to be released
* and onDestroy callbacks need to be called. This cleanup array
* stores both listener data (in chunks of 4) and onDestroy data
* (in chunks of 2), as they'll be processed at the same time.
*
* If it's a listener being stored:
* 1st index is: event name to remove
* 2nd index is: native element
* 3rd index is: listener function
* 4th index is: useCapture boolean
*
* If it's an onDestroy function:
* 1st index is: onDestroy function
* 2nd index is; context for function
*/
cleanup: any[]|null;
/**
* Necessary so views can traverse through their nested views
* to remove listeners and call onDestroy callbacks.
*
* For embedded views, we store the container rather than the
* first view to avoid managing splicing when views are added/removed.
*/
child: ViewState|ContainerState|null;
/**
* The tail allows us to quickly add a new state to the end of the
* view list without having to propagate starting from the first child.
*/
tail: ViewState|ContainerState|null;
/**
* Allows us to propagate between view states.
*
* Embedded views already have a node.next, but it is only set for views
* in the same container. We need a way to link component views as well.
*/
next: ViewState|ContainerState|null;
locals: any[]|null;
}
export interface LNodeInjector {
/**
* We need to store a reference to the injector's parent so DI can keep looking up
* the injector tree until it finds the dependency it's looking for.
*/
readonly parent: LNodeInjector|null;
/**
* Allows access to the directives array in that node's view and to
* the node's flags (for starting directive index and directive size). Necessary
* for DI to retrieve a directive from the directives array if injector indicates
* it is there.
*/
readonly node: LElement|LContainer;
/**
* The following bloom filter determines whether a directive is available
* on the associated node or not. This prevents us from searching the directives
* array at this level unless it's probable the directive is in it.
*
* - bf0: Check directive IDs 0-31 (IDs are % 128)
* - bf1: Check directive IDs 33-63
* - bf2: Check directive IDs 64-95
* - bf3: Check directive IDs 96-127
*/
bf0: number;
bf1: number;
bf2: number;
bf3: number;
/**
* cbf0 - cbf3 properties determine whether a directive is available through a
* parent injector. They refer to the merged values of parent bloom filters. This
* allows us to skip looking up the chain unless it's probable that directive exists
* up the chain.
*/
cbf0: number;
cbf1: number;
cbf2: number;
cbf3: number;
injector: Injector|null;
/** Stores the TemplateRef so subsequent injections of the TemplateRef get the same instance. */
templateRef: TemplateRef<any>|null;
/** Stores the ViewContainerRef so subsequent injections of the ViewContainerRef get the same
* instance. */
viewContainerRef: ViewContainerRef|null;
/** Stores the ElementRef so subsequent injections of the ElementRef get the same instance. */
elementRef: ElementRef|null;
}
/**
* LNode is an internal data structure which is used for the incremental DOM algorithm.
*
* The data structure is optimized for speed and size.
*
* In order to be fast, all subtypes of `LNode` should have the same shape.
* Because size of the `LNode` matters, many fields have multiple roles depending
* on the `LNode` subtype.
*
* NOTE: This is a private data structure and should not be exported by any of the
* instructions.
*/
export interface LNode {
/**
* This number stores three values using its bits:
*
* - the type of the node (first 2 bits)
* - the number of directives on that node (next 10 bits)
* - the starting index of the node's directives in the directives array (last 20 bits).
*
* The latter two values are necessary so DI can effectively search the directives associated
* with a node without searching the whole directives array.
*/
flags: LNodeFlags;
/**
* The associated DOM node. Storing this allows us to:
* - append children to their element parents in the DOM (e.g. `parent.native.appendChild(...)`)
* - retrieve the sibling elements of text nodes whose creation / insertion has been delayed
* - mark locations where child views should be inserted (for containers)
*/
readonly native: RElement|RText|RComment|null;
/**
* We need a reference to a node's parent so we can append the node to its parent's native
* element at the appropriate time.
*/
readonly parent: LNode|null;
/**
* First child of the current node.
*/
child: LNode|null;
/**
* The next sibling node. Necessary so we can propagate through the root nodes of a view
* to insert them or remove them from the DOM.
*/
next: LNode|null;
/**
* If ViewState, then `data` contains lightDOM.
* If LContainer, then `data` contains ContainerState
*/
readonly data: ViewState|ContainerState|ProjectionState|null;
/**
* Each node belongs to a view.
*
* When the injector is walking up a tree, it needs access to the `directives` (part of view).
*/
readonly view: ViewState;
/** The injector associated with this node. Necessary for DI. */
nodeInjector: LNodeInjector|null;
/**
* Optional `QueryState` used for tracking queries.
*
* If present the node creation/updates are reported to the `QueryState`.
*/
query: QueryState|null;
/**
* Pointer to the corresponding NodeBindings object, which stores static
* data about this node.
*/
nodeBindings: NodeBindings|null;
}
/**
* Used for tracking queries.
*/
export interface QueryState {
/**
* Used to ask query if it should be cloned to the child element.
*
* For example in the case of deep queries the `child()` returns
* query for the child node. In case of shallow queries it returns
* `null`.
*/
child(): QueryState|null;
/**
* Notify `QueryState` that a `LNode` has been created.
*/
add(node: LNode): void;
/**
* Notify `QueryState` that a `LView` has been added to `LContainer`.
*/
insert(container: LContainer, view: LView, insertIndex: number): void;
/**
* Notify `QueryState` that a `LView` has been removed from `LContainer`.
*/
remove(container: LContainer, view: LView, removeIndex: number): void;
/**
* Add additional `QueryList` to track.
*
* @param queryList `QueryList` to update with changes.
* @param predicate Either `Type` or selector array of [key, value] predicates.
* @param descend If true the query will recursively apply to the children.
*/
track<T>(queryList: QueryList<T>, predicate: Type<T>|any[], descend?: boolean): void;
}
/** The state associated with an LContainer */
export interface ContainerState {
/**
* The next active index in the children array to read or write to. This helps us
* keep track of where we are in the children array.
*/
nextIndex: number;
/**
* This allows us to jump from a container to a sibling container or
* component view with the same parent, so we can remove listeners efficiently.
*/
next: ViewState|ContainerState|null;
/**
* Access to the parent view is necessary so we can propagate back
* up from inside a container to parent.next.
*/
parent: ViewState|null;
/**
* A list of the container's currently active child views. Views will be inserted
* here as they are added and spliced from here when they are removed. We need
* to keep a record of current views so we know which views are already in the DOM
* (and don't need to be re-added) and so we can remove views from the DOM when they
* are no longer required.
*/
readonly children: LView[];
/**
* Parent Element which will contain the location where all of the Views will be
* inserted into to.
*
* If `renderParent` is `null` it is headless. This means that it is contained
* in another `LView` which in turn is contained in another `LContainer` and therefore
* it does not yet have its own parent.
*
* If `renderParent` is not `null` than it may be:
* - same as `LContainer.parent` in which case it is just a normal container.
* - different from `LContainer.parent` in which case it has been re-projected.
* In other words `LContainer.parent` is logical parent where as
* `ContainerState.projectedParent` is render parent.
*
* When views are inserted into `LContainer` than `renderParent` is:
* - `null`, we are in `LView` keep going up a hierarchy until actual
* `renderParent` is found.
* - not `null`, than use the `projectedParent.native` as the `RElement` to insert
* `LView`s into.
*/
renderParent: LElement|null;
/**
* The template extracted from the location of the Container.
*/
readonly template: ComponentTemplate<any>|null;
}
/**
* This mapping is necessary so we can set input properties and output listeners
* properly at runtime when property names are minified.
*
* Key: original unminified input or output name
* Value: array containing minified name and related directive index
*
* The value must be an array to support inputs and outputs with the same name
* on the same node.
*/
export type MinificationData = {
[key: string]: MinificationDataValue
};
/**
* The value in MinificationData objects.
*
* In each array:
* Even indices: directive index
* Odd indices: minified name
*
* e.g. [0, 'change-minified']
*/
export type MinificationDataValue = (number | string)[];
/**
* This array contains information about input properties that
* need to be set once from attribute data. It's ordered by
* directive index (relative to element) so it's simple to
* look up a specific directive's initial input data.
*
* Within each sub-array:
*
* Even indices: minified input name
* Odd indices: initial value
*
* If a directive on a node does not have any input properties
* that should be set from attributes, its index is set to null
* to avoid a sparse array.
*
* e.g. [null, ['role-min', 'button']]
*/
export type InitialInputData = (InitialInputs | null)[];
/**
* Used by InitialInputData to store input properties
* that should be set once from attributes.
*
* Even indices: minified input name
* Odd indices: initial value
*
* e.g. ['role-min', 'button']
*/
export type InitialInputs = string[];
/**
* LNode binding data for a particular node that is shared between all templates
* of a specific type.
*
* If a property is:
* - Minification Data: that property's data was generated and this is it
* - Null: that property's data was already generated and nothing was found.
* - Undefined: that property's data has not yet been generated
*/
export interface NodeBindings {
/** The tag name associated with this node. */
tagName: string|null;
/**
* Static attributes associated with an element. We need to store
* static attributes to support content projection with selectors.
* Attributes are stored statically because reading them from the DOM
* would be way too slow for content projection and queries.
*
* Since attrs will always be calculated first, they will never need
* to be marked undefined by other instructions.
*/
attrs: string[]|null;
/**
* This property contains information about input properties that
* need to be set once from attribute data.
*/
initialInputs: InitialInputData|null|undefined;
/** Input data for all directives on this node. */
inputs: MinificationData|null|undefined;
/** Output data for all directives on this node. */
outputs: MinificationData|null|undefined;
}
/** Interface necessary to work with view tree traversal */
export interface ViewOrContainerState {
next: ViewState|ContainerState|null;
child?: ViewState|ContainerState|null;
children?: LView[];
parent: ViewState|null;
}
/** LNode representing an element. */
export interface LElement extends LNode {
/** The DOM element associated with this node. */
readonly native: RElement;
child: LContainer|LElement|LText|LProjection|null;
next: LContainer|LElement|LText|LProjection|null;
/** If Component than data has ViewState (light DOM) */
readonly data: ViewState|null;
/** LElement nodes can be inside other LElement nodes or inside LViews. */
readonly parent: LElement|LView;
}
/** LNode representing a #text node. */
export interface LText extends LNode {
/** The text node associated with this node. */
native: RText;
child: null;
next: LContainer|LElement|LText|LProjection|null;
/** LText nodes can be inside LElement nodes or inside LViews. */
readonly parent: LElement|LView;
readonly data: null;
}
/**
* Abstract node which contains root nodes of a view.
*/
export interface LView extends LNode {
readonly native: null;
child: LContainer|LElement|LText|LProjection|null;
next: LView|null;
/** LView nodes can only be added to LContainers. */
readonly parent: LContainer|null;
readonly data: ViewState;
}
/**
* Abstract node container which contains other views.
*/
export interface LContainer extends LNode {
/**
* This comment node is appended to the container's parent element to mark where
* in the DOM the container's child views should be added.
*
* If the container is a root node of a view, this comment will not be appended
* until the parent view is processed.
*/
readonly native: RComment;
readonly data: ContainerState;
child: null;
next: LContainer|LElement|LText|LProjection|null;
/** Containers can be added to elements or views. */
readonly parent: LElement|LView|null;
}
/**
* A projection state is just an array of projected nodes.
*
* It would be nice if we could not need an array, but since a projected note can be
* re-projected, the same node can be part of more than one LProjection which makes
* list approach not possible.
*/
export type ProjectionState = Array<LElement|LText|LContainer>;
export interface LProjection extends LNode {
readonly native: null;
child: null;
next: LContainer|LElement|LText|LProjection|null;
readonly data: ProjectionState;
/** Projections can be added to elements or views. */
readonly parent: LElement|LView;
}
/**
* Parsed selector in the following format:
* [tagName, attr1Name, attr1Val, ..., attrnName, attrnValue, 'class', className1, className2, ...,
* classNameN]
*
* * For example, given the following selector:
* `div.foo.bar[attr1=val1][attr2]` a parsed format would be:
* `['div', 'attr1', 'val1', 'attr2', '', 'class', 'foo', 'bar']`.
*
* Things to notice:
* - tag name is always at the position 0
* - the `class` attribute is always the last attribute in a pre-parsed array
* - class names in a selector are at the end of an array (after the attribute with the name
* 'class').
*/
export type SimpleCSSSelector = string[];
/**
* A complex selector expressed as an Array where:
* - element at index 0 is a selector (SimpleCSSSelector) to match
* - elements at index 1..n is a selector (SimpleCSSSelector) that should NOT match
*/
export type CSSSelectorWithNegations = [SimpleCSSSelector | null, SimpleCSSSelector[] | null];
/**
* A collection of complex selectors (CSSSelectorWithNegations) in a parsed form
*/
export type CSSSelector = CSSSelectorWithNegations[];

View File

@ -0,0 +1,15 @@
/**
* @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
*/
if (typeof ngDevMode == 'undefined') {
if (typeof window != 'undefined') (window as any).ngDevMode = true;
if (typeof self != 'undefined') (self as any).ngDevMode = true;
if (typeof global != 'undefined') (global as any).ngDevMode = true;
}
export const _ngDevMode = true;

View File

@ -0,0 +1,24 @@
/**
* @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 {assertEqual, assertNotEqual} from './assert';
import {LNode, LNodeFlags} from './interfaces';
export function assertNodeType(node: LNode, type: LNodeFlags) {
assertNotEqual(node, null, 'node');
assertEqual(node.flags & LNodeFlags.TYPE_MASK, type, 'Node.type', typeSerializer);
}
function typeSerializer(type: LNodeFlags): string {
if (type == LNodeFlags.Projection) return 'Projection';
if (type == LNodeFlags.Container) return 'Container';
if (type == LNodeFlags.View) return 'View';
if (type == LNodeFlags.Element) return 'Element';
return '??? ' + type + ' ???';
}

View File

@ -0,0 +1,293 @@
/**
* @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 {assertNotNull} from './assert';
import {ContainerState, LContainer, LElement, LNode, LNodeFlags, LProjection, LText, LView, ProjectionState, ViewOrContainerState, ViewState} from './interfaces';
import {assertNodeType} from './node_assert';
import {RComment, RElement, RNode, RText, Renderer3Fn} from './renderer';
export function findNativeParent(containerNode: LContainer): RNode|null {
let container: LContainer|null = containerNode;
while (container) {
ngDevMode && assertNodeType(container, LNodeFlags.Container);
const renderParent = container.data.renderParent;
if (renderParent !== null) {
return renderParent.native;
}
const viewOrElement: LView|LElement = container.parent !;
ngDevMode && assertNotNull(viewOrElement, 'container.parent');
if ((viewOrElement.flags & LNodeFlags.TYPE_MASK) === LNodeFlags.Element) {
// we are an LElement, which means we are past the last LContainer.
// This means than we have not been projected so just ignore.
return null;
}
ngDevMode && assertNodeType(viewOrElement, LNodeFlags.View);
container = (viewOrElement as LView).parent;
}
return null;
}
export function findBeforeNode(index: number, state: ContainerState, native: RComment): RElement|
RText|RComment {
const children = state.children;
// Find the node to insert in front of
return index + 1 < children.length ?
(children[index + 1].data.nodesAndBindings[0] as LText | LElement | LContainer).native :
native;
}
export function addRemoveViewFromContainer(
container: LContainer, rootNode: LView, insertMode: true, beforeNode: RNode | null): void;
export function addRemoveViewFromContainer(
container: LContainer, rootNode: LView, insertMode: false): void;
export function addRemoveViewFromContainer(
container: LContainer, rootNode: LView, insertMode: boolean, beforeNode?: RNode | null): void {
ngDevMode && assertNodeType(container, LNodeFlags.Container);
ngDevMode && assertNodeType(rootNode, LNodeFlags.View);
const parent = findNativeParent(container);
let node: LNode|null = rootNode.data.nodesAndBindings[0];
if (parent) {
while (node) {
const type = node.flags & LNodeFlags.TYPE_MASK;
let nextNode: LNode|null = null;
const renderer = container.view.renderer;
const isFnRenderer = (renderer as Renderer3Fn).listen;
if (type === LNodeFlags.Element) {
insertMode ?
(isFnRenderer ?
(renderer as Renderer3Fn)
.insertBefore !(parent, node.native !, beforeNode as RNode | null) :
parent.insertBefore(node.native !, beforeNode as RNode | null, true)) :
(isFnRenderer ?
(renderer as Renderer3Fn).removeChild !(parent as RElement, node.native !) :
parent.removeChild(node.native !));
nextNode = node.next;
} else if (type === LNodeFlags.Container) {
// if we get to a container, it must be a root node of a view because we are only
// propagating down into child views / containers and not child elements
const childContainerData: ContainerState = (node as LContainer).data;
insertMode ?
(isFnRenderer ?
(renderer as Renderer3Fn).appendChild !(parent as RElement, node.native !) :
parent.appendChild(node.native !)) :
(isFnRenderer ?
(renderer as Renderer3Fn).removeChild !(parent as RElement, node.native !) :
parent.removeChild(node.native !));
nextNode = childContainerData.children.length ?
childContainerData.children[0].data.nodesAndBindings[0] :
null;
} else if (type === LNodeFlags.Projection) {
nextNode = (node as LProjection).data[0];
} else {
nextNode = (node as LView).data.nodesAndBindings[0];
}
if (nextNode === null) {
while (node && !node.next) {
node = node.parent;
if (node === rootNode) node = null;
}
node = node && node.next;
} else {
node = nextNode;
}
}
}
}
/**
* Traverses the tree of component views and containers to remove listeners.
*
* Notes:
* - Will be used for onDestroy calls later, so needs to be bottom-up.
* - Must process containers instead of their views to avoid splicing
* when views are destroyed and re-added.
* - Using a while loop because it's faster than recursing
* - Destroy only called on movement to sibling or movement to parent (laterally or up)
*/
export function destroyViewTree(rootView: ViewState): void {
let viewOrContainerState: ViewOrContainerState|null = rootView;
while (viewOrContainerState) {
let next: ViewOrContainerState|null = null;
if (viewOrContainerState.children && viewOrContainerState.children.length) {
next = viewOrContainerState.children[0].data;
} else if (viewOrContainerState.child) {
next = viewOrContainerState.child;
} else if (viewOrContainerState.next) {
cleanUpView(viewOrContainerState as ViewState);
next = viewOrContainerState.next;
}
if (next == null) {
while (viewOrContainerState && !viewOrContainerState !.next) {
cleanUpView(viewOrContainerState as ViewState);
viewOrContainerState = getParentState(viewOrContainerState, rootView);
}
cleanUpView(viewOrContainerState as ViewState || rootView);
next = viewOrContainerState && viewOrContainerState.next;
}
viewOrContainerState = next;
}
}
export function insertView(container: LContainer, newView: LView, index: number): LView {
const state = container.data;
const children = state.children;
if (index > 0) {
// This is a new view, we need to add it to the children.
setViewNext(children[index - 1], newView);
}
if (index < children.length && children[index].data.id !== newView.data.id) {
// View ID change replace the view.
setViewNext(newView, children[index]);
children.splice(index, 0, newView);
} else if (index >= children.length) {
children.push(newView);
}
if (state.nextIndex <= index) {
state.nextIndex++;
}
// If the container's renderParent is null, we know that it is a root node of its own parent view
// and we should wait until that parent processes its nodes (otherwise, we will insert this view's
// nodes twice - once now and once when its parent inserts its views).
if (container.data.renderParent !== null) {
addRemoveViewFromContainer(
container, newView, true, findBeforeNode(index, state, container.native));
}
// Notify query that view has been inserted
container.query && container.query.insert(container, newView, index);
return newView;
}
export function removeView(container: LContainer, removeIndex: number): LView {
const children = container.data.children;
const viewNode = children[removeIndex];
if (removeIndex > 0) {
setViewNext(children[removeIndex - 1], viewNode.next);
}
children.splice(removeIndex, 1);
destroyViewTree(viewNode.data);
addRemoveViewFromContainer(container, viewNode, false);
// Notify query that view has been removed
container.query && container.query.remove(container, viewNode, removeIndex);
return viewNode;
}
export function setViewNext(view: LView, next: LView | null): void {
view.next = next;
view.data.next = next ? next.data : null;
}
export function getParentState(
state: ViewOrContainerState, rootView: ViewState): ViewOrContainerState|null {
let node;
if ((node = (state as ViewState) !.node) &&
(node.flags & LNodeFlags.TYPE_MASK) === LNodeFlags.View) {
// if it's an embedded view, the state needs to go up to the container, in case the
// container has a next
return node.parent !.data as any;
} else {
// otherwise, use parent view for containers or component views
return state.parent === rootView ? null : state.parent;
}
}
/** Removes all listeners and call all onDestroys in a given view. */
function cleanUpView(viewState: ViewState): void {
if (!viewState.cleanup) return;
const cleanup = viewState.cleanup !;
for (let i = 0; i < cleanup.length - 1; i += 2) {
if (typeof cleanup[i] === 'string') {
cleanup ![i + 1].removeEventListener(cleanup[i], cleanup[i + 2], cleanup[i + 3]);
i += 2;
} else {
cleanup[i].call(cleanup[i + 1]);
}
}
viewState.cleanup = null;
}
export function appendChild(parent: LNode, child: RNode | null, currentView: ViewState): boolean {
// Only add native child element to parent element if the parent element is regular Element.
// If parent is:
// - Regular element => add child
// - Component host element =>
// - Current View, and parent view same => content => don't add -> parent component will
// re-project if needed.
// - Current View, and parent view different => view => add Child
// - View element => View's get added separately.
if (child !== null && (parent.flags & LNodeFlags.TYPE_MASK) === LNodeFlags.Element &&
(parent.view !==
currentView /* Crossing View Boundaries, it is Component, but add Element of View */
|| parent.data === null /* Regular Element. */)) {
// We only add element if not in View or not projected.
const renderer = currentView.renderer;
(renderer as Renderer3Fn).listen ?
(renderer as Renderer3Fn).appendChild !(parent.native !as RElement, child) :
parent.native !.appendChild(child);
return true;
}
return false;
}
export function insertChild(node: LNode, currentView: ViewState) {
const parent = node.parent !;
// Only add child element to parent element if the parent element is regular Element.
// If parent is:
// - Normal element => add child
// - Component element =>
// - Current View, and parent view same => content don't add -> parent component will
// re-project if needed.
// - Current View, and parent view different => view => add Child
// - View element => View's get added separately.
if ((parent.flags & LNodeFlags.TYPE_MASK) === LNodeFlags.Element &&
(parent.view !==
currentView /* Crossing View Boundaries, its Component, but add Element of View */
|| parent.data === null /* Regular Element. */)) {
// We only add element if not in View or not projected.
let sibling = node.next;
let nativeSibling: RNode|null = null;
while (sibling && (nativeSibling = sibling.native) === null) {
sibling = sibling.next;
}
const renderer = currentView.renderer;
(renderer as Renderer3Fn).listen ?
(renderer as Renderer3Fn).insertBefore !(parent.native !, node.native !, nativeSibling) :
parent.native !.insertBefore(node.native !, nativeSibling, false);
}
}
export function processProjectedNode(
projectedNodes: ProjectionState, node: LElement | LText | LContainer,
currentParent: LView | LElement, currentView: ViewState) {
if ((node.flags & LNodeFlags.TYPE_MASK) === LNodeFlags.Container &&
(currentParent.flags & LNodeFlags.TYPE_MASK) === LNodeFlags.Element &&
currentParent.data === null) {
// The node we are adding is a Container and we are adding it to Element
// which is not Component (no more re-projection). Assignee the final
// projection location.
const containerState = (node as LContainer).data;
containerState.renderParent = currentParent as LElement;
const views = containerState.children;
for (let i = 0; i < views.length; i++) {
addRemoveViewFromContainer(node as LContainer, views[i], true, null);
}
}
projectedNodes.push(node);
appendChild(currentParent, node.native, currentView);
}

View File

@ -0,0 +1,116 @@
/**
* @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 './ng_dev_mode';
import {assertNotNull} from './assert';
import {CSSSelector, CSSSelectorWithNegations, NodeBindings, SimpleCSSSelector} from './interfaces';
function isCssClassMatching(nodeClassAttrVal: string, cssClassToMatch: string): boolean {
const nodeClassesLen = nodeClassAttrVal.length;
const matchIndex = nodeClassAttrVal !.indexOf(cssClassToMatch);
const matchEndIdx = matchIndex + cssClassToMatch.length;
if (matchIndex === -1 // no match
|| (matchIndex > 0 && nodeClassAttrVal ![matchIndex - 1] !== ' ') // no space before
||
(matchEndIdx < nodeClassesLen && nodeClassAttrVal ![matchEndIdx] !== ' ')) // no space after
{
return false;
}
return true;
}
/**
* A utility function to match an Ivy node static data against a simple CSS selector
*
* @param {NodeBindings} node static data to match
* @param {SimpleCSSSelector} selector
* @returns {boolean}
*/
export function isNodeMatchingSimpleSelector(
lNodeStaticData: NodeBindings, selector: SimpleCSSSelector): boolean {
const noOfSelectorParts = selector.length;
ngDevMode && assertNotNull(selector[0], 'selector[0]');
const tagNameInSelector = selector[0];
// check tag tame
if (tagNameInSelector !== '' && tagNameInSelector !== lNodeStaticData.tagName) {
return false;
}
// short-circuit case where we are only matching on element's tag name
if (noOfSelectorParts === 1) {
return true;
}
// short-circuit case where an element has no attrs but a selector tries to match some
if (noOfSelectorParts > 1 && !lNodeStaticData.attrs) {
return false;
}
const attrsInNode = lNodeStaticData.attrs !;
for (let i = 1; i < noOfSelectorParts; i += 2) {
const attrNameInSelector = selector[i];
const attrIdxInNode = attrsInNode.indexOf(attrNameInSelector);
if (attrIdxInNode % 2 !== 0) { // attribute names are stored at even indexes
return false;
} else {
const attrValInSelector = selector[i + 1];
if (attrValInSelector !== '') {
// selector should also match on an attribute value
const attrValInNode = attrsInNode[attrIdxInNode + 1];
if (attrNameInSelector === 'class') {
// iterate over all the remaining items in the selector selector array = class names
for (i++; i < noOfSelectorParts; i++) {
if (!isCssClassMatching(attrValInNode, selector[i])) {
return false;
}
}
} else if (attrValInSelector !== attrValInNode) {
return false;
}
}
}
}
return true;
}
export function isNodeMatchingSelectorWithNegations(
lNodeStaticData: NodeBindings, selector: CSSSelectorWithNegations): boolean {
const positiveSelector = selector[0];
if (positiveSelector != null &&
!isNodeMatchingSimpleSelector(lNodeStaticData, positiveSelector)) {
return false;
}
// do we have any negation parts in this selector?
const negativeSelectors = selector[1];
if (negativeSelectors) {
for (let i = 0; i < negativeSelectors.length; i++) {
// if one of negative selectors matched than the whole selector doesn't match
if (isNodeMatchingSimpleSelector(lNodeStaticData, negativeSelectors[i])) {
return false;
}
}
}
return true;
}
export function isNodeMatchingSelector(
lNodeStaticData: NodeBindings, selector: CSSSelector): boolean {
for (let i = 0; i < selector.length; i++) {
if (isNodeMatchingSelectorWithNegations(lNodeStaticData, selector[i])) {
return true;
}
}
return false;
}

View File

@ -0,0 +1,198 @@
/**
* @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 {Type} from '../core';
import {diPublic, refreshComponent} from './instructions';
/**
* Definition of what a template rendering function should look like.
*/
export type ComponentTemplate<T> = {
(ctx: T, creationMode: boolean): void; ngData?: never;
};
export type EmbeddedTemplate<T> = (ctx: T) => void;
export interface ComponentType<T> extends Type<T> { ngComponentDef: ComponentDef<T>; }
export interface DirectiveType<T> extends Type<T> { ngDirectiveDef: DirectiveDef<T>; }
export const enum DirectiveDefFlags {ContentQuery = 0b10}
/**
* `DirectiveDef` is a compiled version of the Directive used by the renderer instructions.
*/
export interface DirectiveDef<T> {
/**
* Token representing the directive. Used by DI.
*/
type: Type<T>;
/** Function that makes a directive public to the DI system. */
diPublic: ((def: DirectiveDef<any>) => void)|null;
/**
* List of inputs which are part of the components public API.
*
* The key is minified property name whereas the value is the original unminified name.
*/
inputs: {[P in keyof T]: P};
/**
* List of outputs which are part of the components public API.
*
* The key is minified property name whereas the value is the original unminified name.=
*/
outputs: {[P in keyof T]: P};
/**
* List of methods which are part of the components public API.
*
* The key is minified property name whereas the value is the original unminified name.
*/
methods: {[P in keyof T]: P};
/**
* factory function used to create a new directive instance.
*
* NOTE: this property is short (1 char) because it is used in
* component templates which is sensitive to size.
*/
n(): T;
/**
* Refresh method. Used by the containing component to signal
* to the directive that it should be refreshed. (Directives
* usually call life cycle methods at this point.)
*
* NOTE: this property is short (1 char) because it is used in
* component templates which is sensitive to size.
*
* @param directiveIndex index of the directive in the containing template
* @param elementIndex index of an host element for a given directive.
*/
r(this: DirectiveDef<T>, directiveIndex: number, elementIndex: number): void;
}
export interface ComponentDef<T> extends DirectiveDef<T> {
/**
* Refresh method. Used by the containing component to signal
* to the directive that it should be refreshed. (Directives
* usually call life cycle methods at this point.)
*
* NOTE: this property is short (1 char) because it is used in
* component templates which is sensitive to size.
*
* @param directiveIndex index of the directive in the containing template
* @param elementIndex index of an host element for a given component.
*/
r(this: ComponentDef<T>, directiveIndex: number, elementIndex: number): void;
/**
* The tag name which should be used by the component.
*
* NOTE: only used with component directives.
*/
tag: string;
/**
* The View template of the component.
*
* NOTE: only used with component directives.
*/
template: ComponentTemplate<T>;
}
export interface DirectiveDefArgs<T> {
type: Type<T>;
factory: () => T;
refresh?: (this: DirectiveDef<T>, directiveIndex: number, elementIndex: number) => void;
inputs?: {[P in keyof T]?: string};
outputs?: {[P in keyof T]?: string};
methods?: {[P in keyof T]?: string};
features?: DirectiveDefFeature[];
}
export interface ComponentDefArgs<T> extends DirectiveDefArgs<T> {
tag: string;
template: ComponentTemplate<T>;
refresh?: (this: ComponentDef<T>, directiveIndex: number, elementIndex: number) => void;
features?: ComponentDefFeature[];
}
export type DirectiveDefFeature = <T>(directiveDef: DirectiveDef<T>) => void;
export type ComponentDefFeature = <T>(directiveDef: DirectiveDef<T>) => void;
/**
* Create a component definition object.
*
*
* # Example
* ```
* class MyDirective {
* // Generated by Angular Template Compiler
* // [Symbol] syntax will not be supported by TypeScript until v2.7
* static [COMPONENT_DEF_SYMBOL] = defineComponent({
* ...
* });
* }
* ```
*/
export function defineComponent<T>(componentDefinition: ComponentDefArgs<T>): ComponentDef<T> {
const def = <ComponentDef<any>>{
type: componentDefinition.type,
diPublic: null,
n: componentDefinition.factory,
tag: (componentDefinition as ComponentDefArgs<T>).tag || null !,
template: (componentDefinition as ComponentDefArgs<T>).template || null !,
r: componentDefinition.refresh || refreshComponent,
inputs: invertObject(componentDefinition.inputs),
outputs: invertObject(componentDefinition.outputs),
methods: invertObject(componentDefinition.methods),
};
const feature = componentDefinition.features;
feature && feature.forEach((fn) => fn(def));
return def;
}
export function NgOnChangesFeature<T>(definition: DirectiveDef<T>) {
// TODO: implement. See: https://app.asana.com/0/443577627818617/465170715764659
}
export function PublicFeature<T>(definition: DirectiveDef<T>) {
definition.diPublic = diPublic;
}
const EMPTY = {};
/** Swaps the keys and values of an object. */
function invertObject(obj: any): any {
if (obj == null) return EMPTY;
const newObj: any = {};
for (let minifiedKey in obj) {
newObj[obj[minifiedKey]] = minifiedKey;
}
return newObj;
}
/**
* Create a directive definition object.
*
* # Example
* ```
* class MyDirective {
* // Generated by Angular Template Compiler
* // [Symbol] syntax will not be supported by TypeScript until v2.7
* static [DIRECTIVE_DEF_SYMBOL] = defineDirective({
* ...
* });
* }
* ```
*/
export const defineDirective = defineComponent as<T>(directiveDefinition: DirectiveDefArgs<T>) =>
DirectiveDef<T>;

View File

@ -0,0 +1,210 @@
/**
* @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 {Observable} from 'rxjs/Observable';
import {QueryList as IQueryList, Type} from '../core';
import {assertNotNull} from './assert';
import {LContainer, LNode, LNodeFlags, LView, QueryState} from './interfaces';
/**
* A predicate which determines if a given element/directive should be included in the query
*/
export interface QueryPredicate<T> {
/**
* Next predicate
*/
next: QueryPredicate<any>|null;
/**
* Destination to which the value should be added.
*/
list: QueryList<T>;
/**
* If looking for directives than it contains the directive type.
*/
type: Type<T>|null;
/**
* If selector then contains the selector parts where:
* - even index:
* - `null`: represents a tag name
* - `"#""`: represents a reference name
* - `string`: name of the attribute
* - odd index:
* - `null`: any value will match
* - `string`: the value which mast match.
*/
selector: any[]|null;
/**
* Values which have been located.
*
* this is what builds up the `QueryList._valuesTree`.
*/
values: any[];
}
export class QueryState_ implements QueryState {
shallow: QueryPredicate<any>|null = null;
deep: QueryPredicate<any>|null = null;
constructor(deep?: QueryPredicate<any>) { this.deep = deep == null ? null : deep; }
track<T>(queryList: IQueryList<T>, predicate: Type<T>|any[], descend?: boolean): void {
// TODO(misko): This is not right. In case of inherited state, a calling track will incorrectly
// mutate parent.
if (descend) {
this.deep = createPredicate(this.deep, queryList, predicate);
} else {
this.shallow = createPredicate(this.shallow, queryList, predicate);
}
}
child(): QueryState|null {
if (this.deep === null) {
// if we don't have any deep queries than no need to track anything more.
return null;
}
if (this.shallow === null) {
// DeepQuery: We can reuse the current state if the child state would be same as current
// state.
return this;
} else {
// We need to create new state
return new QueryState_(this.deep);
}
}
add(node: LNode): void {
add(this.shallow, node);
add(this.deep, node);
}
insert(container: LContainer, view: LView, index: number): void {
throw new Error('Method not implemented.');
}
remove(container: LContainer, view: LView, index: number): void {
throw new Error('Method not implemented.');
}
}
function add(predicate: QueryPredicate<any>| null, node: LNode) {
while (predicate) {
const type = predicate.type;
if (type) {
const directives = node.view.directives;
const flags = node.flags;
for (let i = flags >> LNodeFlags.INDX_SHIFT,
ii = i + ((flags & LNodeFlags.SIZE_MASK) >> LNodeFlags.SIZE_SHIFT);
i < ii; i++) {
const def = directives[i << 1 | 1];
if (def.diPublic && def.type === type) {
predicate.values.push(directives[i << 1]);
}
}
}
predicate = predicate.next;
}
}
function createPredicate<T>(
previous: QueryPredicate<any>| null, queryList: QueryList<T>,
predicate: Type<T>| any[]): QueryPredicate<T> {
const isArray = Array.isArray(predicate);
const values = <any>[];
if ((queryList as any as QueryList_<T>)._valuesTree === null) {
(queryList as any as QueryList_<T>)._valuesTree = values;
}
return {
next: previous,
list: queryList,
type: isArray ? null : predicate as Type<T>,
selector: isArray ? predicate as any[] : null,
values: values
};
}
class QueryList_<T>/* implements IQueryList<T> */ {
dirty: boolean = false;
changes: Observable<T>;
get length(): number {
ngDevMode && assertNotNull(this._values, 'refreshed');
return this._values !.length;
}
get first(): T|null {
ngDevMode && assertNotNull(this._values, 'refreshed');
let values = this._values !;
return values.length ? values[0] : null;
}
get last(): T|null {
ngDevMode && assertNotNull(this._values, 'refreshed');
let values = this._values !;
return values.length ? values[values.length - 1] : null;
}
/** @internal */
_valuesTree: any[]|null = null;
/** @internal */
_values: T[]|null = null;
/** @internal */
_refresh(): boolean {
// TODO(misko): needs more logic to flatten tree.
if (this._values === null) {
this._values = this._valuesTree;
return true;
}
return false;
}
map<U>(fn: (item: T, index: number, array: T[]) => U): U[] {
throw new Error('Method not implemented.');
}
filter(fn: (item: T, index: number, array: T[]) => boolean): T[] {
throw new Error('Method not implemented.');
}
find(fn: (item: T, index: number, array: T[]) => boolean): T|undefined {
throw new Error('Method not implemented.');
}
reduce<U>(fn: (prevValue: U, curValue: T, curIndex: number, array: T[]) => U, init: U): U {
throw new Error('Method not implemented.');
}
forEach(fn: (item: T, index: number, array: T[]) => void): void {
throw new Error('Method not implemented.');
}
some(fn: (value: T, index: number, array: T[]) => boolean): boolean {
throw new Error('Method not implemented.');
}
toArray(): T[] {
ngDevMode && assertNotNull(this._values, 'refreshed');
return this._values !;
}
toString(): string { throw new Error('Method not implemented.'); }
reset(res: (any[]|T)[]): void { throw new Error('Method not implemented.'); }
notifyOnChanges(): void { throw new Error('Method not implemented.'); }
setDirty(): void { throw new Error('Method not implemented.'); }
destroy(): void { throw new Error('Method not implemented.'); }
}
// NOTE: this hack is here because IQueryList has private members and therefore
// it can't be implemented only extended.
export type QueryList<T> = IQueryList<T>;
export const QueryList: typeof IQueryList = QueryList_ as any;
export function refreshQuery(query: QueryList<any>): boolean {
return (query as any as QueryList_<any>)._refresh();
}

View File

@ -0,0 +1,129 @@
/**
* @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
*/
/**
* The goal here is to make sure that the browser DOM API is the Renderer.
* We do this by defining a subset of DOM API to be the renderer and than
* use that time for rendering.
*
* At runtime we can than use the DOM api directly, in server or web-worker
* it will be easy to implement such API.
*/
import {RendererStyleFlags2} from '../core';
import {ComponentDef} from './public_interfaces';
// TODO: cleanup once the code is merged in angular/angular
export enum RendererStyleFlags3 {
Important = 1 << 0,
DashCase = 1 << 1
}
export type Renderer3 = Renderer3oo | Renderer3Fn;
/**
* Object Oriented style of API needed to create elements and text nodes.
*/
export interface Renderer3oo {
createComment(data: string): RComment;
createElement(tagName: string): RElement;
createTextNode(data: string): RText;
querySelector(selectors: string): RElement|null;
}
/**
* Functional style of API needed to create elements and text nodes.
*/
export interface Renderer3Fn {
destroy(): void;
createElement(name: string, namespace?: string|null): RElement;
createComment(value: string): RComment;
createText(value: string): RText;
/**
* This property is allowed to be null / undefined,
* in which case the view engine won't call it.
* This is used as a performance optimization for production mode.
*/
destroyNode?: ((node: RNode) => void)|null;
appendChild(parent: RElement, newChild: RNode): void;
insertBefore(parent: RNode, newChild: RNode, refChild: RNode|null): void;
removeChild(parent: RElement, oldChild: RNode): void;
selectRootElement(selectorOrNode: string|any): RElement;
setAttribute(el: RElement, name: string, value: string, namespace?: string|null): void;
removeAttribute(el: RElement, name: string, namespace?: string|null): void;
addClass(el: RElement, name: string): void;
removeClass(el: RElement, name: string): void;
setStyle(
el: RElement, style: string, value: any,
flags?: RendererStyleFlags2|RendererStyleFlags3): void;
removeStyle(el: RElement, style: string, flags?: RendererStyleFlags2|RendererStyleFlags3): void;
setProperty(el: RElement, name: string, value: any): void;
setValue(node: RText, value: string): void;
// TODO(misko): Deprecate in favor of addEventListener/removeEventListener
listen(target: RNode, eventName: string, callback: (event: any) => boolean | void): () => void;
}
export interface RendererFactory3 {
createRenderer(hostElement: RElement, componentDef: ComponentDef<any>): Renderer3;
begin?(): void;
end?(): void;
}
/**
* Subset of API needed for appending elements and text nodes.
*/
export interface RNode {
removeChild(oldChild: RNode): void;
/**
* Insert a child node.
*
* Used exclusively for adding View root nodes into ViewAnchor location.
*/
insertBefore(newChild: RNode, refChild: RNode|null, isViewRoot: boolean): void;
/**
* Append a child node.
*
* Used exclusively for building up DOM which are static (ie not View roots)
*/
appendChild(newChild: RNode): RNode;
}
/**
* Subset of API needed for writing attributes, properties, and setting up
* listeners on Element.
*/
export interface RElement extends RNode {
style: RCSSStyleDeclaration;
classList: RDOMTokenList;
setAttribute(name: string, value: string): void;
removeAttribute(name: string): void;
setAttributeNS(namespaceURI: string, qualifiedName: string, value: string): void;
addEventListener(type: string, listener: EventListener, useCapture?: boolean): void;
removeEventListener(type: string, listener?: EventListener, options?: boolean): void;
setProperty?(name: string, value: any): void;
}
export interface RCSSStyleDeclaration {
removeProperty(propertyName: string): string;
setProperty(propertyName: string, value: string|null, priority?: string): void;
}
export interface RDOMTokenList {
add(token: string): void;
remove(token: string): void;
}
export interface RText extends RNode { textContent: string|null; }
export interface RComment extends RNode {}

View File

@ -0,0 +1,23 @@
/**
* @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
*/
/**
* Must use this method for CD (instead of === ) since NaN !== NaN
*/
export function isDifferent(a: any, b: any): boolean {
// NaN is the only value that is not equal to itself so the first
// test checks if both a and b are not NaN
return !(a !== a && b !== b) && a !== b;
}
export function stringify(value: any): string {
if (typeof value == 'function') return value.name || value;
if (typeof value == 'string') return value;
if (value == null) return '';
return '' + value;
}