Skip to content

Instantly share code, notes, and snippets.

@jmahmood
Last active May 3, 2022 21:34
Show Gist options
  • Save jmahmood/a10d28b01047d6f168e01d0a974a0be7 to your computer and use it in GitHub Desktop.
Save jmahmood/a10d28b01047d6f168e01d0a974a0be7 to your computer and use it in GitHub Desktop.
Using JavaScript Generators with Angular2+
// I use generators a lot with python. It seems elegant to me; keep only the data you need and iterate along.
// I was happy to see that they had added generators to JavaScript and had an example where I needed the next 30 days.
// I could put it in an array, but why not try using generators?
// After trial and error, I realized it isn't quite possible in Angular
// Attempt #1: Use Generator function directly in ngFor.
static *next30DaysPlainGenerator = function*(){
const initial_date = new Date();
yield initial_date;
for (let i = 1; i < 30; i++) {
yield new Date(initial_date.getTime() + i * 1000 * 60 * 60 * 24);
}
}
// ...
this.iterator_obj = next30DaysPlainGenerator()
// ...
// <li *ngFor='let x of iterator_obj'>{{x}}</li>
// This doesn't work.
// ngFor require an object which fills the iterable protocol; that is to say, it must be
// an object with a [Symbol.iterator] key that links to a function. Moreover, the function must be a
// zero arguments function which conforms to the iterator protocol.
// Generator Functions are iteratorIterables, which implement both Iterable and Iterator protocols. It clearly calls the
// generator function; however unlike most iterators, a generator can only be used once. In this case, the generator
// function is exhausted when angular tries to diff the value / get the length of values using
// DefaultIterableDiffer.prototype.check and DefaultIterableDiffer.prototype.diff functions.
// You cannot rewind a generator in JavaScript.
// https://stackoverflow.com/a/23848531
// Attempt #2: Create an object and bind a generator function to it.
static inlineNext30DaysGenerator () {
const initial_date = new Date();
const ret = {};
ret[Symbol.iterator] = function*() {
yield initial_date;
for (let i = 1; i < 30; i++) {
yield new Date(initial_date.getTime() + i * 1000 * 60 * 60 * 24);
}
};
return ret;
}
// ...
this.iterator_obj = next30DaysPlainGenerator()
// ...
// <li *ngFor='let x of iterator_obj'>{{x}}</li>
// This is ugly from a code smell perspective, but it sort-of "works". The generator results are displayed on the screen.
// However, the behavior is sadly incorrect as it runs new instances of the generator function repeatedly. This is not the
// desired behavior; imagine a function that requires significant amounts of processing. We would be better off caching the
// returned values rather than running it four times. Having a cache of values defeats some of the purpose of using a ¥
// generator.
// I'm not sure if this can be fixed; diffing (showing new elements when they show up in a list) is a fundamental part of
// Angular. Generators are not suited to comparison at different times.
// As such, it doesn't seem to make much sense to use generators in ngFor, because it requires you to enable diff behavior
// somehow, and that will mean caching information somewhere.
// This raises the point that there is a different form of data display from a list. One in which the list is immutable
// and will only yield new items. Once a list value is output, it won't be changed; however, you may re-run the generator
// to get new values. (Something like how PubSub services / websockets work; ex; service that yields new lines only from
// an append-only log.)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment