diff --git a/lib/core/scope.dart b/lib/core/scope.dart index 914c02638..3f67ff552 100644 --- a/lib/core/scope.dart +++ b/lib/core/scope.dart @@ -109,6 +109,36 @@ class ScopeLocals implements Map { dynamic putIfAbsent(key, fn) => _scope.putIfAbsent(key, fn); } +/** + * When a [Component] or the root context class implements [ScopeAware] the scope setter will be + * called to set the [Scope] on this component. + * + * Typically classes implementing [ScopeAware] will declare a `Scope scope` property which will get + * initialized after the [Scope] is available. For this reason the `scope` property will not be + * initialized during the execution of the constructor - it will be immediately after. + * + * However, if you need to execute some code as soon as the scope is available you should implement + * a `scope` setter: + * + * @Component(...) + * class MyComponent implements ScopeAware { + * Watch watch; + * + * MyComponent(Dependency myDep) { + * // It is an error to add a Scope / RootScope argument to the ctor and will result in a DI + * // circular dependency error - the scope is never accessible in the class constructor + * } + * + * void set scope(Scope scope) { + * // This setter gets called to initialize the scope + * watch = scope.rootScope.watch("expression", (v, p) => ...); + * } + * } + */ +abstract class ScopeAware { + void set scope(Scope scope); +} + /** * [Scope] represents a collection of [watch]es [observer]s, and a [context] for the watchers, * observers and [eval]uations. Scopes structure loosely mimics the DOM structure. Scopes and diff --git a/lib/core_dom/shadow_dom_component_factory.dart b/lib/core_dom/shadow_dom_component_factory.dart index 7c8a48c9a..922516983 100644 --- a/lib/core_dom/shadow_dom_component_factory.dart +++ b/lib/core_dom/shadow_dom_component_factory.dart @@ -174,6 +174,7 @@ class BoundShadowDomComponentFactory implements BoundComponentFactory { } var controller = shadowInjector.getByKey(_ref.typeKey); + if (controller is ScopeAware) controller.scope = shadowScope; BoundComponentFactory._setupOnShadowDomAttach(controller, templateLoader, shadowScope); shadowScope.context[_component.publishAs] = controller; diff --git a/lib/core_dom/transcluding_component_factory.dart b/lib/core_dom/transcluding_component_factory.dart index 29f8f2663..5cc1c652c 100644 --- a/lib/core_dom/transcluding_component_factory.dart +++ b/lib/core_dom/transcluding_component_factory.dart @@ -153,6 +153,7 @@ class BoundTranscludingComponentFactory implements BoundComponentFactory { var controller = childInjector.getByKey(_ref.typeKey); shadowScope.context[component.publishAs] = controller; + if (controller is ScopeAware) controller.scope = shadowScope; BoundComponentFactory._setupOnShadowDomAttach(controller, templateLoader, shadowScope); return controller; }; diff --git a/test/angular_spec.dart b/test/angular_spec.dart index f3ee12120..3d1985fba 100644 --- a/test/angular_spec.dart +++ b/test/angular_spec.dart @@ -151,6 +151,7 @@ main() { "angular.core_internal.Interpolate", "angular.core_internal.RootScope", "angular.core_internal.Scope", + "angular.core_internal.ScopeAware", "angular.core_internal.ScopeDigestTTL", "angular.core_internal.ScopeEvent", "angular.core_internal.ScopeStats", diff --git a/test/core_dom/compiler_spec.dart b/test/core_dom/compiler_spec.dart index 6e637fa29..4aa1c4481 100644 --- a/test/core_dom/compiler_spec.dart +++ b/test/core_dom/compiler_spec.dart @@ -78,7 +78,8 @@ void main() { ..bind(MyChildController) ..bind(MyScopeModifyingController) ..bind(SameNameDecorator) - ..bind(SameNameTransclude); + ..bind(SameNameTransclude) + ..bind(ScopeAwareComponent); }); beforeEach((TestBed tb) => _ = tb); @@ -928,6 +929,14 @@ void main() { expect(element.text).toContain('my data'); }); + + it('should call scope setter on ScopeAware components', async((TestBed _, Logger log) { + var element = _.compile(''); + + _.rootScope.apply(); + + expect(log.result()).toEqual('Scope set'); + })); }); @@ -1392,3 +1401,14 @@ class SameNameDecorator { scope.context['sameDecorator'] = this; } } + +@Component( + selector: 'scope-aware-cmp' +) +class ScopeAwareComponent implements ScopeAware { + Logger log; + ScopeAwareComponent(this.log) {} + void set scope(Scope scope) { + log('Scope set'); + } +}