-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
176 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
@module {Object} can-reflect-dependencies | ||
@parent can-typed-data | ||
@collection can-infrastructure | ||
|
||
@description Functions to keep track of observable dependencies. | ||
|
||
@type {Object} | ||
|
||
The `can-reflect-dependencies` package provides methods used to register and | ||
retrieve observable dependencies. | ||
|
||
Exports an object with the following methods: | ||
|
||
```js | ||
{ | ||
addMutatedBy // Register observable mutation dependencies | ||
deleteMutatedBy // Delete observable mutation dependencies | ||
getDependencyDataOf // Get the dependencies of an observable | ||
} | ||
``` | ||
|
||
@body | ||
|
||
## Use | ||
|
||
There are two steps to keep in mind in order to reliably keep track of | ||
observable dependencies: | ||
|
||
- Register what affects the observable and, | ||
- Register what the observable affects (the opposite of the first step) | ||
|
||
### Register what affects the observable | ||
|
||
If the observable derives its value from other observables internally, at least | ||
one of the following symbols must be implemented: | ||
|
||
- [@@@can.getKeyDependencies](can-symbol/symbols/getKeyDependencies.html): The key dependencies of the observable | ||
- [@@@can.getValueDependencies](https://canjs.com/doc/can-symbol/symbols/getValueDependencies.html): The value dependencies of the observable | ||
|
||
In the following example `MyCustomObservable` uses an [Observation](can-observation.html) | ||
instance internally to derive its value: | ||
|
||
```js | ||
var canSymbol = require("can-symbol"); | ||
var observation = require("can-observation"); | ||
|
||
function MyCustomObservable(value) { | ||
this.observation = new Observation(...); | ||
} | ||
|
||
MyCustomObservable.prototype.get = function() { | ||
return this.observation.get(); | ||
}; | ||
``` | ||
|
||
Since `MyCustomObservable` is a value-like observable, it has to implement | ||
[@@@can.getValueDependencies](https://canjs.com/doc/can-symbol/symbols/getValueDependencies.html) | ||
so this dependency is visible to `getDependencyDataOf`. | ||
|
||
```js | ||
var canReflect = require("can-reflect"); | ||
|
||
function MyCustomObservable() { ... } | ||
|
||
canReflect.assignSymbols(MyCustomObservable, { | ||
"can.getValueDependencies": function() { | ||
return { | ||
valueDependencies: new Set([ this.observation ]) | ||
}; | ||
} | ||
}); | ||
``` | ||
|
||
It's possible that a specific instance's value of `MyCustomObservable` is set by | ||
another observable in a specific context, this kind of dependecy won't be registered | ||
by the symbols discussed so far. | ||
|
||
The following example shows two observables, a map-like instance `someMap` and a | ||
value-like instance `myObservable`. When the `foo` property of `someMap` changes, | ||
it sets the value of `myObservable`, in order to keep track of this dependency, | ||
`addMutatedBy` has to be used as follows: | ||
|
||
```js | ||
var someMap = new SomeMap(); | ||
var myObservable = new MyCustomObservable(); | ||
var canReflectDeps = require("can-reflect-dependencies"); | ||
|
||
// when the foo property changes, update myObservable | ||
someMap.on("foo", function() { | ||
myObservable.set(/* some value */); | ||
}); | ||
|
||
// Register that `myObservable` is affected by the `foo` property of `someMap` | ||
canReflectDeps.addMutatedBy(myObservable, { | ||
keyDependencies: new Map([ [someMap, new Set(["foo"])] ]); | ||
}); | ||
``` | ||
|
||
If this dependency is conditional, it's important to call `deleteMutatedBy` to | ||
remove the dependency from `can-reflect-dependencies` internal registry, e.g: | ||
|
||
```js | ||
/* code omitted for brevity */ | ||
|
||
if (hasToStopListeningToFooChanges) { | ||
// remove the event listener | ||
someMap.off("foo", onFooChange); | ||
|
||
// remove the dependency from `can-reflect-dependencies` | ||
canReflectDeps.deleteMutatedBy(myObservable, { | ||
keyDependencies: new Map([ [someMap, new Set(["foo"])] ]); | ||
}); | ||
} | ||
``` | ||
|
||
### Register the observable changes | ||
|
||
In the previous section, `addMutatedBy` was used to register that `someMap.foo` | ||
affects `myObservable`'s value; in its current form `can-reflect-dependencies` | ||
can only _see_ the dependency from `myObservable`, that means: | ||
|
||
```js | ||
// this works! | ||
canReflectDeps.getDependencyDataOf(myObservable); | ||
|
||
// but this does not, it returns `undefined` :( | ||
canReflectDeps.getDependencyDataOf(someMap, "foo"); | ||
``` | ||
|
||
In order to register the dependency in the opossite direction, the following | ||
things need to happen: | ||
|
||
- `SomeMap` must implement the `@@@can.getWhatIChange` symbol | ||
- Event handlers must keep track of the observables affected by implementing the | ||
`@@@can.getChangesDependencyRecord` symbol. | ||
|
||
CanJS observables make this easier by attaching event handling capabilities through | ||
[can-event-queue](https://github.com/canjs/can-event-queue) mixins, adding in the | ||
[value mixin](https://github.com/canjs/can-event-queue/blob/master/value/value.js) | ||
to `SomeMap`'s prototype will add a base implementation of `@@@can.getWhatIChange` | ||
which iterates over the registered handlers and calls `@@@can.getChangesDependencyRecord` | ||
on each. | ||
|
||
Having `@@@can.getWhatIChange` implemented by `can-event-queue`, the next thing | ||
to do is to implement `@@@can.getChangesDependencyRecord` on the event handler | ||
that mutates `myObservable`. | ||
|
||
```js | ||
/* code omitted for brevity */ | ||
|
||
// Bind the callback to a variable to make adding the symbol easier | ||
var onFooChange = function() { | ||
myObservable.set(/* some value */); | ||
}; | ||
|
||
canReflect.assignSymbols(onFooChange, { | ||
"can.getChangesDependencyRecord": function() { | ||
return { | ||
valueDependencies: new Set([ myObservable ]) | ||
}; | ||
} | ||
}); | ||
|
||
someMap.on("foo", onFooChange); | ||
``` | ||
|
||
With this in place the following code should work now: | ||
|
||
```js | ||
canReflectDeps.getDependencyDataOf(someMap, "foo"); // ... myObservable | ||
``` | ||
|
||
**NOTE**: This implementation requires `can-event-queue/value/value` mixin to be | ||
added to `SomeMap`'s prototype, if your observable uses custom event handling logic | ||
you need to implement `@@@can.getWhatIChange` and keep track of what the event | ||
handlers are mutating manually. |