feat(debug): replace DebugElement with new Debug DOM

Now, using `ng.probe(element)` in the browser console returns
a DebugElement when in dev mode.

`ComponentFixture#debugElement` also returns a new DebugElement.

Breaking Change:

This is a breaking change for unit tests. The API for the DebugElement
has changed. Now, there is a DebugElement or DebugNode for every node
in the DOM, not only nodes with an ElementRef. `componentViewChildren` is
removed, and `childNodes` is a list of ElementNodes corresponding to every
child in the DOM. `query` no longer takes a scope parameter, since
the entire rendered DOM is included in the `childNodes`.

Before:

```
componentFixture.debugElement.componentViewChildren[0];
```

After
```
// Depending on the DOM structure of your component, the
// index may have changed or the first component child
// may be a sub-child.
componentFixture.debugElement.children[0];
```

Before:

```
debugElement.query(By.css('div'), Scope.all());
```

After:

```
debugElement.query(By.css('div'));
```

Before:

```
componentFixture.debugElement.elementRef;
```

After:

```
componentFixture.elementRef;
```
This commit is contained in:
Julie Ralph
2016-01-13 21:35:21 -08:00
committed by Alex Eagle
parent ae7d2ab515
commit e1bf3d33f8
45 changed files with 1243 additions and 1220 deletions

View File

@ -15,7 +15,6 @@ import {ResolvedMetadataCache} from 'angular2/src/core/linker/resolved_metadata_
import {AppViewManager} from './linker/view_manager';
import {AppViewManager_} from "./linker/view_manager";
import {ViewResolver} from './linker/view_resolver';
import {AppViewListener} from './linker/view_listener';
import {DirectiveResolver} from './linker/directive_resolver';
import {PipeResolver} from './linker/pipe_resolver';
import {Compiler} from './linker/compiler';
@ -32,7 +31,6 @@ export const APPLICATION_COMMON_PROVIDERS: Array<Type | Provider | any[]> = CONS
APP_ID_RANDOM_PROVIDER,
ResolvedMetadataCache,
new Provider(AppViewManager, {useClass: AppViewManager_}),
AppViewListener,
ViewResolver,
new Provider(IterableDiffers, {useValue: defaultIterableDiffers}),
new Provider(KeyValueDiffers, {useValue: defaultKeyValueDiffers}),

View File

@ -1,248 +0,0 @@
import {Type, isPresent, isBlank} from 'angular2/src/facade/lang';
import {ListWrapper, MapWrapper, Predicate} from 'angular2/src/facade/collection';
import {unimplemented} from 'angular2/src/facade/exceptions';
import {AppElement} from 'angular2/src/core/linker/element';
import {AppView} from 'angular2/src/core/linker/view';
import {ElementRef, ElementRef_} from 'angular2/src/core/linker/element_ref';
/**
* A DebugElement contains information from the Angular compiler about an
* element and provides access to the corresponding ElementInjector and
* underlying DOM Element, as well as a way to query for children.
*
* A DebugElement can be obtained from a {@link ComponentFixture} or from an
* {@link ElementRef} via {@link inspectElement}.
*/
export abstract class DebugElement {
/**
* Return the instance of the component associated with this element, if any.
*/
get componentInstance(): any { return unimplemented(); };
/**
* Return the native HTML element for this DebugElement.
*/
get nativeElement(): any { return unimplemented(); };
/**
* Return an Angular {@link ElementRef} for this element.
*/
get elementRef(): ElementRef { return unimplemented(); };
/**
* Get the directive active for this element with the given index, if any.
*/
abstract getDirectiveInstance(directiveIndex: number): any;
/**
* Get child DebugElements from within the Light DOM.
*
* @return {DebugElement[]}
*/
get children(): DebugElement[] { return unimplemented(); };
/**
* Get the root DebugElement children of a component. Returns an empty
* list if the current DebugElement is not a component root.
*
* @return {DebugElement[]}
*/
get componentViewChildren(): DebugElement[] { return unimplemented(); };
/**
* Simulate an event from this element as if the user had caused
* this event to fire from the page.
*/
abstract triggerEventHandler(eventName: string, eventObj: Event): void;
/**
* Check whether the element has a directive with the given type.
*/
abstract hasDirective(type: Type): boolean;
/**
* Inject the given type from the element injector.
*/
abstract inject(type: Type): any;
/**
* Read a local variable from the element (e.g. one defined with `#variable`).
*/
abstract getLocal(name: string): any;
/**
* Return the first descendant TestElement matching the given predicate
* and scope.
*
* @param {Function: boolean} predicate
* @param {Scope} scope
*
* @return {DebugElement}
*/
query(predicate: Predicate<DebugElement>, scope: Function = Scope.all): DebugElement {
var results = this.queryAll(predicate, scope);
return results.length > 0 ? results[0] : null;
}
/**
* Return descendant TestElememts matching the given predicate
* and scope.
*
* @param {Function: boolean} predicate
* @param {Scope} scope
*
* @return {DebugElement[]}
*/
queryAll(predicate: Predicate<DebugElement>, scope: Function = Scope.all): DebugElement[] {
var elementsInScope: any[] = scope(this);
return elementsInScope.filter(predicate);
}
}
export class DebugElement_ extends DebugElement {
constructor(private _appElement: AppElement) { super(); }
get componentInstance(): any {
if (!isPresent(this._appElement)) {
return null;
}
return this._appElement.getComponent();
}
get nativeElement(): any { return this.elementRef.nativeElement; }
get elementRef(): ElementRef { return this._appElement.ref; }
getDirectiveInstance(directiveIndex: number): any {
return this._appElement.getDirectiveAtIndex(directiveIndex);
}
get children(): DebugElement[] {
return this._getChildElements(this._appElement.parentView, this._appElement);
}
get componentViewChildren(): DebugElement[] {
if (!isPresent(this._appElement.componentView)) {
// The current element is not a component.
return [];
}
return this._getChildElements(this._appElement.componentView, null);
}
triggerEventHandler(eventName: string, eventObj: Event): void {
this._appElement.parentView.triggerEventHandlers(eventName, eventObj,
this._appElement.proto.index);
}
hasDirective(type: Type): boolean {
if (!isPresent(this._appElement)) {
return false;
}
return this._appElement.hasDirective(type);
}
inject(type: Type): any {
if (!isPresent(this._appElement)) {
return null;
}
return this._appElement.get(type);
}
getLocal(name: string): any { return this._appElement.parentView.locals.get(name); }
/** @internal */
_getChildElements(view: AppView, parentAppElement: AppElement): DebugElement[] {
var els = [];
for (var i = 0; i < view.appElements.length; ++i) {
var appEl = view.appElements[i];
if (appEl.parent == parentAppElement) {
els.push(new DebugElement_(appEl));
var views = appEl.nestedViews;
if (isPresent(views)) {
views.forEach(
(nextView) => { els = els.concat(this._getChildElements(nextView, null)); });
}
}
}
return els;
}
}
/**
* Returns a {@link DebugElement} for an {@link ElementRef}.
*
* @param {ElementRef}: elementRef
* @return {DebugElement}
*/
export function inspectElement(elementRef: ElementRef): DebugElement {
return new DebugElement_((<ElementRef_>elementRef).internalElement);
}
/**
* Maps an array of {@link DebugElement}s to an array of native DOM elements.
*/
export function asNativeElements(arr: DebugElement[]): any[] {
return arr.map((debugEl) => debugEl.nativeElement);
}
/**
* Set of scope functions used with {@link DebugElement}'s query functionality.
*/
export class Scope {
/**
* Scope queries to both the light dom and view of an element and its
* children.
*
* ## Example
*
* {@example core/debug/ts/debug_element/debug_element.ts region='scope_all'}
*/
static all(debugElement: DebugElement): DebugElement[] {
var scope = [];
scope.push(debugElement);
debugElement.children.forEach(child => scope = scope.concat(Scope.all(child)));
debugElement.componentViewChildren.forEach(child => scope = scope.concat(Scope.all(child)));
return scope;
}
/**
* Scope queries to the light dom of an element and its children.
*
* ## Example
*
* {@example core/debug/ts/debug_element/debug_element.ts region='scope_light'}
*/
static light(debugElement: DebugElement): DebugElement[] {
var scope = [];
debugElement.children.forEach(child => {
scope.push(child);
scope = scope.concat(Scope.light(child));
});
return scope;
}
/**
* Scope queries to the view of an element of its children.
*
* ## Example
*
* {@example core/debug/ts/debug_element/debug_element.ts region='scope_view'}
*/
static view(debugElement: DebugElement): DebugElement[] {
var scope = [];
debugElement.componentViewChildren.forEach(child => {
scope.push(child);
scope = scope.concat(Scope.light(child));
});
return scope;
}
}

View File

@ -0,0 +1,171 @@
import {isPresent} from 'angular2/src/facade/lang';
import {Predicate} from 'angular2/src/facade/collection';
import {Injector} from 'angular2/src/core/di';
import {ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
import {RenderDebugInfo} from 'angular2/src/core/render/api';
export class EventListener { constructor(public name: string, public callback: Function){}; }
export class DebugNode {
nativeNode: any;
listeners: EventListener[];
parent: DebugElement;
providerTokens: any[];
locals: Map<string, any>;
injector: Injector;
componentInstance: any;
constructor(nativeNode: any, parent: DebugNode) {
this.nativeNode = nativeNode;
if (isPresent(parent) && parent instanceof DebugElement) {
parent.addChild(this);
} else {
this.parent = null;
}
this.listeners = [];
this.providerTokens = [];
}
setDebugInfo(info: RenderDebugInfo) {
this.injector = info.injector;
this.providerTokens = info.providerTokens;
this.locals = info.locals;
this.componentInstance = info.component;
}
inject(token: any): any { return this.injector.get(token); }
getLocal(name: string): any { return this.locals.get(name); }
}
export class DebugElement extends DebugNode {
name: string;
properties: Map<string, any>;
attributes: Map<string, any>;
childNodes: DebugNode[];
nativeElement: any;
constructor(nativeNode: any, parent: any) {
super(nativeNode, parent);
this.properties = new Map<string, any>();
this.attributes = new Map<string, any>();
this.childNodes = [];
this.nativeElement = nativeNode;
}
addChild(child: DebugNode) {
if (isPresent(child)) {
this.childNodes.push(child);
child.parent = this;
}
}
removeChild(child: DebugNode) {
var childIndex = this.childNodes.indexOf(child);
if (childIndex !== -1) {
child.parent = null;
this.childNodes.splice(childIndex, 1);
}
}
insertChildrenAfter(child: DebugNode, newChildren: DebugNode[]) {
var siblingIndex = this.childNodes.indexOf(child);
if (siblingIndex !== -1) {
var previousChildren = this.childNodes.slice(0, siblingIndex + 1);
var nextChildren = this.childNodes.slice(siblingIndex + 1);
this.childNodes =
ListWrapper.concat(ListWrapper.concat(previousChildren, newChildren), nextChildren);
for (var i = 0; i < newChildren.length; ++i) {
var newChild = newChildren[i];
if (isPresent(newChild.parent)) {
newChild.parent.removeChild(newChild);
}
newChild.parent = this;
}
}
}
query(predicate: Predicate<DebugElement>): DebugElement {
var results = this.queryAll(predicate);
return results.length > 0 ? results[0] : null;
}
queryAll(predicate: Predicate<DebugElement>): DebugElement[] {
var matches = [];
_queryElementChildren(this, predicate, matches);
return matches;
}
queryAllNodes(predicate: Predicate<DebugNode>): DebugNode[] {
var matches = [];
_queryNodeChildren(this, predicate, matches);
return matches;
}
get children(): DebugElement[] {
var children = [];
this.childNodes.forEach((node) => {
if (node instanceof DebugElement) {
children.push(node);
}
});
return children;
}
triggerEventHandler(eventName: string, eventObj: Event) {
this.listeners.forEach((listener) => {
if (listener.name == eventName) {
listener.callback(eventObj);
}
});
}
}
export function asNativeElements(debugEls: DebugElement[]): any {
return debugEls.map((el) => el.nativeElement);
}
function _queryElementChildren(element: DebugElement, predicate: Predicate<DebugElement>,
matches: DebugElement[]) {
element.childNodes.forEach(node => {
if (node instanceof DebugElement) {
if (predicate(node)) {
matches.push(node);
}
_queryElementChildren(node, predicate, matches);
}
});
}
function _queryNodeChildren(parentNode: DebugNode, predicate: Predicate<DebugNode>,
matches: DebugNode[]) {
if (parentNode instanceof DebugElement) {
parentNode.childNodes.forEach(node => {
if (predicate(node)) {
matches.push(node);
}
if (node instanceof DebugElement) {
_queryNodeChildren(node, predicate, matches);
}
});
}
}
// Need to keep the nodes in a global Map so that multiple angular apps are supported.
var _nativeNodeToDebugNode = new Map<any, DebugNode>();
export function getDebugNode(nativeNode: any): DebugNode {
return _nativeNodeToDebugNode.get(nativeNode);
}
export function getAllDebugNodes(): DebugNode[] {
return MapWrapper.values(_nativeNodeToDebugNode);
}
export function indexDebugNode(node: DebugNode) {
_nativeNodeToDebugNode.set(node.nativeNode, node);
}
export function removeDebugNodeFromIndex(node: DebugNode) {
_nativeNodeToDebugNode.delete(node.nativeNode);
}

View File

@ -0,0 +1,157 @@
import {isPresent} from 'angular2/src/facade/lang';
import {
Renderer,
RootRenderer,
RenderComponentType,
RenderDebugInfo
} from 'angular2/src/core/render/api';
import {
DebugNode,
DebugElement,
EventListener,
getDebugNode,
indexDebugNode,
removeDebugNodeFromIndex
} from 'angular2/src/core/debug/debug_node';
export class DebugDomRootRenderer implements RootRenderer {
constructor(private _delegate: RootRenderer) {}
renderComponent(componentProto: RenderComponentType): Renderer {
return new DebugDomRenderer(this, this._delegate.renderComponent(componentProto));
}
}
export class DebugDomRenderer implements Renderer {
constructor(private _rootRenderer: DebugDomRootRenderer, private _delegate: Renderer) {}
renderComponent(componentType: RenderComponentType): Renderer {
return this._rootRenderer.renderComponent(componentType);
}
selectRootElement(selector: string): any {
var nativeEl = this._delegate.selectRootElement(selector);
var debugEl = new DebugElement(nativeEl, null);
indexDebugNode(debugEl);
return nativeEl;
}
createElement(parentElement: any, name: string): any {
var nativeEl = this._delegate.createElement(parentElement, name);
var debugEl = new DebugElement(nativeEl, getDebugNode(parentElement));
debugEl.name = name;
indexDebugNode(debugEl);
return nativeEl;
}
createViewRoot(hostElement: any): any { return this._delegate.createViewRoot(hostElement); }
createTemplateAnchor(parentElement: any): any {
var comment = this._delegate.createTemplateAnchor(parentElement);
var debugEl = new DebugNode(comment, getDebugNode(parentElement));
indexDebugNode(debugEl);
return comment;
}
createText(parentElement: any, value: string): any {
var text = this._delegate.createText(parentElement, value);
var debugEl = new DebugNode(text, getDebugNode(parentElement));
indexDebugNode(debugEl);
return text;
}
projectNodes(parentElement: any, nodes: any[]) {
var debugParent = getDebugNode(parentElement);
if (isPresent(debugParent) && debugParent instanceof DebugElement) {
nodes.forEach((node) => { debugParent.addChild(getDebugNode(node)); });
}
return this._delegate.projectNodes(parentElement, nodes);
}
attachViewAfter(node: any, viewRootNodes: any[]) {
var debugNode = getDebugNode(node);
if (isPresent(debugNode)) {
var debugParent = debugNode.parent;
if (viewRootNodes.length > 0 && isPresent(debugParent)) {
var debugViewRootNodes = [];
viewRootNodes.forEach((rootNode) => debugViewRootNodes.push(getDebugNode(rootNode)));
debugParent.insertChildrenAfter(debugNode, debugViewRootNodes);
}
}
return this._delegate.attachViewAfter(node, viewRootNodes);
}
detachView(viewRootNodes: any[]) {
viewRootNodes.forEach((node) => {
var debugNode = getDebugNode(node);
if (isPresent(debugNode) && isPresent(debugNode.parent)) {
debugNode.parent.removeChild(debugNode);
}
});
return this._delegate.detachView(viewRootNodes);
}
destroyView(hostElement: any, viewAllNodes: any[]) {
viewAllNodes.forEach((node) => { removeDebugNodeFromIndex(getDebugNode(node)); });
return this._delegate.destroyView(hostElement, viewAllNodes);
}
listen(renderElement: any, name: string, callback: Function) {
var debugEl = getDebugNode(renderElement);
if (isPresent(debugEl)) {
debugEl.listeners.push(new EventListener(name, callback));
}
return this._delegate.listen(renderElement, name, callback);
}
listenGlobal(target: string, name: string, callback: Function): Function {
return this._delegate.listenGlobal(target, name, callback);
}
setElementProperty(renderElement: any, propertyName: string, propertyValue: any) {
var debugEl = getDebugNode(renderElement);
if (isPresent(debugEl) && debugEl instanceof DebugElement) {
debugEl.properties.set(propertyName, propertyValue);
}
return this._delegate.setElementProperty(renderElement, propertyName, propertyValue);
}
setElementAttribute(renderElement: any, attributeName: string, attributeValue: string) {
var debugEl = getDebugNode(renderElement);
if (isPresent(debugEl) && debugEl instanceof DebugElement) {
debugEl.attributes.set(attributeName, attributeValue);
}
return this._delegate.setElementAttribute(renderElement, attributeName, attributeValue);
}
/**
* Used only in debug mode to serialize property changes to comment nodes,
* such as <template> placeholders.
*/
setBindingDebugInfo(renderElement: any, propertyName: string, propertyValue: string) {
return this._delegate.setBindingDebugInfo(renderElement, propertyName, propertyValue);
}
/**
* Used only in development mode to set information needed by the DebugNode for this element.
*/
setElementDebugInfo(renderElement: any, info: RenderDebugInfo) {
var debugEl = getDebugNode(renderElement);
debugEl.setDebugInfo(info);
return this._delegate.setElementDebugInfo(renderElement, info);
}
setElementClass(renderElement: any, className: string, isAdd: boolean) {
return this._delegate.setElementClass(renderElement, className, isAdd);
}
setElementStyle(renderElement: any, styleName: string, styleValue: string) {
return this._delegate.setElementStyle(renderElement, styleName, styleValue);
}
invokeElementMethod(renderElement: any, methodName: string, args: any[]) {
return this._delegate.invokeElementMethod(renderElement, methodName, args);
}
setText(renderNode: any, text: string) { return this._delegate.setText(renderNode, text); }
}

View File

@ -27,7 +27,7 @@ import {
CONST_EXPR
} from 'angular2/src/facade/lang';
import {BaseException, WrappedException} from 'angular2/src/facade/exceptions';
import {Renderer, RootRenderer} from 'angular2/src/core/render/api';
import {Renderer, RootRenderer, RenderDebugInfo} from 'angular2/src/core/render/api';
import {ViewRef_, HostViewFactoryRef} from './view_ref';
import {ProtoPipes} from 'angular2/src/core/pipes/pipes';
import {camelCaseToDashCase} from 'angular2/src/core/render/util';
@ -119,6 +119,12 @@ export class AppView implements ChangeDispatcher {
(templateName, _) => { localsMap.set(templateName, null); });
for (var i = 0; i < appElements.length; i++) {
var appEl = appElements[i];
var providerTokens = [];
if (isPresent(appEl.proto.protoInjector)) {
for (var j = 0; j < appEl.proto.protoInjector.numberOfProviders; j++) {
providerTokens.push(appEl.proto.protoInjector.getProviderAtIndex(j).key.token);
}
}
StringMapWrapper.forEach(appEl.proto.directiveVariableBindings, (directiveIndex, name) => {
if (isBlank(directiveIndex)) {
localsMap.set(name, appEl.nativeElement);
@ -126,6 +132,9 @@ export class AppView implements ChangeDispatcher {
localsMap.set(name, appEl.getDirectiveAtIndex(directiveIndex));
}
});
this.renderer.setElementDebugInfo(
appEl.nativeElement, new RenderDebugInfo(appEl.getInjector(), appEl.getComponent(),
providerTokens, localsMap));
}
var parentLocals = null;
if (this.proto.type !== ViewType.COMPONENT) {

View File

@ -1,11 +0,0 @@
import {Injectable} from 'angular2/src/core/di';
import * as viewModule from './view';
/**
* Listener for view creation / destruction.
*/
@Injectable()
export class AppViewListener {
onViewCreated(view: viewModule.AppView) {}
onViewDestroyed(view: viewModule.AppView) {}
}

View File

@ -22,7 +22,6 @@ import {
} from './view_ref';
import {ViewContainerRef} from './view_container_ref';
import {TemplateRef, TemplateRef_} from './template_ref';
import {AppViewListener} from './view_listener';
import {RootRenderer, RenderComponentType} from 'angular2/src/core/render/api';
import {wtfCreateScope, wtfLeave, WtfScopeFn} from '../profile/profile';
import {APP_ID} from 'angular2/src/core/application_tokens';
@ -185,10 +184,7 @@ export abstract class AppViewManager {
export class AppViewManager_ extends AppViewManager {
private _nextCompTypeId: number = 0;
constructor(private _renderer: RootRenderer, private _viewListener: AppViewListener,
@Inject(APP_ID) private _appId: string) {
super();
}
constructor(private _renderer: RootRenderer, @Inject(APP_ID) private _appId: string) { super(); }
getViewContainer(location: ElementRef): ViewContainerRef {
return (<ElementRef_>location).internalElement.getViewContainerRef();
@ -316,10 +312,10 @@ export class AppViewManager_ extends AppViewManager {
}
/** @internal */
onViewCreated(view: AppView) { this._viewListener.onViewCreated(view); }
onViewCreated(view: AppView) {}
/** @internal */
onViewDestroyed(view: AppView) { this._viewListener.onViewDestroyed(view); }
onViewDestroyed(view: AppView) {}
/** @internal */
createRenderComponentType(encapsulation: ViewEncapsulation,

View File

@ -1,10 +1,16 @@
import {ViewEncapsulation} from 'angular2/src/core/metadata/view';
import {Injector} from 'angular2/src/core/di/injector';
export class RenderComponentType {
constructor(public id: string, public encapsulation: ViewEncapsulation,
public styles: Array<string | any[]>) {}
}
export class RenderDebugInfo {
constructor(public injector: Injector, public component: any, public providerTokens: any[],
public locals: Map<string, any>) {}
}
export interface ParentRenderer { renderComponent(componentType: RenderComponentType): Renderer; }
export abstract class Renderer implements ParentRenderer {
@ -42,6 +48,8 @@ export abstract class Renderer implements ParentRenderer {
*/
abstract setBindingDebugInfo(renderElement: any, propertyName: string, propertyValue: string);
abstract setElementDebugInfo(renderElement: any, info: RenderDebugInfo);
abstract setElementClass(renderElement: any, className: string, isAdd: boolean);
abstract setElementStyle(renderElement: any, styleName: string, styleValue: string);

View File

@ -30,12 +30,12 @@ import {BrowserDomAdapter} from './browser/browser_adapter';
import {BrowserGetTestability} from 'angular2/src/platform/browser/testability';
import {wtfInit} from 'angular2/src/core/profile/wtf_init';
import {EventManager, EVENT_MANAGER_PLUGINS} from "angular2/src/platform/dom/events/event_manager";
import {ELEMENT_PROBE_PROVIDERS} from 'angular2/platform/common_dom';
export {DOCUMENT} from 'angular2/src/platform/dom/dom_tokens';
export {Title} from 'angular2/src/platform/browser/title';
export {
DebugElementViewListener,
ELEMENT_PROBE_PROVIDERS,
ELEMENT_PROBE_BINDINGS,
ELEMENT_PROBE_PROVIDERS_PROD_MODE,
inspectNativeElement,
By
} from 'angular2/platform/common_dom';
@ -84,7 +84,8 @@ export const BROWSER_APP_COMMON_PROVIDERS: Array<any /*Type | Provider | any[]*/
Testability,
BrowserDetails,
AnimationBuilder,
EventManager
EventManager,
ELEMENT_PROBE_PROVIDERS
]);
export function initDomAdapter() {

View File

@ -39,6 +39,6 @@ export class By {
* {@example platform/dom/debug/ts/by/by.ts region='by_directive'}
*/
static directive(type: Type): Predicate<DebugElement> {
return (debugElement) => { return debugElement.hasDirective(type); };
return (debugElement) => { return debugElement.providerTokens.indexOf(type) !== -1; };
}
}

View File

@ -1,89 +0,0 @@
import {CONST_EXPR, isPresent, NumberWrapper, StringWrapper} from 'angular2/src/facade/lang';
import {MapWrapper, Map, ListWrapper} from 'angular2/src/facade/collection';
import {Injectable, provide, Provider} from 'angular2/src/core/di';
import {AppViewListener} from 'angular2/src/core/linker/view_listener';
import {AppView} from 'angular2/src/core/linker/view';
import {DOM} from 'angular2/src/platform/dom/dom_adapter';
import {DebugElement, DebugElement_} from 'angular2/src/core/debug/debug_element';
const NG_ID_PROPERTY = 'ngid';
const INSPECT_GLOBAL_NAME = 'ng.probe';
const NG_ID_SEPARATOR = '#';
// Need to keep the views in a global Map so that multiple angular apps are supported
var _allIdsByView = new Map<AppView, number>();
var _allViewsById = new Map<number, AppView>();
var _nextId = 0;
function _setElementId(element, indices: number[]) {
if (isPresent(element) && DOM.isElementNode(element)) {
DOM.setData(element, NG_ID_PROPERTY, indices.join(NG_ID_SEPARATOR));
}
}
function _getElementId(element): number[] {
var elId = DOM.getData(element, NG_ID_PROPERTY);
if (isPresent(elId)) {
return elId.split(NG_ID_SEPARATOR).map(partStr => NumberWrapper.parseInt(partStr, 10));
} else {
return null;
}
}
/**
* Returns a {@link DebugElement} for the given native DOM element, or
* null if the given native element does not have an Angular view associated
* with it.
*/
export function inspectNativeElement(element): DebugElement {
var elId = _getElementId(element);
if (isPresent(elId)) {
var view = _allViewsById.get(elId[0]);
if (isPresent(view)) {
return new DebugElement_(view.appElements[elId[1]]);
}
}
return null;
}
@Injectable()
export class DebugElementViewListener implements AppViewListener {
constructor() { DOM.setGlobalVar(INSPECT_GLOBAL_NAME, inspectNativeElement); }
onViewCreated(view: AppView) {
var viewId = _nextId++;
_allViewsById.set(viewId, view);
_allIdsByView.set(view, viewId);
for (var i = 0; i < view.appElements.length; i++) {
var el = view.appElements[i];
_setElementId(el.nativeElement, [viewId, i]);
}
}
onViewDestroyed(view: AppView) {
var viewId = _allIdsByView.get(view);
_allIdsByView.delete(view);
_allViewsById.delete(viewId);
}
}
/**
* Providers which support debugging Angular applications (e.g. via `ng.probe`).
*
* ## Example
*
* {@example platform/dom/debug/ts/debug_element_view_listener/providers.ts region='providers'}
*/
export const ELEMENT_PROBE_PROVIDERS: any[] = CONST_EXPR([
DebugElementViewListener,
CONST_EXPR(new Provider(AppViewListener, {useExisting: DebugElementViewListener})),
]);
/**
* Use {@link ELEMENT_PROBE_PROVIDERS}.
*
* @deprecated
*/
export const ELEMENT_PROBE_BINDINGS = ELEMENT_PROBE_PROVIDERS;

View File

@ -0,0 +1,42 @@
import {CONST_EXPR, assertionsEnabled, isPresent} from 'angular2/src/facade/lang';
import {Injectable, provide, Provider} from 'angular2/src/core/di';
import {DOM} from 'angular2/src/platform/dom/dom_adapter';
import {DebugNode, getDebugNode} from 'angular2/src/core/debug/debug_node';
import {DomRootRenderer} from 'angular2/src/platform/dom/dom_renderer';
import {RootRenderer} from 'angular2/core';
import {DebugDomRootRenderer} from 'angular2/src/core/debug/debug_renderer';
const INSPECT_GLOBAL_NAME = 'ng.probe';
/**
* Returns a {@link DebugElement} for the given native DOM element, or
* null if the given native element does not have an Angular view associated
* with it.
*/
export function inspectNativeElement(element): DebugNode {
return getDebugNode(element);
}
function _createConditionalRootRenderer(rootRenderer) {
if (assertionsEnabled()) {
return _createRootRenderer(rootRenderer);
}
return rootRenderer;
}
function _createRootRenderer(rootRenderer) {
DOM.setGlobalVar(INSPECT_GLOBAL_NAME, inspectNativeElement);
return new DebugDomRootRenderer(rootRenderer);
}
/**
* Providers which support debugging Angular applications (e.g. via `ng.probe`).
*/
export const ELEMENT_PROBE_PROVIDERS: any[] = CONST_EXPR([
new Provider(RootRenderer,
{useFactory: _createConditionalRootRenderer, deps: [DomRootRenderer]})
]);
export const ELEMENT_PROBE_PROVIDERS_PROD_MODE: any[] = CONST_EXPR(
[new Provider(RootRenderer, {useFactory: _createRootRenderer, deps: [DomRootRenderer]})]);

View File

@ -14,7 +14,12 @@ import {
import {BaseException, WrappedException} from 'angular2/src/facade/exceptions';
import {DomSharedStylesHost} from './shared_styles_host';
import {Renderer, RootRenderer, RenderComponentType} from 'angular2/core';
import {
Renderer,
RootRenderer,
RenderComponentType,
RenderDebugInfo
} from 'angular2/src/core/render/api';
import {EventManager} from './events/event_manager';
@ -201,6 +206,8 @@ export class DomRenderer implements Renderer {
}
}
setElementDebugInfo(renderElement: any, info: RenderDebugInfo) {}
setElementClass(renderElement: any, className: string, isAdd: boolean): void {
if (isAdd) {
DOM.addClass(renderElement, className);

View File

@ -1,11 +1,11 @@
import {
ComponentRef,
DebugElement,
DirectiveResolver,
DynamicComponentLoader,
Injector,
Injectable,
ViewMetadata,
ElementRef,
EmbeddedViewRef,
ViewResolver,
provide
@ -23,7 +23,7 @@ import {el} from './utils';
import {DOCUMENT} from 'angular2/src/platform/dom/dom_tokens';
import {DOM} from 'angular2/src/platform/dom/dom_adapter';
import {DebugElement_} from 'angular2/src/core/debug/debug_element';
import {DebugNode, DebugElement, getDebugNode} from 'angular2/src/core/debug/debug_node';
/**
@ -45,6 +45,11 @@ export abstract class ComponentFixture {
*/
nativeElement: any;
/**
* The ElementRef for the element at the root of the component.
*/
elementRef: ElementRef;
/**
* Trigger a change detection cycle for the component.
*/
@ -66,7 +71,9 @@ export class ComponentFixture_ extends ComponentFixture {
constructor(componentRef: ComponentRef) {
super();
this._componentParentView = (<ViewRef_>componentRef.hostView).internalView;
this.debugElement = new DebugElement_(this._componentParentView.appElements[0]);
this.elementRef = this._componentParentView.appElements[0].ref;
this.debugElement = <DebugElement>getDebugNode(
this._componentParentView.rootNodesOrAppElements[0].nativeElement);
this.componentInstance = this.debugElement.componentInstance;
this.nativeElement = this.debugElement.nativeElement;
this._componentRef = componentRef;

View File

@ -1,4 +1,9 @@
import {Renderer, RootRenderer, RenderComponentType} from 'angular2/src/core/render/api';
import {
Renderer,
RootRenderer,
RenderComponentType,
RenderDebugInfo
} from 'angular2/src/core/render/api';
import {
ClientMessageBroker,
ClientMessageBrokerFactory,
@ -186,6 +191,8 @@ export class WebWorkerRenderer implements Renderer, RenderStoreObject {
]);
}
setElementDebugInfo(renderElement: any, info: RenderDebugInfo) {}
setElementClass(renderElement: any, className: string, isAdd: boolean) {
this._runOnService('setElementClass', [
new FnArg(renderElement, RenderStoreObject),