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

Commit 943d619

Browse files
committed
feat(compiler): support ScopeAware for decorators
Even though decorators can safely inject scope (even after the proposed context change), they might pick up ScopeAware from a base class shared with components. Instead of manually calling super.scope = scope, we support ScopeAware for decorators (similarly to Attach/DetachAware.) There is no performance reduction on the tree benchmark.
1 parent 97a87a1 commit 943d619

File tree

3 files changed

+37
-11
lines changed

3 files changed

+37
-11
lines changed

lib/core/scope.dart

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -110,28 +110,29 @@ class ScopeLocals implements Map {
110110
}
111111

112112
/**
113-
* When a [Component] or the root context class implements [ScopeAware] the scope setter will be
114-
* called to set the [Scope] on this component.
113+
* When a [Directive] or the root context class implements [ScopeAware] the scope
114+
* setter will be called to set the [Scope] on this component.
115115
*
116-
* Typically classes implementing [ScopeAware] will declare a `Scope scope` property which will get
117-
* initialized after the [Scope] is available. For this reason the `scope` property will not be
118-
* initialized during the execution of the constructor - it will be immediately after.
116+
* The order of calls is as follows:
117+
* - [Component] instance is created.
118+
* - [Scope] instance is created (taking [Component] instance as evaluation context).
119+
* - if [Component] is [ScopeAware], set scope method is called with scope instance.
119120
*
120-
* However, if you need to execute some code as soon as the scope is available you should implement
121-
* a `scope` setter:
121+
* [ScopeAware] is guaranteed to be called before [AttachAware] or [DetachAware] methods.
122122
*
123+
* Example:
123124
* @Component(...)
124125
* class MyComponent implements ScopeAware {
125126
* Watch watch;
126127
*
127128
* MyComponent(Dependency myDep) {
128-
* // It is an error to add a Scope / RootScope argument to the ctor and will result in a DI
129-
* // circular dependency error - the scope is never accessible in the class constructor
129+
* // It is an error to add a Scope argument to the ctor and will result in a DI
130+
* // circular dependency error - the scope has a dependency on the component instance.
130131
* }
131132
*
132133
* void set scope(Scope scope) {
133134
* // This setter gets called to initialize the scope
134-
* watch = scope.rootScope.watch("expression", (v, p) => ...);
135+
* watch = scope.watch("expression", (v, p) => ...);
135136
* }
136137
* }
137138
*/

lib/core_dom/element_binder.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,10 @@ class ElementBinder {
226226
_createAttrMappings(directive, scope, ref.mappings, nodeAttrs, tasks);
227227
}
228228

229+
// Component is handled in BoundComponentFactories with the correct scope.
230+
if (directive is ScopeAware && !(ref.annotation is Component)) {
231+
directive.scope = scope;
232+
}
229233
if (directive is AttachAware) {
230234
var taskId = (tasks != null) ? tasks.registerTask() : 0;
231235
Watch watch;

test/core_dom/compiler_spec.dart

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1065,12 +1065,26 @@ void main() {
10651065
expect(log.result()).toEqual('IncludeTransclude; SimpleTransclude');
10661066
}));
10671067

1068-
it('should call scope setter on ScopeAware components', async((TestBed _, Logger log) {
1068+
it('should call scope setter on ScopeAware components', async((TestBed _, Logger log, CompilerConfig config) {
10691069
var element = _.compile('<scope-aware-cmp></scope-aware-cmp>');
10701070

10711071
_.rootScope.apply();
10721072

10731073
expect(log.result()).toEqual('Scope set');
1074+
1075+
if (config.elementProbeEnabled) {
1076+
expect(ngInjector(element).get(ScopeAwareComponent).
1077+
scope.context['foo']).toEqual('bar');
1078+
}
1079+
}));
1080+
1081+
it('should call scope setter on ScopeAware decorators', async((TestBed _, Logger log) {
1082+
var element = _.compile('<div scope-aware-dec></div>');
1083+
1084+
_.rootScope.apply();
1085+
1086+
expect(log.result()).toEqual('Scope set');
1087+
expect(_.rootScope.context['foo']).toEqual('bar');
10741088
}));
10751089
});
10761090

@@ -1547,12 +1561,19 @@ class SameNameDecorator {
15471561
@Component(
15481562
selector: 'scope-aware-cmp'
15491563
)
1564+
@Decorator(
1565+
selector: '[scope-aware-dec]'
1566+
)
15501567
class ScopeAwareComponent implements ScopeAware {
15511568
Logger log;
1569+
Scope _scope;
15521570
ScopeAwareComponent(this.log) {}
15531571
void set scope(Scope scope) {
15541572
log('Scope set');
1573+
scope.context['foo'] = 'bar';
1574+
_scope = scope;
15551575
}
1576+
Scope get scope => _scope;
15561577
}
15571578

15581579
@Component(

0 commit comments

Comments
 (0)