Skip to content
This repository has been archived by the owner on Feb 22, 2018. It is now read-only.

Commit

Permalink
feat(probe): Access injector, scope, directives from REPL
Browse files Browse the repository at this point in the history
Add global methods to make it easier to debug Angular application from
the browser's REPL, unit or end-to-end tests.

Closes #305
  • Loading branch information
mhevery committed Nov 27, 2013
1 parent 559761b commit 70c3e8d
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 8 deletions.
1 change: 1 addition & 0 deletions karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module.exports = function(config) {
// all tests must be 'included', but all other libraries must be 'served' and
// optionally 'watched' only.
files: [
'test/jasmine_syntax.dart',
'test/*.dart',
'test/**/*_spec.dart',
'test/config/filter_tests.dart',
Expand Down
3 changes: 3 additions & 0 deletions lib/angular.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
library angular;

import 'dart:html' as dom;
import 'dart:js';
import 'package:di/di.dart';
import 'package:di/dynamic_injector.dart';

Expand All @@ -20,6 +21,7 @@ import 'package:angular/directive/module.dart';
import 'package:angular/filter/module.dart';
import 'package:angular/perf/module.dart';
import 'package:angular/routing/module.dart';
import 'package:js/js.dart' as js;

export 'package:di/di.dart';
export 'package:angular/core/module.dart';
Expand All @@ -30,3 +32,4 @@ export 'package:angular/filter/module.dart';
export 'package:angular/routing/module.dart';

part 'bootstrap.dart';
part 'introspection.dart';
2 changes: 2 additions & 0 deletions lib/bootstrap.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class AngularModule extends Module {
install(new NgRoutingModule());

type(MetadataExtractor);
value(Expando, _elementExpando);
}
}

Expand Down Expand Up @@ -62,6 +63,7 @@ Injector ngBootstrap({
dom.Element element: null,
String selector: '[ng-app]',
Injector injectorFactory(List<Module> modules): _defaultInjectorFactory}) {
_publishToJavaScript();

var ngModules = [new AngularModule()];
if (module != null) ngModules.add(module);
Expand Down
22 changes: 21 additions & 1 deletion lib/core_dom/block_factory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@ class BlockFactory {
final List directivePositions;
final List<dom.Node> templateElements;
final Profiler _perf;
final Expando _expando;


BlockFactory(this.templateElements, this.directivePositions, this._perf);
BlockFactory(this.templateElements, this.directivePositions, this._perf, Expando this._expando);

BoundBlockFactory bind(Injector injector) {
return new BoundBlockFactory(this, injector);
Expand Down Expand Up @@ -105,6 +106,7 @@ class BlockFactory {
Scope scope = parentInjector.get(Scope);
Map<Type, _ComponentFactory> fctrs;
var nodeAttrs = node is dom.Element ? new NodeAttrs(node) : null;
ElementProbe probe;

try {
if (directiveRefs == null || directiveRefs.length == 0) return parentInjector;
Expand Down Expand Up @@ -181,6 +183,7 @@ class BlockFactory {
nodeModule.factory(BlockFactory, blockFactory);
nodeModule.factory(BoundBlockFactory, boundBlockFactory);
nodeInjector = parentInjector.createChild([nodeModule]);
probe = _expando[node] = new ElementProbe(node, nodeInjector, scope);
} finally {
assert(_perf.stopTimer(timerId) != false);
}
Expand All @@ -190,6 +193,7 @@ class BlockFactory {
var linkMapTimer;
assert((linkTimer = _perf.startTimer('ng.block.link', ref.type)) != false);
var controller = nodeInjector.get(ref.type);
probe.directives.add(controller);
assert((linkMapTimer = _perf.startTimer('ng.block.link.map', ref.type)) != false);
var shadowScope = (fctrs != null && fctrs.containsKey(ref.type)) ? fctrs[ref.type].shadowScope : null;
if (ref.annotation.publishAs != null) {
Expand Down Expand Up @@ -381,3 +385,19 @@ String _html(obj) {
return obj.nodeName;
}
}

/**
* [ElementProbe] is attached to each [Element] in the DOM. Its sole purpose is to
* allow access to the [Injector], [Scope], and Directives for debugging and automated
* test purposes. The information here is not used by Angular in any way.
*
* SEE: [ngInjector], [ngScope], [ngDirectives]
*/
class ElementProbe {
final dom.Node element;
final Injector injector;
final Scope scope;
final directives = [];

ElementProbe(this.element, this.injector, this.scope);
}
8 changes: 5 additions & 3 deletions lib/core_dom/compiler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ class Compiler {
final DirectiveMap directives;
final Profiler _perf;
final Parser _parser;
final Expando _expando;

DirectiveSelector selector;

Compiler(DirectiveMap this.directives, Profiler this._perf, Parser this._parser) {
Compiler(DirectiveMap this.directives, Profiler this._perf, Parser this._parser,
Expando this._expando) {
selector = directiveSelectorFactory(directives);
}

Expand Down Expand Up @@ -90,7 +92,7 @@ class Compiler {
var directivePositions = _compileBlock(domCursor, transcludeCursor, transcludedDirectiveRefs);
if (directivePositions == null) directivePositions = [];

blockFactory = new BlockFactory(transcludeCursor.elements, directivePositions, _perf);
blockFactory = new BlockFactory(transcludeCursor.elements, directivePositions, _perf, _expando);
domCursor.index = domCursorIndex;

if (domCursor.isInstance()) {
Expand Down Expand Up @@ -120,7 +122,7 @@ class Compiler {
null);

var blockFactory = new BlockFactory(templateElements,
directivePositions == null ? [] : directivePositions, _perf);
directivePositions == null ? [] : directivePositions, _perf, _expando);

assert(_perf.stopTimer(timerId) != false);
return blockFactory;
Expand Down
88 changes: 88 additions & 0 deletions lib/introspection.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
part of angular;

/**
* A global write only variable which keeps track of objects attached to the elements.
* This is usefull for debugging AngularDart application from the browser's REPL.
*/
Expando _elementExpando = new Expando('element');

/**
* Return the closest [ElementProbe] object for a given [Element].
*
* NOTE: This global method is here to make it easier to debug Angular application from
* the browser's REPL, unit or end-to-end tests. The function is not intended to
* be called from Angular application.
*/
ElementProbe ngProbe(dom.Node node) {
while(node != null) {
var probe = _elementExpando[node];
if (probe != null) return probe;
node = node.parent;
}
return null;
}


/**
* Return the [Injector] associated with a current [Element].
*
* **NOTE**: This global method is here to make it easier to debug Angular application from
* the browser's REPL, unit or end-to-end tests. The function is not intended to be called
* from Angular application.
*/
Injector ngInjector(dom.Node node) => ngProbe(node).injector;


/**
* Return the [Scope] associated with a current [Element].
*
* **NOTE**: This global method is here to make it easier to debug Angular application from
* the browser's REPL, unit or end-to-end tests. The function is not intended to be called
* from Angular application.
*/
Scope ngScope(dom.Node node) => ngProbe(node).scope;


/**
* Return a List of directive controllers associated with a current [Element].
*
* **NOTE**: This global method is here to make it easier to debug Angular application from
* the browser's REPL, unit or end-to-end tests. The function is not intended to be called
* from Angular application.
*/
List<Object> ngDirectives(dom.Node node) {
ElementProbe probe = _elementExpando[node];
return probe == null ? [] : probe.directives;
}

_publishToJavaScript() {
js.context.ngProbe = (dom.Node node) => _jsProbe(ngProbe(node));
js.context.ngInjector = (dom.Node node) => _jsInjector(ngInjector(node));
js.context.ngScope = (dom.Node node) => _jsScope(ngScope(node));
}

JsObject _jsProbe(ElementProbe probe) {
return new JsObject.jsify({
"element": probe.element,
"injector": _jsInjector(probe.injector),
"scope": _jsScope(probe.scope),
"directives": probe.directives.map((directive) => _jsDirective(directive))
})..['_dart_'] = probe;
}

JsObject _jsInjector(Injector injector) {
return new JsObject.jsify({
"get": injector.get
})..['_dart_'] = injector;
}

JsObject _jsScope(Scope scope) {
return new JsObject.jsify({
"\$apply": scope.$apply,
"\$digest": scope.$digest,
"get": (name) => scope[name],
"set": (name, value) => scope[name] = value
})..['_dart_'] = scope;
}

_jsDirective(directive) => directive;
10 changes: 6 additions & 4 deletions test/core_dom/block_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,13 @@ main() {

describe('mutation', () {
var a, b;
var expando = new Expando();

beforeEach(inject((Injector injector, Profiler perf) {
$rootElement.html('<!-- anchor -->');
anchor = new BlockHole($rootElement.contents().eq(0));
a = (new BlockFactory($('<span>A</span>a'), [], perf))(injector);
b = (new BlockFactory($('<span>B</span>b'), [], perf))(injector);
a = (new BlockFactory($('<span>A</span>a'), [], perf, expando))(injector);
b = (new BlockFactory($('<span>B</span>b'), [], perf, expando))(injector);
}));


Expand Down Expand Up @@ -139,11 +140,12 @@ main() {
LoggerBlockDirective,
new NgDirective(children: NgAnnotation.TRANSCLUDE_CHILDREN, selector: 'foo'),
'');
directiveRef.blockFactory = new BlockFactory($('<b>text</b>'), [], perf);
directiveRef.blockFactory = new BlockFactory($('<b>text</b>'), [], perf, new Expando());
var outerBlockType = new BlockFactory(
$('<!--start--><!--end-->'),
[ 0, [ directiveRef ], null],
perf);
perf,
new Expando());

var outterBlock = outerBlockType(injector);
// The LoggerBlockDirective caused a BlockHole for innerBlockType to
Expand Down
16 changes: 16 additions & 0 deletions test/introspection_spec.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
library introspection_spec;

import '_specs.dart';

main() => describe('introspection', () {
it('should retrieve ElementProbe', inject((TestBed _) {
_.compile('<div ng-bind="true"></div>');
ElementProbe probe = ngProbe(_.rootElement);
expect(probe.injector.parent).toBe(_.injector);
expect(ngInjector(_.rootElement).parent).toBe(_.injector);
expect(probe.directives[0] is NgBindDirective).toBe(true);
expect(ngDirectives(_.rootElement)[0] is NgBindDirective).toBe(true);
expect(probe.scope).toBe(_.rootScope);
expect(ngScope(_.rootElement)).toBe(_.rootScope);
}));
});

0 comments on commit 70c3e8d

Please sign in to comment.