Skip to content

Instantly share code, notes, and snippets.

@bendemboski
Created June 19, 2020 19:49
Show Gist options
  • Save bendemboski/cb2a472da3a0f6bb7b8d1f79f7e1419d to your computer and use it in GitHub Desktop.
Save bendemboski/cb2a472da3a0f6bb7b8d1f79f7e1419d to your computer and use it in GitHub Desktop.
code to make getters act like @Tracked state
import { tracked } from '@glimmer/tracking';
//
// A tracked macro is a getter (with optional setter) that acts as tracked state.
// The usage is as follows:
//
// import { trackedMacro, dirtyTrackedMacro } from 'tracked-macro';
//
// class MyClass {
// externalLibrary = new ExternalLibraryThatIsNotTrackedAware()
//
// @trackedMacro
// get externalState() {
// return this.externalLibrary.getState();
// }
// // setter is optional
// set externalState(value) {
// return this.externalLibrary.setState(value);
// }
//
// constructor() {
// this.externalLibrary.addListener('change:state', () => {
// dirtyTrackedMacro(this, 'externalState');
// });
// }
// }
//
// This will cause `externalState` to act as if it were @tracked, even though
// its actually state that is derived from non-tracked state. The getter gets
// the underlying value and the setter sets it, both in @tracked-aware ways, and
// if the underlying state can change through other non-@tracked aware
// mechanisms, when we are notified of such a change, `dirtyTrackedMacro()` can
// be used to notify the tracking infrastructure.
// A class used to hook into the tracking infrastructure. The @tracked tag
// doesn't hold any meaningful state, but by reading it and writing to it at the
// same time as we read/write to a piece of untracked state, we can effectively
// make the tracking infrastructure track that untracked state.
class Tag {
@tracked tag
consume() {
this.tag;
}
dirty() {
this.tag = undefined;
}
}
// A weak map mapping consumer objects to property-name-to-tag maps. The keys
// are consumer objects with @trackedMacro properties, and the values are Maps
// whose keys are the names of the @trackedMacro properties and values are Tag
// objects.
const tags = new WeakMap();
// Helper to get or create a Tag for a given consumer object and @trackedMacro
// property key
function getTag(obj, key) {
let tagsForObject = tags.get(obj);
if (!tagsForObject) {
tagsForObject = {};
tags.set(obj, tagsForObject);
}
let tag = tagsForObject[key];
if (!tag) {
tag = new Tag();
tagsForObject[key] = tag;
}
return tag;
}
// Our @trackedMacro decorator
export function trackedMacro(obj, key, descriptor) {
let getter = descriptor.get;
let setter = descriptor.set;
descriptor.get = function(...args) {
getTag(this, key).consume();
return getter.call(this, ...args);
};
descriptor.set = function(...args) {
getTag(this, key).dirty();
return setter.call(this, ...args);
};
}
// A function that will dirty a @trackedMacro property on an object
export function dirtyTrackedMacro(obj, key) {
getTag(obj, key).dirty();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment