Home
recent posts
{ "kind": "blogger#postList", "nextPageToken": "CgkIChjArOrelysQiNn6s5euieImGAA", "items": [ { "kind": "blogger#post", "id": "6696067281974474972", "blog": { "id": "2793398838126095496" }, "published": "2022-01-10T23:30:00-08:00", "updated": "2022-01-10T23:30:36-08:00", "url": "http://currentlyunderdevelopment.blogspot.com/2022/01/webcritters-v01.html", "selfLink": "https://www.googleapis.com/blogger/v3/blogs/2793398838126095496/posts/6696067281974474972", "title": "Webcritters v0.1", "content": "\u003cp\u003eFirst alpha version:\u003c/p\u003e\u003cp\u003e\u003ca href=\" https://scottschaferenterprises.com/webcritters\" target=\"_blank\"\u003e https://scottschaferenterprises.com/webcritters\u003c/a\u003e\u003c/p\u003e\u003cdiv class=\"separator\" style=\"clear: both; text-align: center;\"\u003e\u003ca href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj4Wv5EYnJ3wBSKvgsJQi50kuO6WCj2GbjxxSLNqucQJiqoiGr-zMkvo_LyzMLruMN-FTWZLuLIdIcUkn_kFl07s7VSAsBLG3JFVj4Y19Vvsofd3IO5BCCJMEqPXbvnPUsxwCC_zLnC0K4/\" style=\"margin-left: 1em; margin-right: 1em;\"\u003e\u003cimg alt=\"\" data-original-height=\"2026\" data-original-width=\"2262\" height=\"240\" src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj4Wv5EYnJ3wBSKvgsJQi50kuO6WCj2GbjxxSLNqucQJiqoiGr-zMkvo_LyzMLruMN-FTWZLuLIdIcUkn_kFl07s7VSAsBLG3JFVj4Y19Vvsofd3IO5BCCJMEqPXbvnPUsxwCC_zLnC0K4/\" width=\"268\" /\u003e\u003c/a\u003e\u003c/div\u003e\u003cbr /\u003e\u003cp\u003e\u003c/p\u003e", "author": { "id": "16894652095365231201", "displayName": "Scott Schafer", "url": "https://www.blogger.com/profile/16894652095365231201", "image": { "url": "//www.blogger.com/img/blogger_logo_round_35.png" } }, "replies": { "totalItems": "0", "selfLink": "https://www.googleapis.com/blogger/v3/blogs/2793398838126095496/posts/6696067281974474972/comments" }, "etag": "\"dGltZXN0YW1wOiAxNjQxODg2MjM2Nzc5Cm9mZnNldDogLTI4ODAwMDAwCg\"" }, { "kind": "blogger#post", "id": "5220493464355110781", "blog": { "id": "2793398838126095496" }, "published": "2020-12-08T10:06:00-08:00", "updated": "2021-07-08T10:46:18-07:00", "url": "http://currentlyunderdevelopment.blogspot.com/2020/12/tracking-down-mobx-reaction-doesnt.html", "selfLink": "https://www.googleapis.com/blogger/v3/blogs/2793398838126095496/posts/5220493464355110781", "title": "Tracking down mobX \"Reaction doesn't converge to a stable state after 100 iterations\" errors", "content": "\u003cp\u003eI haven't yet found the official way of tracking these errors down, but have an unofficial method that seems helpful.\u003c/p\u003e\u003cp\u003eThe error that I occasionally run into looks like this:\u003c/p\u003e\u003cp\u003e\u003cspan style=\"font-family: courier;\"\u003e\u003cb\u003eReaction doesn't converge to a stable state after 100 iterations. Probably there is a cycle in the reactive function: Reaction[Reaction@591]\u003c/b\u003e\u003c/span\u003e\u003c/p\u003e\u003cp\u003eAt this point I've tended to flail around, set breakpoints, try disabling code etc because I have no idea where in my code \u003cb style=\"font-family: courier;\"\u003eReaction@591\u003c/b\u003e is being triggered. The MobX development tools are no help either.\u003c/p\u003e\u003cp\u003eSo here's a little trick I discovered. Enter this in the debugger console when you hit this error (replacing the Reaction@XXX with your own error.\u003c/p\u003e\u003cp\u003e\u003cspan style=\"font-family: courier;\"\u003e__mobxGlobals.pendingReactions.find(r=>(r.name === 'Reaction@591'))\u003c/span\u003e\u003c/p\u003e\u003cp\u003eNow inspect the observing array, and you'll get a good hint as to where the reaction is:\u003cbr /\u003e\u003c/p\u003e\u003col class=\"children expanded\" role=\"group\" style=\"box-sizing: border-box; font-family: Menlo, monospace; font-size: 11px; list-style-type: none; min-height: 0px; min-width: 0px; padding-left: 12px; white-space: pre-wrap;\"\u003e\u003cli aria-expanded=\"true\" aria-label=\"observing\" class=\"parent expanded\" role=\"treeitem\" style=\"align-items: center; box-sizing: border-box; display: flex; min-height: 16px; min-width: 0px; position: relative; text-overflow: ellipsis; user-select: text; white-space: nowrap;\"\u003e\u003cspan class=\"name-and-value\" style=\"box-sizing: border-box; line-height: 16px; min-height: 0px; min-width: 0px; overflow: hidden; text-overflow: ellipsis;\"\u003e\u003cspan style=\"background-color: white;\"\u003e\u003cspan class=\"name\" style=\"box-sizing: border-box; flex-shrink: 0; min-height: 0px; min-width: 0px;\"\u003eobserving\u003c/span\u003e: \u003cspan class=\"value object-value-array\" style=\"box-sizing: border-box; min-height: 0px; min-width: 0px;\"\u003eArray(3)\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/li\u003e\u003col class=\"children expanded\" role=\"group\" style=\"box-sizing: border-box; list-style-type: none; min-height: 0px; min-width: 0px; padding-left: 12px;\"\u003e\u003cli aria-expanded=\"false\" aria-label=\"0\" class=\"parent\" role=\"treeitem\" style=\"align-items: center; box-sizing: border-box; display: flex; min-height: 16px; min-width: 0px; position: relative; text-overflow: ellipsis; user-select: text; white-space: nowrap;\"\u003e\u003cspan class=\"name-and-value\" style=\"box-sizing: border-box; line-height: 16px; min-height: 0px; min-width: 0px; overflow: hidden; text-overflow: ellipsis;\"\u003e\u003cspan style=\"background-color: white;\"\u003e\u003cspan class=\"name\" style=\"box-sizing: border-box; flex-shrink: 0; min-height: 0px; min-width: 0px;\"\u003e0\u003c/span\u003e: \u003cspan class=\"object-value-object value\" style=\"box-sizing: border-box; min-height: 0px; min-width: 0px;\"\u003e\u003cspan class=\"object-description\" style=\"box-sizing: border-box; min-height: 0px; min-width: 0px;\"\u003eComputedValue \u003c/span\u003e\u003cspan class=\"object-properties-preview\" style=\"box-sizing: border-box; min-height: 0px; min-width: 0px;\"\u003e{\u003cspan class=\"name\" style=\"box-sizing: border-box; flex-shrink: 0; min-height: 0px; min-width: 0px;\"\u003edependenciesState\u003c/span\u003e: \u003cspan class=\"object-value-number\" style=\"box-sizing: border-box; min-height: 0px; min-width: 0px;\"\u003e0\u003c/span\u003e, \u003cspan class=\"name\" style=\"box-sizing: border-box; flex-shrink: 0; min-height: 0px; min-width: 0px;\"\u003eobserving\u003c/span\u003e: \u003cspan class=\"object-value-array\" style=\"box-sizing: border-box; min-height: 0px; min-width: 0px;\"\u003eArray(2)\u003c/span\u003e, \u003cspan class=\"name\" style=\"box-sizing: border-box; flex-shrink: 0; min-height: 0px; min-width: 0px;\"\u003enewObserving\u003c/span\u003e: \u003cspan class=\"object-value-null\" style=\"box-sizing: border-box; min-height: 0px; min-width: 0px;\"\u003enull\u003c/span\u003e, \u003cspan class=\"name\" style=\"box-sizing: border-box; flex-shrink: 0; min-height: 0px; min-width: 0px;\"\u003eisBeingObserved\u003c/span\u003e: \u003cspan class=\"object-value-boolean\" style=\"box-sizing: border-box; min-height: 0px; min-width: 0px;\"\u003etrue\u003c/span\u003e, \u003cspan class=\"name\" style=\"box-sizing: border-box; flex-shrink: 0; min-height: 0px; min-width: 0px;\"\u003eisPendingUnobservation\u003c/span\u003e: \u003cspan class=\"object-value-boolean\" style=\"box-sizing: border-box; min-height: 0px; min-width: 0px;\"\u003efalse\u003c/span\u003e\u003cspan style=\"box-sizing: border-box; min-height: 0px; min-width: 0px;\"\u003e, …\u003c/span\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/li\u003e\u003cli aria-expanded=\"false\" aria-label=\"1\" class=\"parent\" role=\"treeitem\" style=\"align-items: center; box-sizing: border-box; display: flex; min-height: 16px; min-width: 0px; position: relative; text-overflow: ellipsis; user-select: text; white-space: nowrap;\"\u003e\u003cspan class=\"name-and-value\" style=\"box-sizing: border-box; line-height: 16px; min-height: 0px; min-width: 0px; overflow: hidden; text-overflow: ellipsis;\"\u003e\u003cspan style=\"background-color: white;\"\u003e\u003cspan class=\"name\" style=\"box-sizing: border-box; flex-shrink: 0; min-height: 0px; min-width: 0px;\"\u003e1\u003c/span\u003e: \u003cspan class=\"object-value-object value\" style=\"box-sizing: border-box; min-height: 0px; min-width: 0px;\"\u003e\u003cspan class=\"object-description\" style=\"box-sizing: border-box; min-height: 0px; min-width: 0px;\"\u003eObservableValue \u003c/span\u003e\u003cspan class=\"object-properties-preview\" style=\"box-sizing: border-box; min-height: 0px; min-width: 0px;\"\u003e{\u003cspan class=\"name\" style=\"box-sizing: border-box; flex-shrink: 0; min-height: 0px; min-width: 0px;\"\u003ename\u003c/span\u003e: \u003cspan class=\"object-value-string\" style=\"box-sizing: border-box; min-height: 0px; min-width: 0px; unicode-bidi: -webkit-isolate; white-space: pre;\"\u003e\"PdpStore@21.highlightOption\"\u003c/span\u003e, \u003cspan class=\"name\" style=\"box-sizing: border-box; flex-shrink: 0; min-height: 0px; min-width: 0px;\"\u003eisPendingUnobservation\u003c/span\u003e: \u003cspan class=\"object-value-boolean\" style=\"box-sizing: border-box; min-height: 0px; min-width: 0px;\"\u003efalse\u003c/span\u003e, \u003cspan class=\"name\" style=\"box-sizing: border-box; flex-shrink: 0; min-height: 0px; min-width: 0px;\"\u003eisBeingObserved\u003c/span\u003e: \u003cspan class=\"object-value-boolean\" style=\"box-sizing: border-box; min-height: 0px; min-width: 0px;\"\u003etrue\u003c/span\u003e, \u003cspan class=\"name\" style=\"box-sizing: border-box; flex-shrink: 0; min-height: 0px; min-width: 0px;\"\u003eobservers\u003c/span\u003e: \u003cspan class=\"object-value-array\" style=\"box-sizing: border-box; min-height: 0px; min-width: 0px;\"\u003eArray(5)\u003c/span\u003e, \u003cspan class=\"name\" style=\"box-sizing: border-box; flex-shrink: 0; min-height: 0px; min-width: 0px;\"\u003eobserversIndexes\u003c/span\u003e: \u003cspan class=\"object-value-object\" style=\"box-sizing: border-box; min-height: 0px; min-width: 0px;\"\u003e{…}\u003c/span\u003e\u003cspan style=\"box-sizing: border-box; min-height: 0px; min-width: 0px;\"\u003e, …\u003c/span\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/li\u003e\u003cli aria-expanded=\"false\" aria-label=\"2\" aria-selected=\"true\" class=\"parent selected\" role=\"treeitem\" style=\"align-items: center; box-sizing: border-box; display: flex; min-height: 16px; min-width: 0px; outline-width: 0px; position: relative; text-overflow: ellipsis; user-select: text; white-space: nowrap;\" tabindex=\"-1\"\u003e\u003cspan class=\"name-and-value\" style=\"box-sizing: border-box; line-height: 16px; min-height: 0px; min-width: 0px; overflow: hidden; text-overflow: ellipsis;\"\u003e\u003cspan style=\"background-color: white;\"\u003e\u003cspan class=\"name\" style=\"box-sizing: border-box; flex-shrink: 0; min-height: 0px; min-width: 0px;\"\u003e2\u003c/span\u003e: \u003cspan class=\"object-value-object value\" style=\"box-sizing: border-box; min-height: 0px; min-width: 0px;\"\u003e\u003cspan class=\"object-description\" style=\"box-sizing: border-box; min-height: 0px; min-width: 0px;\"\u003eObservableValue \u003c/span\u003e\u003cspan class=\"object-properties-preview\" style=\"box-sizing: border-box; min-height: 0px; min-width: 0px;\"\u003e{\u003cspan class=\"name\" style=\"box-sizing: border-box; flex-shrink: 0; min-height: 0px; min-width: 0px;\"\u003ename\u003c/span\u003e: \u003cspan class=\"object-value-string\" style=\"box-sizing: border-box; min-height: 0px; min-width: 0px; unicode-bidi: -webkit-isolate; white-space: pre;\"\u003e\"OpportunitiesStore@295.unfilteredOpportunities\"\u003c/span\u003e,\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/li\u003e\u003c/ol\u003e\u003c/ol\u003e\u003cp\u003e\u003cbr /\u003e\u003c/p\u003e", "author": { "id": "16894652095365231201", "displayName": "Scott Schafer", "url": "https://www.blogger.com/profile/16894652095365231201", "image": { "url": "//www.blogger.com/img/blogger_logo_round_35.png" } }, "replies": { "totalItems": "0", "selfLink": "https://www.googleapis.com/blogger/v3/blogs/2793398838126095496/posts/5220493464355110781/comments" }, "etag": "\"dGltZXN0YW1wOiAxNjI1NzY2Mzc4ODk5Cm9mZnNldDogLTI1MjAwMDAwCg\"" }, { "kind": "blogger#post", "id": "1032805749382324703", "blog": { "id": "2793398838126095496" }, "published": "2020-09-20T15:35:00-07:00", "updated": "2020-09-20T15:36:00-07:00", "url": "http://currentlyunderdevelopment.blogspot.com/2020/09/waiting-for-godot-board-game.html", "selfLink": "https://www.googleapis.com/blogger/v3/blogs/2793398838126095496/posts/1032805749382324703", "title": "Waiting for Godot, the board game (multiplayer game built with Firebase, mobx, Typescript and React)", "content": "\u003cp\u003eIf you're familiar with Samuel Beckett's play \u003cu\u003eWaiting for Godot\u003c/u\u003e, then you'll understand why trying to make a board game out of it would be absurd and nihilistic. I'm proud so say that I believe my efforts here will not disappoint.\u003cbr /\u003e\u003cbr /\u003e\u003ca href=\"https://scottschaferenterprises.com/waitingForGodot\"\u003ehttps://scottschaferenterprises.com/waitingForGodot\u003c/a\u003e\u003cbr /\u003e\u003c/p\u003e\u003cp\u003eHopefully it's as fun as it is futile!\u003c/p\u003e\u003cp\u003eIt's also I think a cool demonstration of a little technology stack I've thrown together that integrates mobx and firebase to allow creating multiplayer games (in which model data is automatically synchronized across clients, using a firebase \"backend\") relatively easily.\u003c/p\u003e\u003cp\u003e\u003c/p\u003e\u003cdiv class=\"separator\" style=\"clear: both; text-align: center;\"\u003e\u003ca href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYx72dDbvuCLpPd8I43bLRTDOWOT1z_lHIxS3MFSR0ddy3XhY2rkYV59jYmdCBtLY1JATbvfTRzl2_DKXqexQPnK9MizSm5aes5-LlrMBKKojJFxR9BmUKi1rM9k4sxWtOcHQLTlTEtRE/\" style=\"margin-left: 1em; margin-right: 1em;\"\u003e\u003cimg alt=\"\" data-original-height=\"1762\" data-original-width=\"1364\" height=\"240\" src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYx72dDbvuCLpPd8I43bLRTDOWOT1z_lHIxS3MFSR0ddy3XhY2rkYV59jYmdCBtLY1JATbvfTRzl2_DKXqexQPnK9MizSm5aes5-LlrMBKKojJFxR9BmUKi1rM9k4sxWtOcHQLTlTEtRE/\" width=\"186\" /\u003e\u003c/a\u003e\u003c/div\u003e\u003cbr /\u003e\u003cbr /\u003e\u003cp\u003e\u003c/p\u003e", "author": { "id": "16894652095365231201", "displayName": "Scott Schafer", "url": "https://www.blogger.com/profile/16894652095365231201", "image": { "url": "//www.blogger.com/img/blogger_logo_round_35.png" } }, "replies": { "totalItems": "0", "selfLink": "https://www.googleapis.com/blogger/v3/blogs/2793398838126095496/posts/1032805749382324703/comments" }, "etag": "\"dGltZXN0YW1wOiAxNjAwNjQxMzYwOTEyCm9mZnNldDogLTI1MjAwMDAwCg\"" }, { "kind": "blogger#post", "id": "9142455109956815741", "blog": { "id": "2793398838126095496" }, "published": "2019-08-13T13:57:00-07:00", "updated": "2019-08-20T09:42:39-07:00", "url": "http://currentlyunderdevelopment.blogspot.com/2019/08/safe-mobx-strict-mode-in-typescript-or.html", "selfLink": "https://www.googleapis.com/blogger/v3/blogs/2793398838126095496/posts/9142455109956815741", "title": "Safe MobX strict mode in Typescript, or How I quit worrying and learned to overwrite readonly properties in Typescript", "content": "I really like MobX. It's fairly easy to use and is magic with React.\u003cbr /\u003e\n\u003cbr /\u003e\nBut we started without strict mode enabled, and now we have code that write to @observable properties. If we turned on strict mode, it would fail - but only at runtime. We could just turn on strict mode and hunt down all the cases this is done, but I don't want to rely on catching this at run-time, and I want to prohibit writes from happening outside of actions.\u003cbr /\u003e\n\u003cbr /\u003e\nAt the same time, I don't want to have to write a lot of boilerplate, and I definitely don't want to introduce any overhead (that is, a private @observeable field with a public @computed accessor etc).\u003cbr /\u003e\n\u003cbr /\u003e\nSo, without further ado, here's my solution. It's simple to write, easy to use and is type safe. If you rename val1 or val2 or change the types, the actions will fail to compile as they should.\u003cbr /\u003e\n\u003cbr /\u003e\nEDIT: made more better w/ help from SO.\u003cbr /\u003e\nEDIT: simplified w/ getter.\u003cbr /\u003e\n\u003cbr /\u003e\n\u003cdiv style=\"background-color: #1e1e1e; font-size: 10px; line-height: 15px; white-space: pre;\"\u003e\n\u003cdiv style=\"line-height: 15px;\"\u003e\n\u003cspan style=\"color: #569cd6; font-family: "menlo" , "monaco" , "courier new" , monospace;\"\u003etype\u003c/span\u003e\u003cspan style=\"color: #d4d4d4; font-family: "menlo" , "monaco" , "courier new" , monospace;\"\u003e \u003c/span\u003e\u003cspan style=\"color: #4ec9b0; font-family: "menlo" , "monaco" , "courier new" , monospace;\"\u003eWritable\u003c/span\u003e\u003cspan style=\"color: #d4d4d4; font-family: "menlo" , "monaco" , "courier new" , monospace;\"\u003e<\u003c/span\u003e\u003cspan style=\"color: #4ec9b0; font-family: "menlo" , "monaco" , "courier new" , monospace;\"\u003eT\u003c/span\u003e\u003cspan style=\"color: #d4d4d4; font-family: "menlo" , "monaco" , "courier new" , monospace;\"\u003e> = {\n -\u003c/span\u003e\u003cspan style=\"color: #569cd6; font-family: "menlo" , "monaco" , "courier new" , monospace;\"\u003ereadonly\u003c/span\u003e\u003cspan style=\"color: #d4d4d4; font-family: "menlo" , "monaco" , "courier new" , monospace;\"\u003e [\u003c/span\u003e\u003cspan style=\"color: #4ec9b0; font-family: "menlo" , "monaco" , "courier new" , monospace;\"\u003eK\u003c/span\u003e\u003cspan style=\"color: #d4d4d4; font-family: "menlo" , "monaco" , "courier new" , monospace;\"\u003e \u003c/span\u003e\u003cspan style=\"color: #569cd6; font-family: "menlo" , "monaco" , "courier new" , monospace;\"\u003ein keyof\u003c/span\u003e\u003cspan style=\"color: #d4d4d4; font-family: "menlo" , "monaco" , "courier new" , monospace;\"\u003e \u003c/span\u003e\u003cspan style=\"color: #4ec9b0; font-family: "menlo" , "monaco" , "courier new" , monospace;\"\u003eT\u003c/span\u003e\u003cspan style=\"color: #d4d4d4; font-family: "menlo" , "monaco" , "courier new" , monospace;\"\u003e]: \u003c/span\u003e\u003cspan style=\"color: #4ec9b0; font-family: "menlo" , "monaco" , "courier new" , monospace;\"\u003eT\u003c/span\u003e\u003cspan style=\"color: #d4d4d4; font-family: "menlo" , "monaco" , "courier new" , monospace;\"\u003e[\u003c/span\u003e\u003cspan style=\"color: #4ec9b0; font-family: "menlo" , "monaco" , "courier new" , monospace;\"\u003eK\u003c/span\u003e\u003cspan style=\"color: #d4d4d4; font-family: "menlo" , "monaco" , "courier new" , monospace;\"\u003e]\n};\n\n\u003c/span\u003e\u003cbr /\u003e\n\u003cdiv style=\"color: #d4d4d4; font-family: Menlo, Monaco, "Courier New", monospace; line-height: 15px;\"\u003e\n\u003cdiv style=\"font-family: Menlo, Monaco, "Courier New", monospace; line-height: 15px;\"\u003e\n\u003cbr /\u003e\u003cdiv\u003e\n\u003cspan style=\"color: #569cd6;\"\u003eclass\u003c/span\u003e \u003cspan style=\"color: #4ec9b0;\"\u003eTestModel\u003c/span\u003e {\u003c/div\u003e\n\u003cdiv\u003e\n @observable \u003cspan style=\"color: #569cd6;\"\u003ereadonly\u003c/span\u003e \u003cspan style=\"color: #9cdcfe;\"\u003ex\u003c/span\u003e: \u003cspan style=\"color: #4ec9b0;\"\u003enumber\u003c/span\u003e;\u003c/div\u003e\n\u003cbr /\u003e\u003cdiv\u003e\n @action \u003cspan style=\"color: #dcdcaa;\"\u003esetX\u003c/span\u003e(\u003cspan style=\"color: #9cdcfe;\"\u003evalue\u003c/span\u003e: \u003cspan style=\"color: #4ec9b0;\"\u003enumber\u003c/span\u003e) {\u003c/div\u003e\n\u003cdiv\u003e\n \u003cspan style=\"color: #569cd6;\"\u003ethis\u003c/span\u003e.\u003cspan style=\"color: #9cdcfe;\"\u003easWriteable\u003c/span\u003e.\u003cspan style=\"color: #9cdcfe;\"\u003ex\u003c/span\u003e = \u003cspan style=\"color: #9cdcfe;\"\u003evalue\u003c/span\u003e;\u003c/div\u003e\n\u003cdiv\u003e\n }\u003c/div\u003e\n\u003cbr /\u003e\u003cdiv\u003e\n \u003cspan style=\"color: #569cd6;\"\u003eprivate\u003c/span\u003e \u003cspan style=\"color: #569cd6;\"\u003eget\u003c/span\u003e \u003cspan style=\"color: #dcdcaa;\"\u003easWriteable\u003c/span\u003e() {\u003c/div\u003e\n\u003cdiv\u003e\n \u003cspan style=\"color: #c586c0;\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color: #569cd6;\"\u003ethis\u003c/span\u003e \u003cspan style=\"color: #c586c0;\"\u003eas\u003c/span\u003e \u003cspan style=\"color: #4ec9b0;\"\u003eMakeWritable\u003c/span\u003e<\u003cspan style=\"color: #4ec9b0;\"\u003eTestModel\u003c/span\u003e>;\u003c/div\u003e\n\u003cdiv\u003e\n }\u003c/div\u003e\n\u003cdiv\u003e\n};\u003c/div\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n", "author": { "id": "16894652095365231201", "displayName": "Scott Schafer", "url": "https://www.blogger.com/profile/16894652095365231201", "image": { "url": "//www.blogger.com/img/blogger_logo_round_35.png" } }, "replies": { "totalItems": "0", "selfLink": "https://www.googleapis.com/blogger/v3/blogs/2793398838126095496/posts/9142455109956815741/comments" }, "labels": [ "MobX", "Typescript" ], "etag": "\"dGltZXN0YW1wOiAxNTY2MzE5MzU5NTkwCm9mZnNldDogLTI1MjAwMDAwCg\"" }, { "kind": "blogger#post", "id": "963312723176003702", "blog": { "id": "2793398838126095496" }, "published": "2019-05-21T17:33:00-07:00", "updated": "2019-05-21T17:33:34-07:00", "url": "http://currentlyunderdevelopment.blogspot.com/2019/05/bowlblaster.html", "selfLink": "https://www.googleapis.com/blogger/v3/blogs/2793398838126095496/posts/963312723176003702", "title": "BowlBlaster", "content": "How did I forget to post this?\u003cbr /\u003e\u003cspan style=\"color: #1155cc; font-family: Garamond, serif; font-variant-east-asian: normal; font-variant-numeric: normal; text-align: justify; text-decoration-line: underline; text-decoration-skip-ink: none; vertical-align: baseline; white-space: pre-wrap;\"\u003e\u003cspan style=\"font-size: 10pt;\"\u003e\n\u003c/span\u003e\u003cspan style=\"font-size: x-large;\"\u003e\u003ca href=\"https://scottschafer.github.io//BowlBlaster//\" target=\"_blank\"\u003eBowlBlaster\u003c/a\u003e!\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"color: #1155cc; font-family: Garamond, serif; font-size: 10pt; font-variant-east-asian: normal; font-variant-numeric: normal; text-align: justify; text-decoration-line: underline; text-decoration-skip-ink: none; vertical-align: baseline; white-space: pre-wrap;\"\u003e\n\u003c/span\u003e", "author": { "id": "16894652095365231201", "displayName": "Scott Schafer", "url": "https://www.blogger.com/profile/16894652095365231201", "image": { "url": "//www.blogger.com/img/blogger_logo_round_35.png" } }, "replies": { "totalItems": "0", "selfLink": "https://www.googleapis.com/blogger/v3/blogs/2793398838126095496/posts/963312723176003702/comments" }, "etag": "\"dGltZXN0YW1wOiAxNTU4NDg1MjE0NTg2Cm9mZnNldDogLTI1MjAwMDAwCg\"" }, { "kind": "blogger#post", "id": "6503401174404350300", "blog": { "id": "2793398838126095496" }, "published": "2019-01-29T17:21:00-08:00", "updated": "2019-01-29T17:39:00-08:00", "url": "http://currentlyunderdevelopment.blogspot.com/2019/01/whats-better-than-self-documenting-code.html", "selfLink": "https://www.googleapis.com/blogger/v3/blogs/2793398838126095496/posts/6503401174404350300", "title": "What's better than self-documenting code?", "content": "Hey, it's been a minute...\u003cbr /\u003e\n\u003cbr /\u003e\nOnce upon a time, we tried to document our (seriously overly complex) APIs. Using Swagger. What a dismal failure that was! Perhaps it's improved since we used it, but Swagger turned out to be just barely usable when trying to format complex JSON. It was so easy to break with a typo or a missing space, and it was such a pain to fix it it that eventually we stopped trying. Also... maintaining documentation requires resources that a shop like ours just doesn't have.\u003cbr /\u003e\n\u003cbr /\u003e\nOK, so fast forward and I've fallen in love with Typescript. IMO, it's the best thing to happen to web development since Chrome developer tools.\u003cbr /\u003e\n\u003cbr /\u003e\nSo I'm happily developing in Typescript, when a new module comes on the horizon that requires a bunch of new APIs. Instead of waiting to see what the Java developers throw over the wall to me, I thought to propose the APIs myself. But how to describe the JSON format?\u003cbr /\u003e\n\u003cbr /\u003e\nWell, duh. Typescript! What can't it do? Not only is it absolutely fantastic for documenting the JSON format itself, but you can turn around and use the definition to validate your example data. And then... this isn't just documentation, this is actual working code.\u003cbr /\u003e\n\u003cbr /\u003e\nJust a trivial example below. I used to think the { [key: string] : ... } format was silly. Why do I need to enter a name for \"key\"? But it makes so much sense here. Truly self-documenting code but even better yet, self-coding documentation!\u003cbr /\u003e\n\u003cbr /\u003e\n\u003cdiv style=\"background-color: #1e1e1e; color: #d4d4d4; font-family: Menlo, Monaco, "Courier New", monospace; font-size: 10px; line-height: 15px; white-space: pre;\"\u003e\n\u003cpre\u003e// example result - list of reports\nconst EXAMPLE_getReports: IResponse_getReports = {\n '984': {\n displayName: `my awesome report`,\n details: `Productivity, created 1/29/19`,\n access: {\n canEdit: true,\n canDelete: true\n }\n },\n '1001': {\n displayName: `your equally awesome report`,\n details: `Summary Report, created 2/01/19`,\n access: {\n canEdit: true,\n canDelete: false\n }\n }\n};\n\n// response definition\ninterface IResponse_getReports {\n [reportId: string]: { // the id of the user-created report\n displayName: string, // the name to display\n details: string, // any details (who created it, the report type, whatevs?)\n access: { // the access that the current user has to this report\n canEdit: boolean,\n canDelete: boolean\n }\n }\n}\u003c/pre\u003e\n\u003c/div\u003e\n", "author": { "id": "16894652095365231201", "displayName": "Scott Schafer", "url": "https://www.blogger.com/profile/16894652095365231201", "image": { "url": "//www.blogger.com/img/blogger_logo_round_35.png" } }, "replies": { "totalItems": "0", "selfLink": "https://www.googleapis.com/blogger/v3/blogs/2793398838126095496/posts/6503401174404350300/comments" }, "etag": "\"dGltZXN0YW1wOiAxNTQ4ODEyMzQwMjgwCm9mZnNldDogLTI4ODAwMDAwCg\"" }, { "kind": "blogger#post", "id": "7811001374406210291", "blog": { "id": "2793398838126095496" }, "published": "2017-04-10T17:38:00-07:00", "updated": "2017-04-10T17:39:51-07:00", "url": "http://currentlyunderdevelopment.blogspot.com/2017/04/upgrading-from-angular-1-to-angular-2.html", "selfLink": "https://www.googleapis.com/blogger/v3/blogs/2793398838126095496/posts/7811001374406210291", "title": "Upgrading from Angular 1 to Angular 2: Getting to the starting line", "content": "We have a large Angular 1.4.7 application to upgrade to Angular 2. It's going to take a while to upgrade all the parts, so I wanted to use the ngUpgrade approach to make a hybrid app. I also wanted to use the Angular CLI to generate the Angular 2 part of our app.\u003cbr /\u003e\n\u003cdiv\u003e\n\u003cbr /\u003e\u003c/div\u003e\n\u003cdiv\u003e\nAfter a very long and frustrating search for a complete example of how to make a hybrid app, I eventually stumbled my way onto a working solution. Here it is, in case this is of use to anyone else.\u003c/div\u003e\n\u003cdiv\u003e\n\u003cbr /\u003e\u003c/div\u003e\n\u003cdiv\u003e\n\u003ca href=\"https://github.com/scottschafer/ng2hybrid\"\u003ehttps://github.com/scottschafer/ng2hybrid\u003c/a\u003e\u003c/div\u003e\n", "author": { "id": "16894652095365231201", "displayName": "Scott Schafer", "url": "https://www.blogger.com/profile/16894652095365231201", "image": { "url": "//www.blogger.com/img/blogger_logo_round_35.png" } }, "replies": { "totalItems": "0", "selfLink": "https://www.googleapis.com/blogger/v3/blogs/2793398838126095496/posts/7811001374406210291/comments" }, "labels": [ "Angular2 AngularJS hybrid ngUpgrade ng-upgrade" ], "etag": "\"dGltZXN0YW1wOiAxNDkxODcxMTkxNzgyCm9mZnNldDogLTI1MjAwMDAwCg\"" }, { "kind": "blogger#post", "id": "7557136834597532067", "blog": { "id": "2793398838126095496" }, "published": "2017-03-15T17:26:00-07:00", "updated": "2017-05-02T11:50:25-07:00", "url": "http://currentlyunderdevelopment.blogspot.com/2017/03/a-distributed-team-approach-for.html", "selfLink": "https://www.googleapis.com/blogger/v3/blogs/2793398838126095496/posts/7557136834597532067", "title": "A distributed team approach for resolving merge conflicts in git", "content": "\u003cspan style=\"font-family: inherit;\"\u003eWhere I work, we frequently have big ugly merge conflicts (probably indicative of a problem in our process, but that's a different blog post).\u003c/span\u003e\u003cbr /\u003e\n\u003cspan style=\"font-family: inherit;\"\u003e\u003cbr /\u003e\u003c/span\u003e\n\u003cspan style=\"font-family: inherit;\"\u003eThe way it goes down is like this. Let's say we need to merge \u003cb\u003erelease/2.0.0 \u003c/b\u003einto \u003cb\u003emaster\u003c/b\u003e. Sally volunteers to do the merge, so she might do something like the following:\u003c/span\u003e\u003cbr /\u003e\n\u003cbr /\u003e\n\u003cspan style=\"font-family: "courier new" , "courier" , monospace;\"\u003e\u003cb\u003egit fetch\u003c/b\u003e\u003c/span\u003e\u003cbr /\u003e\n\u003cspan style=\"font-family: "courier new" , "courier" , monospace;\"\u003e\u003cb\u003egit checkout master\u003c/b\u003e\u003c/span\u003e\u003cbr /\u003e\n\u003cspan style=\"font-family: "courier new" , "courier" , monospace;\"\u003e\u003cb\u003egit pull origin master\u003c/b\u003e\u003c/span\u003e\u003cbr /\u003e\n\u003cspan style=\"font-family: "courier new" , "courier" , monospace;\"\u003e\u003cb\u003egit merge origin/release/2.0.0\u003c/b\u003e\u003c/span\u003e\u003cbr /\u003e\n\u003cspan style=\"font-family: Arial, Helvetica, sans-serif;\"\u003e\u003cbr /\u003e\u003c/span\u003e\n\u003cspan style=\"font-family: inherit;\"\u003eLet's say this results in the following conflicted files:\u003c/span\u003e\u003cbr /\u003e\n\u003cstyle type=\"text/css\"\u003e\np.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #c33720}\nspan.s1 {font-variant-ligatures: no-common-ligatures; color: #000000}\nspan.s2 {font-variant-ligatures: no-common-ligatures}\nspan.Apple-tab-span {white-space:pre}\n\u003c/style\u003e\n\n\n\u003cbr /\u003e\n\u003cdiv class=\"p1\"\u003e\n\u003cspan style=\"font-family: "courier new" , "courier" , monospace; font-size: small;\"\u003e\u003cb\u003e\u003cspan class=\"s1\"\u003e\u003cspan class=\"Apple-tab-span\"\u003e \u003c/span\u003e\u003c/span\u003e\u003cspan class=\"s2\"\u003eboth modified:   database.sql\u003c/span\u003e\u003c/b\u003e\u003c/span\u003e\u003c/div\u003e\n\u003cdiv class=\"p1\"\u003e\n\u003cspan style=\"font-family: "courier new" , "courier" , monospace; font-size: small;\"\u003e\u003cb\u003e\u003cspan class=\"s1\"\u003e\u003cspan class=\"Apple-tab-span\"\u003e \u003c/span\u003e\u003c/span\u003e\u003cspan class=\"s2\"\u003eboth modified:   \u003c/span\u003eapi.java\u003c/b\u003e\u003c/span\u003e\u003c/div\u003e\n\u003cdiv class=\"p1\"\u003e\n\u003cspan style=\"font-family: "courier new" , "courier" , monospace; font-size: small;\"\u003e\u003cb\u003e\u003cspan class=\"s1\"\u003e\u003cspan class=\"Apple-tab-span\"\u003e \u003c/span\u003e\u003c/span\u003e\u003cspan class=\"s2\"\u003eboth modified:   \u003c/span\u003efrontend.js\u003c/b\u003e\u003c/span\u003e\u003c/div\u003e\n\u003cdiv class=\"p1\"\u003e\n\u003cbr /\u003e\u003c/div\u003e\n\u003cspan style=\"font-family: inherit;\"\u003e\u003cspan style=\"font-family: inherit;\"\u003eSally knows how to resolve the merge conflicts in database.sql, but Ahmed is the expert on api.java, and Yasuko is the expert on frontend.js. To make matters worse, let's just say that everyone is in different timezones. \u003c/span\u003e\u003cspan style=\"font-family: inherit;\"\u003eWhat can she do at this point, apart from aborting the merge and giving up or trying to figure out how to resolve the merge conflicts herself? \u003c/span\u003e\u003c/span\u003e\u003cbr /\u003e\n\u003cspan style=\"font-family: inherit;\"\u003e\u003cspan style=\"font-family: inherit;\"\u003e\u003cbr /\u003e\u003c/span\u003e\nHere's one way to do it. The basic strategy is to create a branch that merges two SHA-1 tags (representing specific commits), and push the branch with the conflicted files. Other developers perform the exact same merge using the same tags, resolve their conflicts, and then commit/push their resolved files to the merge branch. By using tags rather than branches, we ensure that all developers are working on the same merge (even if the branches are modified) and the git history will be correct after the merge branch has itself been merged.\u003c/span\u003e\u003cbr /\u003e\n\u003col\u003e\n\u003cli\u003e\u003cspan style=\"font-family: inherit;\"\u003e\u003cspan style=\"font-family: inherit;\"\u003eUse\u003c/span\u003e \u003c/span\u003e\u003cspan style=\"font-family: "courier new" , "courier" , monospace;\"\u003e\u003cb\u003egit merge --abort\u003c/b\u003e\u003c/span\u003e\u003cspan style=\"font-family: inherit;\"\u003e \u003cspan style=\"font-family: inherit;\"\u003eto stop the merge.\u003c/span\u003e\u003c/span\u003e\u003c/li\u003e\n\u003cbr /\u003e\n\u003cli\u003e\u003cspan style=\"font-family: inherit;\"\u003e\u003cspan style=\"font-family: inherit;\"\u003eDetermine the SHA tags of the two branches you will be merging like so:\u003c/span\u003e\u003cbr /\u003e\u003cb style=\"font-family: "courier new", courier, monospace;\"\u003egit fetch origin\u003cbr /\u003egit log origin/master\u003c/b\u003e\u003c/span\u003e\u003cb style=\"font-family: "courier new", courier, monospace;\"\u003e\u003cbr /\u003egit log origin/release/5.1.2\u003cbr\u003e\u003c/b\u003e\u003c/li\u003e\n\u003cli\u003e\u003cspan style=\"font-family: inherit;\"\u003eMake a note of the first seven letters/numbers of the top SHA tag for each branch. For example, if this is the most recent commit, use \"a820826\".\u003c/span\u003e\u003cspan style=\"font-family: "courier new" , "courier" , monospace;\"\u003e\u003cbr /\u003e\n\n\n\n\n\u003cstyle type=\"text/css\"\u003e\np.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #afad24}\np.p2 {margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo}\np.p3 {margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; min-height: 13.0px}\nspan.s1 {font-variant-ligatures: no-common-ligatures}\n\u003c/style\u003e\n\n\n\u003c/span\u003e\u003cdiv class=\"p1\"\u003e\n\u003c/div\u003e\n\u003cblockquote style=\"border: none; margin: 0 0 0 40px; padding: 0px;\"\u003e\n\u003cspan class=\"s1\"\u003e\u003cspan style=\"font-family: "courier new" , "courier" , monospace; font-size: small;\"\u003ecommit a8208268dffbd3ab63ac7466b861a2be33c4f665\u003c/span\u003e\u003c/span\u003e\u003c/blockquote\u003e\n\u003cdiv class=\"p2\"\u003e\n\u003c/div\u003e\n\u003cblockquote style=\"border: none; margin: 0 0 0 40px; padding: 0px;\"\u003e\n\u003cspan class=\"s1\"\u003e\u003cspan style=\"font-family: "courier new" , "courier" , monospace; font-size: small;\"\u003eAuthor: joeshmo <joeshmo@joeshmo.com>\u003c/span\u003e\u003c/span\u003e\u003c/blockquote\u003e\n\u003cdiv class=\"p2\"\u003e\n\u003c/div\u003e\n\u003cblockquote style=\"border: none; margin: 0 0 0 40px; padding: 0px;\"\u003e\n\u003cspan class=\"s1\"\u003e\u003cspan style=\"font-family: "courier new" , "courier" , monospace; font-size: small;\"\u003eDate:   Mon May 1 14:38:41 2017 -0700\u003c/span\u003e\u003c/span\u003e\u003c/blockquote\u003e\n\u003cdiv class=\"p3\"\u003e\n\u003cspan class=\"s1\"\u003e\u003c/span\u003e\u003cbr /\u003e\u003c/div\u003e\nFor the purposes of this tutorial, let's say that the tip of master is 1111111 and the tip of release/2.0.0 is 2222222.\u003c/li\u003e\n\u003cli\u003e\u003cspan style=\"font-family: inherit;\"\u003e\u003cb style=\"font-family: "courier new", courier, monospace;\"\u003e \u003c/b\u003e\u003c/span\u003eCreate a new branch from the master SHA tag like:\u003cbr /\u003e\u003cb style=\"font-family: "courier new", courier, monospace;\"\u003e\u003cspan style=\"font-family: "courier new" , "courier" , monospace;\"\u003egit checkout \u003c/span\u003e\u003cb\u003e\u003cspan style=\"font-family: "courier new" , "courier" , monospace;\"\u003e1111111\u003c/span\u003e\u003c/b\u003e\u003c/b\u003e\u003cspan style=\"font-family: "courier new" , "courier" , monospace;\"\u003e\u003cb\u003e\u003cspan style=\"font-family: "courier new" , "courier" , monospace;\"\u003egit checkout -b \u003c/span\u003e\u003cspan style=\"font-family: "courier new" , "courier" , monospace;\"\u003emerge/\u003cb\u003e1111111\u003c/b\u003e-and-\u003c/span\u003e\u003c/b\u003e\u003c/span\u003e\u003cb\u003e\u003cspan style=\"font-family: "courier new" , "courier" , monospace;\"\u003e2222222\u003c/span\u003e\u003c/b\u003e\u003c/li\u003e\n\u003cbr /\u003e\n\u003cli\u003e\u003cspan style=\"font-family: inherit;\"\u003ePerform the merge:\u003c/span\u003e\u003cbr /\u003e\u003cspan style=\"font-family: "courier new" , "courier" , monospace;\"\u003e\u003cb\u003egit merge \u003c/b\u003e\u003c/span\u003e\u003cb\u003e\u003cspan style=\"font-family: "courier new" , "courier" , monospace;\"\u003e2222222\u003c/span\u003e\u003c/b\u003e\u003c/li\u003e\n\u003cbr /\u003e\n\u003cli\u003e\u003cspan style=\"font-family: inherit;\"\u003eSally would resolve whatever merge conflicts that she could, namely on database.sql. For the other two files, she would commit them in their conflicted state like so:\u003c/span\u003e\u003cbr /\u003e\u003cb\u003e\u003cspan style=\"font-family: "courier new" , "courier" , monospace;\"\u003egit reset HEAD api.java\u003cbr /\u003egit add api.java\u003cbr /\u003egit reset HEAD frontend.js\u003cbr /\u003egit add frontend.js\u003c/span\u003e\u003c/b\u003e\u003c/li\u003e\n\u003cbr /\u003e\n\u003cli\u003e\u003cspan style=\"font-family: inherit;\"\u003eCommit and push like so:\u003c/span\u003e\u003cbr /\u003e\u003cb\u003e\u003cspan style=\"font-family: "courier new" , "courier" , monospace;\"\u003egit commit -m \"contains conflicted files\"\u003cbr /\u003egit push origin merge/\u003cb style=\"font-family: Times;\"\u003e\u003cspan style=\"font-family: "courier new" , "courier" , monospace;\"\u003e1111111\u003c/span\u003e\u003c/b\u003e-and-\u003c/span\u003e\u003c/b\u003e\u003cb\u003e\u003cspan style=\"font-family: "courier new" , "courier" , monospace;\"\u003e2222222\u003c/span\u003e\u003c/b\u003e\u003c/li\u003e\n\u003cbr /\u003e\n\u003c/ol\u003e\n\u003cdiv\u003e\nNext up, she would inform Ahmed and Yasuko that they need to fix conflicts in the files they own. Their instructions would be something like this:\u003c/div\u003e\n\u003cdiv\u003e\n\u003col\u003e\n\u003cli\u003eAssuming they have the repo already cloned in /gitmain, they should clone the repo into a new directory /gitmerge (for example).\u003c/li\u003e\n\u003cbr /\u003e\n\u003cli\u003eWithin /gitmerge, execute the commands:\u003cbr /\u003e\u003cspan style=\"font-family: "courier new" , "courier" , monospace;\"\u003e\u003cb\u003egit checkout \u003cb style=\"font-family: Times;\"\u003e\u003cspan style=\"font-family: "courier new" , "courier" , monospace;\"\u003e1111111\u003c/span\u003e\u003c/b\u003e\u003cbr /\u003egit merge \u003c/b\u003e\u003c/span\u003e\u003cb\u003e\u003cspan style=\"font-family: "courier new" , "courier" , monospace;\"\u003e2222222\u003c/span\u003e\u003c/b\u003e\u003c/li\u003e\n\u003cbr /\u003e\n\u003cli\u003eResolve the conflicts on the files they are concerned about.\u003c/li\u003e\n\u003cbr /\u003e\n\u003cli\u003eIn their main repo, \u003cb\u003e\u003cspan style=\"font-family: "courier new" , "courier" , monospace;\"\u003egit checkout \u003c/span\u003e\u003c/b\u003e\u003cb\u003e\u003cspan style=\"font-family: "courier new" , "courier" , monospace;\"\u003emerge/\u003cb style=\"font-family: Times;\"\u003e\u003cspan style=\"font-family: "courier new" , "courier" , monospace;\"\u003e1111111\u003c/span\u003e\u003c/b\u003e-and-\u003c/span\u003e\u003c/b\u003e\u003cb\u003e\u003cspan style=\"font-family: "courier new" , "courier" , monospace;\"\u003e2222222\u003c/span\u003e\u003c/b\u003e\u003c/li\u003e\n\u003cbr /\u003e\n\u003cli\u003eManually copy the modified file(s) from /gitmerge into /gitmain\u003c/li\u003e\n\u003cbr /\u003e\n\u003cli\u003eAdd, commit and push their resolved files\u003c/li\u003e\n\u003c/ol\u003e\n\u003cdiv\u003e\nEventually, when Yasuko and Ahmed have finished resolving their conflicts, there should be no <<<<< ======= >>>>>> conflict markers in any file, and \u003cspan style=\"font-family: "courier new" , "courier" , monospace;\"\u003e\u003cb style=\"font-family: Times;\"\u003e\u003cspan style=\"font-family: "courier new" , "courier" , monospace;\"\u003emerge/\u003cb style=\"font-family: Times;\"\u003e\u003cspan style=\"font-family: "courier new" , "courier" , monospace;\"\u003e1111111\u003c/span\u003e\u003c/b\u003e-and-\u003c/span\u003e\u003c/b\u003e\u003cb style=\"font-family: Times;\"\u003e\u003cspan style=\"font-family: "courier new" , "courier" , monospace;\"\u003e2222222\u003c/span\u003e\u003c/b\u003e\u003cb\u003e \u003c/b\u003e\u003c/span\u003ecan be merged back into \u003cb style=\"font-family: "Courier New", Courier, monospace;\"\u003emaster.\u003c/b\u003e\u003c/div\u003e\n\u003c/div\u003e\n", "author": { "id": "16894652095365231201", "displayName": "Scott Schafer", "url": "https://www.blogger.com/profile/16894652095365231201", "image": { "url": "//www.blogger.com/img/blogger_logo_round_35.png" } }, "replies": { "totalItems": "0", "selfLink": "https://www.googleapis.com/blogger/v3/blogs/2793398838126095496/posts/7557136834597532067/comments" }, "etag": "\"dGltZXN0YW1wOiAxNDkzNzUxMDI1NDA5Cm9mZnNldDogLTI1MjAwMDAwCg\"" }, { "kind": "blogger#post", "id": "419324855562053388", "blog": { "id": "2793398838126095496" }, "published": "2017-01-18T12:44:00-08:00", "updated": "2017-01-18T12:44:06-08:00", "url": "http://currentlyunderdevelopment.blogspot.com/2017/01/using-angular2-services-as-singletons.html", "selfLink": "https://www.googleapis.com/blogger/v3/blogs/2793398838126095496/posts/419324855562053388", "title": "Using Angular2 services as singletons for busy people", "content": "\u003cdiv class=\"tr_bq\"\u003e\nPerhaps like you, I can't be bothered to read the documentation or blog posts either, so I'll keep this short and to the point.\u003c/div\u003e\n\u003cbr /\u003e\nIn Angular 1, services are always singletons. I followed what I thought was the correct way to do Dependency Injection in Angular 2, and found out the hard way that multiple instances of my services were being created. This meant that they could not be used to share the state across components.\u003cbr /\u003e\n\u003cbr /\u003e\n\u003cb\u003eTL;DR\u003c/b\u003e:\n\nThe problem was that I had put my service in the providers array of every component that needed access. For example, I had:\n\u003cbr /\u003e\n\u003cblockquote\u003e\n\u003cspan style=\"font-family: Courier New, Courier, monospace;\"\u003e@Component({\u003c/span\u003e\u003cbr /\u003e\n\u003cspan style=\"font-family: Courier New, Courier, monospace;\"\u003e  selector: 'game',\u003c/span\u003e\u003cbr /\u003e\n\u003cspan style=\"font-family: Courier New, Courier, monospace;\"\u003e  templateUrl: './game.component.html',\u003c/span\u003e\u003cbr /\u003e\n\u003cspan style=\"font-family: Courier New, Courier, monospace;\"\u003e  styleUrls: ['./game.component.css'],\u003c/span\u003e\u003cbr /\u003e\n\u003cspan style=\"font-family: Courier New, Courier, monospace;\"\u003e\u003cb\u003e  providers: [ AppStateService ]\u003c/b\u003e\u003c/span\u003e\u003cbr /\u003e\n\u003cspan style=\"font-family: Courier New, Courier, monospace;\"\u003e})\u003c/span\u003e\u003cbr /\u003e\n\u003cdiv\u003e\n\u003cspan style=\"font-family: Courier New, Courier, monospace;\"\u003e\u003cbr /\u003e\u003c/span\u003e\u003c/div\u003e\n\u003c/blockquote\u003e\nand\n\u003cbr /\u003e\n\u003cblockquote\u003e\n\u003cspan style=\"font-family: Courier New, Courier, monospace;\"\u003e@Component({\u003c/span\u003e\u003cbr /\u003e\n\u003cspan style=\"font-family: Courier New, Courier, monospace;\"\u003e  selector: 'game',\u003c/span\u003e\u003cbr /\u003e\n\u003cspan style=\"font-family: Courier New, Courier, monospace;\"\u003e  templateUrl: './game.component.html',\u003c/span\u003e\u003cbr /\u003e\n\u003cspan style=\"font-family: Courier New, Courier, monospace;\"\u003e  styleUrls: ['./game.component.css'],\u003c/span\u003e\u003cbr /\u003e\n\u003cspan style=\"font-family: Courier New, Courier, monospace;\"\u003e\u003cb\u003e  providers: [ AppStateService ]\u003c/b\u003e\u003c/span\u003e\u003cbr /\u003e\n\u003cspan style=\"font-family: Courier New, Courier, monospace;\"\u003e})\u003c/span\u003e\u003cbr /\u003e\n\u003cdiv\u003e\n\u003cbr /\u003e\u003c/div\u003e\n\u003c/blockquote\u003e\nThis seemed to be causing the service to be instantiated multiple times. My fix was this:\u003cbr /\u003e\n\u003cbr /\u003e\n1. Only having the AppStateService appear in \u003cb\u003eone\u003c/b\u003e providers array. I put it in my app.component.ts.\u003cbr /\u003e\n\u003cbr /\u003e2. Use @Injectable on my service, like so:\u003cbr /\u003e\n\u003cblockquote class=\"tr_bq\"\u003e\n\u003cspan style=\"font-family: Courier New, Courier, monospace;\"\u003eimport { Injectable } from '@angular/core';\u003c/span\u003e \u003c/blockquote\u003e\n\u003cblockquote class=\"tr_bq\"\u003e\n\u003cspan style=\"font-family: Courier New, Courier, monospace;\"\u003e@Injectable()\u003cbr /\u003eexport class AppStateService { ...\u003c/span\u003e\u003c/blockquote\u003e\n3. Wherever I need to use the service, use @Inject like so:\u003cbr /\u003e\n\u003cblockquote\u003e\n\u003cspan style=\"font-family: Courier New, Courier, monospace;\"\u003eimport { Component, OnInit, Inject } from '@angular/core'; \u003c/span\u003e\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cspan style=\"font-family: Courier New, Courier, monospace;\"\u003e@Component({\u003cbr /\u003e  selector: 'start-screen',\u003cbr /\u003e  templateUrl: './start-screen.component.html',\u003cbr /\u003e  styleUrls: ['./start-screen.component.scss']\u003cbr /\u003e})\u003cbr /\u003e@Inject(AppStateService)\u003cbr /\u003eexport class StartScreenComponent implements OnInit {\u003cbr /\u003e  constructor(public appState: AppStateService) {  \u003c/span\u003e\u003c/blockquote\u003e\n", "author": { "id": "16894652095365231201", "displayName": "Scott Schafer", "url": "https://www.blogger.com/profile/16894652095365231201", "image": { "url": "//www.blogger.com/img/blogger_logo_round_35.png" } }, "replies": { "totalItems": "0", "selfLink": "https://www.googleapis.com/blogger/v3/blogs/2793398838126095496/posts/419324855562053388/comments" }, "etag": "\"dGltZXN0YW1wOiAxNDg0NzcyMjQ2NzI0Cm9mZnNldDogLTI4ODAwMDAwCg\"" }, { "kind": "blogger#post", "id": "4272060425785613467", "blog": { "id": "2793398838126095496" }, "published": "2017-01-07T18:14:00-08:00", "updated": "2017-01-07T18:15:34-08:00", "url": "http://currentlyunderdevelopment.blogspot.com/2017/01/presto-evolution.html", "selfLink": "https://www.googleapis.com/blogger/v3/blogs/2793398838126095496/posts/4272060425785613467", "title": "Presto! Evolution", "content": "Because I dig this sort of thing, and as an excuse to teach myself Typescript and Angular 2, I ported an old C++/Windows program of mine to modern web technologies. I now really, really love Angular 2 and Typescript both.\n\u003cbr /\u003e\n\u003ca href=\"https://github.com/scottschafer/presto-evolution\"\u003eSource on github\u003c/a\u003e\n\u003cbr /\u003e\n\n\u003ciframe src=\"https://scottschafer.github.io//presto-evolution//\" width=\"900\" height=\"1200\"\u003e\u003c/iframe\u003e", "author": { "id": "16894652095365231201", "displayName": "Scott Schafer", "url": "https://www.blogger.com/profile/16894652095365231201", "image": { "url": "//www.blogger.com/img/blogger_logo_round_35.png" } }, "replies": { "totalItems": "0", "selfLink": "https://www.googleapis.com/blogger/v3/blogs/2793398838126095496/posts/4272060425785613467/comments" }, "etag": "\"dGltZXN0YW1wOiAxNDgzODQxNzM0NzI2Cm9mZnNldDogLTI4ODAwMDAwCg\"" } ], "etag": "\"MjAyMi0wOS0yN1QxOTo1MjoyNy45MTZa\"" } { "kind": "blogger#pageList", "items": [ { "kind": "blogger#page", "id": "3397178238739023980", "blog": { "id": "2793398838126095496" }, "published": "2015-11-19T09:24:00-08:00", "updated": "2022-09-27T12:52:27-07:00", "url": "http://currentlyunderdevelopment.blogspot.com/p/about-me.html", "selfLink": "https://www.googleapis.com/blogger/v3/blogs/2793398838126095496/pages/3397178238739023980", "title": "main", "content": "Welcome to my \u003ca href=\"https://www.blogger.com/#\"\u003eblog\u003c/a\u003e and \u003ca href=\"https://www.blogger.com/#\"\u003ewebsite\u003c/a\u003e.\u003cdiv\u003e\u003cbr /\u003e\u003c/div\u003e\u003cdiv\u003eSometimes I need to learn new technologies outside of work, and I do this mainly by making little games and simulations. The following are all written in Typescript, using Angular and React and other technologies.\u003c/div\u003e\u003cdiv\u003e\u003cbr /\u003e\u003c/div\u003e\u003cdiv\u003e\u003ca href=\"https://scottschaferenterprises.com/waitingForGodot/#/\"\u003eHere's a simple multi-player game platform\u003c/a\u003e I put together using React/Firebase/mobx, and a simple game built on top of it which I think really captures the spirit of 2020.\u003cbr /\u003e\u003cbr /\u003e\u003cdiv style=\"text-align: center;\"\u003e\u003ca href=\"https://scottschaferenterprises.com/waitingForGodot/#/\"\u003e\u003cimg src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQyw0Zzpuj9PoUf4_8Tdj0S_ZdFI-2Yw919OCqbw3hx_sj8sxkWT7Z3NvF83dwaUiTLacZkS6oexVXeW0GvHD-XUfiBe5LCPTBYCinjEBvAYnF9uiZf3OS3I45z8N4j1OgnRlh-ADsHPI/\" /\u003e\u003c/a\u003e\u003c/div\u003e\u003cbr /\u003e\u003cbr /\u003e\u003c/div\u003e\u003cdiv\u003eI keep iterating on the same theme. \u003ca href=\"https://scottschaferenterprises.com/webcritters\"\u003eHere's probably the fifth iteration of the same evolutionary life simulation\u003c/a\u003e, written with React/Typescript/MobX/Material-UI: Let it run for a few minutes or so and things will start happening. Toggle the barriers to really force evolution. Insert your own critters, etc.\u003c/div\u003e\u003cdiv\u003e\u003cbr /\u003e\u003c/div\u003e\u003cdiv class=\"separator\" style=\"clear: both; text-align: center;\"\u003e\u003ca href=\"https://scottschaferenterprises.com/webcritters\" style=\"margin-left: 1em; margin-right: 1em;\"\u003e\u003cimg border=\"0\" data-original-height=\"1752\" data-original-width=\"3430\" height=\"246\" src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9SRPEcOqOPcAzOuHVQEaCjH4YYEPFqrc-88EN6nK4T00wZGGPoty9ia_6uXrWM2k1X32EV7ug1gDD1KcUAbczZ7qg_YBpRiF-U3SF7ZjyvIdj49xbZZW-Kdj8_tG4Dp-yE4ai8C5HYX0Mq6M5HzHx_yVa6xYh8zZT_cgYCQzek1K1zj2tip4pEGg5/w483-h246/Screen%20Shot%202022-09-26%20at%2011.49.35%20AM.png\" width=\"483\" /\u003e\u003c/a\u003e\u003c/div\u003e\u003cbr /\u003e\u003cdiv\u003e\u003cbr /\u003e\u003c/div\u003e\u003cdiv\u003e\u003cdiv\u003e\u003ca href=\"https://scottschafer.github.io//BowlBlaster//\"\u003eHere's a game I wrote that's actually kind of fun to play.\u003c/a\u003e Pacman inspired gameplay. Also inspired by my kid's imagination, and my experience as a parent of constantly plunging toilets.\u003c/div\u003e\u003cdiv\u003e\u003cbr /\u003e\u003c/div\u003e\u003cdiv\u003e\u003cdiv style=\"text-align: center;\"\u003e\u003ca href=\"https://scottschafer.github.io//BowlBlaster//\"\u003e\u003cimg src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiBLr-C0e0GfWADSEUnqioNYrN69aqVg4XH42wrB1U3dNqWwdUu-tac9Oi7ZPcepIC0QdAE4jqUIoobFfA_bjnPIB6YCG0IcCEIDXxN3j8heIfwB4BQX6UzY1W_LOS9im71I0qPjcxn6c0/\" /\u003e\u003c/a\u003e\u003c/div\u003e\u003c/div\u003e\u003c/div\u003e\u003cdiv\u003e\u003cbr /\u003e\u003c/div\u003e\u003cdiv\u003e\u003ca href=\"https://speakinginpictures.com/\"\u003eHere's some contracted work I did\u003c/a\u003e to explore having a conversation using pictures. You can like or dislike an image that's presented or upload your own. This uses \u003ca href=\"https://www.clarifai.com\"\u003eclarifai\u003c/a\u003e to extract keywords from images, which are then fed back to a google image search.\u003c/div\u003e\u003cdiv\u003e\u003cbr /\u003e\u003c/div\u003e\u003cdiv class=\"separator\" style=\"clear: both; text-align: center;\"\u003e\u003ca href=\"https://speakinginpictures.com/\" style=\"margin-left: 1em; margin-right: 1em;\"\u003e\u003cimg border=\"0\" data-original-height=\"976\" data-original-width=\"1330\" height=\"235\" src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjS9qTR0BsiBmSU_XUkAun_rEqZsqNJUFIDgfIMuI5CgDLicfmF_HitFNk8BJQhv2cYr5AMZ8MAZofu8mWUkjyD4al3Kw7xfnQsg00WBkb4a49vXUpaOITE2WHuCb3GvLPRdKotrxgqEDtFuBDcSKf06WGxa8zq1tNA9ckAAJyOYttsBYUFz6M9DkHt/s320/Screen%20Shot%202022-09-26%20at%2011.52.22%20AM.png\" width=\"320\" /\u003e\u003c/a\u003e\u003c/div\u003e\u003cdiv class=\"separator\" style=\"clear: both; text-align: center;\"\u003e\u003cbr /\u003e\u003c/div\u003e\u003cdiv\u003eOh, \u003ca href=\"https://scottschaferenterprises.com/bpx/\"\u003eand here's a little experiment\u003c/a\u003e I made to determine if picking berries could be turned into a video game. Well, I suppose it can. Not as addictive as I'd hoped. ;) And yeah, real blackberry bushes don't move like that, but hey.\u003c/div\u003e\u003cdiv\u003e\u003cdiv class=\"separator\" style=\"clear: both; text-align: center;\"\u003e\u003cbr /\u003e\u003c/div\u003e\u003cbr /\u003e\u003c/div\u003e\u003cdiv class=\"separator\" style=\"clear: both; text-align: center;\"\u003e\u003ca href=\"https://scottschaferenterprises.com/bpx/\" style=\"margin-left: 1em; margin-right: 1em;\"\u003e\u003cimg border=\"0\" data-original-height=\"1052\" data-original-width=\"2740\" height=\"123\" src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjY0RTFiBdyvFiUgxmaZgQr3oY-U-NOV2cpnBRc8wOXSaQGwy5HzQ3O_L2G-hhUa_ZKV8VZxRxr036Y4Uzp0Q17xxZSe5skhpOVbGbr5TRjCQovRHFSA8D7XhLVnFwesdxcFdUm6lhjkQjbyhNfqPUKLkOcbEtuOXaQZomlWuF_-9T6mLp5xEkTzyxp/s320/Screen%20Shot%202022-09-26%20at%2011.59.53%20AM.png\" width=\"320\" /\u003e\u003c/a\u003e\u003c/div\u003e\u003cdiv\u003e\u003cdiv\u003e\u003cbr /\u003e\u003c/div\u003e\u003c/div\u003e\u003cdiv\u003e\u003cu\u003e\u003cstrike\u003e\u003cbr /\u003e\u003c/strike\u003e\u003c/u\u003e\u003c/div\u003e\u003cdiv\u003eBTW, if you got here through my old \u003ca href=\"https://www.blogger.com/#\"\u003eYouTube video\u003c/a\u003e you are probably looking for \u003ca href=\"https://www.blogger.com/#\"\u003ethis\u003c/a\u003e, and you might also be interested to watch a more recent (but stalled) evolutionary simulation \u003ca href=\"https://www.blogger.com/#\"\u003ehere\u003c/a\u003e. My most recent efforts use a custom physics engine that can run on the GPU or CPU.\u003c/div\u003e", "author": { "id": "16894652095365231201", "displayName": "Scott Schafer", "url": "https://www.blogger.com/profile/16894652095365231201", "image": { "url": "//www.blogger.com/img/blogger_logo_round_35.png" } }, "etag": "\"dGltZXN0YW1wOiAxNjY0MzA4MzQ3OTE2Cm9mZnNldDogLTI1MjAwMDAwCg\"" } ], "etag": "\"MjAyMi0wOS0yN1QxOTo1MjoyNy45MTZa\"" }