Last active
May 3, 2022 21:34
-
-
Save jmahmood/a10d28b01047d6f168e01d0a974a0be7 to your computer and use it in GitHub Desktop.
Using JavaScript Generators with Angular2+
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
// 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