Skip to content

Instantly share code, notes, and snippets.

@sirbarrence
Created September 5, 2015 20:41
Show Gist options
  • Save sirbarrence/fd96e680b8db0d4ba27a to your computer and use it in GitHub Desktop.
Save sirbarrence/fd96e680b8db0d4ba27a to your computer and use it in GitHub Desktop.
diff --git a/rxDecorateDirective.js b/rxDecorateDirective.js
index 56912ca..1ecb8b5 100644
--- a/rxDecorateDirective.js
+++ b/rxDecorateDirective.js
@@ -1,4 +1,7 @@
+'use strict';
+
function rxDecorateDirective($provide, directiveName) {
+ // Duck-typing function lifted from the rx.js source.
function isObservable(obj) {
return obj && typeof obj.subscribe === 'function';
}
@@ -16,76 +19,65 @@ function rxDecorateDirective($provide, directiveName) {
return function postLink(scope, iElement, iAttrs) {
var originalScope = scope;
- var originalLinkArgs = arguments;
-
- // Make a new child scope that we'll pass along to the original directive link
- // function instead of the actual scope. If we receive an Observable to subscribe
- // to, we'll be able to update the child scope the original directive is watching
- // without modifying the original scope. If we receive anything else, we simply let
- // prototypal inheritance propagate the change from the original scope to the child
- // scope.
- var childScope = originalScope.$new(false);
+ var linkArgs = arguments;
- // Replace `originalScope` with `childScope` in the link function args, used when
- // calling the original link function with `apply()` below.
- originalLinkArgs[0] = childScope;
-
- // Name of the scope property containing the thing we need to watch.
- var propertyName = iAttrs[directiveName];
+ // Expression in the directive's attribute value we need to watch.
+ var attrExpression = iAttrs[directiveName];
// If the current scope property value is null or undefined, watch for the first
- // value that isn't. Otherwise, go ahead and use the current value.
- if (isUndefinedOrNull(originalScope[propertyName])) {
- originalScope.$toObservable(propertyName)
- .pluck('newValue')
- .skipWhile(isUndefinedOrNull)
- .first()
- .subscribe(onFirstUsefulValue);
- } else {
- onFirstUsefulValue();
- }
+ // value that isn't, in case it's an Observable.
+ originalScope.$toObservable(attrExpression)
+ .pluck('newValue')
+ .skipWhile(function isUndefinedOrNull(value) {
+ return typeof value === 'undefined' || value === null;
+ })
+ .first()
+ .subscribe(function onFirstUsefulValue(firstValue) {
+ // If we're bound to an Observable, subscribe to it.
+ if (isObservable(firstValue)) {
+ // The property name we want the original directive linking function to
+ // set a watcher on.
+ var RX_VALUE_SCOPE_PROPERTY = '$$rxValue';
+
+ // Get a shared version of the value stream we're bound to, since we'll
+ // be making multiple subscriptions.
+ var valueStream = firstValue.share();
- // A stream of the new values that the watcher might produce if we need to use it.
- //var watchedValueStream = originalScope.$toObservable(propertyName)
- // .pluck('newValue');
+ // Create a new isolate scope to pass to the original linking function.
+ var isolateScope = originalScope.$new(true);
- function isUndefinedOrNull(value) {
- return typeof value === 'undefined' || value === null;
- }
+ // Replace `originalScope` with `isolateScope` in the link function
+ // args, used when calling the original link function with `apply()`
+ // below.
+ linkArgs[0] = isolateScope;
- function onFirstUsefulValue() {
- // If we're bound to an Observable, subscribe to it.
- if (isObservable(originalScope[propertyName])) {
- // Get a shared version of the value stream we're bound to, since we'll
- // be making multiple subscriptions.
- var valueStream = originalScope[propertyName].share();
+ // Replace the original directive name attribute value with the
+ // property name we want the original directive to watch instead.
+ iAttrs.$set(directiveName, RX_VALUE_SCOPE_PROPERTY);
- // Invoke the original linking function once, the first time we
- // get a value from the Observable.
- valueStream.first().subscribe(function() {
- originalLinkFn.apply(directiveConfig, originalLinkArgs);
- });
+ // Invoke the original linking function with the modified link args we
+ // prepared earlier.
+ originalLinkFn.apply(directiveConfig, linkArgs);
- // Subscribe to the Observable, updating the child scope property the
- // original linking function code is watching whenever we receive a new
- // value.
- var valueStreamDisposable = valueStream
- .safeApply(childScope, function onNextValue(value) {
- childScope[propertyName] = value;
- })
- .subscribe();
+ // Subscribe to the Observable, updating the child scope property the
+ // original linking function code is watching whenever we receive a new
+ // value.
+ var valueStreamDisposable = valueStream
+ .safeApply(isolateScope, function onNextValue(value) {
+ isolateScope[RX_VALUE_SCOPE_PROPERTY] = value;
+ })
+ .subscribe();
- // Our subscription should not live longer than the scope.
- originalScope.$on('$destroy', function() {
- valueStreamDisposable.dispose();
- });
- } else {
- // Else the directive is not bound to an Observable, so the child scope will
- // prototypally inherit the changes to the parent and the original directive's
- // watcher should work normally.
- originalLinkFn.apply(directiveConfig, originalLinkArgs);
- }
- }
+ // Our subscription should not live longer than the scope.
+ originalScope.$on('$destroy', function() {
+ valueStreamDisposable.dispose();
+ });
+ } else {
+ // Else the directive is not bound to an Observable, so we call the
+ // original linking function with its original args.
+ originalLinkFn.apply(directiveConfig, linkArgs);
+ }
+ });
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment